1/*
2 * tkUnixCursor.c --
3 *
4 *	This file contains X specific cursor manipulation routines.
5 *
6 * Copyright (c) 1995-1997 Sun Microsystems, Inc.
7 *
8 * See the file "license.terms" for information on usage and redistribution of
9 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
10 *
11 * RCS: @(#) $Id$
12 */
13
14#include "tkInt.h"
15
16/*
17 * The following data structure is a superset of the TkCursor structure
18 * defined in tkCursor.c. Each system specific cursor module will define a
19 * different cursor structure. All of these structures must have the same
20 * header consisting of the fields in TkCursor.
21 */
22
23typedef struct {
24    TkCursor info;		/* Generic cursor info used by tkCursor.c */
25    Display *display;		/* Display for which cursor is valid. */
26} TkUnixCursor;
27
28/*
29 * The table below is used to map from the name of a cursor to its index in
30 * the official cursor font:
31 */
32
33static struct CursorName {
34    CONST char *name;
35    unsigned int shape;
36} cursorNames[] = {
37    {"X_cursor",		XC_X_cursor},
38    {"arrow",			XC_arrow},
39    {"based_arrow_down",	XC_based_arrow_down},
40    {"based_arrow_up",		XC_based_arrow_up},
41    {"boat",			XC_boat},
42    {"bogosity",		XC_bogosity},
43    {"bottom_left_corner",	XC_bottom_left_corner},
44    {"bottom_right_corner",	XC_bottom_right_corner},
45    {"bottom_side",		XC_bottom_side},
46    {"bottom_tee",		XC_bottom_tee},
47    {"box_spiral",		XC_box_spiral},
48    {"center_ptr",		XC_center_ptr},
49    {"circle",			XC_circle},
50    {"clock",			XC_clock},
51    {"coffee_mug",		XC_coffee_mug},
52    {"cross",			XC_cross},
53    {"cross_reverse",		XC_cross_reverse},
54    {"crosshair",		XC_crosshair},
55    {"diamond_cross",		XC_diamond_cross},
56    {"dot",			XC_dot},
57    {"dotbox",			XC_dotbox},
58    {"double_arrow",		XC_double_arrow},
59    {"draft_large",		XC_draft_large},
60    {"draft_small",		XC_draft_small},
61    {"draped_box",		XC_draped_box},
62    {"exchange",		XC_exchange},
63    {"fleur",			XC_fleur},
64    {"gobbler",			XC_gobbler},
65    {"gumby",			XC_gumby},
66    {"hand1",			XC_hand1},
67    {"hand2",			XC_hand2},
68    {"heart",			XC_heart},
69    {"icon",			XC_icon},
70    {"iron_cross",		XC_iron_cross},
71    {"left_ptr",		XC_left_ptr},
72    {"left_side",		XC_left_side},
73    {"left_tee",		XC_left_tee},
74    {"leftbutton",		XC_leftbutton},
75    {"ll_angle",		XC_ll_angle},
76    {"lr_angle",		XC_lr_angle},
77    {"man",			XC_man},
78    {"middlebutton",		XC_middlebutton},
79    {"mouse",			XC_mouse},
80    {"pencil",			XC_pencil},
81    {"pirate",			XC_pirate},
82    {"plus",			XC_plus},
83    {"question_arrow",		XC_question_arrow},
84    {"right_ptr",		XC_right_ptr},
85    {"right_side",		XC_right_side},
86    {"right_tee",		XC_right_tee},
87    {"rightbutton",		XC_rightbutton},
88    {"rtl_logo",		XC_rtl_logo},
89    {"sailboat",		XC_sailboat},
90    {"sb_down_arrow",		XC_sb_down_arrow},
91    {"sb_h_double_arrow",	XC_sb_h_double_arrow},
92    {"sb_left_arrow",		XC_sb_left_arrow},
93    {"sb_right_arrow",		XC_sb_right_arrow},
94    {"sb_up_arrow",		XC_sb_up_arrow},
95    {"sb_v_double_arrow",	XC_sb_v_double_arrow},
96    {"shuttle",			XC_shuttle},
97    {"sizing",			XC_sizing},
98    {"spider",			XC_spider},
99    {"spraycan",		XC_spraycan},
100    {"star",			XC_star},
101    {"target",			XC_target},
102    {"tcross",			XC_tcross},
103    {"top_left_arrow",		XC_top_left_arrow},
104    {"top_left_corner",		XC_top_left_corner},
105    {"top_right_corner",	XC_top_right_corner},
106    {"top_side",		XC_top_side},
107    {"top_tee",			XC_top_tee},
108    {"trek",			XC_trek},
109    {"ul_angle",		XC_ul_angle},
110    {"umbrella",		XC_umbrella},
111    {"ur_angle",		XC_ur_angle},
112    {"watch",			XC_watch},
113    {"xterm",			XC_xterm},
114    {NULL,			0}
115};
116
117/*
118 * The table below is used to map from a cursor name to the data that defines
119 * the cursor. This table is used for cursors defined by Tk that don't exist
120 * in the X cursor table.
121 */
122
123#define CURSOR_NONE_DATA \
124"#define none_width 1\n" \
125"#define none_height 1\n" \
126"#define none_x_hot 0\n" \
127"#define none_y_hot 0\n" \
128"static unsigned char none_bits[] = {\n" \
129"  0x00};"
130
131/*
132 * Define test cursor to check that mask fg and bg color settings are working.
133 *
134 * . configure -cursor {center_ptr green red}
135 * . configure -cursor {@myarrow.xbm myarrow-mask.xbm green red}
136 * . configure -cursor {myarrow green red}
137 */
138
139/*#define DEFINE_MYARROW_CURSOR*/
140
141#ifdef DEFINE_MYARROW_CURSOR
142#define CURSOR_MYARROW_DATA \
143"#define myarrow_width 16\n" \
144"#define myarrow_height 16\n" \
145"#define myarrow_x_hot 7\n" \
146"#define myarrow_y_hot 0\n" \
147"static unsigned char myarrow_bits[] = {\n" \
148"   0x7f, 0xff, 0xbf, 0xfe, 0xdf, 0xfd, 0xef, 0xfb, 0xf7, 0xf7, 0xfb, 0xef,\n" \
149"   0xfd, 0xdf, 0xfe, 0xbf, 0x80, 0x00, 0xbf, 0xfe, 0xbf, 0xfe, 0xbf, 0xfe,\n" \
150"   0xbf, 0xfe, 0xbf, 0xfe, 0xbf, 0xfe, 0x3f, 0xfe};"
151
152#define CURSOR_MYARROW_MASK \
153"#define myarrow-mask_width 16\n" \
154"#define myarrow-mask_height 16\n" \
155"#define myarrow-mask_x_hot 7\n" \
156"#define myarrow-mask_y_hot 0\n" \
157"static unsigned char myarrow-mask_bits[] = {\n" \
158"   0x80, 0x00, 0xc0, 0x01, 0xe0, 0x03, 0xf0, 0x07, 0xf8, 0x0f, 0xfc, 0x1f,\n" \
159"   0xfe, 0x3f, 0xff, 0x7f, 0xff, 0xff, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01,\n" \
160"   0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01};"
161
162#endif /* DEFINE_MYARROW_CURSOR */
163
164static struct TkCursorName {
165    char *name;
166    char *data;
167    char *mask;
168} tkCursorNames[] = {
169    {"none",	CURSOR_NONE_DATA,	NULL},
170#ifdef DEFINE_MYARROW_CURSOR
171    {"myarrow",	CURSOR_MYARROW_DATA,	CURSOR_MYARROW_MASK},
172#endif /* DEFINE_MYARROW_CURSOR */
173    {NULL,	NULL,			NULL}
174};
175
176/*
177 * Font to use for cursors:
178 */
179
180#ifndef CURSORFONT
181#define CURSORFONT "cursor"
182#endif
183
184static Cursor		CreateCursorFromTableOrFile(Tcl_Interp *interp,
185			    Tk_Window tkwin, int argc, CONST char **argv,
186			    struct TkCursorName *tkCursorPtr);
187
188/*
189 *----------------------------------------------------------------------
190 *
191 * TkGetCursorByName --
192 *
193 *	Retrieve a cursor by name. Parse the cursor name into fields and
194 *	create a cursor, either from the standard cursor font or from bitmap
195 *	files.
196 *
197 * Results:
198 *	Returns a new cursor, or NULL on errors.
199 *
200 * Side effects:
201 *	Allocates a new cursor.
202 *
203 *----------------------------------------------------------------------
204 */
205
206TkCursor *
207TkGetCursorByName(
208    Tcl_Interp *interp,		/* Interpreter to use for error reporting. */
209    Tk_Window tkwin,		/* Window in which cursor will be used. */
210    Tk_Uid string)		/* Description of cursor. See manual entry for
211				 * details on legal syntax. */
212{
213    TkUnixCursor *cursorPtr = NULL;
214    Cursor cursor = None;
215    int argc;
216    CONST char **argv = NULL;
217    Display *display = Tk_Display(tkwin);
218    int inTkTable = 0;
219    struct TkCursorName* tkCursorPtr = NULL;
220
221    if (Tcl_SplitList(interp, string, &argc, &argv) != TCL_OK) {
222	return NULL;
223    }
224    if (argc == 0) {
225	goto badString;
226    }
227
228    /*
229     * Check Tk specific table of cursor names. The cursor names don't overlap
230     * with cursors defined in the X table so search order does not matter.
231     */
232
233    if (argv[0][0] != '@') {
234	for (tkCursorPtr = tkCursorNames; ; tkCursorPtr++) {
235	    if (tkCursorPtr->name == NULL) {
236		tkCursorPtr = NULL;
237		break;
238	    }
239	    if ((tkCursorPtr->name[0] == argv[0][0]) &&
240		    (strcmp(tkCursorPtr->name, argv[0]) == 0)) {
241		inTkTable = 1;
242		break;
243	    }
244	}
245    }
246
247    if ((argv[0][0] != '@') && !inTkTable) {
248	XColor fg, bg;
249	unsigned int maskIndex;
250	register struct CursorName *namePtr;
251	TkDisplay *dispPtr;
252
253	/*
254	 * The cursor is to come from the standard cursor font. If one arg, it
255	 * is cursor name (use black and white for fg and bg). If two args,
256	 * they are name and fg color (ignore mask). If three args, they are
257	 * name, fg, bg. Some of the code below is stolen from the
258	 * XCreateFontCursor Xlib function.
259	 */
260
261	if (argc > 3) {
262	    goto badString;
263	}
264	for (namePtr = cursorNames; ; namePtr++) {
265	    if (namePtr->name == NULL) {
266		goto badString;
267	    }
268	    if ((namePtr->name[0] == argv[0][0])
269		    && (strcmp(namePtr->name, argv[0]) == 0)) {
270		break;
271	    }
272	}
273
274	maskIndex = namePtr->shape + 1;
275	if (argc == 1) {
276	    fg.red = fg.green = fg.blue = 0;
277	    bg.red = bg.green = bg.blue = 65535;
278	} else {
279	    if (XParseColor(display, Tk_Colormap(tkwin), argv[1], &fg) == 0) {
280		Tcl_AppendResult(interp, "invalid color name \"", argv[1],
281			"\"", NULL);
282		goto cleanup;
283	    }
284	    if (argc == 2) {
285		bg.red = bg.green = bg.blue = 0;
286		maskIndex = namePtr->shape;
287	    } else if (XParseColor(display, Tk_Colormap(tkwin), argv[2],
288		    &bg) == 0) {
289		Tcl_AppendResult(interp, "invalid color name \"", argv[2],
290			"\"", NULL);
291		goto cleanup;
292	    }
293	}
294	dispPtr = ((TkWindow *) tkwin)->dispPtr;
295	if (dispPtr->cursorFont == None) {
296	    dispPtr->cursorFont = XLoadFont(display, CURSORFONT);
297	    if (dispPtr->cursorFont == None) {
298		Tcl_SetResult(interp, "couldn't load cursor font", TCL_STATIC);
299		goto cleanup;
300	    }
301	}
302	cursor = XCreateGlyphCursor(display, dispPtr->cursorFont,
303		dispPtr->cursorFont, namePtr->shape, maskIndex,
304		&fg, &bg);
305    } else {
306	/*
307	 * Prevent file system access in safe interpreters.
308	 */
309
310	if (!inTkTable && Tcl_IsSafe(interp)) {
311	    Tcl_AppendResult(interp, "can't get cursor from a file in",
312		    " a safe interpreter", NULL);
313	    cursorPtr = NULL;
314	    goto cleanup;
315	}
316
317	/*
318	 * If the cursor is to be created from bitmap files, then there should
319	 * be either two elements in the list (source, color) or four (source
320	 * mask fg bg). A cursor defined in the Tk table accepts the same
321	 * arguments as an X cursor.
322	 */
323
324	if (inTkTable && (argc != 1) && (argc != 2) && (argc != 3)) {
325	    goto badString;
326	}
327
328	if (!inTkTable && (argc != 2) && (argc != 4)) {
329	    goto badString;
330	}
331
332	cursor = CreateCursorFromTableOrFile(interp, tkwin, argc, argv,
333		tkCursorPtr);
334    }
335
336    if (cursor != None) {
337	cursorPtr = (TkUnixCursor *) ckalloc(sizeof(TkUnixCursor));
338	cursorPtr->info.cursor = (Tk_Cursor) cursor;
339	cursorPtr->display = display;
340    }
341
342  cleanup:
343    if (argv != NULL) {
344	ckfree((char *) argv);
345    }
346    return (TkCursor *) cursorPtr;
347
348  badString:
349    if (argv) {
350	ckfree((char *) argv);
351    }
352    Tcl_AppendResult(interp, "bad cursor spec \"", string, "\"", NULL);
353    return NULL;
354}
355
356/*
357 *----------------------------------------------------------------------
358 *
359 * CreateCursorFromTableOrFile --
360 *
361 *	Create a cursor defined in a file or the Tk static cursor table. A
362 *	cursor defined in a file starts with the '@' character. This method
363 *	assumes that the number of arguments in argv has been validated
364 *	already.
365 *
366 * Results:
367 *	Returns a new cursor, or None on error.
368 *
369 * Side effects:
370 *	Allocates a new X cursor.
371 *
372 *----------------------------------------------------------------------
373 */
374
375static Cursor
376CreateCursorFromTableOrFile(
377    Tcl_Interp *interp,		/* Interpreter to use for error reporting. */
378    Tk_Window tkwin,		/* Window in which cursor will be used. */
379    int argc,
380    CONST char **argv,		/* Cursor spec parsed into elements. */
381    struct TkCursorName *tkCursorPtr)
382				/* Non-NULL when cursor is defined in Tk
383				 * table. */
384{
385    Cursor cursor = None;
386
387    int width, height, maskWidth, maskHeight;
388    int xHot = -1, yHot = -1;
389    int dummy1, dummy2;
390    XColor fg, bg;
391    CONST char *fgColor;
392    CONST char *bgColor;
393    int inTkTable = (tkCursorPtr != NULL);
394
395    Display *display = Tk_Display(tkwin);
396    Drawable drawable = RootWindowOfScreen(Tk_Screen(tkwin));
397
398    Pixmap source = None;
399    Pixmap mask = None;
400
401    /*
402     * A cursor defined in a file accepts either 2 or 4 arguments.
403     *
404     * {srcfile fg}
405     * {srcfile maskfile fg bg}
406     *
407     * A cursor defined in the Tk table accepts 1, 2, or 3 arguments.
408     *
409     * {tkcursorname}
410     * {tkcursorname fg}
411     * {tkcursorname fg bg}
412     */
413
414    if (inTkTable) {
415	/*
416	 * This logic is like TkReadBitmapFile().
417	 */
418
419	char *data;
420
421	data = TkGetBitmapData(NULL, tkCursorPtr->data, NULL,
422		&width, &height, &xHot, &yHot);
423	if (data == NULL) {
424	    Tcl_AppendResult(interp, "error reading bitmap data for \"",
425		    argv[0], "\"", NULL);
426	    goto cleanup;
427	}
428
429	source = XCreateBitmapFromData(display, drawable, data, width,height);
430	ckfree(data);
431    } else {
432	if (TkReadBitmapFile(display, drawable, &argv[0][1],
433		(unsigned int *) &width, (unsigned int *) &height,
434		&source, &xHot, &yHot) != BitmapSuccess) {
435	    Tcl_AppendResult(interp, "cleanup reading bitmap file \"",
436		    &argv[0][1], "\"", NULL);
437	    goto cleanup;
438	}
439    }
440
441    if ((xHot < 0) || (yHot < 0) || (xHot >= width) || (yHot >= height)) {
442	if (inTkTable) {
443	    Tcl_AppendResult(interp, "bad hot spot in bitmap data for \"",
444		    argv[0], "\"", NULL);
445	} else {
446	    Tcl_AppendResult(interp, "bad hot spot in bitmap file \"",
447		    &argv[0][1], "\"", NULL);
448	}
449	goto cleanup;
450    }
451
452    /*
453     * Parse color names from optional fg and bg arguments
454     */
455
456    if (argc == 1) {
457	fg.red = fg.green = fg.blue = 0;
458	bg.red = bg.green = bg.blue = 65535;
459    } else if (argc == 2) {
460	fgColor = argv[1];
461	if (XParseColor(display, Tk_Colormap(tkwin), fgColor, &fg) == 0) {
462	    Tcl_AppendResult(interp, "invalid color name \"",
463		    fgColor, "\"", NULL);
464	    goto cleanup;
465	}
466	if (inTkTable) {
467	    bg.red = bg.green = bg.blue = 0;
468	} else {
469	    bg = fg;
470	}
471    } else {
472	/* 3 or 4 arguments */
473	if (inTkTable) {
474	    fgColor = argv[1];
475	    bgColor = argv[2];
476	} else {
477	    fgColor = argv[2];
478	    bgColor = argv[3];
479	}
480	if (XParseColor(display, Tk_Colormap(tkwin), fgColor, &fg) == 0) {
481	    Tcl_AppendResult(interp, "invalid color name \"",
482		    fgColor, "\"", NULL);
483	    goto cleanup;
484	}
485	if (XParseColor(display, Tk_Colormap(tkwin), bgColor, &bg) == 0) {
486	    Tcl_AppendResult(interp, "invalid color name \"",
487		    bgColor, "\"", NULL);
488	    goto cleanup;
489	}
490    }
491
492    /*
493     * If there is no mask data, then create the cursor now.
494     */
495
496    if ((!inTkTable && (argc == 2)) || (inTkTable && tkCursorPtr->mask == NULL)) {
497	cursor = XCreatePixmapCursor(display, source, source,
498		&fg, &fg, (unsigned) xHot, (unsigned) yHot);
499	goto cleanup;
500    }
501
502    /*
503     * Parse bitmap mask data and create cursor with fg and bg colors.
504     */
505
506    if (inTkTable) {
507	/*
508	 * This logic is like TkReadBitmapFile().
509	 */
510
511	char *data;
512
513	data = TkGetBitmapData(NULL, tkCursorPtr->mask, NULL,
514		&maskWidth, &maskHeight, &dummy1, &dummy2);
515	if (data == NULL) {
516	    Tcl_AppendResult(interp, "error reading bitmap mask data for \"",
517		    argv[0], "\"", NULL);
518	    goto cleanup;
519	}
520
521	mask = XCreateBitmapFromData(display, drawable, data, maskWidth,
522		maskHeight);
523
524	ckfree(data);
525    } else {
526	if (TkReadBitmapFile(display, drawable, argv[1],
527		(unsigned int *) &maskWidth, (unsigned int *) &maskHeight,
528		&mask, &dummy1, &dummy2) != BitmapSuccess) {
529	    Tcl_AppendResult(interp, "cleanup reading bitmap file \"",
530		    argv[1], "\"", NULL);
531	    goto cleanup;
532	}
533    }
534
535    if ((maskWidth != width) || (maskHeight != height)) {
536	Tcl_SetResult(interp, "source and mask bitmaps have different sizes",
537		TCL_STATIC);
538	goto cleanup;
539    }
540
541    cursor = XCreatePixmapCursor(display, source, mask,
542	    &fg, &bg, (unsigned) xHot, (unsigned) yHot);
543
544  cleanup:
545    if (source != None) {
546	Tk_FreePixmap(display, source);
547    }
548    if (mask != None) {
549	Tk_FreePixmap(display, mask);
550    }
551    return cursor;
552}
553
554/*
555 *----------------------------------------------------------------------
556 *
557 * TkCreateCursorFromData --
558 *
559 *	Creates a cursor from the source and mask bits.
560 *
561 * Results:
562 *	Returns a new cursor, or NULL on errors.
563 *
564 * Side effects:
565 *	Allocates a new cursor.
566 *
567 *----------------------------------------------------------------------
568 */
569
570TkCursor *
571TkCreateCursorFromData(
572    Tk_Window tkwin,		/* Window in which cursor will be used. */
573    CONST char *source,		/* Bitmap data for cursor shape. */
574    CONST char *mask,		/* Bitmap data for cursor mask. */
575    int width, int height,	/* Dimensions of cursor. */
576    int xHot, int yHot,		/* Location of hot-spot in cursor. */
577    XColor fgColor,		/* Foreground color for cursor. */
578    XColor bgColor)		/* Background color for cursor. */
579{
580    Cursor cursor;
581    Pixmap sourcePixmap, maskPixmap;
582    TkUnixCursor *cursorPtr = NULL;
583    Display *display = Tk_Display(tkwin);
584
585    sourcePixmap = XCreateBitmapFromData(display,
586	    RootWindowOfScreen(Tk_Screen(tkwin)), source, (unsigned) width,
587	    (unsigned) height);
588    maskPixmap = XCreateBitmapFromData(display,
589	    RootWindowOfScreen(Tk_Screen(tkwin)), mask, (unsigned) width,
590	    (unsigned) height);
591    cursor = XCreatePixmapCursor(display, sourcePixmap,
592	    maskPixmap, &fgColor, &bgColor, (unsigned) xHot, (unsigned) yHot);
593    Tk_FreePixmap(display, sourcePixmap);
594    Tk_FreePixmap(display, maskPixmap);
595
596    if (cursor != None) {
597	cursorPtr = (TkUnixCursor *) ckalloc(sizeof(TkUnixCursor));
598	cursorPtr->info.cursor = (Tk_Cursor) cursor;
599	cursorPtr->display = display;
600    }
601    return (TkCursor *) cursorPtr;
602}
603
604/*
605 *----------------------------------------------------------------------
606 *
607 * TkpFreeCursor --
608 *
609 *	This function is called to release a cursor allocated by
610 *	TkGetCursorByName.
611 *
612 * Results:
613 *	None.
614 *
615 * Side effects:
616 *	The cursor data structure is deallocated.
617 *
618 *----------------------------------------------------------------------
619 */
620
621void
622TkpFreeCursor(
623    TkCursor *cursorPtr)
624{
625    TkUnixCursor *unixCursorPtr = (TkUnixCursor *) cursorPtr;
626
627    XFreeCursor(unixCursorPtr->display, (Cursor) unixCursorPtr->info.cursor);
628    Tk_FreeXId(unixCursorPtr->display, (XID) unixCursorPtr->info.cursor);
629}
630
631/*
632 * Local Variables:
633 * mode: c
634 * c-basic-offset: 4
635 * fill-column: 78
636 * End:
637 */
638