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