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