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 <AutoDeleter.h>
15#include <PortLink.h>
16#include <ServerProtocol.h>
17
18#include <OS.h>
19#include <String.h>
20#include <File.h>
21
22#include <new>
23
24
25/*!
26	\brief Constructor
27	\name Name of the cursor set.
28*/
29CursorSet::CursorSet(const char *name)
30	: BMessage()
31{
32	AddString("name", name != NULL ? name : "Untitled");
33}
34
35
36/*!
37	\brief Saves the data in the cursor set to a file
38	\param path Path of the file to save to.
39	\param saveflags BFile open file flags. B_READ_WRITE is implied.
40	\return
41	- \c B_OK: Everything went fine.
42	- \c B_BAD_VALUE: path is NULL
43	- \c other value: See BFile::SetTo and BMessage::Flatten return codes
44*/
45status_t
46CursorSet::Save(const char *path, int32 saveFlags)
47{
48	if (path == NULL)
49		return B_BAD_VALUE;
50
51	BFile file;
52	status_t status = file.SetTo(path, B_READ_WRITE | saveFlags);
53	if (status != B_OK)
54		return status;
55
56	return Flatten(&file);
57}
58
59
60/*!
61	\brief Loads the data into the cursor set from a file
62	\param path Path of the file to load from.
63	\return
64	- \c B_OK: Everything went fine.
65	- \c B_BAD_VALUE: path is NULL
66	- \c other value: See BFile::SetTo and BMessage::Flatten return codes
67*/
68status_t
69CursorSet::Load(const char *path)
70{
71	if (path == NULL)
72		return B_BAD_VALUE;
73
74	BFile file;
75	status_t status = file.SetTo(path, B_READ_ONLY);
76	if (status != B_OK)
77		return status;
78
79	return Unflatten(&file);
80}
81
82
83/*!
84	\brief Adds the cursor to the set and replaces any existing entry for the given specifier
85	\param which System cursor specifier defined in CursorSet.h
86	\param cursor BBitmap to represent the new cursor. Size should be 48x48 or less.
87	\param hotspot The recipient of the hotspot for the cursor
88	\return
89	- \c B_OK: Everything went fine.
90	- \c B_BAD_VALUE: cursor is NULL
91	- \c other value: See BMessage::AddMessage return codes.
92*/
93status_t
94CursorSet::AddCursor(BCursorID which, const BBitmap *cursor,
95	const BPoint &hotspot)
96{
97	if (cursor == NULL)
98		return B_BAD_VALUE;
99
100	// Remove the data if it exists already
101	RemoveData(_CursorWhichToString(which));
102
103	// Actually add the data to our set
104	BMessage message((int32)which);
105
106	message.AddString("class", "bitmap");
107	message.AddRect("_frame", cursor->Bounds());
108	message.AddInt32("_cspace", cursor->ColorSpace());
109
110	message.AddInt32("_bmflags", 0);
111	message.AddInt32("_rowbytes", cursor->BytesPerRow());
112	message.AddPoint("hotspot", hotspot);
113	message.AddData("_data", B_RAW_TYPE, cursor->Bits(), cursor->BitsLength());
114
115	return AddMessage(_CursorWhichToString(which), &message);
116}
117
118
119/*!
120	\brief Adds the cursor to the set and replaces any existing entry for the given specifier
121	\param which System cursor specifier defined in CursorSet.h
122	\param data R5 cursor data pointer
123	\return B_BAD_VALUE if data is NULL, otherwise B_OK
124
125	When possible, it is better to use the BBitmap version of AddCursor because this
126	function must convert the R5 cursor data into a BBitmap
127*/
128status_t
129CursorSet::AddCursor(BCursorID which, uint8 *data)
130{
131	// Convert cursor data to a bitmap because all cursors are internally stored
132	// as bitmaps
133	if (data == NULL)
134		return B_BAD_VALUE;
135
136	ObjectDeleter<BBitmap> bitmap(_CursorDataToBitmap(data));
137	BPoint hotspot(data[2], data[3]);
138
139	status_t result = AddCursor(which, bitmap.Get(), hotspot);
140
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