1/*
2 * Copyright 2001-2010, Haiku.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		DarkWyrm <bpmagic@columbus.rr.com>
7 */
8
9
10#include "CursorSet.h"
11#include "ServerCursor.h"
12
13#include <AppServerLink.h>
14#include <PortLink.h>
15#include <ServerProtocol.h>
16
17#include <OS.h>
18#include <String.h>
19#include <File.h>
20
21#include <new>
22
23
24/*!
25	\brief Constructor
26	\name Name of the cursor set.
27*/
28CursorSet::CursorSet(const char *name)
29	: BMessage()
30{
31	AddString("name", name != NULL ? name : "Untitled");
32}
33
34
35/*!
36	\brief Saves the data in the cursor set to a file
37	\param path Path of the file to save to.
38	\param saveflags BFile open file flags. B_READ_WRITE is implied.
39	\return
40	- \c B_OK: Everything went fine.
41	- \c B_BAD_VALUE: path is NULL
42	- \c other value: See BFile::SetTo and BMessage::Flatten return codes
43*/
44status_t
45CursorSet::Save(const char *path, int32 saveFlags)
46{
47	if (path == NULL)
48		return B_BAD_VALUE;
49
50	BFile file;
51	status_t status = file.SetTo(path, B_READ_WRITE | saveFlags);
52	if (status != B_OK)
53		return status;
54
55	return Flatten(&file);
56}
57
58
59/*!
60	\brief Loads the data into the cursor set from a file
61	\param path Path of the file to load from.
62	\return
63	- \c B_OK: Everything went fine.
64	- \c B_BAD_VALUE: path is NULL
65	- \c other value: See BFile::SetTo and BMessage::Flatten return codes
66*/
67status_t
68CursorSet::Load(const char *path)
69{
70	if (path == NULL)
71		return B_BAD_VALUE;
72
73	BFile file;
74	status_t status = file.SetTo(path, B_READ_ONLY);
75	if (status != B_OK)
76		return status;
77
78	return Unflatten(&file);
79}
80
81
82/*!
83	\brief Adds the cursor to the set and replaces any existing entry for the given specifier
84	\param which System cursor specifier defined in CursorSet.h
85	\param cursor BBitmap to represent the new cursor. Size should be 48x48 or less.
86	\param hotspot The recipient of the hotspot for the cursor
87	\return
88	- \c B_OK: Everything went fine.
89	- \c B_BAD_VALUE: cursor is NULL
90	- \c other value: See BMessage::AddMessage return codes.
91*/
92status_t
93CursorSet::AddCursor(BCursorID which, const BBitmap *cursor,
94	const BPoint &hotspot)
95{
96	if (cursor == NULL)
97		return B_BAD_VALUE;
98
99	// Remove the data if it exists already
100	RemoveData(_CursorWhichToString(which));
101
102	// Actually add the data to our set
103	BMessage message((int32)which);
104
105	message.AddString("class", "bitmap");
106	message.AddRect("_frame", cursor->Bounds());
107	message.AddInt32("_cspace", cursor->ColorSpace());
108
109	message.AddInt32("_bmflags", 0);
110	message.AddInt32("_rowbytes", cursor->BytesPerRow());
111	message.AddPoint("hotspot", hotspot);
112	message.AddData("_data", B_RAW_TYPE, cursor->Bits(), cursor->BitsLength());
113
114	return AddMessage(_CursorWhichToString(which), &message);
115}
116
117
118/*!
119	\brief Adds the cursor to the set and replaces any existing entry for the given specifier
120	\param which System cursor specifier defined in CursorSet.h
121	\param data R5 cursor data pointer
122	\return B_BAD_VALUE if data is NULL, otherwise B_OK
123
124	When possible, it is better to use the BBitmap version of AddCursor because this
125	function must convert the R5 cursor data into a BBitmap
126*/
127status_t
128CursorSet::AddCursor(BCursorID which, uint8 *data)
129{
130	// Convert cursor data to a bitmap because all cursors are internally stored
131	// as bitmaps
132	if (data == NULL)
133		return B_BAD_VALUE;
134
135	BBitmap *bitmap = _CursorDataToBitmap(data);
136	BPoint hotspot(data[2], data[3]);
137
138	status_t result = AddCursor(which, bitmap, hotspot);
139
140	delete bitmap;
141	return result;
142}
143
144
145/*!
146	\brief Removes the data associated with the specifier from the cursor set
147	\param which System cursor specifier defined in CursorSet.h
148*/
149void
150CursorSet::RemoveCursor(BCursorID which)
151{
152	RemoveData(_CursorWhichToString(which));
153}
154
155
156/*!
157	\brief Retrieves a cursor from the set.
158	\param which System cursor specifier defined in CursorSet.h
159	\param cursor Bitmap** to receive a newly-allocated BBitmap containing the appropriate data
160	\param hotspot The recipient of the hotspot for the cursor
161	\return
162	- \c B_OK: Success
163	- \c B_BAD_VALUE: a NULL parameter was passed
164	- \c B_NAME_NOT_FOUND: The specified cursor does not exist in this set
165	- \c B_ERROR: An internal error occurred
166
167	BBitmaps created by this function are the responsibility of the caller.
168*/
169status_t
170CursorSet::FindCursor(BCursorID which, BBitmap **_cursor, BPoint *_hotspot)
171{
172	if (_cursor == NULL || _hotspot == NULL)
173		return B_BAD_VALUE;
174
175	BMessage message;
176	if (FindMessage(_CursorWhichToString(which), &message) != B_OK)
177		return B_NAME_NOT_FOUND;
178
179	const char *className;
180	if (message.FindString("class", &className) != B_OK
181		|| strcmp(className, "cursor") != 0) {
182		return B_ERROR;
183	}
184
185	BPoint hotspot;
186	if (message.FindPoint("hotspot", &hotspot) != B_OK)
187		return B_ERROR;
188
189	const void *buffer;
190	int32 bufferLength;
191	if (message.FindData("_data", B_RAW_TYPE, (const void **)&buffer,
192			(ssize_t *)&bufferLength) != B_OK) {
193		return B_ERROR;
194	}
195
196	BBitmap *bitmap = new(std::nothrow) BBitmap(message.FindRect("_frame"),
197		(color_space)message.FindInt32("_cspace"), true);
198	if (bitmap == NULL)
199		return B_NO_MEMORY;
200
201	memcpy(bitmap->Bits(), buffer,
202		min_c(bufferLength, bitmap->BitsLength()));
203
204	*_cursor = bitmap;
205	*_hotspot = hotspot;
206	return B_OK;
207}
208
209
210/*!
211	\brief Retrieves a cursor from the set.
212	\param which System cursor specifier defined in CursorSet.h
213	\param cursor ServerCursor** to receive a newly-allocated ServerCursor containing the appropriate data
214	\return
215	- \c B_OK: Success
216	- \c B_BAD_VALUE: a NULL parameter was passed
217	- \c B_NAME_NOT_FOUND: The specified cursor does not exist in this set
218	- \c B_ERROR: An internal error occurred
219
220	BBitmaps created by this function are the responsibility of the caller.
221*/
222status_t
223CursorSet::FindCursor(BCursorID which, ServerCursor **_cursor) const
224{
225	if (_cursor == NULL)
226		return B_BAD_VALUE;
227
228	BMessage message;
229	if (FindMessage(_CursorWhichToString(which), &message) != B_OK)
230		return B_NAME_NOT_FOUND;
231
232	const char *className;
233	if (message.FindString("class", &className) != B_OK
234		|| strcmp(className, "cursor") != 0) {
235		return B_ERROR;
236	}
237
238	BPoint hotspot;
239	if (message.FindPoint("hotspot", &hotspot) != B_OK)
240		return B_ERROR;
241
242	const void *buffer;
243	int32 bufferLength;
244	if (message.FindData("_data", B_RAW_TYPE, (const void **)&buffer,
245			(ssize_t *)&bufferLength) != B_OK) {
246		return B_ERROR;
247	}
248
249	ServerCursor *cursor = new(std::nothrow) ServerCursor(
250		message.FindRect("_frame"), (color_space)message.FindInt32("_cspace"),
251		0, hotspot);
252	if (cursor == NULL)
253		return B_NO_MEMORY;
254
255	memcpy(cursor->Bits(), buffer,
256		min_c(bufferLength, (ssize_t)cursor->BitsLength()));
257
258	*_cursor = cursor;
259	return B_OK;
260}
261
262
263/*!
264	\brief Returns the name of the set
265	\return The name of the set
266*/
267const char *
268CursorSet::GetName()
269{
270	const char *name;
271	if (FindString("name", &name) == B_OK)
272		return name;
273
274	return NULL;
275}
276
277
278/*!
279	\brief Renames the cursor set
280	\param name new name of the set.
281
282	This function will fail if given a NULL name
283*/
284void
285CursorSet::SetName(const char *name)
286{
287	if (name == NULL)
288		return;
289
290	RemoveData("name");
291	AddString("name", name);
292}
293
294
295/*!
296	\brief Returns a string for the specified cursor attribute
297	\param which System cursor specifier defined in CursorSet.h
298	\return Name for the cursor specifier
299*/
300const char *
301CursorSet::_CursorWhichToString(BCursorID which) const
302{
303	switch (which) {
304		case B_CURSOR_ID_SYSTEM_DEFAULT:
305			return "System default";
306		case B_CURSOR_ID_CONTEXT_MENU:
307			return "Context menu";
308		case B_CURSOR_ID_COPY:
309			return "Copy";
310		case B_CURSOR_ID_CROSS_HAIR:
311			return "Cross hair";
312		case B_CURSOR_ID_NO_CURSOR:
313			return "No cursor";
314		case B_CURSOR_ID_FOLLOW_LINK:
315			return "Follow link";
316		case B_CURSOR_ID_GRAB:
317			return "Grab";
318		case B_CURSOR_ID_GRABBING:
319			return "Grabbing";
320		case B_CURSOR_ID_HELP:
321			return "Help";
322		case B_CURSOR_ID_I_BEAM:
323			return "I-beam";
324		case B_CURSOR_ID_I_BEAM_HORIZONTAL:
325			return "I-beam horizontal";
326		case B_CURSOR_ID_MOVE:
327			return "Move";
328		case B_CURSOR_ID_NOT_ALLOWED:
329			return "Not allowed";
330		case B_CURSOR_ID_PROGRESS:
331			return "Progress";
332		case B_CURSOR_ID_RESIZE_NORTH:
333			return "Resize North";
334		case B_CURSOR_ID_RESIZE_EAST:
335			return "Resize East";
336		case B_CURSOR_ID_RESIZE_SOUTH:
337			return "Resize South";
338		case B_CURSOR_ID_RESIZE_WEST:
339			return "Resize West";
340		case B_CURSOR_ID_RESIZE_NORTH_EAST:
341			return "Resize North East";
342		case B_CURSOR_ID_RESIZE_NORTH_WEST:
343			return "Resize North West";
344		case B_CURSOR_ID_RESIZE_SOUTH_EAST:
345			return "Resize South East";
346		case B_CURSOR_ID_RESIZE_SOUTH_WEST:
347			return "Resize South West";
348		case B_CURSOR_ID_RESIZE_NORTH_SOUTH:
349			return "Resize North South";
350		case B_CURSOR_ID_RESIZE_EAST_WEST:
351			return "Resize East West";
352		case B_CURSOR_ID_RESIZE_NORTH_EAST_SOUTH_WEST:
353			return "Resize North East South West";
354		case B_CURSOR_ID_RESIZE_NORTH_WEST_SOUTH_EAST:
355			return "Resize North West South East";
356		case B_CURSOR_ID_ZOOM_IN:
357			return "Zoom in";
358		case B_CURSOR_ID_ZOOM_OUT:
359			return "Zoom out";
360		default:
361			return "Invalid";
362	}
363}
364
365/*!
366	\brief Creates a new BBitmap from R5 cursor data
367	\param data Pointer to data in the R5 cursor data format
368	\return NULL if data was NULL, otherwise a new BBitmap
369
370	BBitmaps returned by this function are always in the RGBA32 color space
371*/
372BBitmap *
373CursorSet::_CursorDataToBitmap(uint8 *data)
374{
375	// 68-byte array used in R5 for holding cursors.
376	// This API has serious problems and should be deprecated (but supported)
377	// in R2
378
379	if (data == NULL)
380		return NULL;
381
382	// Now that we have all the setup, we're going to map (for now) the cursor
383	// to RGBA32. Eventually, there will be support for 16 and 8-bit depths
384	BBitmap *bitmap
385		= new(std::nothrow) BBitmap(BRect(0,0,15,15), B_RGBA32, 0);
386	if (bitmap == NULL)
387		return NULL;
388
389	const uint32 black = 0xff000000;
390	const uint32 white = 0xffffffff;
391
392	uint8 *buffer = (uint8 *)bitmap->Bits();
393	uint16 *cursorPosition = (uint16 *)(data + 4);
394	uint16 *maskPosition = (uint16 *)(data + 36);
395
396	// for each row in the cursor data
397	for (uint8 y = 0; y < 16; y++) {
398		uint32 *bitmapPosition
399			= (uint32 *)(buffer + y * bitmap->BytesPerRow());
400
401		// TODO: use proper byteswap macros
402		// On intel, our bytes end up swapped, so we must swap them back
403		uint16 cursorFlip = (cursorPosition[y] & 0xff) << 8;
404		cursorFlip |= (cursorPosition[y] & 0xff00) >> 8;
405
406		uint16 maskFlip = (maskPosition[y] & 0xff) << 8;
407		maskFlip |= (maskPosition[y] & 0xff00) >> 8;
408
409		// for each column in each row of cursor data
410		for (uint8 x = 0; x < 16; x++) {
411			// Get the values and dump them to the bitmap
412			uint16 bit = 1 << (15 - x);
413			bitmapPosition[x] = ((cursorFlip & bit) != 0 ? black : white)
414				& ((maskFlip & bit) != 0 ? 0xffffffff : 0x00ffffff);
415		}
416	}
417
418	return bitmap;
419}
420