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