* Copyright 2001-2024, Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* DarkWyrm <bpmagic@columbus.rr.com>
*/
#include "CursorManager.h"
#include "ServerCursor.h"
#include "ServerConfig.h"
#include "ServerTokenSpace.h"
#include <Autolock.h>
#include <Directory.h>
#include <String.h>
#include <IconUtils.h>
#include <agg_pixfmt_rgba.h>
#include <agg_blur.h>
#include <new>
#include <stdio.h>
#include "CursorData.cpp"
CursorManager::CursorManager()
:
BLocker("CursorManager")
{
}
CursorManager::~CursorManager()
{
ReleaseCursors();
}
void
CursorManager::InitializeCursors(float scale)
{
if (scale < 1.0)
scale = 1.0;
const BPoint kHandHotspot(1, 1);
const BPoint kResizeHotspot(8, 8);
struct StandardCursor {
ServerCursor*& member;
BCursorID id;
const uint8* data;
uint32 dataLength;
BPoint hotspot;
#define C(IDNAME, NAME, HOTSPOT) {fCursor##NAME, B_CURSOR_ID_##IDNAME, \
kCursor##NAME, B_COUNT_OF(kCursor##NAME), HOTSPOT}
} standardCursors[] = {
{fCursorNoCursor, B_CURSOR_ID_NO_CURSOR, NULL, 0, BPoint(0, 0)},
C(SYSTEM_DEFAULT, SystemDefault, kHandHotspot),
C(CONTEXT_MENU, ContextMenu, kHandHotspot),
C(COPY, Copy, kHandHotspot),
C(CREATE_LINK, CreateLink, kHandHotspot),
C(CROSS_HAIR, CrossHair, BPoint(10, 10)),
C(FOLLOW_LINK, FollowLink, BPoint(5, 0)),
C(GRAB, Grab, kHandHotspot),
C(GRABBING, Grabbing, kHandHotspot),
C(HELP, Help, BPoint(0, 8)),
C(I_BEAM, IBeam, BPoint(7, 9)),
C(I_BEAM_HORIZONTAL, IBeamHorizontal, BPoint(8, 8)),
C(MOVE, Move, kResizeHotspot),
C(NOT_ALLOWED, NotAllowed, BPoint(8, 8)),
C(PROGRESS, Progress, BPoint(7, 10)),
C(RESIZE_EAST, ResizeEast, kResizeHotspot),
C(RESIZE_EAST_WEST, ResizeEastWest, kResizeHotspot),
C(RESIZE_NORTH, ResizeNorth, kResizeHotspot),
C(RESIZE_NORTH_EAST, ResizeNorthEast, kResizeHotspot),
C(RESIZE_NORTH_EAST_SOUTH_WEST, ResizeNorthEastSouthWest, kResizeHotspot),
C(RESIZE_NORTH_SOUTH, ResizeNorthSouth, kResizeHotspot),
C(RESIZE_NORTH_WEST, ResizeNorthWest, kResizeHotspot),
C(RESIZE_NORTH_WEST_SOUTH_EAST, ResizeNorthWestSouthEast, kResizeHotspot),
C(RESIZE_SOUTH, ResizeSouth, kResizeHotspot),
C(RESIZE_SOUTH_EAST, ResizeSouthEast, kResizeHotspot),
C(RESIZE_SOUTH_WEST, ResizeSouthWest, kResizeHotspot),
C(RESIZE_WEST, ResizeWest, kResizeHotspot),
C(ZOOM_IN, ZoomIn, BPoint(6, 6)),
C(ZOOM_OUT, ZoomOut, BPoint(6, 6))
};
#undef C
for (size_t i = 0; i < B_COUNT_OF(standardCursors); i++) {
const StandardCursor& info = standardCursors[i];
_InitCursor(info.member, info.id, info.data, info.dataLength, info.hotspot, scale);
}
}
void
CursorManager::ReleaseCursors()
{
for (int32 i = 0; i < fCursorList.CountItems(); i++) {
ServerCursor* cursor = ((ServerCursor*)fCursorList.ItemAtFast(i));
cursor->fManager = NULL;
fTokenSpace.RemoveToken(cursor->Token());
cursor->ReleaseReference();
}
fCursorList.MakeEmpty();
}
ServerCursor*
CursorManager::CreateCursor(team_id clientTeam, const uint8* cursorData)
{
if (!Lock())
return NULL;
ServerCursorReference cursor(_FindCursor(clientTeam, cursorData), false);
if (!cursor) {
cursor.SetTo(new (std::nothrow) ServerCursor(cursorData), true);
if (cursor) {
cursor->SetOwningTeam(clientTeam);
if (AddCursor(cursor) < B_OK)
cursor = NULL;
}
}
Unlock();
return cursor.Detach();
}
ServerCursor*
CursorManager::CreateCursor(team_id clientTeam, BRect r, color_space format,
int32 flags, BPoint hotspot, int32 bytesPerRow)
{
if (!Lock())
return NULL;
ServerCursor* cursor = new (std::nothrow) ServerCursor(r, format, flags,
hotspot, bytesPerRow);
if (cursor != NULL) {
cursor->SetOwningTeam(clientTeam);
if (AddCursor(cursor) < B_OK) {
delete cursor;
cursor = NULL;
}
}
Unlock();
return cursor;
}
\param cursor ServerCursor object to register
\return The token assigned to the cursor or B_ERROR if cursor is NULL
*/
int32
CursorManager::AddCursor(ServerCursor* cursor, int32 token)
{
if (cursor == NULL)
return B_BAD_VALUE;
if (!Lock())
return B_ERROR;
ServerCursor* oldCursor = FindCursor(token);
if (oldCursor != NULL) {
fCursorList.ReplaceItem(fCursorList.IndexOf(oldCursor), cursor);
oldCursor->ReleaseReference();
} else {
if (!fCursorList.AddItem(cursor)) {
Unlock();
return B_NO_MEMORY;
}
}
if (token == -1)
token = fTokenSpace.NewToken(kCursorToken, cursor);
else
fTokenSpace.SetToken(token, kCursorToken, cursor);
cursor->fToken = token;
cursor->AttachedToManager(this);
Unlock();
return token;
}
If this was the last reference to this cursor, it will be deleted.
*/
void
CursorManager::RemoveCursor(ServerCursor* cursor)
{
if (!Lock())
return;
_RemoveCursor(cursor);
cursor->ReleaseReference();
Unlock();
}
\param signature Signature to which the cursors belong
*/
void
CursorManager::DeleteCursors(team_id team)
{
if (!Lock())
return;
for (int32 index = fCursorList.CountItems(); index-- > 0;) {
ServerCursor* cursor = (ServerCursor*)fCursorList.ItemAtFast(index);
if (cursor->OwningTeam() != team)
continue;
_RemoveCursor(cursor);
cursor->ReleaseReference();
}
Unlock();
}
\param path Path to the cursor set
All cursors in the set will be assigned. If the set does not specify a
cursor for a particular cursor specifier, it will remain unchanged.
This function will fail if passed a NULL path, an invalid path, or the
path to a non-CursorSet file.
*/
void
CursorManager::SetCursorSet(const char* path)
{
BAutolock locker (this);
CursorSet cursorSet(NULL);
if (!path || cursorSet.Load(path) != B_OK)
return;
_LoadCursor(fCursorSystemDefault, cursorSet, B_CURSOR_ID_SYSTEM_DEFAULT);
_LoadCursor(fCursorContextMenu, cursorSet, B_CURSOR_ID_CONTEXT_MENU);
_LoadCursor(fCursorCopy, cursorSet, B_CURSOR_ID_COPY);
_LoadCursor(fCursorCreateLink, cursorSet, B_CURSOR_ID_CREATE_LINK);
_LoadCursor(fCursorCrossHair, cursorSet, B_CURSOR_ID_CROSS_HAIR);
_LoadCursor(fCursorFollowLink, cursorSet, B_CURSOR_ID_FOLLOW_LINK);
_LoadCursor(fCursorGrab, cursorSet, B_CURSOR_ID_GRAB);
_LoadCursor(fCursorGrabbing, cursorSet, B_CURSOR_ID_GRABBING);
_LoadCursor(fCursorHelp, cursorSet, B_CURSOR_ID_HELP);
_LoadCursor(fCursorIBeam, cursorSet, B_CURSOR_ID_I_BEAM);
_LoadCursor(fCursorIBeamHorizontal, cursorSet,
B_CURSOR_ID_I_BEAM_HORIZONTAL);
_LoadCursor(fCursorMove, cursorSet, B_CURSOR_ID_MOVE);
_LoadCursor(fCursorNotAllowed, cursorSet, B_CURSOR_ID_NOT_ALLOWED);
_LoadCursor(fCursorProgress, cursorSet, B_CURSOR_ID_PROGRESS);
_LoadCursor(fCursorResizeEast, cursorSet, B_CURSOR_ID_RESIZE_EAST);
_LoadCursor(fCursorResizeEastWest, cursorSet,
B_CURSOR_ID_RESIZE_EAST_WEST);
_LoadCursor(fCursorResizeNorth, cursorSet, B_CURSOR_ID_RESIZE_NORTH);
_LoadCursor(fCursorResizeNorthEast, cursorSet,
B_CURSOR_ID_RESIZE_NORTH_EAST);
_LoadCursor(fCursorResizeNorthEastSouthWest, cursorSet,
B_CURSOR_ID_RESIZE_NORTH_EAST_SOUTH_WEST);
_LoadCursor(fCursorResizeNorthSouth, cursorSet,
B_CURSOR_ID_RESIZE_NORTH_SOUTH);
_LoadCursor(fCursorResizeNorthWest, cursorSet,
B_CURSOR_ID_RESIZE_NORTH_WEST);
_LoadCursor(fCursorResizeNorthWestSouthEast, cursorSet,
B_CURSOR_ID_RESIZE_NORTH_WEST_SOUTH_EAST);
_LoadCursor(fCursorResizeSouth, cursorSet, B_CURSOR_ID_RESIZE_SOUTH);
_LoadCursor(fCursorResizeSouthEast, cursorSet,
B_CURSOR_ID_RESIZE_SOUTH_EAST);
_LoadCursor(fCursorResizeSouthWest, cursorSet,
B_CURSOR_ID_RESIZE_SOUTH_WEST);
_LoadCursor(fCursorResizeWest, cursorSet, B_CURSOR_ID_RESIZE_WEST);
_LoadCursor(fCursorZoomIn, cursorSet, B_CURSOR_ID_ZOOM_IN);
_LoadCursor(fCursorZoomOut, cursorSet, B_CURSOR_ID_ZOOM_OUT);
}
\param which Which system cursor to get
\return Pointer to the particular cursor used or NULL if which is
invalid or the cursor has not been assigned
*/
ServerCursor*
CursorManager::GetCursor(BCursorID which)
{
BAutolock locker(this);
switch (which) {
case B_CURSOR_ID_SYSTEM_DEFAULT:
return fCursorSystemDefault;
case B_CURSOR_ID_CONTEXT_MENU:
return fCursorContextMenu;
case B_CURSOR_ID_COPY:
return fCursorCopy;
case B_CURSOR_ID_CREATE_LINK:
return fCursorCreateLink;
case B_CURSOR_ID_CROSS_HAIR:
return fCursorCrossHair;
case B_CURSOR_ID_FOLLOW_LINK:
return fCursorFollowLink;
case B_CURSOR_ID_GRAB:
return fCursorGrab;
case B_CURSOR_ID_GRABBING:
return fCursorGrabbing;
case B_CURSOR_ID_HELP:
return fCursorHelp;
case B_CURSOR_ID_I_BEAM:
return fCursorIBeam;
case B_CURSOR_ID_I_BEAM_HORIZONTAL:
return fCursorIBeamHorizontal;
case B_CURSOR_ID_MOVE:
return fCursorMove;
case B_CURSOR_ID_NO_CURSOR:
return fCursorNoCursor;
case B_CURSOR_ID_NOT_ALLOWED:
return fCursorNotAllowed;
case B_CURSOR_ID_PROGRESS:
return fCursorProgress;
case B_CURSOR_ID_RESIZE_EAST:
return fCursorResizeEast;
case B_CURSOR_ID_RESIZE_EAST_WEST:
return fCursorResizeEastWest;
case B_CURSOR_ID_RESIZE_NORTH:
return fCursorResizeNorth;
case B_CURSOR_ID_RESIZE_NORTH_EAST:
return fCursorResizeNorthEast;
case B_CURSOR_ID_RESIZE_NORTH_EAST_SOUTH_WEST:
return fCursorResizeNorthEastSouthWest;
case B_CURSOR_ID_RESIZE_NORTH_SOUTH:
return fCursorResizeNorthSouth;
case B_CURSOR_ID_RESIZE_NORTH_WEST:
return fCursorResizeNorthWest;
case B_CURSOR_ID_RESIZE_NORTH_WEST_SOUTH_EAST:
return fCursorResizeNorthWestSouthEast;
case B_CURSOR_ID_RESIZE_SOUTH:
return fCursorResizeSouth;
case B_CURSOR_ID_RESIZE_SOUTH_EAST:
return fCursorResizeSouthEast;
case B_CURSOR_ID_RESIZE_SOUTH_WEST:
return fCursorResizeSouthWest;
case B_CURSOR_ID_RESIZE_WEST:
return fCursorResizeWest;
case B_CURSOR_ID_ZOOM_IN:
return fCursorZoomIn;
case B_CURSOR_ID_ZOOM_OUT:
return fCursorZoomOut;
default:
return NULL;
}
}
\param token ID of the cursor to find
\return The cursor or NULL if not found
*/
ServerCursor*
CursorManager::FindCursor(int32 token)
{
if (!Lock())
return NULL;
ServerCursor* cursor;
if (fTokenSpace.GetToken(token, kCursorToken, (void**)&cursor) != B_OK)
cursor = NULL;
Unlock();
return cursor;
}
BBitmap
CursorManager::_RenderVectorCursor(uint32 size, const uint8* vector,
uint32 vectorSize, float shadowStrength)
{
const uint32 flags = B_BITMAP_NO_SERVER_LINK;
const int32 renderRectSize = (int32)(size * (32.0f / 22.0f));
BBitmap renderCursor(BRect(0, 0, renderRectSize - 1, renderRectSize - 1), flags, B_RGBA32);
status_t status = BIconUtils::GetVectorIcon(vector, vectorSize, &renderCursor);
if (status != B_OK) {
BBitmap fallback(BRect(0, 0, 21, 21), B_BITMAP_NO_SERVER_LINK, B_RGBA32);
fallback.SetBits(kCursorFallbackBits, 22 * 22 * 4, 0, B_RGBA32);
return fallback;
}
const BRect rect(0, 0, size - 1, size - 1);
BBitmap cursor(rect, flags, B_RGBA32);
cursor.ImportBits(&renderCursor, B_ORIGIN, B_ORIGIN, rect.Size());
BBitmap shadow(rect, flags, B_RGBA32);
memset(shadow.Bits(), 0, shadow.BitsLength());
{
int32 offset = size / 32;
if (offset == 0)
offset = 1;
shadow.ImportBits(&cursor, BPoint(0, 0), BPoint(offset, offset),
BSize(size - offset - 1, size - offset - 1));
agg::rendering_buffer buffer((unsigned char*)shadow.Bits(),
size, size, shadow.BytesPerRow());
agg::pixfmt_rgba32 pixFmt(buffer);
agg::recursive_blur<agg::rgba8, agg::recursive_blur_calc_rgba<> > blur;
blur.blur(pixFmt, 1);
for (int32 i = 0; i < shadow.BitsLength(); i += 4) {
uint8* bits = (uint8*)shadow.Bits() + i;
bits[0] = 0;
bits[1] = 0;
bits[2] = 0;
bits[3] = (uint8)(bits[3] * shadowStrength);
}
}
BBitmap composite(rect, flags, B_RGBA32);
uint8* s = (uint8*)shadow.Bits();
uint8* c = (uint8*)cursor.Bits();
uint8* d = (uint8*)composite.Bits();
for (uint32 y = 0; y < size; y++) {
for (uint32 x = 0; x < size; x++) {
uint8 a = (uint8)(c[3] + (255 - c[3]) * (s[3] / 255.0));
d[3] = a;
for (int32 i = 0; i < 3; ++i) {
d[i] = ((s[i] * (255 - c[3]) + 255) >> 8) + c[i];
d[i] = (uint8)(d[i] * int32(a) / 255.0);
}
s += 4;
c += 4;
d += 4;
}
}
return composite;
}
This method must only be called in the CursorManager's constructor,
as it may throw exceptions.
*/
void
CursorManager::_InitCursor(ServerCursor*& cursorMember, BCursorID id,
const uint8* vector, uint32 vectorSize, const BPoint& hotSpot, float scale)
{
int32 cursorSize = (int32)(22 * scale);
float shadow = 3 / 10.0;
BPoint scaledHotspot((int32)(hotSpot.x * scale), (int32)(hotSpot.y * scale));
if (vector != NULL) {
BBitmap bitmap = _RenderVectorCursor(cursorSize, vector, vectorSize, shadow);
cursorMember = new ServerCursor((uint8*)bitmap.Bits(), cursorSize,
cursorSize, bitmap.ColorSpace());
} else {
const unsigned char noCursor[] = {0x00, 0x00, 0x00, 0x00};
cursorMember = new ServerCursor(noCursor, 1, 1, B_RGBA32);
}
cursorMember->SetHotSpot(scaledHotspot);
AddCursor(cursorMember, id);
}
void
CursorManager::_LoadCursor(ServerCursor*& cursorMember, const CursorSet& set,
BCursorID id)
{
ServerCursor* cursor;
if (set.FindCursor(id, &cursor) == B_OK) {
AddCursor(cursor, id);
cursorMember = cursor;
}
}
ServerCursor*
CursorManager::_FindCursor(team_id clientTeam, const uint8* cursorData)
{
int32 count = fCursorList.CountItems();
for (int32 i = 0; i < count; i++) {
ServerCursor* cursor = (ServerCursor*)fCursorList.ItemAtFast(i);
if (cursor->OwningTeam() == clientTeam
&& cursor->CursorData()
&& memcmp(cursor->CursorData(), cursorData, 68) == 0) {
return cursor;
}
}
return NULL;
}
void
CursorManager::_RemoveCursor(ServerCursor* cursor)
{
fCursorList.RemoveItem(cursor);
fTokenSpace.RemoveToken(cursor->fToken);
cursor->fToken = -1;
}