|
|
1.1 root 1: /* w_piem.c: Pie Menus
2: */
3:
4: /*
5: *
6: * Pie Menus for Tk.
7: * Copyright (C) 1992 by Don Hopkins.
8: *
9: * This program is provided for unrestricted use, provided that this
10: * copyright message is preserved. There is no warranty, and no author
11: * or distributer accepts responsibility for any damage caused by this
12: * program.
13: *
14: * This code and the ideas behind it were developed over time by Don Hopkins
15: * with the support of the University of Maryland, UniPress Software, Sun
16: * Microsystems, DUX Software, the Turing Institute, and Carnegie Mellon
17: * University. Pie menus are NOT patented or restricted, and the interface
18: * and algorithms may be freely copied and improved upon.
19: *
20: */
21:
22:
23: #if 0
24: /* workaround to make gcc work on suns */
25: #ifndef SOLARIS2
26: #define _PTRDIFF_T
27: #define _SIZE_T
28: #ifndef sgi
29: typedef unsigned int size_t;
30: #endif
31: #endif
32: #endif
33:
34: #include "tkconfig.h"
35: #include "default.h"
36: #include "tkint.h"
37: #include <X11/extensions/shape.h>
38:
39: #define PI 3.1415926535897932
40: #define TWO_PI 6.2831853071795865
41: #define DEG_TO_RAD(d) (((d) * TWO_PI) / 360.0)
42: #define RAD_TO_DEG(d) (((d) * 360.0) / TWO_PI)
43: #define PIE_SPOKE_INSET 6
44: #define PIE_BG_COLOR "#bfbfbf"
45: #define PIE_BG_MONO WHITE
46: #define PIE_ACTIVE_FG_COLOR BLACK
47: #define PIE_ACTIVE_FG_MONO BLACK
48: #define PIE_ACTIVE_BG_COLOR "#bfbfbf"
49: #define PIE_ACTIVE_BG_MONO WHITE
50: #define PIE_FG BLACK
51: #define PIE_FONT "-Adobe-Helvetica-Bold-R-Normal-*-120-*"
52: #define PIE_ACTIVE_BORDER_WIDTH "2"
53: #define PIE_INACTIVE_RADIUS "8"
54: #define PIE_INACTIVE_RADIUS_NUM 8
55: #define PIE_MIN_RADIUS "16"
56: #define PIE_MIN_RADIUS_NUM 16
57: #define PIE_EXTRA_RADIUS "2"
58: #define PIE_EXTRA_RADIUS_NUM 2
59: #define PIE_BORDER_WIDTH "2"
60: #define PIE_POPUP_DELAY "250"
61: #define PIE_POPUP_DELAY_NUM 250
62: #define PIE_ENTRY_ACTIVE_BG ((char *) NULL)
63: #define PIE_ENTRY_BG ((char *) NULL)
64: #define PIE_ENTRY_FONT ((char *) NULL)
65:
66: #ifndef MAX
67: #define MAX(x,y) ((x)>(y)?(x):(y))
68: #define MIN(x,y) ((x)<(y)?(x):(y))
69: #endif
70: #define ABS(x) (((x)<0)?(-(x)):(x))
71:
72: static int HaveShape = -1;
73:
74: /*
75: * One of the following data structures is kept for each entry of each
76: * pie menu managed by this file:
77: */
78:
79: typedef struct PieMenuEntry {
80: int type;
81: struct PieMenu *piemenuPtr;
82: char *label;
83: int labelLength;
84: Pixmap bitmap;
85:
86: /*
87: * Information related to displaying entry:
88: */
89:
90: int width, height;
91: int x, y;
92: int x_offset, y_offset;
93: int label_x, label_y;
94:
95: Tk_3DBorder border;
96: Tk_3DBorder activeBorder;
97: XFontStruct *fontPtr;
98: GC textGC;
99: GC activeGC;
100:
101: /*
102: * Information used for pie menu layout & tracking:
103: */
104:
105: int slice; /* Desired relative slice size */
106: float angle; /* Angle through center of slice */
107: float dx, dy; /* Cosine and sine of angle */
108: float subtend; /* Angle subtended by slice */
109: int quadrant; /* Quadrant of leading edge */
110: float slope; /* Slope of leading edge */
111:
112: /*
113: * Information used to implement this entry's action:
114: */
115:
116: char *command;
117: char *preview;
118: char *name;
119:
120: /*
121: * Miscellaneous information:
122: */
123:
124: int flags; /* Various flags. See below for definitions. */
125: } PieMenuEntry;
126:
127: /*
128: * Flag values defined for menu entries:
129: *
130: * ENTRY_NEEDS_REDISPLAY: Non-zero means the entry should be redisplayed.
131: */
132:
133: #define ENTRY_NEEDS_REDISPLAY 1
134:
135: /*
136: * Types defined for PieMenuEntries:
137: */
138:
139: #define COMMAND_ENTRY 0
140: #define PIEMENU_ENTRY 1
141:
142: /*
143: * Mask bits for above types:
144: */
145:
146: #define COMMAND_MASK TK_CONFIG_USER_BIT
147: #define PIEMENU_MASK (TK_CONFIG_USER_BIT << 1)
148: #define ALL_MASK (COMMAND_MASK | PIEMENU_MASK)
149:
150: /*
151: * Configuration specs for individual menu entries:
152: */
153:
154: static Tk_ConfigSpec entryConfigSpecs[] = {
155: {TK_CONFIG_BORDER, "-activebackground", (char *) NULL, (char *) NULL,
156: PIE_ENTRY_ACTIVE_BG, Tk_Offset(PieMenuEntry, activeBorder),
157: ALL_MASK|TK_CONFIG_NULL_OK},
158: {TK_CONFIG_BORDER, "-background", (char *) NULL, (char *) NULL,
159: PIE_ENTRY_BG, Tk_Offset(PieMenuEntry, border),
160: ALL_MASK|TK_CONFIG_NULL_OK},
161: {TK_CONFIG_PIXMAP, "-bitmap", (char *) NULL, (char *) NULL,
162: (char *) NULL, Tk_Offset(PieMenuEntry, bitmap),
163: ALL_MASK|TK_CONFIG_NULL_OK},
164: {TK_CONFIG_STRING, "-command", (char *) NULL, (char *) NULL,
165: (char *) NULL, Tk_Offset(PieMenuEntry, command),
166: COMMAND_MASK},
167: {TK_CONFIG_STRING, "-preview", (char *) NULL, (char *) NULL,
168: (char *) NULL, Tk_Offset(PieMenuEntry, preview),
169: ALL_MASK},
170: {TK_CONFIG_FONT, "-font", (char *) NULL, (char *) NULL,
171: PIE_ENTRY_FONT, Tk_Offset(PieMenuEntry, fontPtr),
172: ALL_MASK|TK_CONFIG_NULL_OK},
173: {TK_CONFIG_STRING, "-label", (char *) NULL, (char *) NULL,
174: (char *) NULL, Tk_Offset(PieMenuEntry, label),
175: ALL_MASK},
176: {TK_CONFIG_STRING, "-piemenu", (char *) NULL, (char *) NULL,
177: (char *) NULL, Tk_Offset(PieMenuEntry, name),
178: ALL_MASK},
179: {TK_CONFIG_INT, "-xoffset", "xOffset", "XOffset",
180: "0", Tk_Offset(PieMenuEntry, x_offset),
181: ALL_MASK},
182: {TK_CONFIG_INT, "-yoffset", "yOffset", "YOffset",
183: "0", Tk_Offset(PieMenuEntry, y_offset),
184: ALL_MASK},
185: {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
186: (char *) NULL, 0, 0}
187: };
188:
189: /*
190: * A data structure of the following type is kept for each
191: * pie menu managed by this file:
192: */
193:
194: typedef struct PieMenu {
195: Tk_Window tkwin;
196: Tcl_Interp *interp;
197: char *title;
198: int titleLength;
199: char *preview;
200: PieMenuEntry **entries;
201: int numEntries;
202: int active;
203: Tk_Uid group;
204: int root_x, root_y;
205: int dx, dy;
206:
207: /*
208: * Information used when displaying widget:
209: */
210:
211: Tk_3DBorder border;
212: int borderWidth;
213: Tk_3DBorder activeBorder;
214: int activeBorderWidth;
215: XFontStruct *fontPtr;
216: XFontStruct *titlefontPtr;
217: XColor *fg;
218: GC textGC;
219: XColor *activeFg;
220: GC activeGC;
221:
222: /*
223: * Information used to layout pie menu:
224: */
225:
226: int width, height; /* size of the pie menu */
227: int title_x, title_y; /* position of menu title */
228: int title_width, title_height; /* size of menu title */
229: int initial_angle; /* pie menu initial angle in radians */
230: int inactive_radius; /* inactive inner radius */
231: int min_radius; /* minimum label radius */
232: int fixed_radius; /* fixed label radius */
233: int extra_radius; /* extra label radius pad */
234: int label_radius; /* Radius of labels from menu center */
235: int center_x, center_y; /* Menu center */
236: XSegment *segments; /* Line segments to draw */
237:
238: /*
239: * Miscellaneous information:
240: */
241:
242: Tk_TimerToken popup_timer_token;
243: Cursor cursor;
244: PieMenuEntry *postedPie;
245: int flags;
246: int phase;
247: int popup_delay; /* Delay before popup */
248: int shaped; /* Use SHAPE extension */
249: } PieMenu;
250:
251: /*
252: * Flag bits for menus:
253: *
254: * REDRAW_PENDING: Non-zero means a DoWhenIdle handler
255: * has already been queued to redraw
256: * this window.
257: * UPDATE_PENDING: Non-zero means a DoWhenIdle handler
258: * has already been queued to update
259: * this window.
260: * RESIZE_PENDING: Non-zero means a call to ComputeMenuGeometry
261: * has already been scheduled.
262: * POPUP_PENDING: Non-zero means a call to PopupPieMenu has
263: * already been scheduled.
264: */
265:
266: #define REDRAW_PENDING 1
267: #define UPDATE_PENDING 2
268: #define RESIZE_PENDING 4
269: #define POPUP_PENDING 8
270:
271: /*
272: * Configuration specs valid for the menu as a whole:
273: */
274:
275: static Tk_ConfigSpec configSpecs[] = {
276: {TK_CONFIG_BORDER, "-activebackground", "activeBackground", "Foreground",
277: PIE_ACTIVE_BG_COLOR, Tk_Offset(PieMenu, activeBorder),
278: TK_CONFIG_COLOR_ONLY},
279: {TK_CONFIG_BORDER, "-activebackground", "activeBackground", "Foreground",
280: PIE_ACTIVE_BG_MONO, Tk_Offset(PieMenu, activeBorder),
281: TK_CONFIG_MONO_ONLY},
282: {TK_CONFIG_PIXELS, "-activeborderwidth", "activeBorderWidth", "BorderWidth",
283: PIE_ACTIVE_BORDER_WIDTH, Tk_Offset(PieMenu, activeBorderWidth), 0},
284: {TK_CONFIG_COLOR, "-activeforeground", "activeForeground", "Background",
285: PIE_ACTIVE_FG_COLOR, Tk_Offset(PieMenu, activeFg),
286: TK_CONFIG_COLOR_ONLY},
287: {TK_CONFIG_COLOR, "-activeforeground", "activeForeground", "Background",
288: PIE_ACTIVE_FG_MONO, Tk_Offset(PieMenu, activeFg),
289: TK_CONFIG_MONO_ONLY},
290: {TK_CONFIG_BORDER, "-background", "background", "Background",
291: PIE_BG_COLOR, Tk_Offset(PieMenu, border), TK_CONFIG_COLOR_ONLY},
292: {TK_CONFIG_BORDER, "-background", "background", "Background",
293: PIE_BG_MONO, Tk_Offset(PieMenu, border), TK_CONFIG_MONO_ONLY},
294: {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *) NULL,
295: (char *) NULL, 0, 0},
296: {TK_CONFIG_SYNONYM, "-bg", "background", (char *) NULL,
297: (char *) NULL, 0, 0},
298: {TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
299: PIE_BORDER_WIDTH, Tk_Offset(PieMenu, borderWidth), 0},
300: {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor",
301: "circle", Tk_Offset(PieMenu, cursor), TK_CONFIG_NULL_OK},
302: {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *) NULL,
303: (char *) NULL, 0, 0},
304: {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
305: PIE_FG, Tk_Offset(PieMenu, fg), 0},
306: {TK_CONFIG_FONT, "-font", "font", "Font",
307: PIE_FONT, Tk_Offset(PieMenu, fontPtr), 0},
308: {TK_CONFIG_STRING, "-title", (char *) NULL, (char *) NULL,
309: "", Tk_Offset(PieMenu, title), 0},
310: {TK_CONFIG_STRING, "-preview", (char *) NULL, (char *) NULL,
311: "", Tk_Offset(PieMenu, preview), 0},
312: {TK_CONFIG_FONT, "-titlefont", "font", "Font",
313: PIE_FONT, Tk_Offset(PieMenu, titlefontPtr), 0},
314: {TK_CONFIG_INT, "-initialangle", "initialAngle", "InitialAngle",
315: "0", Tk_Offset(PieMenu, initial_angle), 0},
316: {TK_CONFIG_INT, "-inactiveradius", "inactiveRadius", "InactiveRadius",
317: PIE_INACTIVE_RADIUS, Tk_Offset(PieMenu, inactive_radius), 0},
318: {TK_CONFIG_INT, "-minradius", "minRadius", "MinRadius",
319: PIE_MIN_RADIUS, Tk_Offset(PieMenu, min_radius), 0},
320: {TK_CONFIG_INT, "-extraradius", "extraRadius", "ExtraRadius",
321: PIE_EXTRA_RADIUS, Tk_Offset(PieMenu, extra_radius), 0},
322: {TK_CONFIG_INT, "-fixedradius", "fixedRadius", "FixedRadius",
323: "0", Tk_Offset(PieMenu, fixed_radius), 0},
324: {TK_CONFIG_INT, "-active", "active", "Active",
325: "-1", Tk_Offset(PieMenu, active), 0},
326: {TK_CONFIG_INT, "-popupdelay", "popupDelay", "PopupDelay",
327: PIE_POPUP_DELAY, Tk_Offset(PieMenu, popup_delay), 0},
328: {TK_CONFIG_INT, "-shaped", "shaped", "Shaped",
329: "1", Tk_Offset(PieMenu, shaped), 0},
330: {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
331: (char *) NULL, 0, 0}
332: };
333:
334: /*
335: * Forward declarations for procedures defined later in this file:
336: */
337:
338: int Tk_PieMenuCmd(ClientData clientData, Tcl_Interp *interp,
339: int argc, char **argv);
340: static int ActivatePieMenuEntry _ANSI_ARGS_((PieMenu *menuPtr,
341: int index, int preview));
342: static void ComputePieMenuGeometry _ANSI_ARGS_((
343: ClientData clientData));
344: static int ConfigurePieMenu _ANSI_ARGS_((Tcl_Interp *interp,
345: PieMenu *menuPtr, int argc, char **argv,
346: int flags));
347: static int ConfigurePieMenuEntry _ANSI_ARGS_((Tcl_Interp *interp,
348: PieMenu *menuPtr, PieMenuEntry *mePtr, int index,
349: int argc, char **argv, int flags));
350: static void DestroyPieMenu _ANSI_ARGS_((ClientData clientData));
351: static void DestroyPieMenuEntry _ANSI_ARGS_((ClientData clientData));
352: static void DisplayPieMenu _ANSI_ARGS_((ClientData clientData));
353: static void UpdatePieMenu _ANSI_ARGS_((ClientData clientData));
354: static void PopupPieMenu _ANSI_ARGS_((ClientData clientData));
355: static void EventuallyRedrawPieMenu _ANSI_ARGS_((PieMenu *menuPtr,
356: int index));
357: static int GetPieMenuIndex _ANSI_ARGS_((Tcl_Interp *interp,
358: PieMenu *menuPtr, char *string, int *indexPtr));
359: static void PieMenuEventProc _ANSI_ARGS_((ClientData clientData,
360: XEvent *eventPtr));
361: static int PieMenuWidgetCmd _ANSI_ARGS_((ClientData clientData,
362: Tcl_Interp *interp, int argc, char **argv));
363: static int UnpostSubPieMenu _ANSI_ARGS_((Tcl_Interp *interp,
364: PieMenu *menuPtr));
365: static void PopupPieMenu _ANSI_ARGS_((ClientData clientData));
366: static void NowPopupPieMenu _ANSI_ARGS_((PieMenu *menuPtr));
367: static void NeverPopupPieMenu _ANSI_ARGS_((PieMenu *menuPtr));
368: static void EventuallyPopupPieMenu _ANSI_ARGS_((PieMenu *menuPtr));
369: static void DeferPopupPieMenu _ANSI_ARGS_((PieMenu *menuPtr));
370: static void ShapePieMenu _ANSI_ARGS_((PieMenu *menuPtr));
371:
372:
373: /*
374: *--------------------------------------------------------------
375: *
376: * Tk_PieMenuCmd --
377: *
378: * This procedure is invoked to process the "piemenu" Tcl
379: * command. Read the code and write some user documentation for
380: * details on what it does.
381: *
382: * Results:
383: * A standard Tcl result.
384: *
385: * Side effects:
386: * See the user documentation for "menu", which this was based on.
387: *
388: *--------------------------------------------------------------
389: */
390:
391: int
392: Tk_PieMenuCmd(clientData, interp, argc, argv)
393: ClientData clientData; /* Main window associated with
394: * interpreter. */
395: Tcl_Interp *interp; /* Current interpreter. */
396: int argc; /* Number of arguments. */
397: char **argv; /* Argument strings. */
398: {
399: Tk_Window tkwin = (Tk_Window) clientData;
400: Tk_Window new;
401: register PieMenu *menuPtr;
402: XSetWindowAttributes atts;
403:
404: if (argc < 2) {
405: Tcl_AppendResult(interp, "wrong # args: should be \"",
406: argv[0], " pathName ?options?\"", (char *) NULL);
407: return TCL_ERROR;
408: }
409:
410: /*
411: * Create the new window. Set override-redirect so the window
412: * manager won't add a border or argue about placement, and set
413: * save-under so that the window can pop up and down without a
414: * lot of re-drawing.
415: */
416:
417: new = Tk_CreateWindowFromPath(interp, tkwin, argv[1], "");
418: if (new == NULL) {
419: return TCL_ERROR;
420: }
421: atts.override_redirect = True;
422: atts.save_under = True;
423: Tk_ChangeWindowAttributes(new, CWOverrideRedirect|CWSaveUnder, &atts);
424:
425: /*
426: * Initialize the data structure for the menu.
427: */
428:
429: menuPtr = (PieMenu *) ckalloc(sizeof(PieMenu));
430: menuPtr->tkwin = new;
431: menuPtr->interp = interp;
432: menuPtr->title = NULL;
433: menuPtr->titleLength = 0;
434: menuPtr->preview = NULL;
435: menuPtr->entries = NULL;
436: menuPtr->numEntries = 0;
437: menuPtr->active = -1;
438: menuPtr->group = NULL;
439: menuPtr->root_x = 0;
440: menuPtr->root_y = 0;
441: menuPtr->border = NULL;
442: menuPtr->activeBorder = NULL;
443: menuPtr->fontPtr = NULL;
444: menuPtr->titlefontPtr = NULL;
445: menuPtr->fg = NULL;
446: menuPtr->textGC = None;
447: menuPtr->activeFg = NULL;
448: menuPtr->activeGC = None;
449: menuPtr->width = 0;
450: menuPtr->height = 0;
451: menuPtr->title_x = 0;
452: menuPtr->title_y = 0;
453: menuPtr->title_width = 0;
454: menuPtr->title_height = 0;
455: menuPtr->initial_angle = 0;
456: menuPtr->inactive_radius = PIE_INACTIVE_RADIUS_NUM;
457: menuPtr->min_radius = PIE_MIN_RADIUS_NUM;
458: menuPtr->extra_radius = PIE_EXTRA_RADIUS_NUM;
459: menuPtr->fixed_radius = 0;
460: menuPtr->label_radius = 0;
461: menuPtr->center_x = 0;
462: menuPtr->center_y = 0;
463: menuPtr->segments = NULL;
464: menuPtr->cursor = None;
465: menuPtr->postedPie = NULL;
466: menuPtr->flags = 0;
467: menuPtr->phase = 0;
468: menuPtr->shaped = 1;
469: menuPtr->popup_delay = PIE_POPUP_DELAY_NUM;
470:
471: Tk_SetClass(new, "PieMenu");
472: Tk_CreateEventHandler(menuPtr->tkwin,
473: ExposureMask | StructureNotifyMask |
474: ButtonPressMask | ButtonReleaseMask |
475: PointerMotionMask,
476: PieMenuEventProc, (ClientData) menuPtr);
477: Tcl_CreateCommand(interp, Tk_PathName(menuPtr->tkwin), PieMenuWidgetCmd,
478: (ClientData) menuPtr, (void (*)()) NULL);
479: if (ConfigurePieMenu(interp, menuPtr, argc-2, argv+2, 0) != TCL_OK) {
480: goto error;
481: }
482:
483: interp->result = Tk_PathName(menuPtr->tkwin);
484: return TCL_OK;
485:
486: error:
487: Tk_DestroyWindow(menuPtr->tkwin);
488: return TCL_ERROR;
489: }
490:
491: /*
492: *--------------------------------------------------------------
493: *
494: * PieMenuWidgetCmd --
495: *
496: * This procedure is invoked to process the Tcl command
497: * that corresponds to a widget managed by this module.
498: * See the user documentation for details on what it does.
499: *
500: * Results:
501: * A standard Tcl result.
502: *
503: * Side effects:
504: * See the user documentation.
505: *
506: *--------------------------------------------------------------
507: */
508:
509: static int
510: PieMenuWidgetCmd(clientData, interp, argc, argv)
511: ClientData clientData; /* Information about menu widget. */
512: Tcl_Interp *interp; /* Current interpreter. */
513: int argc; /* Number of arguments. */
514: char **argv; /* Argument strings. */
515: {
516: register PieMenu *menuPtr = (PieMenu *) clientData;
517: register PieMenuEntry *mePtr;
518: int result = TCL_OK;
519: int length, type;
520: char c;
521:
522: if (argc < 2) {
523: Tcl_AppendResult(interp, "wrong # args: should be \"",
524: argv[0], " option ?arg arg ...?\"", (char *) NULL);
525: return TCL_ERROR;
526: }
527: Tk_Preserve((ClientData) menuPtr);
528: c = argv[1][0];
529: length = strlen(argv[1]);
530: if ((c == 'a') && (strncmp(argv[1], "activate", length) == 0)
531: && (length >= 2)) {
532: int index;
533:
534: if (argc != 3) {
535: Tcl_AppendResult(interp, "wrong # args: should be \"",
536: argv[0], " activate index\"", (char *) NULL);
537: goto error;
538: }
539: if (GetPieMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK) {
540: goto error;
541: }
542: if (menuPtr->active == index) {
543: goto done;
544: }
545: result = ActivatePieMenuEntry(menuPtr, index, 1);
546: DeferPopupPieMenu(menuPtr);
547: } else if ((c == 's') && (strncmp(argv[1], "show", length) == 0)
548: && (length >= 2)) {
549: int index;
550:
551: if (argc != 2) {
552: Tcl_AppendResult(interp, "wrong # args: should be \"",
553: argv[0], " show\"", (char *) NULL);
554: goto error;
555: }
556: NowPopupPieMenu(menuPtr);
557: } else if ((c == 'p') && (strncmp(argv[1], "pending", length) == 0)
558: && (length >= 2)) {
559: int index;
560:
561: if (argc != 2) {
562: Tcl_AppendResult(interp, "wrong # args: should be \"",
563: argv[0], " pending\"", (char *) NULL);
564: goto error;
565: }
566: sprintf(interp->result, "%d",
567: (menuPtr->flags & POPUP_PENDING) ? 1 : 0);
568: } else if ((c == 'd') && (strncmp(argv[1], "defer", length) == 0)
569: && (length >= 2)) {
570: int index;
571:
572: if (argc != 2) {
573: Tcl_AppendResult(interp, "wrong # args: should be \"",
574: argv[0], " defer\"", (char *) NULL);
575: goto error;
576: }
577: DeferPopupPieMenu(menuPtr);
578: } else if ((c == 'a') && (strncmp(argv[1], "add", length) == 0)
579: && (length >= 2)) {
580: PieMenuEntry **newEntries;
581:
582: if (argc < 3) {
583: Tcl_AppendResult(interp, "wrong # args: should be \"",
584: argv[0], " add type ?options?\"", (char *) NULL);
585: goto error;
586: }
587:
588: /*
589: * Figure out the type of the new entry.
590: */
591:
592: c = argv[2][0];
593: length = strlen(argv[2]);
594: if ((c == 'c') && (strncmp(argv[2], "command", length) == 0)) {
595: type = COMMAND_ENTRY;
596: } else if ((c == 'p') && (strncmp(argv[2], "piemenu", length) == 0)) {
597: type = PIEMENU_ENTRY;
598: } else {
599: Tcl_AppendResult(interp, "bad menu entry type \"",
600: argv[2], "\": must be command or piemenu",
601: (char *) NULL);
602: goto error;
603: }
604:
605: /*
606: * Add a new entry to the end of the menu's array of entries,
607: * and process options for it.
608: */
609:
610: mePtr = (PieMenuEntry *) ckalloc(sizeof(PieMenuEntry));
611: newEntries = (PieMenuEntry **) ckalloc((unsigned)
612: ((menuPtr->numEntries+1)*sizeof(PieMenuEntry *)));
613: if (menuPtr->numEntries != 0) {
614: memcpy((VOID *) newEntries, (VOID *) menuPtr->entries,
615: menuPtr->numEntries*sizeof(PieMenuEntry *));
616: ckfree((char *) menuPtr->entries);
617: }
618: menuPtr->entries = newEntries;
619: menuPtr->entries[menuPtr->numEntries] = mePtr;
620: menuPtr->numEntries++;
621: mePtr->type = type;
622: mePtr->piemenuPtr = menuPtr;
623: mePtr->label = NULL;
624: mePtr->labelLength = 0;
625: mePtr->bitmap = None;
626: mePtr->width = 0;
627: mePtr->height = 0;
628: mePtr->x_offset = 0;
629: mePtr->y_offset = 0;
630: mePtr->label_x = 0;
631: mePtr->label_y = 0;
632: mePtr->border = NULL;
633: mePtr->activeBorder = NULL;
634: mePtr->fontPtr = NULL;
635: mePtr->textGC = None;
636: mePtr->activeGC = None;
637: mePtr->slice = 1.0;
638: mePtr->angle = 0.0;
639: mePtr->dx = 0.0;
640: mePtr->dy = 0.0;
641: mePtr->subtend = 0.0;
642: mePtr->quadrant = 0;
643: mePtr->slope = 0.0;
644: mePtr->command = NULL;
645: mePtr->preview = NULL;
646: mePtr->name = NULL;
647: mePtr->flags = 0;
648: if (ConfigurePieMenuEntry(interp, menuPtr, mePtr,
649: menuPtr->numEntries-1,
650: argc-3, argv+3, 0) != TCL_OK) {
651: DestroyPieMenuEntry((ClientData) mePtr);
652: menuPtr->numEntries--;
653: goto error;
654: }
655: if (!(menuPtr->flags & RESIZE_PENDING)) {
656: menuPtr->flags |= RESIZE_PENDING;
657: Tk_DoWhenIdle(ComputePieMenuGeometry, (ClientData) menuPtr);
658: }
659: } else if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0)) {
660: if (argc == 2) {
661: result = Tk_ConfigureInfo(interp, menuPtr->tkwin, configSpecs,
662: (char *) menuPtr, (char *) NULL, 0);
663: } else if (argc == 3) {
664: result = Tk_ConfigureInfo(interp, menuPtr->tkwin, configSpecs,
665: (char *) menuPtr, argv[2], 0);
666: } else {
667: result = ConfigurePieMenu(interp, menuPtr, argc-2, argv+2,
668: TK_CONFIG_ARGV_ONLY);
669: }
670: } else if ((c == 'd') && (strncmp(argv[1], "delete", length) == 0)
671: && (length >= 2)) {
672: int index, i;
673:
674: if (argc != 3) {
675: Tcl_AppendResult(interp, "wrong # args: should be \"",
676: argv[0], " delete index\"", (char *) NULL);
677: goto error;
678: }
679: if (GetPieMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK) {
680: goto error;
681: }
682: if (index < 0) {
683: goto done;
684: }
685: Tk_EventuallyFree((ClientData) menuPtr->entries[index],
686: DestroyPieMenuEntry);
687: for (i = index; i < menuPtr->numEntries-1; i++) {
688: menuPtr->entries[i] = menuPtr->entries[i+1];
689: }
690: menuPtr->numEntries -= 1;
691: if (menuPtr->active == index) {
692: menuPtr->active = -1;
693: } else if (menuPtr->active > index) {
694: menuPtr->active -= 1;
695: }
696: if (!(menuPtr->flags & RESIZE_PENDING)) {
697: menuPtr->flags |= RESIZE_PENDING;
698: Tk_DoWhenIdle(ComputePieMenuGeometry, (ClientData) menuPtr);
699: }
700: } else if ((c == 'e') && (length >= 3)
701: && (strncmp(argv[1], "entryconfigure", length) == 0)) {
702: int index;
703:
704: if (argc < 3) {
705: Tcl_AppendResult(interp, "wrong # args: should be \"",
706: argv[0], " entryconfigure index ?option value ...?\"",
707: (char *) NULL);
708: goto error;
709: }
710: if (GetPieMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK) {
711: goto error;
712: }
713: if (index < 0) {
714: goto done;
715: }
716: mePtr = menuPtr->entries[index];
717: Tk_Preserve((ClientData) mePtr);
718: if (argc == 3) {
719: result = Tk_ConfigureInfo(interp, menuPtr->tkwin, entryConfigSpecs,
720: (char *) mePtr, (char *) NULL,
721: COMMAND_MASK << mePtr->type);
722: } else if (argc == 4) {
723: result = Tk_ConfigureInfo(interp, menuPtr->tkwin, entryConfigSpecs,
724: (char *) mePtr, argv[3], COMMAND_MASK << mePtr->type);
725: } else {
726: result = ConfigurePieMenuEntry(interp, menuPtr, mePtr, index,
727: argc-3, argv+3,
728: TK_CONFIG_ARGV_ONLY |
729: COMMAND_MASK << mePtr->type);
730: }
731: Tk_Release((ClientData) mePtr);
732: } else if ((c == 'i') && (strncmp(argv[1], "index", length) == 0)
733: && (length >= 3)) {
734: int index;
735:
736: if (argc != 3) {
737: Tcl_AppendResult(interp, "wrong # args: should be \"",
738: argv[0], " index string\"", (char *) NULL);
739: goto error;
740: }
741: if (GetPieMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK) {
742: goto error;
743: }
744: if (index < 0) {
745: interp->result = "none";
746: } else {
747: sprintf(interp->result, "%d", index);
748: }
749: } else if ((c == 'i') && (strncmp(argv[1], "invoke", length) == 0)
750: && (length >= 3)) {
751: int index;
752:
753: if (argc != 3) {
754: Tcl_AppendResult(interp, "wrong # args: should be \"",
755: argv[0], " invoke index\"", (char *) NULL);
756: goto error;
757: }
758: if (GetPieMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK) {
759: goto error;
760: }
761: if (index < 0) {
762: goto done;
763: }
764: mePtr = menuPtr->entries[index];
765: Tk_Preserve((ClientData) mePtr);
766: if (mePtr->command != NULL) {
767: result = Tcl_GlobalEval(interp, mePtr->command);
768: }
769: Tk_Release((ClientData) mePtr);
770: } else if ((c == 'p') && (strncmp(argv[1], "post", length) == 0)) {
771: int x, y, ix, iy, tmp, err;
772: Tk_Uid group;
773:
774: if ((argc != 4) && (argc != 5)) {
775: Tcl_AppendResult(interp, "wrong # args: should be \"",
776: argv[0], " post x y ?group?\"", (char *) NULL);
777: goto error;
778: }
779: if ((Tcl_GetInt(interp, argv[2], &x) != TCL_OK)
780: || (Tcl_GetInt(interp, argv[3], &y) != TCL_OK)) {
781: goto error;
782: }
783: if (argc == 5) {
784: group = Tk_GetUid(argv[4]);
785: } else {
786: group = Tk_GetUid("default");
787: }
788:
789: /*
790: * Adjust the position of the menu if necessary to keep it
791: * on-screen.
792: */
793:
794: x -= menuPtr->center_x; y -= menuPtr->center_y;
795: #if 0
796: ix = x; iy = y;
797:
798: tmp = WidthOfScreen(Tk_Screen(menuPtr->tkwin))
799: - Tk_Width(menuPtr->tkwin);
800: if (x > tmp) {
801: x = tmp;
802: }
803: if (x < 0) {
804: x = 0;
805: }
806: tmp = HeightOfScreen(Tk_Screen(menuPtr->tkwin))
807: - Tk_Height(menuPtr->tkwin);
808: if (y > tmp) {
809: y = tmp;
810: }
811: if (y < 0) {
812: y = 0;
813: }
814:
815: /* XXX: warp pointer by (x-ix, y-iy) upon popup */
816: #endif
817:
818: Tk_MakeWindowExist(menuPtr->tkwin);
819: XRaiseWindow(Tk_Display(menuPtr->tkwin), Tk_WindowId(menuPtr->tkwin));
820:
821: Tk_MoveWindow(menuPtr->tkwin, x, y);
822: menuPtr->root_x = x + menuPtr->center_x;
823: menuPtr->root_y = y + menuPtr->center_y;
824:
825: if (Tk_IsMapped(menuPtr->tkwin)) {
826: if (group != menuPtr->group) {
827: Tk_UnshareEvents(menuPtr->tkwin, menuPtr->group);
828: Tk_ShareEvents(menuPtr->tkwin, group);
829: }
830: } else {
831: Tk_ShareEvents(menuPtr->tkwin, group);
832: EventuallyPopupPieMenu(menuPtr);
833: result = ActivatePieMenuEntry(menuPtr, -1, 1);
834: }
835: menuPtr->group = group;
836: } else if ((c == 'u') && (strncmp(argv[1], "unpost", length) == 0)) {
837: if (argc != 2) {
838: Tcl_AppendResult(interp, "wrong # args: should be \"",
839: argv[0], " unpost\"", (char *) NULL);
840: goto error;
841: }
842: NeverPopupPieMenu(menuPtr);
843: Tk_UnshareEvents(menuPtr->tkwin, menuPtr->group);
844: Tk_UnmapWindow(menuPtr->tkwin);
845: result = ActivatePieMenuEntry(menuPtr, -1, 0);
846: if (result == TCL_OK) {
847: result = UnpostSubPieMenu(interp, menuPtr);
848: }
849: } else if ((c == 'g') && (strncmp(argv[1], "grab", length) == 0)) {
850: Tk_Window tkwin;
851: int err;
852:
853: if ((argc != 3) ||
854: ((tkwin = Tk_NameToWindow(interp, argv[2],
855: menuPtr->tkwin)) == NULL)) {
856: Tcl_AppendResult(interp, "wrong # args: should be \"",
857: argv[0], " grab window\"", (char *) NULL);
858: goto error;
859: }
860:
861: err =
862: XGrabPointer(Tk_Display(tkwin),
863: Tk_WindowId(tkwin),
864: False,
865: ButtonPressMask | ButtonReleaseMask |
866: ButtonMotionMask | PointerMotionMask,
867: GrabModeAsync, GrabModeAsync, None, None,
868: TkCurrentTime(((TkWindow *)tkwin)->dispPtr));
869: if (err != 0) {
870: if (err == GrabNotViewable) {
871: interp->result = "grab failed: window not viewable";
872: } else if (err == AlreadyGrabbed) {
873: interp->result = "grab failed: another application has grab";
874: } else if (err == GrabFrozen) {
875: interp->result = "grab failed: keyboard or pointer frozen";
876: } else if (err == GrabInvalidTime) {
877: interp->result = "grab failed: invalid time";
878: } else {
879: char msg[100];
880:
881: sprintf(msg, "grab failed for unknown reason (code %d)",
882: err);
883: Tcl_AppendResult(interp, msg, (char *) NULL);
884: }
885: return TCL_ERROR;
886: }
887: } else if ((c == 'u') && (strncmp(argv[1], "ungrab", length) == 0)) {
888: Tk_Window tkwin;
889:
890: if ((argc != 3) ||
891: ((tkwin = Tk_NameToWindow(interp, argv[2],
892: menuPtr->tkwin)) == NULL)) {
893: Tcl_AppendResult(interp, "wrong # args: should be \"",
894: argv[0], " ungrab window\"", (char *) NULL);
895: goto error;
896: }
897:
898: XUngrabPointer(Tk_Display(tkwin),
899: TkCurrentTime(((TkWindow *)tkwin)->dispPtr));
900:
901: } else if ((c == 'd') && (strncmp(argv[1], "distance", length) == 0)
902: && (length >= 3)) {
903: int distance;
904:
905: if (argc != 2) {
906: Tcl_AppendResult(interp, "wrong # args: should be \"",
907: argv[0], " distance\"", (char *) NULL);
908: goto error;
909: }
910: distance = (int)(sqrt((menuPtr->dx * menuPtr->dx) + (menuPtr->dy * menuPtr->dy)) + 0.499);
911: sprintf(interp->result, "%d", distance);
912: } else if ((c == 'd') && (strncmp(argv[1], "direction", length) == 0)
913: && (length >= 3)) {
914: int direction;
915:
916: if (argc != 2) {
917: Tcl_AppendResult(interp, "wrong # args: should be \"",
918: argv[0], " direction\"", (char *) NULL);
919: goto error;
920: }
921: direction = (int)(RAD_TO_DEG(atan2(menuPtr->dy, menuPtr->dx)) + 0.499);
922: if (direction < 0) direction += 360;
923: sprintf(interp->result, "%d", direction);
924: } else {
925: Tcl_AppendResult(interp, "bad option \"", argv[1],
926: "\": must be activate, show, add, configure, delete, ",
927: "entryconfigure, index, invoke, post, unpost, pending, ",
928: "defer, grab, or ungrab", (char *) NULL);
929: goto error;
930: }
931: done:
932: Tk_Release((ClientData) menuPtr);
933: return result;
934:
935: error:
936: Tk_Release((ClientData) menuPtr);
937: return TCL_ERROR;
938: }
939:
940: /*
941: *----------------------------------------------------------------------
942: *
943: * DestroyPieMenu --
944: *
945: * This procedure is invoked by Tk_EventuallyFree or Tk_Release
946: * to clean up the internal structure of a pie menu at a safe time
947: * (when no-one is using it anymore).
948: *
949: * Results:
950: * None.
951: *
952: * Side effects:
953: * Everything associated with the pie menu is freed up.
954: *
955: *----------------------------------------------------------------------
956: */
957:
958: static void
959: DestroyPieMenu(clientData)
960: ClientData clientData; /* Info about menu widget. */
961: {
962: register PieMenu *menuPtr = (PieMenu *) clientData;
963: int i;
964:
965: /* Should we delete the event handler? */
966:
967: for (i = 0; i < menuPtr->numEntries; i++) {
968: DestroyPieMenuEntry((ClientData) menuPtr->entries[i]);
969: }
970: if (menuPtr->entries != NULL) {
971: ckfree((char *) menuPtr->entries);
972: }
973: if (menuPtr->border != NULL) {
974: Tk_Free3DBorder(menuPtr->border);
975: }
976: if (menuPtr->activeBorder != NULL) {
977: Tk_Free3DBorder(menuPtr->activeBorder);
978: }
979: if (menuPtr->fontPtr != NULL) {
980: Tk_FreeFontStruct(menuPtr->fontPtr);
981: }
982: if (menuPtr->fg != NULL) {
983: Tk_FreeColor(menuPtr->fg);
984: }
985: if (menuPtr->textGC != None) {
986: Tk_FreeGC(menuPtr->textGC);
987: }
988: if (menuPtr->activeFg != NULL) {
989: Tk_FreeColor(menuPtr->activeFg);
990: }
991: if (menuPtr->activeGC != None) {
992: Tk_FreeGC(menuPtr->activeGC);
993: }
994: if (menuPtr->cursor != None) {
995: Tk_FreeCursor(menuPtr->cursor);
996: }
997: ckfree((char *) menuPtr);
998: }
999:
1000: /*
1001: *----------------------------------------------------------------------
1002: *
1003: * DestroyPieMenuEntry --
1004: *
1005: * This procedure is invoked by Tk_EventuallyFree or Tk_Release
1006: * to clean up the internal structure of a pie menu entry at a safe
1007: * time (when no-one is using it anymore).
1008: *
1009: * Results:
1010: * None.
1011: *
1012: * Side effects:
1013: * Everything associated with the pie menu entry is freed up.
1014: *
1015: *----------------------------------------------------------------------
1016: */
1017:
1018: static void
1019: DestroyPieMenuEntry(clientData)
1020: ClientData clientData; /* Pointer to entry to be freed. */
1021: {
1022: register PieMenuEntry *mePtr = (PieMenuEntry *) clientData;
1023: PieMenu *menuPtr = mePtr->piemenuPtr;
1024:
1025: if (menuPtr->postedPie == mePtr) {
1026: if (UnpostSubPieMenu(menuPtr->interp, menuPtr)
1027: != TCL_OK) {
1028: TkBindError(menuPtr->interp);
1029: }
1030: }
1031: if (mePtr->label != NULL) {
1032: ckfree(mePtr->label);
1033: }
1034: if (mePtr->bitmap != None) {
1035: Tk_FreePixmap(mePtr->bitmap);
1036: }
1037: if (mePtr->border != NULL) {
1038: Tk_Free3DBorder(mePtr->border);
1039: }
1040: if (mePtr->activeBorder != NULL) {
1041: Tk_Free3DBorder(mePtr->activeBorder);
1042: }
1043: if (mePtr->fontPtr != NULL) {
1044: Tk_FreeFontStruct(mePtr->fontPtr);
1045: }
1046: if (mePtr->textGC != NULL) {
1047: Tk_FreeGC(mePtr->textGC);
1048: }
1049: if (mePtr->activeGC != NULL) {
1050: Tk_FreeGC(mePtr->activeGC);
1051: }
1052: if (mePtr->command != NULL) {
1053: ckfree(mePtr->command);
1054: }
1055: if (mePtr->name != NULL) {
1056: ckfree(mePtr->name);
1057: }
1058: ckfree((char *) mePtr);
1059: }
1060:
1061: /*
1062: *----------------------------------------------------------------------
1063: *
1064: * ConfigurePieMenu --
1065: *
1066: * This procedure is called to process an argv/argc list, plus
1067: * the Tk option database, in order to configure (or
1068: * reconfigure) a menu widget.
1069: *
1070: * Results:
1071: * The return value is a standard Tcl result. If TCL_ERROR is
1072: * returned, then interp->result contains an error message.
1073: *
1074: * Side effects:
1075: * Configuration information, such as colors, font, etc. get set
1076: * for menuPtr; old resources get freed, if there were any.
1077: *
1078: *----------------------------------------------------------------------
1079: */
1080:
1081: static int
1082: ConfigurePieMenu(interp, menuPtr, argc, argv, flags)
1083: Tcl_Interp *interp; /* Used for error reporting. */
1084: register PieMenu *menuPtr; /* Information about widget; may or may
1085: * not already have values for some fields. */
1086: int argc; /* Number of valid entries in argv. */
1087: char **argv; /* Arguments. */
1088: int flags; /* Flags to pass to Tk_ConfigureWidget. */
1089: {
1090: XGCValues gcValues;
1091: GC newGC;
1092: int i;
1093:
1094: if (Tk_ConfigureWidget(interp, menuPtr->tkwin, configSpecs,
1095: argc, argv, (char *) menuPtr, flags) != TCL_OK) {
1096: return TCL_ERROR;
1097: }
1098:
1099: /*
1100: * A few options need special processing, such as setting the
1101: * background from a 3-D border, or filling in complicated
1102: * defaults that couldn't be specified to Tk_ConfigureWidget.
1103: */
1104:
1105: if (menuPtr->title == NULL) {
1106: menuPtr->titleLength = 0;
1107: } else {
1108: menuPtr->titleLength = strlen(menuPtr->title);
1109: }
1110:
1111: Tk_SetBackgroundFromBorder(menuPtr->tkwin, menuPtr->border);
1112:
1113: gcValues.font = menuPtr->fontPtr->fid;
1114: gcValues.foreground = menuPtr->fg->pixel;
1115: gcValues.background = Tk_3DBorderColor(menuPtr->border)->pixel;
1116: newGC = Tk_GetGC(menuPtr->tkwin, GCForeground|GCBackground|GCFont,
1117: &gcValues);
1118: if (menuPtr->textGC != None) {
1119: Tk_FreeGC(menuPtr->textGC);
1120: }
1121: menuPtr->textGC = newGC;
1122:
1123: gcValues.font = menuPtr->fontPtr->fid;
1124: gcValues.foreground = menuPtr->activeFg->pixel;
1125: gcValues.background = Tk_3DBorderColor(menuPtr->activeBorder)->pixel;
1126: newGC = Tk_GetGC(menuPtr->tkwin, GCForeground|GCBackground|GCFont,
1127: &gcValues);
1128: if (menuPtr->activeGC != None) {
1129: Tk_FreeGC(menuPtr->activeGC);
1130: }
1131: menuPtr->activeGC = newGC;
1132:
1133: /*
1134: * After reconfiguring a menu, we need to reconfigure all of the
1135: * entries in the menu, since some of the things in the children
1136: * (such as graphics contexts) may have to change to reflect changes
1137: * in the parent.
1138: */
1139:
1140: for (i = 0; i < menuPtr->numEntries; i++) {
1141: PieMenuEntry *mePtr;
1142:
1143: mePtr = menuPtr->entries[i];
1144: ConfigurePieMenuEntry(interp, menuPtr, mePtr, i, 0, (char **) NULL,
1145: TK_CONFIG_ARGV_ONLY | COMMAND_MASK << mePtr->type);
1146: }
1147:
1148: if (!(menuPtr->flags & RESIZE_PENDING)) {
1149: menuPtr->flags |= RESIZE_PENDING;
1150: Tk_DoWhenIdle(ComputePieMenuGeometry, (ClientData) menuPtr);
1151: }
1152:
1153: return TCL_OK;
1154: }
1155:
1156: /*
1157: *----------------------------------------------------------------------
1158: *
1159: * ConfigurePieMenuEntry --
1160: *
1161: * This procedure is called to process an argv/argc list, plus
1162: * the Tk option database, in order to configure (or
1163: * reconfigure) one entry in a menu.
1164: *
1165: * Results:
1166: * The return value is a standard Tcl result. If TCL_ERROR is
1167: * returned, then interp->result contains an error message.
1168: *
1169: * Side effects:
1170: * Configuration information such as label and accelerator get
1171: * set for mePtr; old resources get freed, if there were any.
1172: *
1173: *----------------------------------------------------------------------
1174: */
1175:
1176: static int
1177: ConfigurePieMenuEntry(interp, menuPtr, mePtr, index, argc, argv, flags)
1178: Tcl_Interp *interp; /* Used for error reporting. */
1179: PieMenu *menuPtr; /* Information about whole menu. */
1180: register PieMenuEntry *mePtr; /* Information about menu entry; may
1181: * or may not already have values for
1182: * some fields. */
1183: int index; /* Index of mePtr within menuPtr's
1184: * entries. */
1185: int argc; /* Number of valid entries in argv. */
1186: char **argv; /* Arguments. */
1187: int flags; /* Additional flags to pass to
1188: * Tk_ConfigureWidget. */
1189: {
1190: XGCValues gcValues;
1191: GC newGC, newActiveGC;
1192:
1193: /*
1194: * If this entry is a piemenu and the piemenu is posted, then unpost
1195: * it before reconfiguring the entry (otherwise the reconfigure might
1196: * change the name of the piemenu entry, leaving a posted menu
1197: * high and dry).
1198: */
1199:
1200: if (menuPtr->postedPie == mePtr) {
1201: if (UnpostSubPieMenu(menuPtr->interp, menuPtr)
1202: != TCL_OK) {
1203: TkBindError(menuPtr->interp);
1204: }
1205: }
1206:
1207: if (Tk_ConfigureWidget(interp, menuPtr->tkwin, entryConfigSpecs,
1208: argc, argv, (char *) mePtr,
1209: flags | (COMMAND_MASK << mePtr->type)) != TCL_OK) {
1210: return TCL_ERROR;
1211: }
1212:
1213: /*
1214: * The code below handles special configuration stuff not taken
1215: * care of by Tk_ConfigureWidget, such as special processing for
1216: * defaults, sizing strings, graphics contexts, etc.
1217: */
1218:
1219: if (mePtr->label == NULL) {
1220: mePtr->labelLength = 0;
1221: } else {
1222: mePtr->labelLength = strlen(mePtr->label);
1223: }
1224:
1225: if (index != menuPtr->active) {
1226: ActivatePieMenuEntry(menuPtr, index, 0);
1227: }
1228:
1229: if ((mePtr->fontPtr != NULL) ||
1230: (mePtr->type == PIEMENU_ENTRY)) {
1231: gcValues.foreground = menuPtr->fg->pixel;
1232: gcValues.background = Tk_3DBorderColor(
1233: (mePtr->border != NULL) ? mePtr->border : menuPtr->border)
1234: ->pixel;
1235: if (mePtr->fontPtr != NULL) {
1236: gcValues.font = mePtr->fontPtr->fid;
1237: } else {
1238: if (menuPtr->titlefontPtr != NULL)
1239: gcValues.font = menuPtr->titlefontPtr->fid;
1240: else
1241: gcValues.font = menuPtr->fontPtr->fid;
1242: }
1243:
1244: /*
1245: * Note: disable GraphicsExpose events; we know there won't be
1246: * obscured areas when copying from an off-screen pixmap to the
1247: * screen and this gets rid of unnecessary events.
1248: */
1249:
1250: gcValues.graphics_exposures = False;
1251: newGC = Tk_GetGC(menuPtr->tkwin,
1252: GCForeground|GCBackground|GCFont|GCGraphicsExposures,
1253: &gcValues);
1254:
1255: gcValues.foreground = menuPtr->activeFg->pixel;
1256: gcValues.background = Tk_3DBorderColor(
1257: (mePtr->activeBorder != NULL) ? mePtr->activeBorder
1258: : menuPtr->activeBorder)->pixel;
1259: newActiveGC = Tk_GetGC(menuPtr->tkwin,
1260: GCForeground|GCBackground|GCFont|GCGraphicsExposures,
1261: &gcValues);
1262: } else {
1263: newGC = NULL;
1264: newActiveGC = NULL;
1265: }
1266:
1267: if (mePtr->textGC != NULL) {
1268: Tk_FreeGC(mePtr->textGC);
1269: }
1270: mePtr->textGC = newGC;
1271:
1272: if (mePtr->activeGC != NULL) {
1273: Tk_FreeGC(mePtr->activeGC);
1274: }
1275: mePtr->activeGC = newActiveGC;
1276:
1277: if (!(menuPtr->flags & RESIZE_PENDING)) {
1278: menuPtr->flags |= RESIZE_PENDING;
1279: Tk_DoWhenIdle(ComputePieMenuGeometry, (ClientData) menuPtr);
1280: }
1281: return TCL_OK;
1282: }
1283:
1284: /*
1285: *--------------------------------------------------------------
1286: *
1287: * ComputePieMenuGeometry --
1288: *
1289: * This procedure is invoked to recompute the size and
1290: * layout of a menu. It is called as a when-idle handler so
1291: * that it only gets done once, even if a group of changes is
1292: * made to the menu.
1293: *
1294: * Results:
1295: * None.
1296: *
1297: * Side effects:
1298: * Fields of menu entries are changed to reflect their
1299: * current positions, and the size of the menu window
1300: * itself may be changed.
1301: *
1302: *--------------------------------------------------------------
1303: */
1304:
1305: static void
1306: ComputePieMenuGeometry(clientData)
1307: ClientData clientData; /* Structure describing menu. */
1308: {
1309: PieMenu *menuPtr = (PieMenu *) clientData;
1310:
1311: if (menuPtr->tkwin == NULL) {
1312: return;
1313: }
1314:
1315: LayoutPieMenu(menuPtr);
1316:
1317: if ((menuPtr->width != Tk_ReqWidth(menuPtr->tkwin)) ||
1318: (menuPtr->height != Tk_ReqHeight(menuPtr->tkwin))) {
1319: Tk_GeometryRequest(menuPtr->tkwin, menuPtr->width, menuPtr->height);
1320: } else {
1321: /*
1322: * Must always force a redisplay here if the window is mapped
1323: * (even if the size didn't change, something else might have
1324: * changed in the menu, such as a label or accelerator). The
1325: * resize will force a redisplay above.
1326: */
1327:
1328: EventuallyRedrawPieMenu(menuPtr, -1);
1329: }
1330:
1331: menuPtr->flags &= ~RESIZE_PENDING;
1332: }
1333:
1334: /*
1335: *----------------------------------------------------------------------
1336: *
1337: * DisplayPieMenu --
1338: *
1339: * This procedure is invoked to display a pie menu widget.
1340: *
1341: * Results:
1342: * None.
1343: *
1344: * Side effects:
1345: * Commands are output to X to display the pie menu in its
1346: * current mode.
1347: *
1348: *----------------------------------------------------------------------
1349: */
1350:
1351: static void
1352: DisplayPieMenu(clientData)
1353: ClientData clientData; /* Information about widget. */
1354: {
1355: register PieMenu *menuPtr = (PieMenu *) clientData;
1356: register Tk_Window tkwin = menuPtr->tkwin;
1357: XFontStruct *fontPtr;
1358: int index;
1359:
1360: menuPtr->flags &= ~REDRAW_PENDING;
1361: if ((menuPtr->tkwin == NULL) || !Tk_IsMapped(menuPtr->tkwin)) {
1362: return;
1363: }
1364:
1365: if (menuPtr->titlefontPtr != NULL) {
1366: fontPtr = menuPtr->titlefontPtr;
1367: } else {
1368: fontPtr = menuPtr->fontPtr;
1369: }
1370:
1371: if (menuPtr->titleLength != 0) {
1372: Tk_Draw3DRectangle(Tk_Display(tkwin), Tk_WindowId(tkwin),
1373: menuPtr->border,
1374: menuPtr->borderWidth, menuPtr->borderWidth,
1375: Tk_Width(tkwin) - 2*menuPtr->borderWidth,
1376: menuPtr->title_height + 2*menuPtr->borderWidth,
1377: menuPtr->borderWidth, TK_RELIEF_RAISED);
1378:
1379: TkDisplayChars(Tk_Display(tkwin), Tk_WindowId(tkwin), menuPtr->textGC,
1380: fontPtr, menuPtr->title, menuPtr->titleLength,
1381: menuPtr->title_x, menuPtr->title_y,
1382: TK_NEWLINES_NOT_SPECIAL);
1383: }
1384:
1385: if (menuPtr->segments) {
1386: XSetLineAttributes(Tk_Display(tkwin), menuPtr->textGC,
1387: 0, LineSolid, CapButt, JoinMiter);
1388: XDrawSegments(Tk_Display(tkwin), Tk_WindowId(tkwin),
1389: menuPtr->textGC, menuPtr->segments, menuPtr->numEntries);
1390: }
1391:
1392: Tk_Draw3DRectangle(Tk_Display(tkwin), Tk_WindowId(tkwin), menuPtr->border,
1393: 0, 0, Tk_Width(tkwin), Tk_Height(tkwin),
1394: menuPtr->borderWidth, TK_RELIEF_RAISED);
1395:
1396: UpdatePieMenuEntries(menuPtr);
1397: }
1398:
1399: /*
1400: *----------------------------------------------------------------------
1401: *
1402: * UpdatePieMenu --
1403: *
1404: * This procedure is invoked to update a pie menu widget.
1405: *
1406: * Results:
1407: * None.
1408: *
1409: * Side effects:
1410: * Commands are output to X to update the pie menu in its
1411: * current mode.
1412: *
1413: *----------------------------------------------------------------------
1414: */
1415:
1416: static void
1417: UpdatePieMenu(clientData)
1418: ClientData clientData; /* Information about widget. */
1419: {
1420: register PieMenu *menuPtr = (PieMenu *) clientData;
1421:
1422: menuPtr->flags &= ~UPDATE_PENDING;
1423: if ((menuPtr->tkwin == NULL) || !Tk_IsMapped(menuPtr->tkwin)) {
1424: return;
1425: }
1426:
1427: UpdatePieMenuEntries(menuPtr);
1428: }
1429:
1430:
1431: UpdatePieMenuEntries(menuPtr)
1432: PieMenu *menuPtr;
1433: {
1434: register PieMenuEntry *mePtr;
1435: register Tk_Window tkwin = menuPtr->tkwin;
1436: XFontStruct *fontPtr;
1437: int index;
1438: GC gc;
1439:
1440: for (index = 0; index < menuPtr->numEntries; index++) {
1441: mePtr = menuPtr->entries[index];
1442: if (!(mePtr->flags & ENTRY_NEEDS_REDISPLAY)) {
1443: continue;
1444: }
1445: mePtr->flags &= ~ENTRY_NEEDS_REDISPLAY;
1446:
1447: /*
1448: * Background.
1449: */
1450:
1451: Tk_Fill3DRectangle(Tk_Display(tkwin), Tk_WindowId(tkwin),
1452: ((mePtr->activeBorder != NULL)
1453: ? mePtr->activeBorder
1454: : menuPtr->activeBorder),
1455: mePtr->x, mePtr->y,
1456: mePtr->width, mePtr->height,
1457: menuPtr->activeBorderWidth,
1458: ((index == menuPtr->active)
1459: ? TK_RELIEF_SUNKEN
1460: : ((HaveShape && menuPtr->shaped)
1461: ? TK_RELIEF_RAISED
1462: : TK_RELIEF_FLAT)));
1463:
1464: gc = mePtr->textGC;
1465: if (gc == NULL) {
1466: gc = menuPtr->textGC;
1467: }
1468:
1469: /*
1470: * Draw label or bitmap for entry.
1471: */
1472:
1473: fontPtr = mePtr->fontPtr;
1474: if (fontPtr == NULL) {
1475: fontPtr = menuPtr->fontPtr;
1476: }
1477: if (mePtr->bitmap != None) {
1478: unsigned int width, height;
1479:
1480: Tk_SizeOfPixmap(mePtr->bitmap, &width, &height);
1481: XCopyArea(Tk_Display(tkwin), mePtr->bitmap, Tk_WindowId(tkwin),
1482: gc, 0, 0, width, height,
1483: mePtr->label_x, mePtr->label_y);
1484: } else {
1485: if (mePtr->label != NULL) {
1486: TkDisplayChars(Tk_Display(tkwin), Tk_WindowId(tkwin), gc,
1487: fontPtr, mePtr->label, mePtr->labelLength,
1488: mePtr->label_x, mePtr->label_y,
1489: TK_NEWLINES_NOT_SPECIAL);
1490: }
1491: }
1492: }
1493: }
1494:
1495: /*
1496: *--------------------------------------------------------------
1497: *
1498: * GetPieMenuIndex --
1499: *
1500: * Parse a textual index into a pie menu and return the numerical
1501: * index of the indicated entry.
1502: *
1503: * Results:
1504: * A standard Tcl result. If all went well, then *indexPtr is
1505: * filled in with the entry index corresponding to string
1506: * (ranges from -1 to the number of entries in the pie menu minus
1507: * one). Otherwise an error message is left in interp->result.
1508: *
1509: * Side effects:
1510: * None.
1511: *
1512: *--------------------------------------------------------------
1513: */
1514:
1515: static int
1516: GetPieMenuIndex(interp, menuPtr, string, indexPtr)
1517: Tcl_Interp *interp; /* For error messages. */
1518: PieMenu *menuPtr; /* Menu for which the index is being
1519: * specified. */
1520: char *string; /* Specification of an entry in menu. See
1521: * manual entry for valid .*/
1522: int *indexPtr; /* Where to store converted relief. */
1523: {
1524: int i, y;
1525:
1526: if ((string[0] == 'a') && (strcmp(string, "active") == 0)) {
1527: *indexPtr = menuPtr->active;
1528: return TCL_OK;
1529: }
1530:
1531: if ((string[0] == 'l') && (strcmp(string, "last") == 0)) {
1532: *indexPtr = menuPtr->numEntries-1;
1533: return TCL_OK;
1534: }
1535:
1536: if ((string[0] == 'n') && (strcmp(string, "none") == 0)) {
1537: *indexPtr = -1;
1538: return TCL_OK;
1539: }
1540:
1541: if (string[0] == '@') {
1542: char xstr[32], ystr[32];
1543: int x, y;
1544:
1545: if ((sscanf(&string[1], "%31[^,],%31[^,]", xstr, ystr) == 2) &&
1546: (Tcl_GetInt(interp, xstr, &x) == TCL_OK) &&
1547: (Tcl_GetInt(interp, ystr, &y) == TCL_OK)) {
1548: *indexPtr = CalcPieMenuItem(menuPtr, x, y);
1549: return TCL_OK;
1550: } else {
1551: Tcl_SetResult(interp, (char *) NULL, TCL_STATIC);
1552: }
1553: }
1554:
1555: if (isdigit(string[0])) {
1556: if (Tcl_GetInt(interp, string, &i) == TCL_OK) {
1557: if ((i < menuPtr->numEntries) && (i >= 0)) {
1558: *indexPtr = i;
1559: return TCL_OK;
1560: }
1561: } else {
1562: Tcl_SetResult(interp, (char *) NULL, TCL_STATIC);
1563: }
1564: }
1565:
1566: for (i = 0; i < menuPtr->numEntries; i++) {
1567: char *label;
1568:
1569: label = menuPtr->entries[i]->label;
1570: if ((label != NULL)
1571: && (Tcl_StringMatch(menuPtr->entries[i]->label, string))) {
1572: *indexPtr = i;
1573: return TCL_OK;
1574: }
1575: }
1576:
1577: Tcl_AppendResult(interp, "bad menu entry index \"",
1578: string, "\"", (char *) NULL);
1579: return TCL_ERROR;
1580: }
1581:
1582: /*
1583: *--------------------------------------------------------------
1584: *
1585: * PieMenuEventProc --
1586: *
1587: * This procedure is invoked by the Tk dispatcher for various
1588: * events on pie menus.
1589: *
1590: * Results:
1591: * None.
1592: *
1593: * Side effects:
1594: * When the window gets deleted, internal structures get
1595: * cleaned up. When it gets exposed, it is redisplayed.
1596: *
1597: *--------------------------------------------------------------
1598: */
1599:
1600: static void
1601: PieMenuEventProc(clientData, eventPtr)
1602: ClientData clientData; /* Information about window. */
1603: XEvent *eventPtr; /* Information about event. */
1604: {
1605: PieMenu *menuPtr = (PieMenu *) clientData;
1606: switch (eventPtr->type) {
1607: case Expose:
1608: if (eventPtr->xexpose.count == 0) {
1609: EventuallyRedrawPieMenu(menuPtr, -1);
1610: }
1611: break;
1612: case DestroyNotify:
1613: Tcl_DeleteCommand(menuPtr->interp, Tk_PathName(menuPtr->tkwin));
1614:
1615: /*
1616: * Careful! Must delete the event-sharing information here
1617: * rather than in DestroyPieMenu. By the time that procedure
1618: * is called the tkwin may have been reused, resulting in some
1619: * other window accidentally being cut off from shared events.
1620: */
1621:
1622: Tk_UnshareEvents(menuPtr->tkwin, menuPtr->group);
1623: menuPtr->tkwin = NULL;
1624: if (menuPtr->flags & REDRAW_PENDING) {
1625: Tk_CancelIdleCall(DisplayPieMenu, (ClientData) menuPtr);
1626: }
1627: if (menuPtr->flags & UPDATE_PENDING) {
1628: Tk_CancelIdleCall(UpdatePieMenu, (ClientData) menuPtr);
1629: }
1630: if (menuPtr->flags & RESIZE_PENDING) {
1631: Tk_CancelIdleCall(ComputePieMenuGeometry, (ClientData) menuPtr);
1632: }
1633: if (menuPtr->flags & POPUP_PENDING) {
1634: Tk_CancelIdleCall(PopupPieMenu, (ClientData) menuPtr);
1635: }
1636: Tk_EventuallyFree((ClientData) menuPtr, DestroyPieMenu);
1637: break;
1638: case MotionNotify:
1639: break;
1640: case ButtonPress:
1641: break;
1642: case ButtonRelease:
1643: break;
1644: }
1645: }
1646:
1647:
1648:
1649: /*
1650: *----------------------------------------------------------------------
1651: *
1652: * EventuallyRedrawPieMenu --
1653: *
1654: * Arrange for an entry of a pie menu, or the whole pie menu,
1655: * to be redisplayed at some point in the future.
1656: *
1657: * Results:
1658: * None.
1659: *
1660: * Side effects:
1661: * A when-idle hander is scheduled to do the redisplay, if there
1662: * isn't one already scheduled.
1663: *
1664: *----------------------------------------------------------------------
1665: */
1666:
1667: static void
1668: EventuallyRedrawPieMenu(menuPtr, index)
1669: register PieMenu *menuPtr; /* Information about menu to redraw. */
1670: int index; /* Which entry to redraw. If -1, then
1671: * all the entries in the menu are redrawn. */
1672: {
1673: if (menuPtr->tkwin == NULL) {
1674: return;
1675: }
1676: if (index != -1) {
1677: menuPtr->entries[index]->flags |= ENTRY_NEEDS_REDISPLAY;
1678: } else {
1679: for (index = 0; index < menuPtr->numEntries; index++) {
1680: menuPtr->entries[index]->flags |= ENTRY_NEEDS_REDISPLAY;
1681: }
1682: index = -1;
1683: }
1684: if ((menuPtr->tkwin == NULL) || !Tk_IsMapped(menuPtr->tkwin)
1685: || (menuPtr->flags & REDRAW_PENDING)) {
1686: return;
1687: }
1688:
1689: if (index == -1) {
1690: if (menuPtr->flags & UPDATE_PENDING) {
1691: Tk_CancelIdleCall(UpdatePieMenu, (ClientData) menuPtr);
1692: }
1693: Tk_DoWhenIdle(DisplayPieMenu, (ClientData) menuPtr);
1694: menuPtr->flags |= REDRAW_PENDING;
1695: } else {
1696: Tk_DoWhenIdle(UpdatePieMenu, (ClientData) menuPtr);
1697: menuPtr->flags |= UPDATE_PENDING;
1698: }
1699: }
1700:
1701:
1702: static void
1703: PopupPieMenu(clientData)
1704: ClientData clientData; /* Information about widget. */
1705: {
1706: register PieMenu *menuPtr = (PieMenu *) clientData;
1707:
1708: NeverPopupPieMenu(menuPtr);
1709:
1710: if (Tk_IsMapped(menuPtr->tkwin)) {
1711: return;
1712: }
1713:
1714: ShapePieMenu(menuPtr);
1715: Tk_MapWindow(menuPtr->tkwin);
1716: }
1717:
1718:
1719: static void
1720: NowPopupPieMenu(menuPtr)
1721: register PieMenu *menuPtr;
1722: {
1723: PopupPieMenu((ClientData)menuPtr);
1724: }
1725:
1726:
1727: static void
1728: NeverPopupPieMenu(menuPtr)
1729: register PieMenu *menuPtr;
1730: {
1731: if (menuPtr->flags & POPUP_PENDING) {
1732: Tk_DeleteTimerHandler(menuPtr->popup_timer_token);
1733: menuPtr->popup_timer_token = 0;
1734: menuPtr->flags &= ~POPUP_PENDING;
1735: }
1736: }
1737:
1738:
1739: static void
1740: EventuallyPopupPieMenu(menuPtr)
1741: register PieMenu *menuPtr;
1742: {
1743: NeverPopupPieMenu(menuPtr);
1744:
1745: if (Tk_IsMapped(menuPtr->tkwin)) {
1746: return;
1747: }
1748:
1749: menuPtr->popup_timer_token =
1750: Tk_CreateTimerHandler(menuPtr->popup_delay,
1751: PopupPieMenu, (ClientData) menuPtr);
1752: menuPtr->flags |= POPUP_PENDING;
1753: }
1754:
1755:
1756: static void
1757: DeferPopupPieMenu(menuPtr)
1758: register PieMenu *menuPtr;
1759: {
1760: if (menuPtr->flags & POPUP_PENDING) {
1761: EventuallyPopupPieMenu(menuPtr);
1762: }
1763: }
1764:
1765:
1766:
1767: /*
1768: *--------------------------------------------------------------
1769: *
1770: * UnpostSubPieMenu --
1771: *
1772: * This procedure unposts any submenu.
1773: *
1774: * Results:
1775: * A standard Tcl return result. Errors may occur in the
1776: * Tcl commands generated to unpost submenus.
1777: *
1778: * Side effects:
1779: * If there is already a submenu posted, it is unposted.
1780: *
1781: *--------------------------------------------------------------
1782: */
1783:
1784: static int
1785: UnpostSubPieMenu(interp, menuPtr)
1786: Tcl_Interp *interp; /* Used for invoking sub-commands and
1787: * reporting errors. */
1788: register PieMenu *menuPtr; /* Information about menu as a whole. */
1789: {
1790: char string[30];
1791: int result, x, y, win_x, win_y;
1792: unsigned int key_buttons;
1793: Window root, child;
1794:
1795: if (menuPtr->postedPie == NULL) {
1796: return TCL_OK;
1797: }
1798:
1799: result = Tcl_VarEval(interp, menuPtr->postedPie->name,
1800: " unpost", (char *) NULL);
1801: menuPtr->postedPie = NULL;
1802:
1803: return result;
1804: }
1805:
1806: /*
1807: *----------------------------------------------------------------------
1808: *
1809: * ActivatePieMenuEntry --
1810: *
1811: * This procedure is invoked to make a particular pie menu
1812: * entry the active one, deactivating any other entry that
1813: * might currently be active.
1814: *
1815: * Results:
1816: * The return value is a standard Tcl result (errors can occur
1817: * while posting and unposting submenus).
1818: *
1819: * Side effects:
1820: * Pie menu entries get redisplayed, and the active entry
1821: * changes. Submenus may get posted and unposted.
1822: *
1823: *----------------------------------------------------------------------
1824: */
1825:
1826: static int
1827: ActivatePieMenuEntry(menuPtr, index, preview)
1828: register PieMenu *menuPtr; /* Menu in which to activate. */
1829: int index; /* Index of entry to activate, or
1830: * -1 to deactivate all entries. */
1831: int preview; /* 1 to execute previewer */
1832: {
1833: register PieMenuEntry *mePtr;
1834: int result = TCL_OK;
1835:
1836: if (menuPtr->active >= 0) {
1837: mePtr = menuPtr->entries[menuPtr->active];
1838:
1839: EventuallyRedrawPieMenu(menuPtr, menuPtr->active);
1840: }
1841: menuPtr->active = index;
1842: if (index >= 0) {
1843: mePtr = menuPtr->entries[index];
1844: EventuallyRedrawPieMenu(menuPtr, index);
1845: if (preview) {
1846: Tk_Preserve((ClientData) mePtr);
1847: if (mePtr->preview != NULL) {
1848: result = Tcl_GlobalEval(menuPtr->interp, mePtr->preview);
1849: }
1850: Tk_Release((ClientData) mePtr);
1851: }
1852: } else {
1853: /* We're doing this in tcl these days, for finer control. */
1854: #if 0
1855: if (preview && menuPtr->preview) {
1856: result = Tcl_GlobalEval(menuPtr->interp, menuPtr->preview);
1857: }
1858: #endif
1859: }
1860: return result;
1861: }
1862:
1863:
1864: /*
1865: * This pie menu tracking code determines the slice the cursor
1866: * is in by representing slice edge angles as (quadrant, slope)
1867: * pairs that can be quickly computed and compared.
1868: *
1869: * The slope is defined such that it is greater than or equal to zero,
1870: * less than infinity, and increasing counter-clockwise around the menu.
1871: * Each of the four quadrants encompasses one range of slope.
1872: *
1873: * Y
1874: * ^
1875: * | x>0, y>=0
1876: * x<=0, y>0 <--+ y/x
1877: * -x/y | ^
1878: * quad 1 | quad 0 | X
1879: * -----+--------+--------+---->
1880: * | quad 2 | quad 3
1881: * V | -x/y
1882: * x<0, y<=0 +--> x>=0, y<0
1883: * y/x |
1884: * |
1885: *
1886: * The quadrants and slopes of the item edges are all precalculated,
1887: * during menu layout.
1888: * The quadrant and slope of the cursor must be calculated frequently
1889: * during menu tracking, so we just calculate the numerator and
1890: * denominator of the slope, and avoid an unnecessary division.
1891: * Instead of calculating "slope = numerator / denominator" then
1892: * testing "slope < it->slope", every time the cursor moves, we can
1893: * just test "numerator < (denominator * it->slope)".
1894: *
1895: * This algorithm works in a right-side-up coordinate space, but the final
1896: * results are tranformed into X-windows's up-side-down coordinate system
1897: * by subtracting the y values from the window height.
1898: */
1899:
1900:
1901: #define CALC_QUADRANT_SLOPE(x, y, quadrant, numerator, denominator) \
1902: if ((y) > 0) (quadrant) = ((x) > 0 ? 0 : 1); \
1903: else if ((y) < 0) (quadrant) = ((x) < 0 ? 2 : 3); \
1904: else (quadrant) = ((x) > 0 ? 0 : 2); \
1905: if ((quadrant) & 1) { \
1906: (numerator) = ABS((x)); (denominator) = ABS((y)); \
1907: } else { \
1908: (numerator) = ABS((y)); (denominator) = ABS((x)); \
1909: }
1910:
1911:
1912: int
1913: CalcPieMenuItem(menu, x, y)
1914: PieMenu *menu;
1915: int x, y;
1916: {
1917: register PieMenuEntry *it, *last_it;
1918: int i, j, order, quadrant;
1919: int numerator, denominator;
1920: int first, last_i, last_order;
1921:
1922: /*
1923: * Translate x and y from root window coordinates so they are
1924: * relative to the menu center, in right side up coordinates.
1925: */
1926:
1927: menu->dx = x = (x - menu->root_x) + 1;
1928: menu->dy = y = (menu->root_y - y) - 1;
1929:
1930: /*
1931: * If there are no menu items,
1932: * or we are within the inactive region in the menu center,
1933: * then there is no item selected.
1934: */
1935: if ((menu->numEntries == 0) ||
1936: ((x * x) + (y * y) <
1937: (menu->inactive_radius * menu->inactive_radius))) {
1938: return(-1);
1939: }
1940:
1941: /*
1942: * If there's only one item, then that must be it.
1943: */
1944: if (menu->numEntries == 1) {
1945: return(0);
1946: }
1947:
1948: /*
1949: * Calculate the quadrant, slope numerator, and slope denominator of
1950: * the cursor slope, to be used for comparisons.
1951: */
1952: CALC_QUADRANT_SLOPE(x, y, quadrant, numerator, denominator);
1953:
1954: /*
1955: * In most cases, during cursor tracking, the menu item that the
1956: * cursor is over will be the same as it was before (almost all
1957: * of the time), or one of the neighboring items (most of the
1958: * rest of the time). So we check those items first. But to keep
1959: * things simple, instead of actually checking the items in order of
1960: * frequency (the current, the two neighbors, then the rest), we just
1961: * start our loop around the menu items at the item *before* the
1962: * last selected menu item, so we still check the three most common
1963: * cases first (neighbor, current, neighbor, rest), without having
1964: * to complicate the code with special cases. Another strategy, that
1965: * might be good for menus with ridiculously many items, would be
1966: * [to check the current item first, then the two neighbors, then]
1967: * to do a binary search of the menu items (since they are ordered).
1968: * But that's more complicated and you shouldn't have that many menu
1969: * items anyway.
1970: */
1971:
1972: /*
1973: * Start at the item before current one.
1974: */
1975: first = menu->active - 1;
1976: if (first < 0)
1977: first = menu->numEntries - 1;
1978:
1979: /*
1980: * Initialize last_order such that we will go through the loop
1981: * at least once, validating last_i, last_order, and last_it for
1982: * the next time through the loop.
1983: */
1984: last_i = last_order = -1;
1985: i = first;
1986:
1987: it = menu->entries[i];
1988:
1989: while (1) {
1990:
1991: /* Legend: c = cursor, e = edge
1992: <cursor quad>,<edge quad>
1993: quad 1 | quad 0
1994: -------+-------
1995: quad 2 | quad 3
1996: */
1997:
1998: /* Set order = 1, if shortest direction from edge to cursor is ccw */
1999: switch ((quadrant - it->quadrant) & 3) {
2000:
2001: case 0: /*
2002: 0,0 1,1 2,2 3,3
2003: |ce ce| | |
2004: --+-- --+-- --+-- --+--
2005: | | ce| |ce
2006: */
2007: /* slope >= it->slope */
2008: order = ((float)numerator >= (float)(denominator * it->slope));
2009: break;
2010:
2011: case 1: /*
2012: 1,0 2,1 3,2 0,3
2013: c|e e| | |c
2014: --+-- --+-- --+-- --+--
2015: | c| e|c |e
2016: */
2017: order = 1;
2018: break;
2019:
2020: case 2: /*
2021: 2,0 3,1 0,2 1,3
2022: |e e| |c c|
2023: --+-- --+-- --+-- --+--
2024: c| |c e| |e
2025: */
2026: /* slope < it->slope */
2027: order = ((float)numerator < (float)(denominator * it->slope));
2028: break;
2029:
2030: case 3: /*
2031: 3,0 0,1 1,2 2,3
2032: |e e|c c| |
2033: --+-- --+-- --+-- --+--
2034: |c | e| c|e
2035: */
2036: order = 0;
2037: break;
2038: }
2039:
2040: /*
2041: * If we were counter-clockwise of the last leading edge,
2042: * and we're clockwise of this leading edge,
2043: * then we were in the last menu item.
2044: * (Note: first time through this loop last_order = -1 so we'll
2045: * go back through the loop at least once, after validating
2046: * last_order, last_i, and last_it.)
2047: */
2048: if ((last_order == 1) && (order == 0)) {
2049: return(last_i);
2050: }
2051: last_order = order;
2052:
2053: /*
2054: * Remember this menu item index, and move on to the next one
2055: * counter-clockwise around the circle.
2056: */
2057: last_i = i; last_it = it;
2058: if (++i >= menu->numEntries) {
2059: i = 0;
2060: }
2061: it = menu->entries[i];
2062:
2063: /*
2064: * If we've checked all the others, then that must have been it.
2065: * This saves us from checking the leading edge of the first
2066: * item again (It's also insurance against layout bugs.)
2067: */
2068: if (i == first) {
2069: return(last_i);
2070: }
2071: }
2072: }
2073:
2074:
2075: LayoutPieMenu(menu)
2076: PieMenu *menu;
2077: {
2078: int i;
2079: int total_slice, radius;
2080: int minx, miny, maxx, maxy;
2081: float angle;
2082: PieMenuEntry *it, *last;
2083: XFontStruct *font, *titlefont;
2084:
2085: /*
2086: * Calculate the sum of the menu item slice sizes.
2087: * Each menu item will get a (slice / total_slice) sized slice of the pie.
2088: */
2089: total_slice = 0;
2090: for (i = 0; i < menu->numEntries; i++) {
2091: total_slice += menu->entries[i]->slice;
2092: }
2093:
2094: if ((titlefont = menu->titlefontPtr) == NULL)
2095: titlefont = menu->fontPtr;
2096:
2097: /*
2098: * Calculate the subtend, angle, cosine, sine, quadrant, slope,
2099: * and size of each menu item.
2100: */
2101: angle = DEG_TO_RAD(menu->initial_angle);
2102: for (i = 0; i < menu->numEntries; i++) {
2103: register float edge_dx, edge_dy, numerator, denominator, twist;
2104: register int quadrant;
2105:
2106: it = menu->entries[i];
2107: if ((font = it->fontPtr) == NULL)
2108: font = menu->fontPtr;
2109:
2110: if (it->bitmap != None) {
2111: unsigned int bitmapWidth, bitmapHeight;
2112:
2113: Tk_SizeOfPixmap(it->bitmap, &bitmapWidth, &bitmapHeight);
2114: it->height = bitmapHeight;
2115: it->width = bitmapWidth;
2116: } else {
2117: it->height = font->ascent + font->descent;
2118: if (it->label != NULL) {
2119: (void) TkMeasureChars(font, it->label,
2120: it->labelLength, 0, (int) 100000,
2121: TK_NEWLINES_NOT_SPECIAL, &it->width);
2122: } else {
2123: it->width = 0;
2124: }
2125: }
2126: it->height += 2*menu->activeBorderWidth + 2;
2127: it->width += 2*menu->activeBorderWidth + 2;
2128:
2129: it->subtend = TWO_PI * it->slice / total_slice;
2130: twist = it->subtend / 2.0;
2131: if (i != 0) angle += twist;
2132: it->angle = angle;
2133: it->dx = cos(angle);
2134: it->dy = sin(angle);
2135: edge_dx = cos(angle - twist);
2136: edge_dy = sin(angle - twist);
2137: CALC_QUADRANT_SLOPE(edge_dx, edge_dy, quadrant, numerator, denominator);
2138: it->quadrant = quadrant;
2139: it->slope = (float)numerator / (float)denominator;
2140: angle += twist;
2141: }
2142:
2143: if ((radius = menu->fixed_radius) == 0) {
2144: radius = menu->min_radius;
2145: if (menu->numEntries > 1) {
2146: last = menu->entries[menu->numEntries - 1];
2147: for (i = 0; i < menu->numEntries; i++) {
2148: float dx, dy, ldx, ldy;
2149: int width, height, lwidth, lheight;
2150:
2151: it = menu->entries[i];
2152:
2153: dx = it->dx; dy = it->dy;
2154: width = it->width; height = it->height;
2155: ldx = last->dx; ldy = last->dy;
2156: lwidth = last->width; lheight = last->height;
2157: while (1) {
2158: register int x, y, lx, ly,
2159: x0max, y0max, x1min, y1min;
2160:
2161: x = dx * radius + it->x_offset;
2162: y = dy * radius + it->y_offset;
2163: lx = ldx * radius + last->x_offset;
2164: ly = ldy * radius + last->y_offset;
2165:
2166: /* Translate x y with respect to label size and position */
2167: if (ABS(x) <= 2) {
2168: x -= width/2;
2169: if (y < 0)
2170: y -= height;
2171: } else {
2172: if (x < 0)
2173: x -= width;
2174: y -= height/2;
2175: }
2176:
2177: if (ABS(lx) <= 2) {
2178: lx -= lwidth/2;
2179: if (ly < 0)
2180: ly -= lheight;
2181: } else {
2182: if (lx < 0)
2183: lx -= lwidth;
2184: ly -= lheight/2;
2185: }
2186:
2187: /* Do rects (x y width height) and (lx ly lwidth lheight) overlap? */
2188: x0max = x > lx ? x : lx;
2189: y0max = y > ly ? y : ly;
2190: x1min = x+width < lx+lwidth ? x+width : lx+lwidth;
2191: y1min = y+height < ly+lheight ? y+height : ly+lheight;
2192: if (!((x0max < x1min) &&
2193: (y0max < y1min))) { /* If they don't overlap */
2194: /* They are far enough out, so move on. */
2195: break;
2196: }
2197: /* Push the menu radius out a step and try again */
2198: radius++;
2199: }
2200: /* Loop on to next menu item */
2201: last = it;
2202: }
2203: }
2204: radius += menu->extra_radius;
2205: }
2206: menu->label_radius = radius;
2207:
2208: /* Finally position all the menu labels at the same radius.
2209: Figure out the bounding box of the labels. */
2210: minx = miny = maxx = maxy = 0;
2211: for (i = 0; i < menu->numEntries; i++) {
2212: it = menu->entries[i];
2213:
2214: it->x = radius * it->dx + it->x_offset;
2215: it->y = radius * it->dy + it->y_offset;
2216:
2217: /* Translate x y with respect to label size and position */
2218: if (ABS(it->x) <= 2) {
2219: it->x -= it->width/2;
2220: if (it->y < 0)
2221: it->y -= it->height;
2222: } else {
2223: if (it->x < 0)
2224: it->x -= it->width;
2225: it->y -= it->height/2;
2226: }
2227:
2228: it->label_x = it->x + menu->activeBorderWidth + 1;
2229: it->label_y = it->y - menu->activeBorderWidth - 1;
2230: if (it->bitmap == None) {
2231: it->label_y -= (it->fontPtr ? it->fontPtr : menu->fontPtr)->ascent;
2232: }
2233:
2234: if (it->x < minx) minx = it->x;
2235: if ((it->x + it->width) > maxx) maxx = (it->x + it->width);
2236: if (it->y < miny) miny = it->y;
2237: if ((it->y + it->height) > maxy) maxy = (it->y + it->height);
2238: }
2239:
2240:
2241: if (menu->titleLength != 0) {
2242: menu->title_height = titlefont->ascent + titlefont->descent + 2;
2243: (void) TkMeasureChars(titlefont, menu->title,
2244: menu->titleLength, 0, (int) 100000,
2245: TK_NEWLINES_NOT_SPECIAL, &menu->title_width);
2246: menu->title_width += 2;
2247: if (-(menu->title_width / 2) < minx)
2248: minx = -(menu->title_width / 2);
2249: if ((menu->title_width / 2) > maxx)
2250: maxx = (menu->title_width / 2);
2251: maxy += (2 * menu->borderWidth) + menu->title_height;
2252: } else {
2253: menu->title_width = menu->title_height = 0;
2254: }
2255:
2256:
2257: minx -= 2*menu->borderWidth; miny -= 2*menu->borderWidth;
2258: maxx += 2*menu->borderWidth; maxy += 2*menu->borderWidth;
2259:
2260: menu->center_x = -minx;
2261: menu->center_y = maxy; /* y flip */
2262: menu->width = maxx - minx;
2263: menu->height = maxy - miny;
2264:
2265: /* menu->title_x = (menu->width - menu->title_width) / 2 + 1; */
2266: menu->title_x = menu->center_x - menu->title_width/2 + 1;
2267: menu->title_y = 2*menu->borderWidth + titlefont->ascent + 1;
2268:
2269: /* Translate the menu items to the center of the menu, in X coordinates. */
2270: for (i = 0; i < menu->numEntries; i++) {
2271: it = menu->entries[i];
2272: it->x = menu->center_x + it->x;
2273: it->y = (menu->center_y - it->y) - it->height; /* y flip */
2274: it->label_x = menu->center_x + it->label_x;
2275: it->label_y = (menu->center_y - it->label_y) - it->height; /* y flip */
2276: }
2277:
2278: if (menu->segments != NULL) {
2279: ckfree((char *)menu->segments);
2280: }
2281: menu->segments = (XSegment *)
2282: ckalloc(menu->numEntries * sizeof(XSegment));
2283:
2284: if (menu->numEntries > 1) {
2285: XSegment *seg = menu->segments;
2286:
2287: angle = DEG_TO_RAD(menu->initial_angle) -
2288: (menu->entries[0]->subtend / 2.0);
2289: for (i = 0; i < menu->numEntries; i++) {
2290: it = menu->entries[i];
2291: seg->x1 = menu->center_x + (cos(angle) * menu->inactive_radius);
2292: seg->y1 = menu->center_y - (sin(angle) * menu->inactive_radius);
2293: seg->x2 = menu->center_x +
2294: (cos(angle) * (menu->label_radius - PIE_SPOKE_INSET));
2295: seg->y2 = menu->center_y -
2296: (sin(angle) * (menu->label_radius - PIE_SPOKE_INSET));
2297: seg++;
2298: angle += it->subtend;
2299: }
2300: }
2301: }
2302:
2303:
2304: static void
2305: ShapePieMenu(menuPtr)
2306: PieMenu *menuPtr;
2307: {
2308: Display *dpy;
2309: Window win, shape;
2310: GC gc;
2311: XGCValues values;
2312: PieMenuEntry *it;
2313: int i;
2314:
2315: if (HaveShape == 0)
2316: return;
2317:
2318: if (menuPtr->shaped == 0) {
2319: return;
2320: }
2321:
2322: dpy = Tk_Display(menuPtr->tkwin);
2323:
2324: if (HaveShape == -1) {
2325: int t1, t2;
2326: if (XShapeQueryExtension(dpy, &t1, &t2)) {
2327: HaveShape = 1;
2328: } else {
2329: HaveShape = 0;
2330: return;
2331: }
2332: }
2333:
2334: Tk_MakeWindowExist(menuPtr->tkwin);
2335: win = Tk_WindowId(menuPtr->tkwin);
2336:
2337: shape = XCreatePixmap(dpy, RootWindowOfScreen(Tk_Screen(menuPtr->tkwin)),
2338: menuPtr->width, menuPtr->height, 1);
2339: gc = XCreateGC(dpy, shape, 0, &values);
2340:
2341:
2342: XSetForeground(dpy, gc, 0);
2343: XFillRectangle(dpy, shape, gc, 0, 0, menuPtr->width, menuPtr->height);
2344:
2345: XSetForeground(dpy, gc, 1);
2346: if (menuPtr->titleLength != 0) {
2347: int bw = menuPtr->borderWidth;
2348:
2349: XFillRectangle(dpy, shape, gc, bw, bw, menuPtr->width - bw*2, menuPtr->title_height + bw*2);
2350: }
2351:
2352: for (i = 0; i < menuPtr->numEntries; i++) {
2353: it = menuPtr->entries[i];
2354: XFillRectangle(dpy, shape, gc, it->x, it->y, it->width, it->height);
2355: }
2356:
2357: XFreeGC(dpy, gc);
2358: XShapeCombineMask(dpy, win, ShapeBounding, 0, 0, shape, ShapeSet);
2359: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.