1 /*
2 * tkMenuDraw.c --
3 *
4 * This module implements the platform-independent drawing and geometry
5 * calculations of menu widgets.
6 *
7 * Copyright (c) 1996-1997 by Sun Microsystems, Inc.
8 *
9 * See the file "license.terms" for information on usage and redistribution of
10 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
11 */
12
13 #include "tkInt.h"
14 #include "tkMenu.h"
15
16 /*
17 * Forward declarations for functions defined later in this file:
18 */
19
20 static void AdjustMenuCoords(TkMenu *menuPtr, TkMenuEntry *mePtr,
21 int *xPtr, int *yPtr);
22 static void ComputeMenuGeometry(ClientData clientData);
23 static void DisplayMenu(ClientData clientData);
24
25 /*
26 *----------------------------------------------------------------------
27 *
28 * TkMenuInitializeDrawingFields --
29 *
30 * Fills in drawing fields of a new menu. Called when new menu is created
31 * by MenuCmd.
32 *
33 * Results:
34 * None.
35 *
36 * Side effects:
37 * menuPtr fields are initialized.
38 *
39 *----------------------------------------------------------------------
40 */
41
42 void
TkMenuInitializeDrawingFields(TkMenu * menuPtr)43 TkMenuInitializeDrawingFields(
44 TkMenu *menuPtr) /* The menu we are initializing. */
45 {
46 menuPtr->textGC = None;
47 menuPtr->gray = None;
48 menuPtr->disabledGC = None;
49 menuPtr->activeGC = None;
50 menuPtr->indicatorGC = None;
51 menuPtr->disabledImageGC = None;
52 menuPtr->totalWidth = menuPtr->totalHeight = 0;
53 }
54
55 /*
56 *----------------------------------------------------------------------
57 *
58 * TkMenuInitializeEntryDrawingFields --
59 *
60 * Fills in drawing fields of a new menu entry. Called when an entry is
61 * created.
62 *
63 * Results:
64 * None.
65 *
66 * Side effects:
67 * None.
68 *
69 *----------------------------------------------------------------------
70 */
71
72 void
TkMenuInitializeEntryDrawingFields(TkMenuEntry * mePtr)73 TkMenuInitializeEntryDrawingFields(
74 TkMenuEntry *mePtr) /* The menu we are initializing. */
75 {
76 mePtr->width = 0;
77 mePtr->height = 0;
78 mePtr->x = 0;
79 mePtr->y = 0;
80 mePtr->indicatorSpace = 0;
81 mePtr->labelWidth = 0;
82 mePtr->textGC = None;
83 mePtr->activeGC = None;
84 mePtr->disabledGC = None;
85 mePtr->indicatorGC = None;
86 }
87
88 /*
89 *----------------------------------------------------------------------
90 *
91 * TkMenuFreeDrawOptions --
92 *
93 * Frees up any structures allocated for the drawing of a menu. Called
94 * when menu is deleted.
95 *
96 * Results:
97 * None.
98 *
99 * Side effects:
100 * Storage is released.
101 *
102 *----------------------------------------------------------------------
103 */
104
105 void
TkMenuFreeDrawOptions(TkMenu * menuPtr)106 TkMenuFreeDrawOptions(
107 TkMenu *menuPtr)
108 {
109 if (menuPtr->textGC != None) {
110 Tk_FreeGC(menuPtr->display, menuPtr->textGC);
111 }
112 if (menuPtr->disabledImageGC != None) {
113 Tk_FreeGC(menuPtr->display, menuPtr->disabledImageGC);
114 }
115 if (menuPtr->gray != None) {
116 Tk_FreeBitmap(menuPtr->display, menuPtr->gray);
117 }
118 if (menuPtr->disabledGC != None) {
119 Tk_FreeGC(menuPtr->display, menuPtr->disabledGC);
120 }
121 if (menuPtr->activeGC != None) {
122 Tk_FreeGC(menuPtr->display, menuPtr->activeGC);
123 }
124 if (menuPtr->indicatorGC != None) {
125 Tk_FreeGC(menuPtr->display, menuPtr->indicatorGC);
126 }
127 }
128
129 /*
130 *----------------------------------------------------------------------
131 *
132 * TkMenuEntryFreeDrawOptions --
133 *
134 * Frees up drawing structures for a menu entry. Called when menu entry
135 * is freed.
136 *
137 * RESULTS:
138 * None.
139 *
140 * Side effects:
141 * Storage is freed.
142 *
143 *----------------------------------------------------------------------
144 */
145
146 void
TkMenuEntryFreeDrawOptions(TkMenuEntry * mePtr)147 TkMenuEntryFreeDrawOptions(
148 TkMenuEntry *mePtr)
149 {
150 if (mePtr->textGC != None) {
151 Tk_FreeGC(mePtr->menuPtr->display, mePtr->textGC);
152 }
153 if (mePtr->disabledGC != None) {
154 Tk_FreeGC(mePtr->menuPtr->display, mePtr->disabledGC);
155 }
156 if (mePtr->activeGC != None) {
157 Tk_FreeGC(mePtr->menuPtr->display, mePtr->activeGC);
158 }
159 if (mePtr->indicatorGC != None) {
160 Tk_FreeGC(mePtr->menuPtr->display, mePtr->indicatorGC);
161 }
162 }
163
164 /*
165 *----------------------------------------------------------------------
166 *
167 * TkMenuConfigureDrawOptions --
168 *
169 * Sets the menu's drawing attributes in preparation for drawing the
170 * menu.
171 *
172 * RESULTS:
173 * None.
174 *
175 * Side effects:
176 * Storage is allocated.
177 *
178 *----------------------------------------------------------------------
179 */
180
181 void
TkMenuConfigureDrawOptions(TkMenu * menuPtr)182 TkMenuConfigureDrawOptions(
183 TkMenu *menuPtr) /* The menu we are configuring. */
184 {
185 XGCValues gcValues;
186 GC newGC;
187 unsigned long mask;
188 Tk_3DBorder border, activeBorder;
189 Tk_Font tkfont;
190 XColor *fg, *activeFg, *indicatorFg;
191
192 /*
193 * A few options need special processing, such as setting the background
194 * from a 3-D border, or filling in complicated defaults that couldn't be
195 * specified to Tk_ConfigureWidget.
196 */
197
198 border = Tk_Get3DBorderFromObj(menuPtr->tkwin, menuPtr->borderPtr);
199 Tk_SetBackgroundFromBorder(menuPtr->tkwin, border);
200
201 tkfont = Tk_GetFontFromObj(menuPtr->tkwin, menuPtr->fontPtr);
202 gcValues.font = Tk_FontId(tkfont);
203 fg = Tk_GetColorFromObj(menuPtr->tkwin, menuPtr->fgPtr);
204 gcValues.foreground = fg->pixel;
205 gcValues.background = Tk_3DBorderColor(border)->pixel;
206 newGC = Tk_GetGC(menuPtr->tkwin, GCForeground|GCBackground|GCFont,
207 &gcValues);
208 if (menuPtr->textGC != None) {
209 Tk_FreeGC(menuPtr->display, menuPtr->textGC);
210 }
211 menuPtr->textGC = newGC;
212
213 gcValues.font = Tk_FontId(tkfont);
214 gcValues.background = Tk_3DBorderColor(border)->pixel;
215 if (menuPtr->disabledFgPtr != NULL) {
216 XColor *disabledFg;
217
218 disabledFg = Tk_GetColorFromObj(menuPtr->tkwin,
219 menuPtr->disabledFgPtr);
220 gcValues.foreground = disabledFg->pixel;
221 mask = GCForeground|GCBackground|GCFont;
222 } else {
223 gcValues.foreground = gcValues.background;
224 mask = GCForeground;
225 if (menuPtr->gray == None) {
226 menuPtr->gray = Tk_GetBitmap(menuPtr->interp, menuPtr->tkwin,
227 "gray50");
228 }
229 if (menuPtr->gray != None) {
230 gcValues.fill_style = FillStippled;
231 gcValues.stipple = menuPtr->gray;
232 mask = GCForeground|GCFillStyle|GCStipple;
233 }
234 }
235 newGC = Tk_GetGC(menuPtr->tkwin, mask, &gcValues);
236 if (menuPtr->disabledGC != None) {
237 Tk_FreeGC(menuPtr->display, menuPtr->disabledGC);
238 }
239 menuPtr->disabledGC = newGC;
240
241 gcValues.foreground = Tk_3DBorderColor(border)->pixel;
242 if (menuPtr->gray == None) {
243 menuPtr->gray = Tk_GetBitmap(menuPtr->interp, menuPtr->tkwin,
244 "gray50");
245 }
246 if (menuPtr->gray != None) {
247 gcValues.fill_style = FillStippled;
248 gcValues.stipple = menuPtr->gray;
249 newGC = Tk_GetGC(menuPtr->tkwin,
250 GCForeground|GCFillStyle|GCStipple, &gcValues);
251 }
252 if (menuPtr->disabledImageGC != None) {
253 Tk_FreeGC(menuPtr->display, menuPtr->disabledImageGC);
254 }
255 menuPtr->disabledImageGC = newGC;
256
257 gcValues.font = Tk_FontId(tkfont);
258 activeFg = Tk_GetColorFromObj(menuPtr->tkwin, menuPtr->activeFgPtr);
259 gcValues.foreground = activeFg->pixel;
260 activeBorder = Tk_Get3DBorderFromObj(menuPtr->tkwin,
261 menuPtr->activeBorderPtr);
262 gcValues.background = Tk_3DBorderColor(activeBorder)->pixel;
263 newGC = Tk_GetGC(menuPtr->tkwin, GCForeground|GCBackground|GCFont,
264 &gcValues);
265 if (menuPtr->activeGC != None) {
266 Tk_FreeGC(menuPtr->display, menuPtr->activeGC);
267 }
268 menuPtr->activeGC = newGC;
269
270 indicatorFg = Tk_GetColorFromObj(menuPtr->tkwin,
271 menuPtr->indicatorFgPtr);
272 gcValues.foreground = indicatorFg->pixel;
273 gcValues.background = Tk_3DBorderColor(border)->pixel;
274 newGC = Tk_GetGC(menuPtr->tkwin, GCForeground|GCBackground|GCFont,
275 &gcValues);
276 if (menuPtr->indicatorGC != None) {
277 Tk_FreeGC(menuPtr->display, menuPtr->indicatorGC);
278 }
279 menuPtr->indicatorGC = newGC;
280 }
281
282 /*
283 *----------------------------------------------------------------------
284 *
285 * TkMenuConfigureEntryDrawOptions --
286 *
287 * Calculates any entry-specific draw options for the given menu entry.
288 *
289 * Results:
290 * Returns a standard Tcl error.
291 *
292 * Side effects:
293 * Storage may be allocated.
294 *
295 *----------------------------------------------------------------------
296 */
297
298 int
TkMenuConfigureEntryDrawOptions(TkMenuEntry * mePtr,int index)299 TkMenuConfigureEntryDrawOptions(
300 TkMenuEntry *mePtr,
301 int index)
302 {
303 XGCValues gcValues;
304 GC newGC, newActiveGC, newDisabledGC, newIndicatorGC;
305 unsigned long mask;
306 Tk_Font tkfont;
307 TkMenu *menuPtr = mePtr->menuPtr;
308
309 tkfont = Tk_GetFontFromObj(menuPtr->tkwin,
310 (mePtr->fontPtr != NULL) ? mePtr->fontPtr : menuPtr->fontPtr);
311
312 if (mePtr->state == ENTRY_ACTIVE) {
313 if (index != menuPtr->active) {
314 TkActivateMenuEntry(menuPtr, index);
315 }
316 } else {
317 if (index == menuPtr->active) {
318 TkActivateMenuEntry(menuPtr, -1);
319 }
320 }
321
322 if ((mePtr->fontPtr != NULL)
323 || (mePtr->borderPtr != NULL)
324 || (mePtr->fgPtr != NULL)
325 || (mePtr->activeBorderPtr != NULL)
326 || (mePtr->activeFgPtr != NULL)
327 || (mePtr->indicatorFgPtr != NULL)) {
328 XColor *fg, *indicatorFg, *activeFg;
329 Tk_3DBorder border, activeBorder;
330
331 fg = Tk_GetColorFromObj(menuPtr->tkwin, (mePtr->fgPtr != NULL)
332 ? mePtr->fgPtr : menuPtr->fgPtr);
333 gcValues.foreground = fg->pixel;
334 border = Tk_Get3DBorderFromObj(menuPtr->tkwin,
335 (mePtr->borderPtr != NULL) ? mePtr->borderPtr
336 : menuPtr->borderPtr);
337 gcValues.background = Tk_3DBorderColor(border)->pixel;
338
339 gcValues.font = Tk_FontId(tkfont);
340
341 /*
342 * Note: disable GraphicsExpose events; we know there won't be
343 * obscured areas when copying from an off-screen pixmap to the screen
344 * and this gets rid of unnecessary events.
345 */
346
347 gcValues.graphics_exposures = False;
348 newGC = Tk_GetGC(menuPtr->tkwin,
349 GCForeground|GCBackground|GCFont|GCGraphicsExposures,
350 &gcValues);
351
352 indicatorFg = Tk_GetColorFromObj(menuPtr->tkwin,
353 (mePtr->indicatorFgPtr != NULL) ? mePtr->indicatorFgPtr
354 : menuPtr->indicatorFgPtr);
355 gcValues.foreground = indicatorFg->pixel;
356 newIndicatorGC = Tk_GetGC(menuPtr->tkwin,
357 GCForeground|GCBackground|GCGraphicsExposures,
358 &gcValues);
359
360 if ((menuPtr->disabledFgPtr != NULL) || (mePtr->image != NULL)) {
361 XColor *disabledFg;
362
363 disabledFg = Tk_GetColorFromObj(menuPtr->tkwin,
364 menuPtr->disabledFgPtr);
365 gcValues.foreground = disabledFg->pixel;
366 mask = GCForeground|GCBackground|GCFont|GCGraphicsExposures;
367 } else {
368 gcValues.foreground = gcValues.background;
369 gcValues.fill_style = FillStippled;
370 gcValues.stipple = menuPtr->gray;
371 mask = GCForeground|GCFillStyle|GCStipple;
372 }
373 newDisabledGC = Tk_GetGC(menuPtr->tkwin, mask, &gcValues);
374
375 activeFg = Tk_GetColorFromObj(menuPtr->tkwin,
376 (mePtr->activeFgPtr != NULL) ? mePtr->activeFgPtr
377 : menuPtr->activeFgPtr);
378 activeBorder = Tk_Get3DBorderFromObj(menuPtr->tkwin,
379 (mePtr->activeBorderPtr != NULL) ? mePtr->activeBorderPtr
380 : menuPtr->activeBorderPtr);
381
382 gcValues.foreground = activeFg->pixel;
383 gcValues.background = Tk_3DBorderColor(activeBorder)->pixel;
384 newActiveGC = Tk_GetGC(menuPtr->tkwin,
385 GCForeground|GCBackground|GCFont|GCGraphicsExposures,
386 &gcValues);
387 } else {
388 newGC = None;
389 newActiveGC = None;
390 newDisabledGC = None;
391 newIndicatorGC = None;
392 }
393 if (mePtr->textGC != None) {
394 Tk_FreeGC(menuPtr->display, mePtr->textGC);
395 }
396 mePtr->textGC = newGC;
397 if (mePtr->activeGC != None) {
398 Tk_FreeGC(menuPtr->display, mePtr->activeGC);
399 }
400 mePtr->activeGC = newActiveGC;
401 if (mePtr->disabledGC != None) {
402 Tk_FreeGC(menuPtr->display, mePtr->disabledGC);
403 }
404 mePtr->disabledGC = newDisabledGC;
405 if (mePtr->indicatorGC != None) {
406 Tk_FreeGC(menuPtr->display, mePtr->indicatorGC);
407 }
408 mePtr->indicatorGC = newIndicatorGC;
409 return TCL_OK;
410 }
411
412 /*
413 *----------------------------------------------------------------------
414 *
415 * TkEventuallyRecomputeMenu --
416 *
417 * Tells Tcl to redo the geometry because this menu has changed.
418 *
419 * Results:
420 * None.
421 *
422 * Side effects:
423 * Menu geometry is recomputed at idle time, and the menu will be
424 * redisplayed.
425 *
426 *----------------------------------------------------------------------
427 */
428
429 void
TkEventuallyRecomputeMenu(TkMenu * menuPtr)430 TkEventuallyRecomputeMenu(
431 TkMenu *menuPtr)
432 {
433 if (!(menuPtr->menuFlags & RESIZE_PENDING)) {
434 menuPtr->menuFlags |= RESIZE_PENDING;
435 Tcl_DoWhenIdle(ComputeMenuGeometry, (ClientData) menuPtr);
436 }
437 }
438
439 /*
440 *----------------------------------------------------------------------
441 *
442 * TkRecomputeMenu --
443 *
444 * Tells Tcl to redo the geometry because this menu has changed. Does it
445 * now; removes any ComputeMenuGeometries from the idler.
446 *
447 * Results:
448 * None.
449 *
450 * Side effects:
451 * Menu geometry is immediately reconfigured.
452 *
453 *----------------------------------------------------------------------
454 */
455
456 void
TkRecomputeMenu(TkMenu * menuPtr)457 TkRecomputeMenu(
458 TkMenu *menuPtr)
459 {
460 if (menuPtr->menuFlags & RESIZE_PENDING) {
461 Tcl_CancelIdleCall(ComputeMenuGeometry, (ClientData) menuPtr);
462 ComputeMenuGeometry((ClientData) menuPtr);
463 }
464 }
465
466 /*
467 *----------------------------------------------------------------------
468 *
469 * TkEventuallyRedrawMenu --
470 *
471 * Arrange for an entry of a menu, or the whole menu, to be redisplayed
472 * at some point in the future.
473 *
474 * Results:
475 * None.
476 *
477 * Side effects:
478 * A when-idle hander is scheduled to do the redisplay, if there isn't
479 * one already scheduled.
480 *
481 *----------------------------------------------------------------------
482 */
483
484 void
TkEventuallyRedrawMenu(register TkMenu * menuPtr,register TkMenuEntry * mePtr)485 TkEventuallyRedrawMenu(
486 register TkMenu *menuPtr, /* Information about menu to redraw. */
487 register TkMenuEntry *mePtr)/* Entry to redraw. NULL means redraw all the
488 * entries in the menu. */
489 {
490 int i;
491
492 if (menuPtr->tkwin == NULL) {
493 return;
494 }
495 if (mePtr != NULL) {
496 mePtr->entryFlags |= ENTRY_NEEDS_REDISPLAY;
497 } else {
498 for (i = 0; i < menuPtr->numEntries; i++) {
499 menuPtr->entries[i]->entryFlags |= ENTRY_NEEDS_REDISPLAY;
500 }
501 }
502 if (!Tk_IsMapped(menuPtr->tkwin)
503 || (menuPtr->menuFlags & REDRAW_PENDING)) {
504 return;
505 }
506 Tcl_DoWhenIdle(DisplayMenu, (ClientData) menuPtr);
507 menuPtr->menuFlags |= REDRAW_PENDING;
508 }
509
510 /*
511 *--------------------------------------------------------------
512 *
513 * ComputeMenuGeometry --
514 *
515 * This function is invoked to recompute the size and layout of a menu.
516 * It is called as a when-idle handler so that it only gets done once,
517 * even if a group of changes is made to the menu.
518 *
519 * Results:
520 * None.
521 *
522 * Side effects:
523 * Fields of menu entries are changed to reflect their current positions,
524 * and the size of the menu window itself may be changed.
525 *
526 *--------------------------------------------------------------
527 */
528
529 static void
ComputeMenuGeometry(ClientData clientData)530 ComputeMenuGeometry(
531 ClientData clientData) /* Structure describing menu. */
532 {
533 TkMenu *menuPtr = (TkMenu *) clientData;
534
535 if (menuPtr->tkwin == NULL) {
536 return;
537 }
538
539 if (menuPtr->menuType == MENUBAR) {
540 TkpComputeMenubarGeometry(menuPtr);
541 } else {
542 TkpComputeStandardMenuGeometry(menuPtr);
543 }
544
545 if ((menuPtr->totalWidth != Tk_ReqWidth(menuPtr->tkwin)) ||
546 (menuPtr->totalHeight != Tk_ReqHeight(menuPtr->tkwin))) {
547 Tk_GeometryRequest(menuPtr->tkwin, menuPtr->totalWidth,
548 menuPtr->totalHeight);
549 }
550
551 /*
552 * Must always force a redisplay here if the window is mapped (even if the
553 * size didn't change, something else might have changed in the menu, such
554 * as a label or accelerator). The resize will force a redisplay above.
555 */
556
557 TkEventuallyRedrawMenu(menuPtr, NULL);
558
559 menuPtr->menuFlags &= ~RESIZE_PENDING;
560 }
561
562 /*
563 *----------------------------------------------------------------------
564 *
565 * TkMenuSelectImageProc --
566 *
567 * This function is invoked by the image code whenever the manager for an
568 * image does something that affects the size of contents of an image
569 * displayed in a menu entry when it is selected.
570 *
571 * Results:
572 * None.
573 *
574 * Side effects:
575 * Arranges for the menu to get redisplayed.
576 *
577 *----------------------------------------------------------------------
578 */
579
580 void
TkMenuSelectImageProc(ClientData clientData,int x,int y,int width,int height,int imgWidth,int imgHeight)581 TkMenuSelectImageProc(
582 ClientData clientData, /* Pointer to widget record. */
583 int x, int y, /* Upper left pixel (within image) that must
584 * be redisplayed. */
585 int width, int height, /* Dimensions of area to redisplay (may be
586 * <=0). */
587 int imgWidth, int imgHeight)/* New dimensions of image. */
588 {
589 register TkMenuEntry *mePtr = (TkMenuEntry *) clientData;
590
591 if ((mePtr->entryFlags & ENTRY_SELECTED)
592 && !(mePtr->menuPtr->menuFlags & REDRAW_PENDING)) {
593 mePtr->menuPtr->menuFlags |= REDRAW_PENDING;
594 Tcl_DoWhenIdle(DisplayMenu, (ClientData) mePtr->menuPtr);
595 }
596 }
597
598 /*
599 *----------------------------------------------------------------------
600 *
601 * DisplayMenu --
602 *
603 * This function is invoked to display a menu widget.
604 *
605 * Results:
606 * None.
607 *
608 * Side effects:
609 * Commands are output to X to display the menu in its current mode.
610 *
611 *----------------------------------------------------------------------
612 */
613
614 static void
DisplayMenu(ClientData clientData)615 DisplayMenu(
616 ClientData clientData) /* Information about widget. */
617 {
618 register TkMenu *menuPtr = (TkMenu *) clientData;
619 register TkMenuEntry *mePtr;
620 register Tk_Window tkwin = menuPtr->tkwin;
621 int index, strictMotif;
622 Tk_Font tkfont;
623 Tk_FontMetrics menuMetrics;
624 int width;
625 int borderWidth;
626 Tk_3DBorder border;
627 int activeBorderWidth;
628 int relief;
629
630
631 menuPtr->menuFlags &= ~REDRAW_PENDING;
632 if ((menuPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) {
633 return;
634 }
635
636 Tk_GetPixelsFromObj(NULL, menuPtr->tkwin, menuPtr->borderWidthPtr,
637 &borderWidth);
638 border = Tk_Get3DBorderFromObj(menuPtr->tkwin, menuPtr->borderPtr);
639 Tk_GetPixelsFromObj(NULL, menuPtr->tkwin,
640 menuPtr->activeBorderWidthPtr, &activeBorderWidth);
641
642 if (menuPtr->menuType == MENUBAR) {
643 Tk_Fill3DRectangle(tkwin, Tk_WindowId(tkwin), border, borderWidth,
644 borderWidth, Tk_Width(tkwin) - 2 * borderWidth,
645 Tk_Height(tkwin) - 2 * borderWidth, 0, TK_RELIEF_FLAT);
646 }
647
648 strictMotif = Tk_StrictMotif(menuPtr->tkwin);
649
650 /*
651 * See note in ComputeMenuGeometry. We don't want to be doing font metrics
652 * all of the time.
653 */
654
655 tkfont = Tk_GetFontFromObj(menuPtr->tkwin, menuPtr->fontPtr);
656 Tk_GetFontMetrics(tkfont, &menuMetrics);
657
658 /*
659 * Loop through all of the entries, drawing them one at a time.
660 */
661
662 for (index = 0; index < menuPtr->numEntries; index++) {
663 mePtr = menuPtr->entries[index];
664 if (menuPtr->menuType != MENUBAR) {
665 if (!(mePtr->entryFlags & ENTRY_NEEDS_REDISPLAY)) {
666 continue;
667 }
668 }
669 mePtr->entryFlags &= ~ENTRY_NEEDS_REDISPLAY;
670
671 if (menuPtr->menuType == MENUBAR) {
672 width = mePtr->width;
673 } else {
674 if (mePtr->entryFlags & ENTRY_LAST_COLUMN) {
675 width = Tk_Width(menuPtr->tkwin) - mePtr->x
676 - activeBorderWidth;
677 } else {
678 width = mePtr->width + borderWidth;
679 }
680 }
681 TkpDrawMenuEntry(mePtr, Tk_WindowId(menuPtr->tkwin), tkfont,
682 &menuMetrics, mePtr->x, mePtr->y, width,
683 mePtr->height, strictMotif, 1);
684 if ((index > 0) && (menuPtr->menuType != MENUBAR)
685 && mePtr->columnBreak) {
686 mePtr = menuPtr->entries[index - 1];
687 Tk_Fill3DRectangle(tkwin, Tk_WindowId(tkwin), border,
688 mePtr->x, mePtr->y + mePtr->height,
689 mePtr->width,
690 Tk_Height(tkwin) - mePtr->y - mePtr->height -
691 activeBorderWidth, 0,
692 TK_RELIEF_FLAT);
693 }
694 }
695
696 if (menuPtr->menuType != MENUBAR) {
697 int x, y, height;
698
699 if (menuPtr->numEntries == 0) {
700 x = y = borderWidth;
701 width = Tk_Width(tkwin) - 2 * activeBorderWidth;
702 height = Tk_Height(tkwin) - 2 * activeBorderWidth;
703 } else {
704 mePtr = menuPtr->entries[menuPtr->numEntries - 1];
705 Tk_Fill3DRectangle(tkwin, Tk_WindowId(tkwin),
706 border, mePtr->x, mePtr->y + mePtr->height, mePtr->width,
707 Tk_Height(tkwin) - mePtr->y - mePtr->height
708 - activeBorderWidth, 0,
709 TK_RELIEF_FLAT);
710 x = mePtr->x + mePtr->width;
711 y = mePtr->y + mePtr->height;
712 width = Tk_Width(tkwin) - x - activeBorderWidth;
713 height = Tk_Height(tkwin) - y - activeBorderWidth;
714 }
715 Tk_Fill3DRectangle(tkwin, Tk_WindowId(tkwin), border, x, y,
716 width, height, 0, TK_RELIEF_FLAT);
717 }
718
719 Tk_GetReliefFromObj(NULL, menuPtr->reliefPtr, &relief);
720 Tk_Draw3DRectangle(menuPtr->tkwin, Tk_WindowId(tkwin),
721 border, 0, 0, Tk_Width(tkwin), Tk_Height(tkwin), borderWidth,
722 relief);
723 }
724
725 /*
726 *--------------------------------------------------------------
727 *
728 * TkMenuEventProc --
729 *
730 * This function is invoked by the Tk dispatcher for various events on
731 * menus.
732 *
733 * Results:
734 * None.
735 *
736 * Side effects:
737 * When the window gets deleted, internal structures get cleaned up. When
738 * it gets exposed, it is redisplayed.
739 *
740 *--------------------------------------------------------------
741 */
742
743 void
TkMenuEventProc(ClientData clientData,XEvent * eventPtr)744 TkMenuEventProc(
745 ClientData clientData, /* Information about window. */
746 XEvent *eventPtr) /* Information about event. */
747 {
748 TkMenu *menuPtr = (TkMenu *) clientData;
749
750 if ((eventPtr->type == Expose) && (eventPtr->xexpose.count == 0)) {
751 TkEventuallyRedrawMenu(menuPtr, NULL);
752 } else if (eventPtr->type == ConfigureNotify) {
753 TkEventuallyRecomputeMenu(menuPtr);
754 TkEventuallyRedrawMenu(menuPtr, NULL);
755 } else if (eventPtr->type == ActivateNotify) {
756 if (menuPtr->menuType == TEAROFF_MENU) {
757 TkpSetMainMenubar(menuPtr->interp, menuPtr->tkwin, NULL);
758 }
759 } else if (eventPtr->type == DestroyNotify) {
760 if (menuPtr->tkwin != NULL) {
761 if (!(menuPtr->menuFlags & MENU_DELETION_PENDING)) {
762 TkDestroyMenu(menuPtr);
763 }
764 menuPtr->tkwin = NULL;
765 }
766 if (menuPtr->menuFlags & MENU_WIN_DESTRUCTION_PENDING) {
767 return;
768 }
769 menuPtr->menuFlags |= MENU_WIN_DESTRUCTION_PENDING;
770 if (menuPtr->widgetCmd != NULL) {
771 Tcl_DeleteCommandFromToken(menuPtr->interp, menuPtr->widgetCmd);
772 menuPtr->widgetCmd = NULL;
773 }
774 if (menuPtr->menuFlags & REDRAW_PENDING) {
775 Tcl_CancelIdleCall(DisplayMenu, (ClientData) menuPtr);
776 menuPtr->menuFlags &= ~REDRAW_PENDING;
777 }
778 if (menuPtr->menuFlags & RESIZE_PENDING) {
779 Tcl_CancelIdleCall(ComputeMenuGeometry, (ClientData) menuPtr);
780 menuPtr->menuFlags &= ~RESIZE_PENDING;
781 }
782 Tcl_EventuallyFree((ClientData) menuPtr, TCL_DYNAMIC);
783 }
784 }
785
786 /*
787 *----------------------------------------------------------------------
788 *
789 * TkMenuImageProc --
790 *
791 * This function is invoked by the image code whenever the manager for an
792 * image does something that affects the size of contents of an image
793 * displayed in a menu entry.
794 *
795 * Results:
796 * None.
797 *
798 * Side effects:
799 * Arranges for the menu to get redisplayed.
800 *
801 *----------------------------------------------------------------------
802 */
803
804 void
TkMenuImageProc(ClientData clientData,int x,int y,int width,int height,int imgWidth,int imgHeight)805 TkMenuImageProc(
806 ClientData clientData, /* Pointer to widget record. */
807 int x, int y, /* Upper left pixel (within image) that must
808 * be redisplayed. */
809 int width, int height, /* Dimensions of area to redisplay (may be
810 * <=0). */
811 int imgWidth, int imgHeight)/* New dimensions of image. */
812 {
813 register TkMenu *menuPtr = ((TkMenuEntry *)clientData)->menuPtr;
814
815 if ((menuPtr->tkwin != NULL) && !(menuPtr->menuFlags & RESIZE_PENDING)) {
816 menuPtr->menuFlags |= RESIZE_PENDING;
817 Tcl_DoWhenIdle(ComputeMenuGeometry, (ClientData) menuPtr);
818 }
819 }
820
821 /*
822 *----------------------------------------------------------------------
823 *
824 * TkPostTearoffMenu --
825 *
826 * Posts a menu on the screen. Used to post tearoff menus. On Unix, all
827 * menus are posted this way. Adjusts the menu's position so that it fits
828 * on the screen, and maps and raises the menu.
829 *
830 * Results:
831 * Returns a standard Tcl Error.
832 *
833 * Side effects:
834 * The menu is posted.
835 *
836 *----------------------------------------------------------------------
837 */
838
839 int
TkPostTearoffMenu(Tcl_Interp * interp,TkMenu * menuPtr,int x,int y)840 TkPostTearoffMenu(
841 Tcl_Interp *interp, /* The interpreter of the menu */
842 TkMenu *menuPtr, /* The menu we are posting */
843 int x, int y) /* The root X,Y coordinates where we are
844 * posting */
845 {
846 int vRootX, vRootY, vRootWidth, vRootHeight;
847 int result;
848
849 TkActivateMenuEntry(menuPtr, -1);
850 TkRecomputeMenu(menuPtr);
851 result = TkPostCommand(menuPtr);
852 if (result != TCL_OK) {
853 return result;
854 }
855
856 /*
857 * The post commands could have deleted the menu, which means we are dead
858 * and should go away.
859 */
860
861 if (menuPtr->tkwin == NULL) {
862 return TCL_OK;
863 }
864
865 /*
866 * Adjust the position of the menu if necessary to keep it visible on the
867 * screen. There are two special tricks to make this work right:
868 *
869 * 1. If a virtual root window manager is being used then the coordinates
870 * are in the virtual root window of menuPtr's parent; since the menu
871 * uses override-redirect mode it will be in the *real* root window for
872 * the screen, so we have to map the coordinates from the virtual root
873 * (if any) to the real root. Can't get the virtual root from the menu
874 * itself (it will never be seen by the wm) so use its parent instead
875 * (it would be better to have an an option that names a window to use
876 * for this...).
877 * 2. The menu may not have been mapped yet, so its current size might be
878 * the default 1x1. To compute how much space it needs, use its
879 * requested size, not its actual size.
880 */
881
882 Tk_GetVRootGeometry(Tk_Parent(menuPtr->tkwin), &vRootX, &vRootY,
883 &vRootWidth, &vRootHeight);
884 vRootWidth -= Tk_ReqWidth(menuPtr->tkwin);
885 if (x > vRootX + vRootWidth) {
886 x = vRootX + vRootWidth;
887 }
888 if (x < vRootX) {
889 x = vRootX;
890 }
891 vRootHeight -= Tk_ReqHeight(menuPtr->tkwin);
892 if (y > vRootY + vRootHeight) {
893 y = vRootY + vRootHeight;
894 }
895 if (y < vRootY) {
896 y = vRootY;
897 }
898 Tk_MoveToplevelWindow(menuPtr->tkwin, x, y);
899 if (!Tk_IsMapped(menuPtr->tkwin)) {
900 Tk_MapWindow(menuPtr->tkwin);
901 }
902 TkWmRestackToplevel((TkWindow *) menuPtr->tkwin, Above, NULL);
903 return TCL_OK;
904 }
905
906 /*
907 *--------------------------------------------------------------
908 *
909 * TkPostSubmenu --
910 *
911 * This function arranges for a particular submenu (i.e. the menu
912 * corresponding to a given cascade entry) to be posted.
913 *
914 * Results:
915 * A standard Tcl return result. Errors may occur in the Tcl commands
916 * generated to post and unpost submenus.
917 *
918 * Side effects:
919 * If there is already a submenu posted, it is unposted. The new submenu
920 * is then posted.
921 *
922 *--------------------------------------------------------------
923 */
924
925 int
TkPostSubmenu(Tcl_Interp * interp,register TkMenu * menuPtr,register TkMenuEntry * mePtr)926 TkPostSubmenu(
927 Tcl_Interp *interp, /* Used for invoking sub-commands and
928 * reporting errors. */
929 register TkMenu *menuPtr, /* Information about menu as a whole. */
930 register TkMenuEntry *mePtr)/* Info about submenu that is to be posted.
931 * NULL means make sure that no submenu is
932 * posted. */
933 {
934 int result, x, y;
935 Tcl_Obj *subary[4];
936
937 if (mePtr == menuPtr->postedCascade) {
938 return TCL_OK;
939 }
940
941 if (menuPtr->postedCascade != NULL) {
942 /*
943 * Note: when unposting a submenu, we have to redraw the entire parent
944 * menu. This is because of a combination of the following things:
945 * (a) the submenu partially overlaps the parent.
946 * (b) the submenu specifies "save under", which causes the X server
947 * to make a copy of the information under it when it is posted.
948 * When the submenu is unposted, the X server copies this data
949 * back and doesn't generate any Expose events for the parent.
950 * (c) the parent may have redisplayed itself after the submenu was
951 * posted, in which case the saved information is no longer
952 * correct.
953 * The simplest solution is just force a complete redisplay of the
954 * parent.
955 */
956
957 subary[0] = menuPtr->postedCascade->namePtr;
958 subary[1] = Tcl_NewStringObj("unpost", -1);
959 Tcl_IncrRefCount(subary[1]);
960 TkEventuallyRedrawMenu(menuPtr, NULL);
961 result = Tcl_EvalObjv(interp, 2, subary, 0);
962 Tcl_DecrRefCount(subary[1]);
963 menuPtr->postedCascade = NULL;
964 if (result != TCL_OK) {
965 return result;
966 }
967 }
968
969 if ((mePtr != NULL) && (mePtr->namePtr != NULL)
970 && Tk_IsMapped(menuPtr->tkwin)) {
971 /*
972 * Position the cascade with its upper left corner slightly below and
973 * to the left of the upper right corner of the menu entry (this is an
974 * attempt to match Motif behavior).
975 *
976 * The menu has to redrawn so that the entry can change relief.
977 *
978 * Set postedCascade early to ensure tear-off submenus work on
979 * Windows. [Bug 873613]
980 */
981
982 Tk_GetRootCoords(menuPtr->tkwin, &x, &y);
983 AdjustMenuCoords(menuPtr, mePtr, &x, &y);
984
985 menuPtr->postedCascade = mePtr;
986 subary[0] = mePtr->namePtr;
987 subary[1] = Tcl_NewStringObj("post", -1);
988 subary[2] = Tcl_NewIntObj(x);
989 subary[3] = Tcl_NewIntObj(y);
990 Tcl_IncrRefCount(subary[1]);
991 Tcl_IncrRefCount(subary[2]);
992 Tcl_IncrRefCount(subary[3]);
993 result = Tcl_EvalObjv(interp, 4, subary, 0);
994 Tcl_DecrRefCount(subary[1]);
995 Tcl_DecrRefCount(subary[2]);
996 Tcl_DecrRefCount(subary[3]);
997 if (result != TCL_OK) {
998 menuPtr->postedCascade = NULL;
999 return result;
1000 }
1001 TkEventuallyRedrawMenu(menuPtr, mePtr);
1002 }
1003 return TCL_OK;
1004 }
1005
1006 /*
1007 *----------------------------------------------------------------------
1008 *
1009 * AdjustMenuCoords --
1010 *
1011 * Adjusts the given coordinates down and the left to give a Motif look.
1012 *
1013 * Results:
1014 * None.
1015 *
1016 * Side effects:
1017 * The menu is eventually redrawn if necessary.
1018 *
1019 *----------------------------------------------------------------------
1020 */
1021
1022 static void
AdjustMenuCoords(TkMenu * menuPtr,TkMenuEntry * mePtr,int * xPtr,int * yPtr)1023 AdjustMenuCoords(
1024 TkMenu *menuPtr,
1025 TkMenuEntry *mePtr,
1026 int *xPtr,
1027 int *yPtr)
1028 {
1029 if (menuPtr->menuType == MENUBAR) {
1030 *xPtr += mePtr->x;
1031 *yPtr += mePtr->y + mePtr->height;
1032 } else {
1033 int borderWidth, activeBorderWidth;
1034
1035 Tk_GetPixelsFromObj(NULL, menuPtr->tkwin, menuPtr->borderWidthPtr,
1036 &borderWidth);
1037 Tk_GetPixelsFromObj(NULL, menuPtr->tkwin,
1038 menuPtr->activeBorderWidthPtr, &activeBorderWidth);
1039 *xPtr += Tk_Width(menuPtr->tkwin) - borderWidth - activeBorderWidth
1040 - 2;
1041 *yPtr += mePtr->y + activeBorderWidth + 2;
1042 }
1043 }
1044
1045 /*
1046 * Local Variables:
1047 * mode: c
1048 * c-basic-offset: 4
1049 * fill-column: 78
1050 * End:
1051 */
1052