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