]> git.zerfleddert.de Git - micropolis/blob - src/sim/w_piem.c
60b7ea947bd4d7a254b5efd603e521f0515bea2e
[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
372 \f
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 \f
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 \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 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 \f
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 \f
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 \f
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 \f
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 \f
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 \f
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 }
Impressum, Datenschutz