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
21static void		AdjustMenuCoords _ANSI_ARGS_ ((TkMenu *menuPtr,
22			    TkMenuEntry *mePtr, int *xPtr, int *yPtr,
23			    char *string));
24static void		ComputeMenuGeometry _ANSI_ARGS_((
25			    ClientData clientData));
26static 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
45void
46TkMenuInitializeDrawingFields(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
75void
76TkMenuInitializeEntryDrawingFields(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
108void
109TkMenuFreeDrawOptions(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
149void
150TkMenuEntryFreeDrawOptions(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
184void
185TkMenuConfigureDrawOptions(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
302int
303TkMenuConfigureEntryDrawOptions(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
434void
435TkEventuallyRecomputeMenu(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
461void
462TkRecomputeMenu(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
489void
490TkEventuallyRedrawMenu(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
536static void
537ComputeMenuGeometry(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
588void
589TkMenuSelectImageProc(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
625static void
626DisplayMenu(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
754void
755TkMenuEventProc(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
815void
816TkMenuImageProc(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
852int
853TkPostTearoffMenu(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
952int
953TkPostSubmenu(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	TkEventuallyRedrawMenu(menuPtr, (TkMenuEntry *) NULL);
989	result = Tcl_VarEval(interp, "{", name, "} unpost", (char *) NULL);
990	menuPtr->postedCascade = NULL;
991	if (result != TCL_OK) {
992	    return result;
993	}
994    }
995
996    if ((mePtr != NULL) && (mePtr->namePtr != NULL)
997	    && Tk_IsMapped(menuPtr->tkwin)) {
998	/*
999	 * Position the cascade with its upper left corner slightly
1000	 * below and to the left of the upper right corner of the
1001	 * menu entry (this is an attempt to match Motif behavior).
1002	 *
1003	 * The menu has to redrawn so that the entry can change relief.
1004	 */
1005
1006	char string[TCL_INTEGER_SPACE * 2];
1007	char *name;
1008
1009	name = Tcl_GetStringFromObj(mePtr->namePtr, NULL);
1010	Tk_GetRootCoords(menuPtr->tkwin, &x, &y);
1011	AdjustMenuCoords(menuPtr, mePtr, &x, &y, string);
1012	menuPtr->postedCascade = mePtr;
1013	result = Tcl_VarEval(interp, "{", name, "} post ", string, (char *) NULL);
1014	if (result != TCL_OK) {
1015	    menuPtr->postedCascade = NULL;
1016	    return result;
1017	}
1018	TkEventuallyRedrawMenu(menuPtr, mePtr);
1019    }
1020    return TCL_OK;
1021}
1022
1023/*
1024 *----------------------------------------------------------------------
1025 *
1026 * AdjustMenuCoords --
1027 *
1028 *	Adjusts the given coordinates down and the left to give a Motif
1029 *	look.
1030 *
1031 * Results:
1032 *	None.
1033 *
1034 * Side effects:
1035 *	The menu is eventually redrawn if necessary.
1036 *
1037 *----------------------------------------------------------------------
1038 */
1039
1040static void
1041AdjustMenuCoords(menuPtr, mePtr, xPtr, yPtr, string)
1042    TkMenu *menuPtr;
1043    TkMenuEntry *mePtr;
1044    int *xPtr;
1045    int *yPtr;
1046    char *string;
1047{
1048    if (menuPtr->menuType == MENUBAR) {
1049	*xPtr += mePtr->x;
1050	*yPtr += mePtr->y + mePtr->height;
1051    } else {
1052	int borderWidth, activeBorderWidth;
1053
1054	Tk_GetPixelsFromObj(NULL, menuPtr->tkwin, menuPtr->borderWidthPtr,
1055		&borderWidth);
1056	Tk_GetPixelsFromObj(NULL, menuPtr->tkwin,
1057		menuPtr->activeBorderWidthPtr, &activeBorderWidth);
1058	*xPtr += Tk_Width(menuPtr->tkwin) - borderWidth	- activeBorderWidth
1059		- 2;
1060	*yPtr += mePtr->y + activeBorderWidth + 2;
1061    }
1062    sprintf(string, "%d %d", *xPtr, *yPtr);
1063}
1064