1/*
2 * tkMacOSXCursor.c --
3 *
4 *	This file contains Macintosh specific cursor related routines.
5 *
6 * Copyright (c) 1995-1997 Sun Microsystems, Inc.
7 * Copyright 2001, Apple Computer, Inc.
8 * Copyright (c) 2006-2007 Daniel A. Steffen <das@users.sourceforge.net>
9 *
10 * See the file "license.terms" for information on usage and redistribution
11 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
12 *
13 * RCS: @(#) $Id: tkMacOSXCursor.c,v 1.4.2.6 2007/06/29 03:22:01 das Exp $
14 */
15
16#include "tkMacOSXPrivate.h"
17
18/*
19 * There are three different ways to set the cursor on the Mac.
20 * The default theme cursors (listed in cursorNames below),
21 * color resource cursors, & normal cursors.
22 */
23
24#define NONE		-1	/* Hidden cursor */
25#define THEME		0	/* Theme cursors */
26#define ANIMATED	1	/* Animated theme cursors */
27#define COLOR		2	/* Cursors of type crsr. */
28#define NORMAL		3	/* Cursors of type CURS. */
29
30/*
31 * The following data structure contains the system specific data
32 * necessary to control Windows cursors.
33 */
34
35typedef struct {
36    TkCursor info;		/* Generic cursor info used by tkCursor.c */
37    Handle macCursor;		/* Resource containing Macintosh cursor.
38				 * For theme cursors, this is -1. */
39    int type;			/* Type of Mac cursor: for theme cursors
40				 * this is the theme cursor constant,
41				 * otherwise one of crsr or CURS */
42    int count;			/* For animating cursors, the count for the
43				 * cursor. */
44} TkMacOSXCursor;
45
46/*
47 * The table below is used to map from the name of a predefined cursor
48 * to its resource identifier.
49 */
50
51struct CursorName {
52    const char *name;
53    int id;
54};
55
56static struct CursorName noneCursorName = {"none", 0};
57
58static struct CursorName themeCursorNames[] = {
59    {"arrow",			kThemeArrowCursor},
60    {"copyarrow",		kThemeCopyArrowCursor},
61    {"aliasarrow",		kThemeAliasArrowCursor},
62    {"contextualmenuarrow",	kThemeContextualMenuArrowCursor},
63    {"ibeam",			kThemeIBeamCursor},
64    {"text",			kThemeIBeamCursor},
65    {"xterm",			kThemeIBeamCursor},
66    {"cross",			kThemeCrossCursor},
67    {"crosshair",		kThemeCrossCursor},
68    {"cross-hair",		kThemeCrossCursor},
69    {"plus",			kThemePlusCursor},
70    {"closedhand",		kThemeClosedHandCursor},
71    {"openhand",		kThemeOpenHandCursor},
72    {"pointinghand",		kThemePointingHandCursor},
73    {"resizeleft",		kThemeResizeLeftCursor},
74    {"resizeright",		kThemeResizeRightCursor},
75    {"resizeleftright",		kThemeResizeLeftRightCursor},
76    {"resizeup",		kThemeResizeUpCursor},
77    {"resizedown",		kThemeResizeDownCursor},
78    {"resizeupdown",		kThemeResizeUpDownCursor},
79    {"notallowed",		kThemeNotAllowedCursor},
80    {"poof",			kThemePoofCursor},
81    {NULL,			0}
82};
83
84static struct CursorName animatedThemeCursorNames[] = {
85    {"watch",			kThemeWatchCursor},
86    {"countinguphand",		kThemeCountingUpHandCursor},
87    {"countingdownhand",	kThemeCountingDownHandCursor},
88    {"countingupanddownhand",	kThemeCountingUpAndDownHandCursor},
89    {"spinning",		kThemeSpinningCursor},
90    {NULL,			0}
91};
92
93/*
94 * Declarations of static variables used in this file.
95 */
96
97static TkMacOSXCursor * gCurrentCursor = NULL;	/* A pointer to the current
98						 * cursor. */
99static int gResizeOverride = false;	/* A boolean indicating whether
100					 * we should use the resize
101					 * cursor during installations. */
102static int gTkOwnsCursor = true;	/* A boolean indicating whether
103					 * Tk owns the cursor. If not (for
104					 * instance, in the case where a Tk
105					 * window is embedded in another app's
106					 * window, and the cursor is out of
107					 * the tk window, we will not attempt
108					 * to adjust the cursor */
109
110/*
111 * Declarations of procedures local to this file
112 */
113
114static void FindCursorByName(TkMacOSXCursor *macCursorPtr, const char *string);
115
116
117/*
118 *----------------------------------------------------------------------
119 *
120 * FindCursorByName --
121 *
122 *	Retrieve a system cursor by name, and fill the macCursorPtr
123 *	structure. If the cursor cannot be found, the macCursor field
124 *	will be NULL. The function first attempts to load a color
125 *	cursor. If that fails it will attempt to load a black & white
126 *	cursor.
127 *
128 * Results:
129 *	Fills the macCursorPtr record.
130 *
131 * Side effects:
132 *	None
133 *
134 *----------------------------------------------------------------------
135 */
136
137void
138FindCursorByName(
139    TkMacOSXCursor *macCursorPtr,
140    const char *string)
141{
142    Handle resource;
143    Str255 curName;
144    int destWrote, inCurLen;
145    Tcl_Encoding encoding;
146
147    inCurLen = strlen(string);
148    if (inCurLen > 255) {
149	return;
150    }
151
152    /*
153     * macRoman is the encoding that the resource fork uses.
154     */
155
156    encoding = Tcl_GetEncoding(NULL, "macRoman");
157    Tcl_UtfToExternal(NULL, encoding, string, inCurLen, 0, NULL,
158	    (char *) &curName[1], 255, NULL, &destWrote, NULL);
159    curName[0] = destWrote;
160    Tcl_FreeEncoding(encoding);
161
162    resource = GetNamedResource('crsr', curName);
163    if (resource) {
164	short id;
165	Str255 theName;
166	ResType theType;
167
168	GetResInfo(resource, &id, &theType, theName);
169	macCursorPtr->macCursor = (Handle) GetCCursor(id);
170	macCursorPtr->type = COLOR;
171    } else {
172	macCursorPtr->macCursor = GetNamedResource('CURS', curName);
173	macCursorPtr->type = NORMAL;
174    }
175}
176
177/*
178 *----------------------------------------------------------------------
179 *
180 * TkGetCursorByName --
181 *
182 *	Retrieve a system cursor by name.
183 *
184 * Results:
185 *	Returns a new cursor, or NULL on errors.
186 *
187 * Side effects:
188 *	Allocates a new cursor.
189 *
190 *----------------------------------------------------------------------
191 */
192
193TkCursor *
194TkGetCursorByName(
195    Tcl_Interp *interp,		/* Interpreter to use for error reporting. */
196    Tk_Window tkwin,		/* Window in which cursor will be used. */
197    Tk_Uid string)		/* Description of cursor. See manual entry
198				 * for details on legal syntax. */
199{
200    struct CursorName *namePtr;
201    TkMacOSXCursor *macCursorPtr;
202    int count = -1;
203
204    macCursorPtr = (TkMacOSXCursor *) ckalloc(sizeof(TkMacOSXCursor));
205    macCursorPtr->info.cursor = (Tk_Cursor) macCursorPtr;
206
207    /*
208     * To find a cursor we must first determine if it is one of the
209     * builtin cursors or the standard arrow cursor. Otherwise, we
210     * attempt to load the cursor as a named Mac resource.
211     */
212
213    if (strcmp(noneCursorName.name, string) == 0) {
214	namePtr = &noneCursorName;
215	macCursorPtr->type = NONE;
216    } else {
217	for (namePtr = themeCursorNames; namePtr->name != NULL; namePtr++) {
218	    if (strcmp(namePtr->name, string) == 0) {
219		macCursorPtr->type = THEME;
220		break;
221	    }
222	}
223    }
224
225    if (namePtr->name == NULL) {
226	for (namePtr = animatedThemeCursorNames;
227		namePtr->name != NULL; namePtr++) {
228	    int namelen = strlen (namePtr->name);
229	    if (strncmp(namePtr->name, string, namelen) == 0) {
230		const char *numPtr = string + namelen;
231		if (*numPtr) {
232		    int result = Tcl_GetInt(NULL, numPtr, &count);
233		    if (result != TCL_OK) {
234			continue;
235		    }
236		}
237		macCursorPtr->type = ANIMATED;
238		break;
239	    }
240	}
241    }
242
243    if (namePtr->name != NULL) {
244	macCursorPtr->macCursor = (Handle) namePtr;
245	macCursorPtr->count = count;
246    } else {
247	FindCursorByName(macCursorPtr, string);
248
249	if (macCursorPtr->macCursor == NULL) {
250	    const char **argv;
251	    int argc;
252
253	    /*
254	     * The user may be trying to specify an XCursor with fore
255	     * & back colors. We don't want this to be an error, so pick
256	     * off the first word, and try again.
257	     */
258
259	    if (Tcl_SplitList(interp, string, &argc, &argv) == TCL_OK ) {
260		if (argc > 1) {
261		    FindCursorByName(macCursorPtr, argv[0]);
262		}
263		ckfree((char *) argv);
264	    }
265	}
266    }
267
268    if (macCursorPtr->macCursor == NULL) {
269	ckfree((char *)macCursorPtr);
270	Tcl_AppendResult(interp, "bad cursor spec \"", string, "\"", NULL);
271	return NULL;
272    } else {
273	return (TkCursor *) macCursorPtr;
274    }
275}
276
277/*
278 *----------------------------------------------------------------------
279 *
280 * TkCreateCursorFromData --
281 *
282 *	Creates a cursor from the source and mask bits.
283 *
284 * Results:
285 *	Returns a new cursor, or NULL on errors.
286 *
287 * Side effects:
288 *	Allocates a new cursor.
289 *
290 *----------------------------------------------------------------------
291 */
292
293TkCursor *
294TkCreateCursorFromData(
295    Tk_Window tkwin,		/* Window in which cursor will be used. */
296    CONST char *source,		/* Bitmap data for cursor shape. */
297    CONST char *mask,		/* Bitmap data for cursor mask. */
298    int width, int height,	/* Dimensions of cursor. */
299    int xHot, int yHot,		/* Location of hot-spot in cursor. */
300    XColor fgColor,		/* Foreground color for cursor. */
301    XColor bgColor)		/* Background color for cursor. */
302{
303    return NULL;
304}
305
306/*
307 *----------------------------------------------------------------------
308 *
309 * TkpFreeCursor --
310 *
311 *	This procedure is called to release a cursor allocated by
312 *	TkGetCursorByName.
313 *
314 * Results:
315 *	None.
316 *
317 * Side effects:
318 *	The cursor data structure is deallocated.
319 *
320 *----------------------------------------------------------------------
321 */
322
323void
324TkpFreeCursor(
325    TkCursor *cursorPtr)
326{
327    TkMacOSXCursor *macCursorPtr = (TkMacOSXCursor *) cursorPtr;
328
329    switch (macCursorPtr->type) {
330	case COLOR:
331	    DisposeCCursor((CCrsrHandle) macCursorPtr->macCursor);
332	    break;
333	case NORMAL:
334	    ReleaseResource(macCursorPtr->macCursor);
335	    break;
336    }
337
338    if (macCursorPtr == gCurrentCursor) {
339	gCurrentCursor = NULL;
340    }
341}
342
343/*
344 *----------------------------------------------------------------------
345 *
346 * TkMacOSXInstallCursor --
347 *
348 *	Installs either the current cursor as defined by TkpSetCursor
349 *	or a resize cursor as the cursor the Macintosh should currently
350 *	display.
351 *
352 * Results:
353 *	None.
354 *
355 * Side effects:
356 *	Changes the Macintosh mouse cursor.
357 *
358 *----------------------------------------------------------------------
359 */
360
361void
362TkMacOSXInstallCursor(
363    int resizeOverride)
364{
365    TkMacOSXCursor *macCursorPtr = gCurrentCursor;
366    CCrsrHandle ccursor;
367    CursHandle cursor;
368    static unsigned int cursorStep = 0;
369    static int cursorHidden = 0;
370    int cursorNone = 0;
371
372    gResizeOverride = resizeOverride;
373
374    if (resizeOverride) {
375	cursor = (CursHandle) GetNamedResource('CURS', "\presize");
376	if (cursor) {
377	    SetCursor(*cursor);
378	} else {
379	    TkMacOSXDbgMsg("Resize cursor failed: %d", ResError());
380	}
381    } else if (macCursorPtr == NULL) {
382	SetThemeCursor(kThemeArrowCursor);
383    } else {
384	struct CursorName *namePtr;
385	switch (macCursorPtr->type) {
386	    case NONE:
387		if (!cursorHidden) {
388		    cursorHidden = 1;
389		    HideCursor();
390		}
391		cursorNone = 1;
392		break;
393	    case THEME:
394		namePtr = (struct CursorName *) macCursorPtr->macCursor;
395		SetThemeCursor(
396			namePtr->id);
397		break;
398	    case ANIMATED:
399		namePtr = (struct CursorName *) macCursorPtr->macCursor;
400		if (macCursorPtr->count == -1) {
401		    SetAnimatedThemeCursor(namePtr->id, cursorStep++);
402		} else {
403		    SetAnimatedThemeCursor(namePtr->id, macCursorPtr->count);
404		}
405		break;
406	    case COLOR:
407		ccursor = (CCrsrHandle) macCursorPtr->macCursor;
408		SetCCursor(ccursor);
409		break;
410	    case NORMAL:
411		cursor = (CursHandle) macCursorPtr->macCursor;
412		SetCursor(*cursor);
413		break;
414	}
415    }
416    if (cursorHidden && !cursorNone) {
417	cursorHidden = 0;
418	ShowCursor();
419    }
420}
421
422/*
423 *----------------------------------------------------------------------
424 *
425 * TkpSetCursor --
426 *
427 *	Set the current cursor and install it.
428 *
429 * Results:
430 *	None.
431 *
432 * Side effects:
433 *	Changes the current cursor.
434 *
435 *----------------------------------------------------------------------
436 */
437
438void
439TkpSetCursor(
440    TkpCursor cursor)
441{
442    int cursorChanged = 1;
443
444    if (!gTkOwnsCursor) {
445	return;
446    }
447
448    if (cursor == None) {
449	/*
450	 * This is a little tricky. We can't really tell whether
451	 * gCurrentCursor is NULL because it was NULL last time around
452	 * or because we just freed the current cursor. So if the input
453	 * cursor is NULL, we always need to reset it, we can't trust the
454	 * cursorChanged logic.
455	 */
456
457	gCurrentCursor = NULL;
458    } else {
459	if (gCurrentCursor == (TkMacOSXCursor *) cursor) {
460	    cursorChanged = 0;
461	}
462	gCurrentCursor = (TkMacOSXCursor *) cursor;
463    }
464
465    if (Tk_MacOSXIsAppInFront() && cursorChanged) {
466	TkMacOSXInstallCursor(gResizeOverride);
467    }
468}
469
470/*
471 *----------------------------------------------------------------------
472 *
473 * Tk_MacOSXTkOwnsCursor --
474 *
475 *	Sets whether Tk has the right to adjust the cursor.
476 *
477 * Results:
478 *	None.
479 *
480 * Side effects:
481 *	May keep Tk from changing the cursor.
482 *
483 *----------------------------------------------------------------------
484 */
485
486void
487Tk_MacOSXTkOwnsCursor(
488    int tkOwnsIt)
489{
490    gTkOwnsCursor = tkOwnsIt;
491}
492