]> cvs.zerfleddert.de Git - micropolis/blob - src/sim/w_piem.c
XINCLUDE: use /usr/X11R6/include everywhere
[micropolis] / src / sim / w_piem.c
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 static void LayoutPieMenu(PieMenu *menu);
372 static void UpdatePieMenuEntries(PieMenu *menuPtr);
373 static int CalcPieMenuItem(PieMenu *menu, int x, int y);
374
375 \f
376 /*
377 *--------------------------------------------------------------
378 *
379 * Tk_PieMenuCmd --
380 *
381 * This procedure is invoked to process the "piemenu" Tcl
382 * command. Read the code and write some user documentation for
383 * details on what it does.
384 *
385 * Results:
386 * A standard Tcl result.
387 *
388 * Side effects:
389 * See the user documentation for "menu", which this was based on.
390 *
391 *--------------------------------------------------------------
392 */
393
394 int
395 Tk_PieMenuCmd(clientData, interp, argc, argv)
396 ClientData clientData; /* Main window associated with
397 * interpreter. */
398 Tcl_Interp *interp; /* Current interpreter. */
399 int argc; /* Number of arguments. */
400 char **argv; /* Argument strings. */
401 {
402 Tk_Window tkwin = (Tk_Window) clientData;
403 Tk_Window new;
404 register PieMenu *menuPtr;
405 XSetWindowAttributes atts;
406
407 if (argc < 2) {
408 Tcl_AppendResult(interp, "wrong # args: should be \"",
409 argv[0], " pathName ?options?\"", (char *) NULL);
410 return TCL_ERROR;
411 }
412
413 /*
414 * Create the new window. Set override-redirect so the window
415 * manager won't add a border or argue about placement, and set
416 * save-under so that the window can pop up and down without a
417 * lot of re-drawing.
418 */
419
420 new = Tk_CreateWindowFromPath(interp, tkwin, argv[1], "");
421 if (new == NULL) {
422 return TCL_ERROR;
423 }
424 atts.override_redirect = True;
425 atts.save_under = True;
426 Tk_ChangeWindowAttributes(new, CWOverrideRedirect|CWSaveUnder, &atts);
427
428 /*
429 * Initialize the data structure for the menu.
430 */
431
432 menuPtr = (PieMenu *) ckalloc(sizeof(PieMenu));
433 menuPtr->tkwin = new;
434 menuPtr->interp = interp;
435 menuPtr->title = NULL;
436 menuPtr->titleLength = 0;
437 menuPtr->preview = NULL;
438 menuPtr->entries = NULL;
439 menuPtr->numEntries = 0;
440 menuPtr->active = -1;
441 menuPtr->group = NULL;
442 menuPtr->root_x = 0;
443 menuPtr->root_y = 0;
444 menuPtr->border = NULL;
445 menuPtr->activeBorder = NULL;
446 menuPtr->fontPtr = NULL;
447 menuPtr->titlefontPtr = NULL;
448 menuPtr->fg = NULL;
449 menuPtr->textGC = None;
450 menuPtr->activeFg = NULL;
451 menuPtr->activeGC = None;
452 menuPtr->width = 0;
453 menuPtr->height = 0;
454 menuPtr->title_x = 0;
455 menuPtr->title_y = 0;
456 menuPtr->title_width = 0;
457 menuPtr->title_height = 0;
458 menuPtr->initial_angle = 0;
459 menuPtr->inactive_radius = PIE_INACTIVE_RADIUS_NUM;
460 menuPtr->min_radius = PIE_MIN_RADIUS_NUM;
461 menuPtr->extra_radius = PIE_EXTRA_RADIUS_NUM;
462 menuPtr->fixed_radius = 0;
463 menuPtr->label_radius = 0;
464 menuPtr->center_x = 0;
465 menuPtr->center_y = 0;
466 menuPtr->segments = NULL;
467 menuPtr->cursor = None;
468 menuPtr->postedPie = NULL;
469 menuPtr->flags = 0;
470 menuPtr->phase = 0;
471 menuPtr->shaped = 1;
472 menuPtr->popup_delay = PIE_POPUP_DELAY_NUM;
473
474 Tk_SetClass(new, "PieMenu");
475 Tk_CreateEventHandler(menuPtr->tkwin,
476 ExposureMask | StructureNotifyMask |
477 ButtonPressMask | ButtonReleaseMask |
478 PointerMotionMask,
479 PieMenuEventProc, (ClientData) menuPtr);
480 Tcl_CreateCommand(interp, Tk_PathName(menuPtr->tkwin), PieMenuWidgetCmd,
481 (ClientData) menuPtr, (void (*)()) NULL);
482 if (ConfigurePieMenu(interp, menuPtr, argc-2, argv+2, 0) != TCL_OK) {
483 goto error;
484 }
485
486 interp->result = Tk_PathName(menuPtr->tkwin);
487 return TCL_OK;
488
489 error:
490 Tk_DestroyWindow(menuPtr->tkwin);
491 return TCL_ERROR;
492 }
493 \f
494 /*
495 *--------------------------------------------------------------
496 *
497 * PieMenuWidgetCmd --
498 *
499 * This procedure is invoked to process the Tcl command
500 * that corresponds to a widget managed by this module.
501 * See the user documentation for details on what it does.
502 *
503 * Results:
504 * A standard Tcl result.
505 *
506 * Side effects:
507 * See the user documentation.
508 *
509 *--------------------------------------------------------------
510 */
511
512 static int
513 PieMenuWidgetCmd(clientData, interp, argc, argv)
514 ClientData clientData; /* Information about menu widget. */
515 Tcl_Interp *interp; /* Current interpreter. */
516 int argc; /* Number of arguments. */
517 char **argv; /* Argument strings. */
518 {
519 register PieMenu *menuPtr = (PieMenu *) clientData;
520 register PieMenuEntry *mePtr;
521 int result = TCL_OK;
522 int length, type;
523 char c;
524
525 if (argc < 2) {
526 Tcl_AppendResult(interp, "wrong # args: should be \"",
527 argv[0], " option ?arg arg ...?\"", (char *) NULL);
528 return TCL_ERROR;
529 }
530 Tk_Preserve((ClientData) menuPtr);
531 c = argv[1][0];
532 length = strlen(argv[1]);
533 if ((c == 'a') && (strncmp(argv[1], "activate", length) == 0)
534 && (length >= 2)) {
535 int index;
536
537 if (argc != 3) {
538 Tcl_AppendResult(interp, "wrong # args: should be \"",
539 argv[0], " activate index\"", (char *) NULL);
540 goto error;
541 }
542 if (GetPieMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK) {
543 goto error;
544 }
545 if (menuPtr->active == index) {
546 goto done;
547 }
548 result = ActivatePieMenuEntry(menuPtr, index, 1);
549 DeferPopupPieMenu(menuPtr);
550 } else if ((c == 's') && (strncmp(argv[1], "show", length) == 0)
551 && (length >= 2)) {
552 if (argc != 2) {
553 Tcl_AppendResult(interp, "wrong # args: should be \"",
554 argv[0], " show\"", (char *) NULL);
555 goto error;
556 }
557 NowPopupPieMenu(menuPtr);
558 } else if ((c == 'p') && (strncmp(argv[1], "pending", length) == 0)
559 && (length >= 2)) {
560 if (argc != 2) {
561 Tcl_AppendResult(interp, "wrong # args: should be \"",
562 argv[0], " pending\"", (char *) NULL);
563 goto error;
564 }
565 sprintf(interp->result, "%d",
566 (menuPtr->flags & POPUP_PENDING) ? 1 : 0);
567 } else if ((c == 'd') && (strncmp(argv[1], "defer", length) == 0)
568 && (length >= 2)) {
569 if (argc != 2) {
570 Tcl_AppendResult(interp, "wrong # args: should be \"",
571 argv[0], " defer\"", (char *) NULL);
572 goto error;
573 }
574 DeferPopupPieMenu(menuPtr);
575 } else if ((c == 'a') && (strncmp(argv[1], "add", length) == 0)
576 && (length >= 2)) {
577 PieMenuEntry **newEntries;
578
579 if (argc < 3) {
580 Tcl_AppendResult(interp, "wrong # args: should be \"",
581 argv[0], " add type ?options?\"", (char *) NULL);
582 goto error;
583 }
584
585 /*
586 * Figure out the type of the new entry.
587 */
588
589 c = argv[2][0];
590 length = strlen(argv[2]);
591 if ((c == 'c') && (strncmp(argv[2], "command", length) == 0)) {
592 type = COMMAND_ENTRY;
593 } else if ((c == 'p') && (strncmp(argv[2], "piemenu", length) == 0)) {
594 type = PIEMENU_ENTRY;
595 } else {
596 Tcl_AppendResult(interp, "bad menu entry type \"",
597 argv[2], "\": must be command or piemenu",
598 (char *) NULL);
599 goto error;
600 }
601
602 /*
603 * Add a new entry to the end of the menu's array of entries,
604 * and process options for it.
605 */
606
607 mePtr = (PieMenuEntry *) ckalloc(sizeof(PieMenuEntry));
608 newEntries = (PieMenuEntry **) ckalloc((unsigned)
609 ((menuPtr->numEntries+1)*sizeof(PieMenuEntry *)));
610 if (menuPtr->numEntries != 0) {
611 memcpy((VOID *) newEntries, (VOID *) menuPtr->entries,
612 menuPtr->numEntries*sizeof(PieMenuEntry *));
613 ckfree((char *) menuPtr->entries);
614 }
615 menuPtr->entries = newEntries;
616 menuPtr->entries[menuPtr->numEntries] = mePtr;
617 menuPtr->numEntries++;
618 mePtr->type = type;
619 mePtr->piemenuPtr = menuPtr;
620 mePtr->label = NULL;
621 mePtr->labelLength = 0;
622 mePtr->bitmap = None;
623 mePtr->width = 0;
624 mePtr->height = 0;
625 mePtr->x_offset = 0;
626 mePtr->y_offset = 0;
627 mePtr->label_x = 0;
628 mePtr->label_y = 0;
629 mePtr->border = NULL;
630 mePtr->activeBorder = NULL;
631 mePtr->fontPtr = NULL;
632 mePtr->textGC = None;
633 mePtr->activeGC = None;
634 mePtr->slice = 1.0;
635 mePtr->angle = 0.0;
636 mePtr->dx = 0.0;
637 mePtr->dy = 0.0;
638 mePtr->subtend = 0.0;
639 mePtr->quadrant = 0;
640 mePtr->slope = 0.0;
641 mePtr->command = NULL;
642 mePtr->preview = NULL;
643 mePtr->name = NULL;
644 mePtr->flags = 0;
645 if (ConfigurePieMenuEntry(interp, menuPtr, mePtr,
646 menuPtr->numEntries-1,
647 argc-3, argv+3, 0) != TCL_OK) {
648 DestroyPieMenuEntry((ClientData) mePtr);
649 menuPtr->numEntries--;
650 goto error;
651 }
652 if (!(menuPtr->flags & RESIZE_PENDING)) {
653 menuPtr->flags |= RESIZE_PENDING;
654 Tk_DoWhenIdle(ComputePieMenuGeometry, (ClientData) menuPtr);
655 }
656 } else if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0)) {
657 if (argc == 2) {
658 result = Tk_ConfigureInfo(interp, menuPtr->tkwin, configSpecs,
659 (char *) menuPtr, (char *) NULL, 0);
660 } else if (argc == 3) {
661 result = Tk_ConfigureInfo(interp, menuPtr->tkwin, configSpecs,
662 (char *) menuPtr, argv[2], 0);
663 } else {
664 result = ConfigurePieMenu(interp, menuPtr, argc-2, argv+2,
665 TK_CONFIG_ARGV_ONLY);
666 }
667 } else if ((c == 'd') && (strncmp(argv[1], "delete", length) == 0)
668 && (length >= 2)) {
669 int index, i;
670
671 if (argc != 3) {
672 Tcl_AppendResult(interp, "wrong # args: should be \"",
673 argv[0], " delete index\"", (char *) NULL);
674 goto error;
675 }
676 if (GetPieMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK) {
677 goto error;
678 }
679 if (index < 0) {
680 goto done;
681 }
682 Tk_EventuallyFree((ClientData) menuPtr->entries[index],
683 DestroyPieMenuEntry);
684 for (i = index; i < menuPtr->numEntries-1; i++) {
685 menuPtr->entries[i] = menuPtr->entries[i+1];
686 }
687 menuPtr->numEntries -= 1;
688 if (menuPtr->active == index) {
689 menuPtr->active = -1;
690 } else if (menuPtr->active > index) {
691 menuPtr->active -= 1;
692 }
693 if (!(menuPtr->flags & RESIZE_PENDING)) {
694 menuPtr->flags |= RESIZE_PENDING;
695 Tk_DoWhenIdle(ComputePieMenuGeometry, (ClientData) menuPtr);
696 }
697 } else if ((c == 'e') && (length >= 3)
698 && (strncmp(argv[1], "entryconfigure", length) == 0)) {
699 int index;
700
701 if (argc < 3) {
702 Tcl_AppendResult(interp, "wrong # args: should be \"",
703 argv[0], " entryconfigure index ?option value ...?\"",
704 (char *) NULL);
705 goto error;
706 }
707 if (GetPieMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK) {
708 goto error;
709 }
710 if (index < 0) {
711 goto done;
712 }
713 mePtr = menuPtr->entries[index];
714 Tk_Preserve((ClientData) mePtr);
715 if (argc == 3) {
716 result = Tk_ConfigureInfo(interp, menuPtr->tkwin, entryConfigSpecs,
717 (char *) mePtr, (char *) NULL,
718 COMMAND_MASK << mePtr->type);
719 } else if (argc == 4) {
720 result = Tk_ConfigureInfo(interp, menuPtr->tkwin, entryConfigSpecs,
721 (char *) mePtr, argv[3], COMMAND_MASK << mePtr->type);
722 } else {
723 result = ConfigurePieMenuEntry(interp, menuPtr, mePtr, index,
724 argc-3, argv+3,
725 TK_CONFIG_ARGV_ONLY |
726 COMMAND_MASK << mePtr->type);
727 }
728 Tk_Release((ClientData) mePtr);
729 } else if ((c == 'i') && (strncmp(argv[1], "index", length) == 0)
730 && (length >= 3)) {
731 int index;
732
733 if (argc != 3) {
734 Tcl_AppendResult(interp, "wrong # args: should be \"",
735 argv[0], " index string\"", (char *) NULL);
736 goto error;
737 }
738 if (GetPieMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK) {
739 goto error;
740 }
741 if (index < 0) {
742 interp->result = "none";
743 } else {
744 sprintf(interp->result, "%d", index);
745 }
746 } else if ((c == 'i') && (strncmp(argv[1], "invoke", length) == 0)
747 && (length >= 3)) {
748 int index;
749
750 if (argc != 3) {
751 Tcl_AppendResult(interp, "wrong # args: should be \"",
752 argv[0], " invoke index\"", (char *) NULL);
753 goto error;
754 }
755 if (GetPieMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK) {
756 goto error;
757 }
758 if (index < 0) {
759 goto done;
760 }
761 mePtr = menuPtr->entries[index];
762 Tk_Preserve((ClientData) mePtr);
763 if (mePtr->command != NULL) {
764 result = Tcl_GlobalEval(interp, mePtr->command);
765 }
766 Tk_Release((ClientData) mePtr);
767 } else if ((c == 'p') && (strncmp(argv[1], "post", length) == 0)) {
768 int x, y;
769 #if 0
770 int ix, iy, tmp, err;
771 #endif
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 \f
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 \f
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 \f
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 \f
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 \f
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 \f
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
1359 menuPtr->flags &= ~REDRAW_PENDING;
1360 if ((menuPtr->tkwin == NULL) || !Tk_IsMapped(menuPtr->tkwin)) {
1361 return;
1362 }
1363
1364 if (menuPtr->titlefontPtr != NULL) {
1365 fontPtr = menuPtr->titlefontPtr;
1366 } else {
1367 fontPtr = menuPtr->fontPtr;
1368 }
1369
1370 if (menuPtr->titleLength != 0) {
1371 Tk_Draw3DRectangle(Tk_Display(tkwin), Tk_WindowId(tkwin),
1372 menuPtr->border,
1373 menuPtr->borderWidth, menuPtr->borderWidth,
1374 Tk_Width(tkwin) - 2*menuPtr->borderWidth,
1375 menuPtr->title_height + 2*menuPtr->borderWidth,
1376 menuPtr->borderWidth, TK_RELIEF_RAISED);
1377
1378 TkDisplayChars(Tk_Display(tkwin), Tk_WindowId(tkwin), menuPtr->textGC,
1379 fontPtr, menuPtr->title, menuPtr->titleLength,
1380 menuPtr->title_x, menuPtr->title_y,
1381 TK_NEWLINES_NOT_SPECIAL);
1382 }
1383
1384 if (menuPtr->segments) {
1385 XSetLineAttributes(Tk_Display(tkwin), menuPtr->textGC,
1386 0, LineSolid, CapButt, JoinMiter);
1387 XDrawSegments(Tk_Display(tkwin), Tk_WindowId(tkwin),
1388 menuPtr->textGC, menuPtr->segments, menuPtr->numEntries);
1389 }
1390
1391 Tk_Draw3DRectangle(Tk_Display(tkwin), Tk_WindowId(tkwin), menuPtr->border,
1392 0, 0, Tk_Width(tkwin), Tk_Height(tkwin),
1393 menuPtr->borderWidth, TK_RELIEF_RAISED);
1394
1395 UpdatePieMenuEntries(menuPtr);
1396 }
1397 \f
1398 /*
1399 *----------------------------------------------------------------------
1400 *
1401 * UpdatePieMenu --
1402 *
1403 * This procedure is invoked to update a pie menu widget.
1404 *
1405 * Results:
1406 * None.
1407 *
1408 * Side effects:
1409 * Commands are output to X to update the pie menu in its
1410 * current mode.
1411 *
1412 *----------------------------------------------------------------------
1413 */
1414
1415 static void
1416 UpdatePieMenu(clientData)
1417 ClientData clientData; /* Information about widget. */
1418 {
1419 register PieMenu *menuPtr = (PieMenu *) clientData;
1420
1421 menuPtr->flags &= ~UPDATE_PENDING;
1422 if ((menuPtr->tkwin == NULL) || !Tk_IsMapped(menuPtr->tkwin)) {
1423 return;
1424 }
1425
1426 UpdatePieMenuEntries(menuPtr);
1427 }
1428
1429
1430 static void
1431 UpdatePieMenuEntries(PieMenu *menuPtr)
1432 {
1433 register PieMenuEntry *mePtr;
1434 register Tk_Window tkwin = menuPtr->tkwin;
1435 XFontStruct *fontPtr;
1436 int index;
1437 GC gc;
1438
1439 for (index = 0; index < menuPtr->numEntries; index++) {
1440 mePtr = menuPtr->entries[index];
1441 if (!(mePtr->flags & ENTRY_NEEDS_REDISPLAY)) {
1442 continue;
1443 }
1444 mePtr->flags &= ~ENTRY_NEEDS_REDISPLAY;
1445
1446 /*
1447 * Background.
1448 */
1449
1450 Tk_Fill3DRectangle(Tk_Display(tkwin), Tk_WindowId(tkwin),
1451 ((mePtr->activeBorder != NULL)
1452 ? mePtr->activeBorder
1453 : menuPtr->activeBorder),
1454 mePtr->x, mePtr->y,
1455 mePtr->width, mePtr->height,
1456 menuPtr->activeBorderWidth,
1457 ((index == menuPtr->active)
1458 ? TK_RELIEF_SUNKEN
1459 : ((HaveShape && menuPtr->shaped)
1460 ? TK_RELIEF_RAISED
1461 : TK_RELIEF_FLAT)));
1462
1463 gc = mePtr->textGC;
1464 if (gc == NULL) {
1465 gc = menuPtr->textGC;
1466 }
1467
1468 /*
1469 * Draw label or bitmap for entry.
1470 */
1471
1472 fontPtr = mePtr->fontPtr;
1473 if (fontPtr == NULL) {
1474 fontPtr = menuPtr->fontPtr;
1475 }
1476 if (mePtr->bitmap != None) {
1477 unsigned int width, height;
1478
1479 Tk_SizeOfPixmap(mePtr->bitmap, &width, &height);
1480 XCopyArea(Tk_Display(tkwin), mePtr->bitmap, Tk_WindowId(tkwin),
1481 gc, 0, 0, width, height,
1482 mePtr->label_x, mePtr->label_y);
1483 } else {
1484 if (mePtr->label != NULL) {
1485 TkDisplayChars(Tk_Display(tkwin), Tk_WindowId(tkwin), gc,
1486 fontPtr, mePtr->label, mePtr->labelLength,
1487 mePtr->label_x, mePtr->label_y,
1488 TK_NEWLINES_NOT_SPECIAL);
1489 }
1490 }
1491 }
1492 }
1493 \f
1494 /*
1495 *--------------------------------------------------------------
1496 *
1497 * GetPieMenuIndex --
1498 *
1499 * Parse a textual index into a pie menu and return the numerical
1500 * index of the indicated entry.
1501 *
1502 * Results:
1503 * A standard Tcl result. If all went well, then *indexPtr is
1504 * filled in with the entry index corresponding to string
1505 * (ranges from -1 to the number of entries in the pie menu minus
1506 * one). Otherwise an error message is left in interp->result.
1507 *
1508 * Side effects:
1509 * None.
1510 *
1511 *--------------------------------------------------------------
1512 */
1513
1514 static int
1515 GetPieMenuIndex(interp, menuPtr, string, indexPtr)
1516 Tcl_Interp *interp; /* For error messages. */
1517 PieMenu *menuPtr; /* Menu for which the index is being
1518 * specified. */
1519 char *string; /* Specification of an entry in menu. See
1520 * manual entry for valid .*/
1521 int *indexPtr; /* Where to store converted relief. */
1522 {
1523 int i;
1524
1525 if ((string[0] == 'a') && (strcmp(string, "active") == 0)) {
1526 *indexPtr = menuPtr->active;
1527 return TCL_OK;
1528 }
1529
1530 if ((string[0] == 'l') && (strcmp(string, "last") == 0)) {
1531 *indexPtr = menuPtr->numEntries-1;
1532 return TCL_OK;
1533 }
1534
1535 if ((string[0] == 'n') && (strcmp(string, "none") == 0)) {
1536 *indexPtr = -1;
1537 return TCL_OK;
1538 }
1539
1540 if (string[0] == '@') {
1541 char xstr[32], ystr[32];
1542 int x, y;
1543
1544 if ((sscanf(&string[1], "%31[^,],%31[^,]", xstr, ystr) == 2) &&
1545 (Tcl_GetInt(interp, xstr, &x) == TCL_OK) &&
1546 (Tcl_GetInt(interp, ystr, &y) == TCL_OK)) {
1547 *indexPtr = CalcPieMenuItem(menuPtr, x, y);
1548 return TCL_OK;
1549 } else {
1550 Tcl_SetResult(interp, (char *) NULL, TCL_STATIC);
1551 }
1552 }
1553
1554 if (isdigit(string[0])) {
1555 if (Tcl_GetInt(interp, string, &i) == TCL_OK) {
1556 if ((i < menuPtr->numEntries) && (i >= 0)) {
1557 *indexPtr = i;
1558 return TCL_OK;
1559 }
1560 } else {
1561 Tcl_SetResult(interp, (char *) NULL, TCL_STATIC);
1562 }
1563 }
1564
1565 for (i = 0; i < menuPtr->numEntries; i++) {
1566 char *label;
1567
1568 label = menuPtr->entries[i]->label;
1569 if ((label != NULL)
1570 && (Tcl_StringMatch(menuPtr->entries[i]->label, string))) {
1571 *indexPtr = i;
1572 return TCL_OK;
1573 }
1574 }
1575
1576 Tcl_AppendResult(interp, "bad menu entry index \"",
1577 string, "\"", (char *) NULL);
1578 return TCL_ERROR;
1579 }
1580 \f
1581 /*
1582 *--------------------------------------------------------------
1583 *
1584 * PieMenuEventProc --
1585 *
1586 * This procedure is invoked by the Tk dispatcher for various
1587 * events on pie menus.
1588 *
1589 * Results:
1590 * None.
1591 *
1592 * Side effects:
1593 * When the window gets deleted, internal structures get
1594 * cleaned up. When it gets exposed, it is redisplayed.
1595 *
1596 *--------------------------------------------------------------
1597 */
1598
1599 static void
1600 PieMenuEventProc(clientData, eventPtr)
1601 ClientData clientData; /* Information about window. */
1602 XEvent *eventPtr; /* Information about event. */
1603 {
1604 PieMenu *menuPtr = (PieMenu *) clientData;
1605 switch (eventPtr->type) {
1606 case Expose:
1607 if (eventPtr->xexpose.count == 0) {
1608 EventuallyRedrawPieMenu(menuPtr, -1);
1609 }
1610 break;
1611 case DestroyNotify:
1612 Tcl_DeleteCommand(menuPtr->interp, Tk_PathName(menuPtr->tkwin));
1613
1614 /*
1615 * Careful! Must delete the event-sharing information here
1616 * rather than in DestroyPieMenu. By the time that procedure
1617 * is called the tkwin may have been reused, resulting in some
1618 * other window accidentally being cut off from shared events.
1619 */
1620
1621 Tk_UnshareEvents(menuPtr->tkwin, menuPtr->group);
1622 menuPtr->tkwin = NULL;
1623 if (menuPtr->flags & REDRAW_PENDING) {
1624 Tk_CancelIdleCall(DisplayPieMenu, (ClientData) menuPtr);
1625 }
1626 if (menuPtr->flags & UPDATE_PENDING) {
1627 Tk_CancelIdleCall(UpdatePieMenu, (ClientData) menuPtr);
1628 }
1629 if (menuPtr->flags & RESIZE_PENDING) {
1630 Tk_CancelIdleCall(ComputePieMenuGeometry, (ClientData) menuPtr);
1631 }
1632 if (menuPtr->flags & POPUP_PENDING) {
1633 Tk_CancelIdleCall(PopupPieMenu, (ClientData) menuPtr);
1634 }
1635 Tk_EventuallyFree((ClientData) menuPtr, DestroyPieMenu);
1636 break;
1637 case MotionNotify:
1638 break;
1639 case ButtonPress:
1640 break;
1641 case ButtonRelease:
1642 break;
1643 }
1644 }
1645
1646
1647 \f
1648 /*
1649 *----------------------------------------------------------------------
1650 *
1651 * EventuallyRedrawPieMenu --
1652 *
1653 * Arrange for an entry of a pie menu, or the whole pie menu,
1654 * to be redisplayed at some point in the future.
1655 *
1656 * Results:
1657 * None.
1658 *
1659 * Side effects:
1660 * A when-idle hander is scheduled to do the redisplay, if there
1661 * isn't one already scheduled.
1662 *
1663 *----------------------------------------------------------------------
1664 */
1665
1666 static void
1667 EventuallyRedrawPieMenu(menuPtr, index)
1668 register PieMenu *menuPtr; /* Information about menu to redraw. */
1669 int index; /* Which entry to redraw. If -1, then
1670 * all the entries in the menu are redrawn. */
1671 {
1672 if (menuPtr->tkwin == NULL) {
1673 return;
1674 }
1675 if (index != -1) {
1676 menuPtr->entries[index]->flags |= ENTRY_NEEDS_REDISPLAY;
1677 } else {
1678 for (index = 0; index < menuPtr->numEntries; index++) {
1679 menuPtr->entries[index]->flags |= ENTRY_NEEDS_REDISPLAY;
1680 }
1681 index = -1;
1682 }
1683 if ((menuPtr->tkwin == NULL) || !Tk_IsMapped(menuPtr->tkwin)
1684 || (menuPtr->flags & REDRAW_PENDING)) {
1685 return;
1686 }
1687
1688 if (index == -1) {
1689 if (menuPtr->flags & UPDATE_PENDING) {
1690 Tk_CancelIdleCall(UpdatePieMenu, (ClientData) menuPtr);
1691 }
1692 Tk_DoWhenIdle(DisplayPieMenu, (ClientData) menuPtr);
1693 menuPtr->flags |= REDRAW_PENDING;
1694 } else {
1695 Tk_DoWhenIdle(UpdatePieMenu, (ClientData) menuPtr);
1696 menuPtr->flags |= UPDATE_PENDING;
1697 }
1698 }
1699
1700
1701 static void
1702 PopupPieMenu(clientData)
1703 ClientData clientData; /* Information about widget. */
1704 {
1705 register PieMenu *menuPtr = (PieMenu *) clientData;
1706
1707 NeverPopupPieMenu(menuPtr);
1708
1709 if (Tk_IsMapped(menuPtr->tkwin)) {
1710 return;
1711 }
1712
1713 ShapePieMenu(menuPtr);
1714 Tk_MapWindow(menuPtr->tkwin);
1715 }
1716
1717
1718 static void
1719 NowPopupPieMenu(menuPtr)
1720 register PieMenu *menuPtr;
1721 {
1722 PopupPieMenu((ClientData)menuPtr);
1723 }
1724
1725
1726 static void
1727 NeverPopupPieMenu(menuPtr)
1728 register PieMenu *menuPtr;
1729 {
1730 if (menuPtr->flags & POPUP_PENDING) {
1731 Tk_DeleteTimerHandler(menuPtr->popup_timer_token);
1732 menuPtr->popup_timer_token = 0;
1733 menuPtr->flags &= ~POPUP_PENDING;
1734 }
1735 }
1736
1737
1738 static void
1739 EventuallyPopupPieMenu(menuPtr)
1740 register PieMenu *menuPtr;
1741 {
1742 NeverPopupPieMenu(menuPtr);
1743
1744 if (Tk_IsMapped(menuPtr->tkwin)) {
1745 return;
1746 }
1747
1748 menuPtr->popup_timer_token =
1749 Tk_CreateTimerHandler(menuPtr->popup_delay,
1750 PopupPieMenu, (ClientData) menuPtr);
1751 menuPtr->flags |= POPUP_PENDING;
1752 }
1753
1754
1755 static void
1756 DeferPopupPieMenu(menuPtr)
1757 register PieMenu *menuPtr;
1758 {
1759 if (menuPtr->flags & POPUP_PENDING) {
1760 EventuallyPopupPieMenu(menuPtr);
1761 }
1762 }
1763
1764
1765 \f
1766 /*
1767 *--------------------------------------------------------------
1768 *
1769 * UnpostSubPieMenu --
1770 *
1771 * This procedure unposts any submenu.
1772 *
1773 * Results:
1774 * A standard Tcl return result. Errors may occur in the
1775 * Tcl commands generated to unpost submenus.
1776 *
1777 * Side effects:
1778 * If there is already a submenu posted, it is unposted.
1779 *
1780 *--------------------------------------------------------------
1781 */
1782
1783 static int
1784 UnpostSubPieMenu(interp, menuPtr)
1785 Tcl_Interp *interp; /* Used for invoking sub-commands and
1786 * reporting errors. */
1787 register PieMenu *menuPtr; /* Information about menu as a whole. */
1788 {
1789 int result;
1790
1791 if (menuPtr->postedPie == NULL) {
1792 return TCL_OK;
1793 }
1794
1795 result = Tcl_VarEval(interp, menuPtr->postedPie->name,
1796 " unpost", (char *) NULL);
1797 menuPtr->postedPie = NULL;
1798
1799 return result;
1800 }
1801 \f
1802 /*
1803 *----------------------------------------------------------------------
1804 *
1805 * ActivatePieMenuEntry --
1806 *
1807 * This procedure is invoked to make a particular pie menu
1808 * entry the active one, deactivating any other entry that
1809 * might currently be active.
1810 *
1811 * Results:
1812 * The return value is a standard Tcl result (errors can occur
1813 * while posting and unposting submenus).
1814 *
1815 * Side effects:
1816 * Pie menu entries get redisplayed, and the active entry
1817 * changes. Submenus may get posted and unposted.
1818 *
1819 *----------------------------------------------------------------------
1820 */
1821
1822 static int
1823 ActivatePieMenuEntry(menuPtr, index, preview)
1824 register PieMenu *menuPtr; /* Menu in which to activate. */
1825 int index; /* Index of entry to activate, or
1826 * -1 to deactivate all entries. */
1827 int preview; /* 1 to execute previewer */
1828 {
1829 register PieMenuEntry *mePtr;
1830 int result = TCL_OK;
1831
1832 if (menuPtr->active >= 0) {
1833 mePtr = menuPtr->entries[menuPtr->active];
1834
1835 EventuallyRedrawPieMenu(menuPtr, menuPtr->active);
1836 }
1837 menuPtr->active = index;
1838 if (index >= 0) {
1839 mePtr = menuPtr->entries[index];
1840 EventuallyRedrawPieMenu(menuPtr, index);
1841 if (preview) {
1842 Tk_Preserve((ClientData) mePtr);
1843 if (mePtr->preview != NULL) {
1844 result = Tcl_GlobalEval(menuPtr->interp, mePtr->preview);
1845 }
1846 Tk_Release((ClientData) mePtr);
1847 }
1848 } else {
1849 /* We're doing this in tcl these days, for finer control. */
1850 #if 0
1851 if (preview && menuPtr->preview) {
1852 result = Tcl_GlobalEval(menuPtr->interp, menuPtr->preview);
1853 }
1854 #endif
1855 }
1856 return result;
1857 }
1858
1859
1860 /*
1861 * This pie menu tracking code determines the slice the cursor
1862 * is in by representing slice edge angles as (quadrant, slope)
1863 * pairs that can be quickly computed and compared.
1864 *
1865 * The slope is defined such that it is greater than or equal to zero,
1866 * less than infinity, and increasing counter-clockwise around the menu.
1867 * Each of the four quadrants encompasses one range of slope.
1868 *
1869 * Y
1870 * ^
1871 * | x>0, y>=0
1872 * x<=0, y>0 <--+ y/x
1873 * -x/y | ^
1874 * quad 1 | quad 0 | X
1875 * -----+--------+--------+---->
1876 * | quad 2 | quad 3
1877 * V | -x/y
1878 * x<0, y<=0 +--> x>=0, y<0
1879 * y/x |
1880 * |
1881 *
1882 * The quadrants and slopes of the item edges are all precalculated,
1883 * during menu layout.
1884 * The quadrant and slope of the cursor must be calculated frequently
1885 * during menu tracking, so we just calculate the numerator and
1886 * denominator of the slope, and avoid an unnecessary division.
1887 * Instead of calculating "slope = numerator / denominator" then
1888 * testing "slope < it->slope", every time the cursor moves, we can
1889 * just test "numerator < (denominator * it->slope)".
1890 *
1891 * This algorithm works in a right-side-up coordinate space, but the final
1892 * results are tranformed into X-windows's up-side-down coordinate system
1893 * by subtracting the y values from the window height.
1894 */
1895
1896
1897 #define CALC_QUADRANT_SLOPE(x, y, quadrant, numerator, denominator) \
1898 if ((y) > 0) (quadrant) = ((x) > 0 ? 0 : 1); \
1899 else if ((y) < 0) (quadrant) = ((x) < 0 ? 2 : 3); \
1900 else (quadrant) = ((x) > 0 ? 0 : 2); \
1901 if ((quadrant) & 1) { \
1902 (numerator) = ABS((x)); (denominator) = ABS((y)); \
1903 } else { \
1904 (numerator) = ABS((y)); (denominator) = ABS((x)); \
1905 }
1906
1907
1908 static int
1909 CalcPieMenuItem(PieMenu *menu, int x, int y)
1910 {
1911 register PieMenuEntry *it, *last_it;
1912 int i, order = 0, quadrant;
1913 int numerator, denominator;
1914 int first, last_i, last_order;
1915
1916 /*
1917 * Translate x and y from root window coordinates so they are
1918 * relative to the menu center, in right side up coordinates.
1919 */
1920
1921 menu->dx = x = (x - menu->root_x) + 1;
1922 menu->dy = y = (menu->root_y - y) - 1;
1923
1924 /*
1925 * If there are no menu items,
1926 * or we are within the inactive region in the menu center,
1927 * then there is no item selected.
1928 */
1929 if ((menu->numEntries == 0) ||
1930 ((x * x) + (y * y) <
1931 (menu->inactive_radius * menu->inactive_radius))) {
1932 return(-1);
1933 }
1934
1935 /*
1936 * If there's only one item, then that must be it.
1937 */
1938 if (menu->numEntries == 1) {
1939 return(0);
1940 }
1941
1942 /*
1943 * Calculate the quadrant, slope numerator, and slope denominator of
1944 * the cursor slope, to be used for comparisons.
1945 */
1946 CALC_QUADRANT_SLOPE(x, y, quadrant, numerator, denominator);
1947
1948 /*
1949 * In most cases, during cursor tracking, the menu item that the
1950 * cursor is over will be the same as it was before (almost all
1951 * of the time), or one of the neighboring items (most of the
1952 * rest of the time). So we check those items first. But to keep
1953 * things simple, instead of actually checking the items in order of
1954 * frequency (the current, the two neighbors, then the rest), we just
1955 * start our loop around the menu items at the item *before* the
1956 * last selected menu item, so we still check the three most common
1957 * cases first (neighbor, current, neighbor, rest), without having
1958 * to complicate the code with special cases. Another strategy, that
1959 * might be good for menus with ridiculously many items, would be
1960 * [to check the current item first, then the two neighbors, then]
1961 * to do a binary search of the menu items (since they are ordered).
1962 * But that's more complicated and you shouldn't have that many menu
1963 * items anyway.
1964 */
1965
1966 /*
1967 * Start at the item before current one.
1968 */
1969 first = menu->active - 1;
1970 if (first < 0)
1971 first = menu->numEntries - 1;
1972
1973 /*
1974 * Initialize last_order such that we will go through the loop
1975 * at least once, validating last_i, last_order, and last_it for
1976 * the next time through the loop.
1977 */
1978 last_i = last_order = -1;
1979 i = first;
1980
1981 it = menu->entries[i];
1982
1983 while (1) {
1984
1985 /* Legend: c = cursor, e = edge
1986 <cursor quad>,<edge quad>
1987 quad 1 | quad 0
1988 -------+-------
1989 quad 2 | quad 3
1990 */
1991
1992 /* Set order = 1, if shortest direction from edge to cursor is ccw */
1993 switch ((quadrant - it->quadrant) & 3) {
1994
1995 case 0: /*
1996 0,0 1,1 2,2 3,3
1997 |ce ce| | |
1998 --+-- --+-- --+-- --+--
1999 | | ce| |ce
2000 */
2001 /* slope >= it->slope */
2002 order = ((float)numerator >= (float)(denominator * it->slope));
2003 break;
2004
2005 case 1: /*
2006 1,0 2,1 3,2 0,3
2007 c|e e| | |c
2008 --+-- --+-- --+-- --+--
2009 | c| e|c |e
2010 */
2011 order = 1;
2012 break;
2013
2014 case 2: /*
2015 2,0 3,1 0,2 1,3
2016 |e e| |c c|
2017 --+-- --+-- --+-- --+--
2018 c| |c e| |e
2019 */
2020 /* slope < it->slope */
2021 order = ((float)numerator < (float)(denominator * it->slope));
2022 break;
2023
2024 case 3: /*
2025 3,0 0,1 1,2 2,3
2026 |e e|c c| |
2027 --+-- --+-- --+-- --+--
2028 |c | e| c|e
2029 */
2030 order = 0;
2031 break;
2032 }
2033
2034 /*
2035 * If we were counter-clockwise of the last leading edge,
2036 * and we're clockwise of this leading edge,
2037 * then we were in the last menu item.
2038 * (Note: first time through this loop last_order = -1 so we'll
2039 * go back through the loop at least once, after validating
2040 * last_order, last_i, and last_it.)
2041 */
2042 if ((last_order == 1) && (order == 0)) {
2043 return(last_i);
2044 }
2045 last_order = order;
2046
2047 /*
2048 * Remember this menu item index, and move on to the next one
2049 * counter-clockwise around the circle.
2050 */
2051 last_i = i; last_it = it;
2052 if (++i >= menu->numEntries) {
2053 i = 0;
2054 }
2055 it = menu->entries[i];
2056
2057 /*
2058 * If we've checked all the others, then that must have been it.
2059 * This saves us from checking the leading edge of the first
2060 * item again (It's also insurance against layout bugs.)
2061 */
2062 if (i == first) {
2063 return(last_i);
2064 }
2065 }
2066 }
2067
2068
2069 static void
2070 LayoutPieMenu(PieMenu *menu)
2071 {
2072 int i;
2073 int total_slice, radius;
2074 int minx, miny, maxx, maxy;
2075 float angle;
2076 PieMenuEntry *it, *last;
2077 XFontStruct *font, *titlefont;
2078
2079 /*
2080 * Calculate the sum of the menu item slice sizes.
2081 * Each menu item will get a (slice / total_slice) sized slice of the pie.
2082 */
2083 total_slice = 0;
2084 for (i = 0; i < menu->numEntries; i++) {
2085 total_slice += menu->entries[i]->slice;
2086 }
2087
2088 if ((titlefont = menu->titlefontPtr) == NULL)
2089 titlefont = menu->fontPtr;
2090
2091 /*
2092 * Calculate the subtend, angle, cosine, sine, quadrant, slope,
2093 * and size of each menu item.
2094 */
2095 angle = DEG_TO_RAD(menu->initial_angle);
2096 for (i = 0; i < menu->numEntries; i++) {
2097 register float edge_dx, edge_dy, numerator, denominator, twist;
2098 register int quadrant;
2099
2100 it = menu->entries[i];
2101 if ((font = it->fontPtr) == NULL)
2102 font = menu->fontPtr;
2103
2104 if (it->bitmap != None) {
2105 unsigned int bitmapWidth, bitmapHeight;
2106
2107 Tk_SizeOfPixmap(it->bitmap, &bitmapWidth, &bitmapHeight);
2108 it->height = bitmapHeight;
2109 it->width = bitmapWidth;
2110 } else {
2111 it->height = font->ascent + font->descent;
2112 if (it->label != NULL) {
2113 (void) TkMeasureChars(font, it->label,
2114 it->labelLength, 0, (int) 100000,
2115 TK_NEWLINES_NOT_SPECIAL, &it->width);
2116 } else {
2117 it->width = 0;
2118 }
2119 }
2120 it->height += 2*menu->activeBorderWidth + 2;
2121 it->width += 2*menu->activeBorderWidth + 2;
2122
2123 it->subtend = TWO_PI * it->slice / total_slice;
2124 twist = it->subtend / 2.0;
2125 if (i != 0) angle += twist;
2126 it->angle = angle;
2127 it->dx = cos(angle);
2128 it->dy = sin(angle);
2129 edge_dx = cos(angle - twist);
2130 edge_dy = sin(angle - twist);
2131 CALC_QUADRANT_SLOPE(edge_dx, edge_dy, quadrant, numerator, denominator);
2132 it->quadrant = quadrant;
2133 it->slope = (float)numerator / (float)denominator;
2134 angle += twist;
2135 }
2136
2137 if ((radius = menu->fixed_radius) == 0) {
2138 radius = menu->min_radius;
2139 if (menu->numEntries > 1) {
2140 last = menu->entries[menu->numEntries - 1];
2141 for (i = 0; i < menu->numEntries; i++) {
2142 float dx, dy, ldx, ldy;
2143 int width, height, lwidth, lheight;
2144
2145 it = menu->entries[i];
2146
2147 dx = it->dx; dy = it->dy;
2148 width = it->width; height = it->height;
2149 ldx = last->dx; ldy = last->dy;
2150 lwidth = last->width; lheight = last->height;
2151 while (1) {
2152 register int x, y, lx, ly,
2153 x0max, y0max, x1min, y1min;
2154
2155 x = dx * radius + it->x_offset;
2156 y = dy * radius + it->y_offset;
2157 lx = ldx * radius + last->x_offset;
2158 ly = ldy * radius + last->y_offset;
2159
2160 /* Translate x y with respect to label size and position */
2161 if (ABS(x) <= 2) {
2162 x -= width/2;
2163 if (y < 0)
2164 y -= height;
2165 } else {
2166 if (x < 0)
2167 x -= width;
2168 y -= height/2;
2169 }
2170
2171 if (ABS(lx) <= 2) {
2172 lx -= lwidth/2;
2173 if (ly < 0)
2174 ly -= lheight;
2175 } else {
2176 if (lx < 0)
2177 lx -= lwidth;
2178 ly -= lheight/2;
2179 }
2180
2181 /* Do rects (x y width height) and (lx ly lwidth lheight) overlap? */
2182 x0max = x > lx ? x : lx;
2183 y0max = y > ly ? y : ly;
2184 x1min = x+width < lx+lwidth ? x+width : lx+lwidth;
2185 y1min = y+height < ly+lheight ? y+height : ly+lheight;
2186 if (!((x0max < x1min) &&
2187 (y0max < y1min))) { /* If they don't overlap */
2188 /* They are far enough out, so move on. */
2189 break;
2190 }
2191 /* Push the menu radius out a step and try again */
2192 radius++;
2193 }
2194 /* Loop on to next menu item */
2195 last = it;
2196 }
2197 }
2198 radius += menu->extra_radius;
2199 }
2200 menu->label_radius = radius;
2201
2202 /* Finally position all the menu labels at the same radius.
2203 Figure out the bounding box of the labels. */
2204 minx = miny = maxx = maxy = 0;
2205 for (i = 0; i < menu->numEntries; i++) {
2206 it = menu->entries[i];
2207
2208 it->x = radius * it->dx + it->x_offset;
2209 it->y = radius * it->dy + it->y_offset;
2210
2211 /* Translate x y with respect to label size and position */
2212 if (ABS(it->x) <= 2) {
2213 it->x -= it->width/2;
2214 if (it->y < 0)
2215 it->y -= it->height;
2216 } else {
2217 if (it->x < 0)
2218 it->x -= it->width;
2219 it->y -= it->height/2;
2220 }
2221
2222 it->label_x = it->x + menu->activeBorderWidth + 1;
2223 it->label_y = it->y - menu->activeBorderWidth - 1;
2224 if (it->bitmap == None) {
2225 it->label_y -= (it->fontPtr ? it->fontPtr : menu->fontPtr)->ascent;
2226 }
2227
2228 if (it->x < minx) minx = it->x;
2229 if ((it->x + it->width) > maxx) maxx = (it->x + it->width);
2230 if (it->y < miny) miny = it->y;
2231 if ((it->y + it->height) > maxy) maxy = (it->y + it->height);
2232 }
2233
2234
2235 if (menu->titleLength != 0) {
2236 menu->title_height = titlefont->ascent + titlefont->descent + 2;
2237 (void) TkMeasureChars(titlefont, menu->title,
2238 menu->titleLength, 0, (int) 100000,
2239 TK_NEWLINES_NOT_SPECIAL, &menu->title_width);
2240 menu->title_width += 2;
2241 if (-(menu->title_width / 2) < minx)
2242 minx = -(menu->title_width / 2);
2243 if ((menu->title_width / 2) > maxx)
2244 maxx = (menu->title_width / 2);
2245 maxy += (2 * menu->borderWidth) + menu->title_height;
2246 } else {
2247 menu->title_width = menu->title_height = 0;
2248 }
2249
2250
2251 minx -= 2*menu->borderWidth; miny -= 2*menu->borderWidth;
2252 maxx += 2*menu->borderWidth; maxy += 2*menu->borderWidth;
2253
2254 menu->center_x = -minx;
2255 menu->center_y = maxy; /* y flip */
2256 menu->width = maxx - minx;
2257 menu->height = maxy - miny;
2258
2259 /* menu->title_x = (menu->width - menu->title_width) / 2 + 1; */
2260 menu->title_x = menu->center_x - menu->title_width/2 + 1;
2261 menu->title_y = 2*menu->borderWidth + titlefont->ascent + 1;
2262
2263 /* Translate the menu items to the center of the menu, in X coordinates. */
2264 for (i = 0; i < menu->numEntries; i++) {
2265 it = menu->entries[i];
2266 it->x = menu->center_x + it->x;
2267 it->y = (menu->center_y - it->y) - it->height; /* y flip */
2268 it->label_x = menu->center_x + it->label_x;
2269 it->label_y = (menu->center_y - it->label_y) - it->height; /* y flip */
2270 }
2271
2272 if (menu->segments != NULL) {
2273 ckfree((char *)menu->segments);
2274 }
2275 menu->segments = (XSegment *)
2276 ckalloc(menu->numEntries * sizeof(XSegment));
2277
2278 if (menu->numEntries > 1) {
2279 XSegment *seg = menu->segments;
2280
2281 angle = DEG_TO_RAD(menu->initial_angle) -
2282 (menu->entries[0]->subtend / 2.0);
2283 for (i = 0; i < menu->numEntries; i++) {
2284 it = menu->entries[i];
2285 seg->x1 = menu->center_x + (cos(angle) * menu->inactive_radius);
2286 seg->y1 = menu->center_y - (sin(angle) * menu->inactive_radius);
2287 seg->x2 = menu->center_x +
2288 (cos(angle) * (menu->label_radius - PIE_SPOKE_INSET));
2289 seg->y2 = menu->center_y -
2290 (sin(angle) * (menu->label_radius - PIE_SPOKE_INSET));
2291 seg++;
2292 angle += it->subtend;
2293 }
2294 }
2295 }
2296
2297
2298 static void
2299 ShapePieMenu(menuPtr)
2300 PieMenu *menuPtr;
2301 {
2302 Display *dpy;
2303 Window win, shape;
2304 GC gc;
2305 XGCValues values;
2306 PieMenuEntry *it;
2307 int i;
2308
2309 if (HaveShape == 0)
2310 return;
2311
2312 if (menuPtr->shaped == 0) {
2313 return;
2314 }
2315
2316 dpy = Tk_Display(menuPtr->tkwin);
2317
2318 if (HaveShape == -1) {
2319 int t1, t2;
2320 if (XShapeQueryExtension(dpy, &t1, &t2)) {
2321 HaveShape = 1;
2322 } else {
2323 HaveShape = 0;
2324 return;
2325 }
2326 }
2327
2328 Tk_MakeWindowExist(menuPtr->tkwin);
2329 win = Tk_WindowId(menuPtr->tkwin);
2330
2331 shape = XCreatePixmap(dpy, RootWindowOfScreen(Tk_Screen(menuPtr->tkwin)),
2332 menuPtr->width, menuPtr->height, 1);
2333 gc = XCreateGC(dpy, shape, 0, &values);
2334
2335
2336 XSetForeground(dpy, gc, 0);
2337 XFillRectangle(dpy, shape, gc, 0, 0, menuPtr->width, menuPtr->height);
2338
2339 XSetForeground(dpy, gc, 1);
2340 if (menuPtr->titleLength != 0) {
2341 int bw = menuPtr->borderWidth;
2342
2343 XFillRectangle(dpy, shape, gc, bw, bw, menuPtr->width - bw*2, menuPtr->title_height + bw*2);
2344 }
2345
2346 for (i = 0; i < menuPtr->numEntries; i++) {
2347 it = menuPtr->entries[i];
2348 XFillRectangle(dpy, shape, gc, it->x, it->y, it->width, it->height);
2349 }
2350
2351 XFreeGC(dpy, gc);
2352 XShapeCombineMask(dpy, win, ShapeBounding, 0, 0, shape, ShapeSet);
2353 }
Impressum, Datenschutz