1 /*
2  * tkWinMenu.c --
3  *
4  *	This module implements the Windows platform-specific features of
5  *	menus.
6  *
7  * Copyright (c) 1996-1998 by Sun Microsystems, Inc.
8  * Copyright (c) 1998-1999 by Scriptics Corporation.
9  *
10  * See the file "license.terms" for information on usage and redistribution of
11  * this file, and for a DISCLAIMER OF ALL WARRANTIES.
12  */
13 
14 #define OEMRESOURCE
15 #include "tkWinInt.h"
16 #include "tkMenu.h"
17 
18 /*
19  * The class of the window for popup menus.
20  */
21 
22 #define MENU_CLASS_NAME			L"MenuWindowClass"
23 #define EMBEDDED_MENU_CLASS_NAME	L"EmbeddedMenuWindowClass"
24 
25 /*
26  * Used to align a windows bitmap inside a rectangle
27  */
28 
29 #define ALIGN_BITMAP_LEFT	0x00000001
30 #define ALIGN_BITMAP_RIGHT	0x00000002
31 #define ALIGN_BITMAP_TOP	0x00000004
32 #define ALIGN_BITMAP_BOTTOM	0x00000008
33 
34 
35 /*
36  * Platform-specific menu flags:
37  *
38  * MENU_SYSTEM_MENU	Non-zero means that the Windows menu handle was
39  *			retrieved with GetSystemMenu and needs to be disposed
40  *			of specially.
41  * MENU_RECONFIGURE_PENDING
42  *			Non-zero means that an idle handler has been set up to
43  *			reconfigure the Windows menu handle for this menu.
44  */
45 
46 #define MENU_SYSTEM_MENU		MENU_PLATFORM_FLAG1
47 #define MENU_RECONFIGURE_PENDING	MENU_PLATFORM_FLAG2
48 
49 /*
50  * ODS_NOACCEL flag forbids drawing accelerator cues (i.e. underlining labels)
51  * on Windows 2000 and above.  The ODS_NOACCEL define is missing from mingw32
52  * headers and undefined for _WIN32_WINNT < 0x0500 in Microsoft SDK.  We might
53  * check for _WIN32_WINNT here, but I think it's not needed, as checking for
54  * this flag does no harm on even on NT: reserved bits should be zero, and in
55  * fact they are.
56  */
57 
58 #ifndef ODS_NOACCEL
59 #define ODS_NOACCEL 0x100
60 #endif
61 #ifndef SPI_GETKEYBOARDCUES
62 #define SPI_GETKEYBOARDCUES             0x100A
63 #endif
64 #ifndef WM_UPDATEUISTATE
65 #define WM_UPDATEUISTATE                0x0128
66 #endif
67 #ifndef UIS_SET
68 #define UIS_SET                         1
69 #endif
70 #ifndef UIS_CLEAR
71 #define UIS_CLEAR                       2
72 #endif
73 #ifndef UISF_HIDEACCEL
74 #define UISF_HIDEACCEL                  2
75 #endif
76 
77 #ifndef WM_UNINITMENUPOPUP
78 #define WM_UNINITMENUPOPUP              0x0125
79 #endif
80 
81 static int indicatorDimensions[2];
82 				/* The dimensions of the indicator space in a
83 				 * menu entry. Calculated at init time to save
84 				 * time. */
85 
86 static BOOL showMenuAccelerators;
87 
88 typedef struct {
89     int inPostMenu;		/* We cannot be re-entrant like X Windows. */
90     WORD lastCommandID;		/* The last command ID we allocated. */
91     HWND menuHWND;		/* A window to service popup-menu messages
92 				 * in. */
93     HWND embeddedMenuHWND;	/* A window to service embedded menu
94 				 * messages */
95     int oldServiceMode;		/* Used while processing a menu; we need to
96 				 * set the event mode specially when we enter
97 				 * the menu processing modal loop and reset it
98 				 * when menus go away. */
99     TkMenu *modalMenuPtr;	/* The menu we are processing inside the modal
100 				 * loop. We need this to reset all of the
101 				 * active items when menus go away since
102 				 * Windows does not see fit to give this to us
103 				 * when it sends its WM_MENUSELECT. */
104     Tcl_HashTable commandTable;	/* A map of command ids to menu entries */
105     Tcl_HashTable winMenuTable;	/* Need this to map HMENUs back to menuPtrs */
106 } ThreadSpecificData;
107 static Tcl_ThreadDataKey dataKey;
108 
109 /*
110  * The following are default menu value strings.
111  */
112 
113 static int defaultBorderWidth;	/* The windows default border width. */
114 static Tcl_DString menuFontDString;
115 				/* A buffer to store the default menu font
116 				 * string. */
117 /*
118  * Forward declarations for functions defined later in this file:
119  */
120 
121 static void		DrawMenuEntryAccelerator(TkMenu *menuPtr,
122 			    TkMenuEntry *mePtr, Drawable d, GC gc,
123 			    Tk_Font tkfont, const Tk_FontMetrics *fmPtr,
124 			    Tk_3DBorder activeBorder, int x, int y,
125 			    int width, int height);
126 static void		DrawMenuEntryArrow(TkMenu *menuPtr, TkMenuEntry *mePtr,
127 			    Drawable d, GC gc, Tk_3DBorder activeBorder,
128 			    int x,int y, int width, int height, int drawArrow);
129 static void		DrawMenuEntryBackground(TkMenu *menuPtr,
130 			    TkMenuEntry *mePtr, Drawable d,
131 			    Tk_3DBorder activeBorder, Tk_3DBorder bgBorder,
132 			    int x, int y, int width, int heigth);
133 static void		DrawMenuEntryIndicator(TkMenu *menuPtr,
134 			    TkMenuEntry *mePtr, Drawable d, GC gc,
135 			    GC indicatorGC, Tk_Font tkfont,
136 			    const Tk_FontMetrics *fmPtr, int x, int y,
137 			    int width, int height);
138 static void		DrawMenuEntryLabel(TkMenu *menuPtr, TkMenuEntry *mePtr,
139 			    Drawable d, GC gc, Tk_Font tkfont,
140 			    const Tk_FontMetrics *fmPtr, int x, int y,
141 			    int width, int height, int underline);
142 static void		DrawMenuSeparator(TkMenu *menuPtr, TkMenuEntry *mePtr,
143 			    Drawable d, GC gc, Tk_Font tkfont,
144 			    const Tk_FontMetrics *fmPtr,
145 			    int x, int y, int width, int height);
146 static void		DrawTearoffEntry(TkMenu *menuPtr, TkMenuEntry *mePtr,
147 			    Drawable d, GC gc, Tk_Font tkfont,
148 			    const Tk_FontMetrics *fmPtr, int x, int y,
149 			    int width, int height);
150 static void		DrawMenuUnderline(TkMenu *menuPtr, TkMenuEntry *mePtr,
151 			    Drawable d, GC gc, Tk_Font tkfont,
152 			    const Tk_FontMetrics *fmPtr, int x, int y,
153 			    int width, int height);
154 static void		DrawWindowsSystemBitmap(Display *display,
155 			    Drawable drawable, GC gc, const RECT *rectPtr,
156 			    int bitmapID, int alignFlags);
157 static void		FreeID(WORD commandID);
158 static char *		GetEntryText(TkMenu *menuPtr, TkMenuEntry *mePtr);
159 static void		GetMenuAccelGeometry(TkMenu *menuPtr,
160 			    TkMenuEntry *mePtr, Tk_Font tkfont,
161 			    const Tk_FontMetrics *fmPtr, int *widthPtr,
162 			    int *heightPtr);
163 static void		GetMenuLabelGeometry(TkMenuEntry *mePtr,
164 			    Tk_Font tkfont, const Tk_FontMetrics *fmPtr,
165 			    int *widthPtr, int *heightPtr);
166 static void		GetMenuIndicatorGeometry(TkMenu *menuPtr,
167 			    TkMenuEntry *mePtr, Tk_Font tkfont,
168 			    const Tk_FontMetrics *fmPtr,
169 			    int *widthPtr, int *heightPtr);
170 static void		GetMenuSeparatorGeometry(TkMenu *menuPtr,
171 			    TkMenuEntry *mePtr, Tk_Font tkfont,
172 			    const Tk_FontMetrics *fmPtr,
173 			    int *widthPtr, int *heightPtr);
174 static void		GetTearoffEntryGeometry(TkMenu *menuPtr,
175 			    TkMenuEntry *mePtr, Tk_Font tkfont,
176 			    const Tk_FontMetrics *fmPtr, int *widthPtr,
177 			    int *heightPtr);
178 static int		GetNewID(TkMenuEntry *mePtr, WORD *menuIDPtr);
179 static int		TkWinMenuKeyObjCmd(ClientData clientData,
180 			    Tcl_Interp *interp, int objc,
181 			    Tcl_Obj *const objv[]);
182 static void		MenuSelectEvent(TkMenu *menuPtr);
183 static void		ReconfigureWindowsMenu(ClientData clientData);
184 static void		RecursivelyClearActiveMenu(TkMenu *menuPtr);
185 static void		SetDefaults(int firstTime);
186 static LRESULT CALLBACK	TkWinMenuProc(HWND hwnd, UINT message, WPARAM wParam,
187 			    LPARAM lParam);
188 static LRESULT CALLBACK	TkWinEmbeddedMenuProc(HWND hwnd, UINT message,
189 			    WPARAM wParam, LPARAM lParam);
190 
191 static inline void
ScheduleMenuReconfigure(TkMenu * menuPtr)192 ScheduleMenuReconfigure(
193     TkMenu *menuPtr)
194 {
195     if (!(menuPtr->menuFlags & MENU_RECONFIGURE_PENDING)) {
196 	menuPtr->menuFlags |= MENU_RECONFIGURE_PENDING;
197 	Tcl_DoWhenIdle(ReconfigureWindowsMenu, menuPtr);
198     }
199 }
200 
201 static inline void
CallPendingReconfigureImmediately(TkMenu * menuPtr)202 CallPendingReconfigureImmediately(
203     TkMenu *menuPtr)
204 {
205     if (menuPtr->menuFlags & MENU_RECONFIGURE_PENDING) {
206 	Tcl_CancelIdleCall(ReconfigureWindowsMenu, menuPtr);
207 	ReconfigureWindowsMenu(menuPtr);
208     }
209 }
210 
211 /*
212  *----------------------------------------------------------------------
213  *
214  * GetNewID --
215  *
216  *	Allocates a new menu id and marks it in use.
217  *
218  * Results:
219  *	Returns TCL_OK if succesful; TCL_ERROR if there are no more ids of the
220  *	appropriate type to allocate. menuIDPtr contains the new id if
221  *	succesful.
222  *
223  * Side effects:
224  *	An entry is created for the menu in the command hash table, and the
225  *	hash entry is stored in the appropriate field in the menu data
226  *	structure.
227  *
228  *----------------------------------------------------------------------
229  */
230 
231 static int
GetNewID(TkMenuEntry * mePtr,WORD * menuIDPtr)232 GetNewID(
233     TkMenuEntry *mePtr,		/* The menu we are working with. */
234     WORD *menuIDPtr)		/* The resulting id. */
235 {
236     ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
237 	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
238     WORD curID = tsdPtr->lastCommandID;
239 
240     while (1) {
241 	Tcl_HashEntry *commandEntryPtr;
242 	int isNew;
243 
244 	/*
245 	 * Try the next ID number, taking care to wrap rather than stray
246 	 * into the system menu IDs.  [Bug 3235256]
247 	 */
248 	if (++curID >= 0xF000) {
249 	    curID = 1;
250 	}
251 
252 	/* Return error when we've checked all IDs without success. */
253 	if (curID == tsdPtr->lastCommandID) {
254 	    return TCL_ERROR;
255 	}
256 
257 	commandEntryPtr = Tcl_CreateHashEntry(&tsdPtr->commandTable,
258 		INT2PTR(curID), &isNew);
259 	if (isNew) {
260 	    Tcl_SetHashValue(commandEntryPtr, mePtr);
261 	    *menuIDPtr = curID;
262 	    tsdPtr->lastCommandID = curID;
263 	    return TCL_OK;
264 	}
265     }
266 }
267 
268 /*
269  *----------------------------------------------------------------------
270  *
271  * FreeID --
272  *
273  *	Marks the itemID as free.
274  *
275  * Results:
276  *	None.
277  *
278  * Side effects:
279  *	The hash table entry for the ID is cleared.
280  *
281  *----------------------------------------------------------------------
282  */
283 
284 static void
FreeID(WORD commandID)285 FreeID(
286     WORD commandID)
287 {
288     ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
289 	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
290 
291     /*
292      * If the menuHWND is NULL, this table has been finalized already.
293      */
294 
295     if (tsdPtr->menuHWND != NULL) {
296 	Tcl_HashEntry *entryPtr = Tcl_FindHashEntry(&tsdPtr->commandTable,
297 		INT2PTR(commandID));
298 
299 	if (entryPtr != NULL) {
300 	    Tcl_DeleteHashEntry(entryPtr);
301 	}
302     }
303 }
304 
305 /*
306  *----------------------------------------------------------------------
307  *
308  * TkpNewMenu --
309  *
310  *	Gets a new blank menu. Only the platform specific options are filled
311  *	in.
312  *
313  * Results:
314  *	Standard TCL error.
315  *
316  * Side effects:
317  *	Allocates a Windows menu handle and places it in the platformData
318  *	field of the menuPtr.
319  *
320  *----------------------------------------------------------------------
321  */
322 
323 int
TkpNewMenu(TkMenu * menuPtr)324 TkpNewMenu(
325     TkMenu *menuPtr)		/* The common structure we are making the
326 				 * platform structure for. */
327 {
328     HMENU winMenuHdl;
329     Tcl_HashEntry *hashEntryPtr;
330     int newEntry;
331     ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
332 	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
333 
334     winMenuHdl = CreatePopupMenu();
335     if (winMenuHdl == NULL) {
336     	Tcl_SetObjResult(menuPtr->interp, Tcl_NewStringObj(
337 		"No more menus can be allocated.", -1));
338 	Tcl_SetErrorCode(menuPtr->interp, "TK", "MENU", "SYSTEM_RESOURCES", NULL);
339     	return TCL_ERROR;
340     }
341 
342     /*
343      * We hash all of the HMENU's so that we can get their menu ptrs back when
344      * dispatch messages.
345      */
346 
347     hashEntryPtr = Tcl_CreateHashEntry(&tsdPtr->winMenuTable,
348 	    (char *) winMenuHdl, &newEntry);
349     Tcl_SetHashValue(hashEntryPtr, menuPtr);
350 
351     menuPtr->platformData = (TkMenuPlatformData) winMenuHdl;
352     return TCL_OK;
353 }
354 
355 /*
356  *----------------------------------------------------------------------
357  *
358  * TkpDestroyMenu --
359  *
360  *	Destroys platform-specific menu structures.
361  *
362  * Results:
363  *	None.
364  *
365  * Side effects:
366  *	All platform-specific allocations are freed up.
367  *
368  *----------------------------------------------------------------------
369  */
370 
371 void
TkpDestroyMenu(TkMenu * menuPtr)372 TkpDestroyMenu(
373     TkMenu *menuPtr)		/* The common menu structure */
374 {
375     HMENU winMenuHdl = (HMENU) menuPtr->platformData;
376     const char *searchName;
377     ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
378 	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
379 
380     if (menuPtr->menuFlags & MENU_RECONFIGURE_PENDING) {
381 	Tcl_CancelIdleCall(ReconfigureWindowsMenu, menuPtr);
382     }
383 
384     if (winMenuHdl == NULL) {
385 	return;
386     }
387 
388     if (menuPtr->menuFlags & MENU_SYSTEM_MENU) {
389 	TkMenuEntry *searchEntryPtr;
390 	Tcl_HashTable *tablePtr = TkGetMenuHashTable(menuPtr->interp);
391 	char *menuName = (char *)Tcl_GetHashKey(tablePtr,
392 		menuPtr->menuRefPtr->hashEntryPtr);
393 
394 	/*
395 	 * Search for the menu in the menubar, if it is present, get the
396 	 * wrapper window associated with the toplevel and reset its
397 	 * system menu to the default menu.
398 	 */
399 
400 	for (searchEntryPtr = menuPtr->menuRefPtr->parentEntryPtr;
401 		searchEntryPtr != NULL;
402 		searchEntryPtr = searchEntryPtr->nextCascadePtr) {
403 	    searchName = Tcl_GetString(searchEntryPtr->namePtr);
404 	    if (strcmp(searchName, menuName) == 0) {
405 		Tk_Window parentTopLevelPtr = searchEntryPtr
406 			->menuPtr->parentTopLevelPtr;
407 
408 		if (parentTopLevelPtr != NULL) {
409 		    GetSystemMenu(
410 			    TkWinGetWrapperWindow(parentTopLevelPtr), TRUE);
411 		}
412 		break;
413 	    }
414 	}
415     } else {
416 	/*
417 	 * Remove the menu from the menu hash table, then destroy the handle.
418 	 * If the menuHWND is NULL, this table has been finalized already.
419 	 */
420 
421 	if (tsdPtr->menuHWND != NULL) {
422 	    Tcl_HashEntry *hashEntryPtr =
423 		Tcl_FindHashEntry(&tsdPtr->winMenuTable, winMenuHdl);
424 
425 	    if (hashEntryPtr != NULL) {
426 		Tcl_DeleteHashEntry(hashEntryPtr);
427 	    }
428 	}
429  	DestroyMenu(winMenuHdl);
430     }
431     menuPtr->platformData = NULL;
432 
433     if (menuPtr == tsdPtr->modalMenuPtr) {
434 	tsdPtr->modalMenuPtr = NULL;
435     }
436 }
437 
438 /*
439  *----------------------------------------------------------------------
440  *
441  * TkpDestroyMenuEntry --
442  *
443  *	Cleans up platform-specific menu entry items.
444  *
445  * Results:
446  *	None
447  *
448  * Side effects:
449  *	All platform-specific allocations are freed up.
450  *
451  *----------------------------------------------------------------------
452  */
453 
454 void
TkpDestroyMenuEntry(TkMenuEntry * mePtr)455 TkpDestroyMenuEntry(
456     TkMenuEntry *mePtr)		/* The entry to destroy */
457 {
458     TkMenu *menuPtr = mePtr->menuPtr;
459     HMENU winMenuHdl = (HMENU) menuPtr->platformData;
460 
461     if (NULL != winMenuHdl) {
462 	ScheduleMenuReconfigure(menuPtr);
463     }
464     FreeID((WORD) PTR2INT(mePtr->platformEntryData));
465     mePtr->platformEntryData = NULL;
466 }
467 
468 /*
469  *----------------------------------------------------------------------
470  *
471  * GetEntryText --
472  *
473  *	Given a menu entry, gives back the text that should go in it.
474  *	Separators should be done by the caller, as they have to be handled
475  *	specially. Allocates the memory with alloc. The caller should free the
476  *	memory.
477  *
478  * Results:
479  *	itemText points to the new text for the item.
480  *
481  * Side effects:
482  *	None.
483  *
484  *----------------------------------------------------------------------
485  */
486 
487 static char *
GetEntryText(TkMenu * menuPtr,TkMenuEntry * mePtr)488 GetEntryText(
489     TkMenu *menuPtr,		/* The menu considered. */
490     TkMenuEntry *mePtr)		/* A pointer to the menu entry. */
491 {
492     char *itemText;
493 
494     if (mePtr->type == TEAROFF_ENTRY) {
495 	itemText = (char *)ckalloc(sizeof("(Tear-off)"));
496 	strcpy(itemText, "(Tear-off)");
497     } else if (mePtr->imagePtr != NULL) {
498 	itemText = (char *)ckalloc(sizeof("(Image)"));
499 	strcpy(itemText, "(Image)");
500     } else if (mePtr->bitmapPtr != NULL) {
501 	itemText = (char *)ckalloc(sizeof("(Pixmap)"));
502 	strcpy(itemText, "(Pixmap)");
503     } else if (mePtr->labelPtr == NULL || mePtr->labelLength == 0) {
504 	itemText = (char *)ckalloc(sizeof("( )"));
505 	strcpy(itemText, "( )");
506     } else {
507 	int i;
508 	const char *label = (mePtr->labelPtr == NULL) ? ""
509 		: Tcl_GetString(mePtr->labelPtr);
510 	const char *accel = ((menuPtr->menuType == MENUBAR) || (mePtr->accelPtr == NULL)) ? ""
511 		: Tcl_GetString(mePtr->accelPtr);
512 	const char *p, *next;
513 	Tcl_DString itemString;
514 	Tcl_UniChar ch = 0;
515 
516 	/*
517 	 * We have to construct the string with an ampersand preceeding the
518 	 * underline character, and a tab seperating the text and the accel
519 	 * text. We have to be careful with ampersands in the string.
520 	 */
521 
522 	Tcl_DStringInit(&itemString);
523 
524 	for (p = label, i = 0; *p != '\0'; i++, p = next) {
525 	    if (i == mePtr->underline) {
526 		Tcl_DStringAppend(&itemString, "&", 1);
527 	    }
528 	    if (*p == '&') {
529 		Tcl_DStringAppend(&itemString, "&", 1);
530 	    }
531 	    next = p + Tcl_UtfToUniChar(p, &ch);
532 	    Tcl_DStringAppend(&itemString, p, (int) (next - p));
533 	}
534 	ch = 0;
535 	if (mePtr->accelLength > 0) {
536 	    Tcl_DStringAppend(&itemString, "\t", 1);
537 	    for (p = accel, i = 0; *p != '\0'; i++, p = next) {
538 		if (*p == '&') {
539 		    Tcl_DStringAppend(&itemString, "&", 1);
540 		}
541 		next = p + Tcl_UtfToUniChar(p, &ch);
542 		Tcl_DStringAppend(&itemString, p, (int) (next - p));
543 	    }
544 	}
545 
546 	itemText = (char *)ckalloc(Tcl_DStringLength(&itemString) + 1);
547 	strcpy(itemText, Tcl_DStringValue(&itemString));
548 	Tcl_DStringFree(&itemString);
549     }
550     return itemText;
551 }
552 
553 /*
554  *----------------------------------------------------------------------
555  *
556  * ReconfigureWindowsMenu --
557  *
558  *	Tears down and rebuilds the platform-specific part of this menu.
559  *
560  * Results:
561  *	None.
562  *
563  * Side effects:
564  *	Configuration information get set for mePtr; old resources get freed,
565  *	if any need it.
566  *
567  *----------------------------------------------------------------------
568  */
569 
570 static void
ReconfigureWindowsMenu(ClientData clientData)571 ReconfigureWindowsMenu(
572     ClientData clientData)	/* The menu we are rebuilding */
573 {
574     TkMenu *menuPtr = (TkMenu *)clientData;
575     TkMenuEntry *mePtr;
576     HMENU winMenuHdl = (HMENU) menuPtr->platformData;
577     char *itemText = NULL;
578     LPCWSTR lpNewItem;
579     UINT flags;
580     UINT itemID;
581     int i, count, systemMenu = 0, base;
582     Tcl_DString translatedText;
583 
584     if (NULL == winMenuHdl) {
585     	return;
586     }
587 
588     /*
589      * Reconstruct the entire menu. Takes care of nasty system menu and index
590      * problem.
591      */
592 
593     base = (menuPtr->menuFlags & MENU_SYSTEM_MENU) ? 7 : 0;
594     count = GetMenuItemCount(winMenuHdl);
595     for (i = base; i < count; i++) {
596 	RemoveMenu(winMenuHdl, base, MF_BYPOSITION);
597     }
598 
599     count = menuPtr->numEntries;
600     for (i = 0; i < count; i++) {
601 	mePtr = menuPtr->entries[i];
602 	lpNewItem = NULL;
603 	flags = MF_BYPOSITION;
604 	itemID = 0;
605 	Tcl_DStringInit(&translatedText);
606 
607 	if ((menuPtr->menuType == MENUBAR) && (mePtr->type == TEAROFF_ENTRY)) {
608 	    continue;
609 	}
610 
611 	itemText = GetEntryText(menuPtr, mePtr);
612 	if ((menuPtr->menuType == MENUBAR)
613 		|| (menuPtr->menuFlags & MENU_SYSTEM_MENU)) {
614 		Tcl_DStringInit(&translatedText);
615 		Tcl_UtfToWCharDString(itemText, -1, &translatedText);
616 	    lpNewItem = (LPCWSTR) Tcl_DStringValue(&translatedText);
617 	    flags |= MF_STRING;
618 	} else {
619 	    lpNewItem = (LPCWSTR) mePtr;
620 	    flags |= MF_OWNERDRAW;
621 	}
622 
623 	/*
624 	 * Set enabling and disabling correctly.
625 	 */
626 
627 	if (mePtr->state == ENTRY_DISABLED) {
628 	    flags |= MF_DISABLED | MF_GRAYED;
629 	}
630 
631 	/*
632 	 * Set the check mark for check entries and radio entries.
633 	 */
634 
635 	if (((mePtr->type == CHECK_BUTTON_ENTRY)
636 		|| (mePtr->type == RADIO_BUTTON_ENTRY))
637 		&& (mePtr->entryFlags & ENTRY_SELECTED)) {
638 	    flags |= MF_CHECKED;
639 	}
640 
641 	/*
642 	 * Set the SEPARATOR bit for separator entries. This bit is not used
643 	 * by our internal drawing functions, but it is used by the system
644 	 * when drawing the system menu (we do not draw the system menu
645 	 * ourselves). If this bit is not set, separator entries on the system
646 	 * menu will not be drawn correctly.
647 	 */
648 
649 	if (mePtr->type == SEPARATOR_ENTRY) {
650 	    flags |= MF_SEPARATOR;
651 	}
652 
653 	if (mePtr->columnBreak) {
654 	    flags |= MF_MENUBREAK;
655 	}
656 
657 	itemID = PTR2INT(mePtr->platformEntryData);
658 	if ((mePtr->type == CASCADE_ENTRY)
659 		&& (mePtr->childMenuRefPtr != NULL)
660 		&& (mePtr->childMenuRefPtr->menuPtr != NULL)) {
661 	    HMENU childMenuHdl = (HMENU) mePtr->childMenuRefPtr->menuPtr
662 		->platformData;
663 	    if (childMenuHdl != NULL) {
664 		/*
665 		 * Win32 draws the popup arrow in the wrong color for a
666 		 * disabled cascade menu, so do it by hand. Given it is
667 		 * disabled, there's no need for it to be connected to its
668 		 * child.
669 		 */
670 
671 		if (mePtr->state != ENTRY_DISABLED) {
672 		    flags |= MF_POPUP;
673 		    /*
674 		     * If the MF_POPUP flag is set, then the id is interpreted
675 		     * as the handle of a submenu.
676 		     */
677 		    itemID = PTR2INT(childMenuHdl);
678 		}
679 	    }
680 	    if ((menuPtr->menuType == MENUBAR)
681 		    && !(mePtr->childMenuRefPtr->menuPtr->menuFlags
682 			    & MENU_SYSTEM_MENU)) {
683 		Tcl_DString ds;
684 		TkMenuReferences *menuRefPtr;
685 		TkMenu *systemMenuPtr = mePtr->childMenuRefPtr->menuPtr;
686 
687 		Tcl_DStringInit(&ds);
688 		Tcl_DStringAppend(&ds,
689 			Tk_PathName(menuPtr->masterMenuPtr->tkwin), -1);
690 		Tcl_DStringAppend(&ds, ".system", 7);
691 
692 		menuRefPtr = TkFindMenuReferences(menuPtr->interp,
693 			Tcl_DStringValue(&ds));
694 
695 		Tcl_DStringFree(&ds);
696 
697 		if ((menuRefPtr != NULL)
698 			&& (menuRefPtr->menuPtr != NULL)
699 			&& (menuPtr->parentTopLevelPtr != NULL)
700 			&& (systemMenuPtr->masterMenuPtr
701 				== menuRefPtr->menuPtr)) {
702 		    HMENU systemMenuHdl = (HMENU) systemMenuPtr->platformData;
703 		    HWND wrapper = TkWinGetWrapperWindow(menuPtr
704 			    ->parentTopLevelPtr);
705 
706 		    if (wrapper != NULL) {
707 			DestroyMenu(systemMenuHdl);
708 			systemMenuHdl = GetSystemMenu(wrapper, FALSE);
709 			systemMenuPtr->menuFlags |= MENU_SYSTEM_MENU;
710 			systemMenuPtr->platformData =
711 				(TkMenuPlatformData) systemMenuHdl;
712 			ScheduleMenuReconfigure(systemMenuPtr);
713 		    }
714 		}
715 	    }
716 	    if (mePtr->childMenuRefPtr->menuPtr->menuFlags
717 		    & MENU_SYSTEM_MENU) {
718 		systemMenu++;
719 	    }
720 	}
721 	if (!systemMenu) {
722 	    InsertMenuW(winMenuHdl, 0xFFFFFFFF, flags, itemID, lpNewItem);
723 	}
724 	Tcl_DStringFree(&translatedText);
725 	if (itemText != NULL) {
726 	    ckfree(itemText);
727 	    itemText = NULL;
728 	}
729     }
730 
731 
732     if ((menuPtr->menuType == MENUBAR)
733 	    && (menuPtr->parentTopLevelPtr != NULL)) {
734 	HWND bar = TkWinGetWrapperWindow(menuPtr->parentTopLevelPtr);
735 
736 	if (bar) {
737 	    DrawMenuBar(bar);
738 	}
739     }
740 
741     menuPtr->menuFlags &= ~(MENU_RECONFIGURE_PENDING);
742 }
743 
744 /*
745  *----------------------------------------------------------------------
746  *
747  * TkpPostMenu --
748  *
749  *	Posts a menu on the screen so that the top left corner of the
750  *      specified entry is located at the point (x, y) in screen coordinates.
751  *      If the entry parameter is negative, the upper left corner of the
752  *      menu itself is placed at the point.
753  *
754  * Results:
755  *	None.
756  *
757  * Side effects:
758  *	The menu is posted and handled.
759  *
760  *----------------------------------------------------------------------
761  */
762 
763 int
TkpPostMenu(Tcl_Interp * dummy,TkMenu * menuPtr,int x,int y,int index)764 TkpPostMenu(
765     Tcl_Interp *dummy,
766     TkMenu *menuPtr,
767     int x, int y, int index)
768 {
769     HMENU winMenuHdl = (HMENU) menuPtr->platformData;
770     int result, flags;
771     RECT noGoawayRect;
772     POINT point;
773     Tk_Window parentWindow = Tk_Parent(menuPtr->tkwin);
774     int oldServiceMode = Tcl_GetServiceMode();
775     ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
776 	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
777     (void)dummy;
778 
779     tsdPtr->inPostMenu++;
780     CallPendingReconfigureImmediately(menuPtr);
781 
782     result = TkPreprocessMenu(menuPtr);
783     if (result != TCL_OK) {
784 	tsdPtr->inPostMenu--;
785 	return result;
786     }
787 
788     if (index >= menuPtr->numEntries) {
789 	index = menuPtr->numEntries - 1;
790     }
791     if (index >= 0) {
792 	y -= menuPtr->entries[index]->y;
793     }
794 
795     /*
796      * The post commands could have deleted the menu, which means
797      * we are dead and should go away.
798      */
799 
800     if (menuPtr->tkwin == NULL) {
801 	tsdPtr->inPostMenu--;
802     	return TCL_OK;
803     }
804 
805     if (NULL == parentWindow) {
806 	noGoawayRect.top = y - 50;
807 	noGoawayRect.bottom = y + 50;
808 	noGoawayRect.left = x - 50;
809 	noGoawayRect.right = x + 50;
810     } else {
811 	int left, top;
812 	Tk_GetRootCoords(parentWindow, &left, &top);
813 	noGoawayRect.left = left;
814 	noGoawayRect.top = top;
815 	noGoawayRect.bottom = noGoawayRect.top + Tk_Height(parentWindow);
816 	noGoawayRect.right = noGoawayRect.left + Tk_Width(parentWindow);
817     }
818 
819     Tcl_SetServiceMode(TCL_SERVICE_NONE);
820 
821     /*
822      * Make an assumption here. If the right button is down,
823      * then we want to track it. Otherwise, track the left mouse button.
824      */
825 
826     flags = TPM_LEFTALIGN;
827     if (GetSystemMetrics(SM_SWAPBUTTON)) {
828 	if (GetAsyncKeyState(VK_LBUTTON) < 0) {
829 	    flags |= TPM_RIGHTBUTTON;
830 	} else {
831 	    flags |= TPM_LEFTBUTTON;
832 	}
833     } else {
834 	if (GetAsyncKeyState(VK_RBUTTON) < 0) {
835 	    flags |= TPM_RIGHTBUTTON;
836 	} else {
837 	    flags |= TPM_LEFTBUTTON;
838 	}
839     }
840 
841     TrackPopupMenu(winMenuHdl, flags, x, y, 0,
842 	    tsdPtr->menuHWND, &noGoawayRect);
843     Tcl_SetServiceMode(oldServiceMode);
844 
845     GetCursorPos(&point);
846     Tk_PointerEvent(NULL, point.x, point.y);
847 
848     if (tsdPtr->inPostMenu) {
849 	tsdPtr->inPostMenu = 0;
850     }
851     return TCL_OK;
852 }
853 
854 /*
855  *----------------------------------------------------------------------
856  *
857  * TkpPostTearoffMenu --
858  *
859  *	Posts a tearoff menu on the screen so that the top left corner of the
860  *      specified entry is located at the point (x, y) in screen coordinates.
861  *      If the index parameter is negative, the upper left corner of the menu
862  *      itself is placed at the point.  Adjusts the menu's position so that it
863  *      fits on the screen, and maps and raises the menu.
864  *
865  * Results:
866  *	Returns a standard Tcl Error.
867  *
868  * Side effects:
869  *	The menu is posted.
870  *
871  *----------------------------------------------------------------------
872  */
873 
874 int
TkpPostTearoffMenu(Tcl_Interp * dummy,TkMenu * menuPtr,int x,int y,int index)875 TkpPostTearoffMenu(
876     Tcl_Interp *dummy,		/* The interpreter of the menu */
877     TkMenu *menuPtr,		/* The menu we are posting */
878     int x, int y, int index)	/* The root X,Y coordinates where we are
879 				 * posting */
880 {
881     int vRootX, vRootY, vRootWidth, vRootHeight;
882     int result;
883     (void)dummy;
884 
885     if (index >= menuPtr->numEntries) {
886 	index = menuPtr->numEntries - 1;
887     }
888     if (index >= 0) {
889 	y -= menuPtr->entries[index]->y;
890     }
891 
892     TkActivateMenuEntry(menuPtr, -1);
893     TkRecomputeMenu(menuPtr);
894     result = TkPostCommand(menuPtr);
895     if (result != TCL_OK) {
896     	return result;
897     }
898 
899     /*
900      * The post commands could have deleted the menu, which means we are dead
901      * and should go away.
902      */
903 
904     if (menuPtr->tkwin == NULL) {
905     	return TCL_OK;
906     }
907 
908     /*
909      * Adjust the position of the menu if necessary to keep it visible on the
910      * screen. There are two special tricks to make this work right:
911      *
912      * 1. If a virtual root window manager is being used then the coordinates
913      *    are in the virtual root window of menuPtr's parent; since the menu
914      *    uses override-redirect mode it will be in the *real* root window for
915      *    the screen, so we have to map the coordinates from the virtual root
916      *    (if any) to the real root. Can't get the virtual root from the menu
917      *    itself (it will never be seen by the wm) so use its parent instead
918      *    (it would be better to have an an option that names a window to use
919      *    for this...).
920      * 2. The menu may not have been mapped yet, so its current size might be
921      *    the default 1x1. To compute how much space it needs, use its
922      *    requested size, not its actual size.
923      */
924 
925     Tk_GetVRootGeometry(Tk_Parent(menuPtr->tkwin), &vRootX, &vRootY,
926 	&vRootWidth, &vRootHeight);
927     vRootWidth -= Tk_ReqWidth(menuPtr->tkwin);
928     if (x > vRootX + vRootWidth) {
929 	x = vRootX + vRootWidth;
930     }
931     if (x < vRootX) {
932 	x = vRootX;
933     }
934     vRootHeight -= Tk_ReqHeight(menuPtr->tkwin);
935     if (y > vRootY + vRootHeight) {
936 	y = vRootY + vRootHeight;
937     }
938     if (y < vRootY) {
939 	y = vRootY;
940     }
941     Tk_MoveToplevelWindow(menuPtr->tkwin, x, y);
942     if (!Tk_IsMapped(menuPtr->tkwin)) {
943 	Tk_MapWindow(menuPtr->tkwin);
944     }
945     TkWmRestackToplevel((TkWindow *) menuPtr->tkwin, Above, NULL);
946     return TCL_OK;
947 }
948 
949 /*
950  *----------------------------------------------------------------------
951  *
952  * TkpMenuNewEntry --
953  *
954  *	Adds a pointer to a new menu entry structure with the platform-
955  *	specific fields filled in.
956  *
957  * Results:
958  *	Standard TCL error.
959  *
960  * Side effects:
961  *	A new command ID is allocated and stored in the platformEntryData
962  *	field of mePtr.
963  *
964  *----------------------------------------------------------------------
965  */
966 
967 int
TkpMenuNewEntry(TkMenuEntry * mePtr)968 TkpMenuNewEntry(
969     TkMenuEntry *mePtr)
970 {
971     WORD commandID;
972     TkMenu *menuPtr = mePtr->menuPtr;
973 
974     if (GetNewID(mePtr, &commandID) != TCL_OK) {
975     	return TCL_ERROR;
976     }
977     ScheduleMenuReconfigure(menuPtr);
978     mePtr->platformEntryData = (TkMenuPlatformEntryData) INT2PTR(commandID);
979 
980     return TCL_OK;
981 }
982 
983 /*
984  *----------------------------------------------------------------------
985  *
986  * TkWinMenuProc --
987  *
988  *	The window proc for the dummy window we put popups in. This allows
989  *	is to post a popup whether or not we know what the parent window
990  *	is.
991  *
992  * Results:
993  *	Returns whatever is appropriate for the message in question.
994  *
995  * Side effects:
996  *	Normal side-effect for windows messages.
997  *
998  *----------------------------------------------------------------------
999  */
1000 
1001 static LRESULT CALLBACK
TkWinMenuProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)1002 TkWinMenuProc(
1003     HWND hwnd,
1004     UINT message,
1005     WPARAM wParam,
1006     LPARAM lParam)
1007 {
1008     LRESULT lResult;
1009 
1010     if (!TkWinHandleMenuEvent(&hwnd, &message, &wParam, &lParam, &lResult)) {
1011 	lResult = DefWindowProcW(hwnd, message, wParam, lParam);
1012     }
1013     return lResult;
1014 }
1015 
1016 /*
1017  *----------------------------------------------------------------------
1018  *
1019  * UpdateEmbeddedMenu --
1020  *
1021  *	This function is used as work-around for updating the pull-down window
1022  *	of an embedded menu which may show as a blank popup window.
1023  *
1024  * Results:
1025  *	Invalidate the client area of the embedded pull-down menu and
1026  *	redraw it.
1027  *
1028  * Side effects:
1029  *	Redraw the embedded menu window.
1030  *
1031  *----------------------------------------------------------------------
1032  */
1033 
1034 static void
UpdateEmbeddedMenu(ClientData clientData)1035 UpdateEmbeddedMenu(
1036     ClientData clientData)
1037 {
1038     RECT rc;
1039     HWND hMenuWnd = (HWND)clientData;
1040 
1041     GetClientRect(hMenuWnd, &rc);
1042     InvalidateRect(hMenuWnd, &rc, FALSE);
1043     UpdateWindow(hMenuWnd);
1044 }
1045 
1046 /*
1047  *----------------------------------------------------------------------
1048  *
1049  * TkWinEmbeddedMenuProc --
1050  *
1051  *	This window proc is for the embedded menu windows. It provides
1052  *	message services to an embedded menu in a different process.
1053  *
1054  * Results:
1055  *	Returns 1 if the message has been handled or 0 otherwise.
1056  *
1057  * Side effects:
1058  *	None.
1059  *
1060  *----------------------------------------------------------------------
1061  */
1062 
1063 static LRESULT CALLBACK
TkWinEmbeddedMenuProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)1064 TkWinEmbeddedMenuProc(
1065     HWND hwnd,
1066     UINT message,
1067     WPARAM wParam,
1068     LPARAM lParam)
1069 {
1070     static int nIdles = 0;
1071     LRESULT lResult = 1;
1072     ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
1073 	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
1074 
1075     switch(message) {
1076     case WM_ENTERIDLE:
1077 	if ((wParam == MSGF_MENU) && (nIdles < 1)
1078 		&& (hwnd == tsdPtr->embeddedMenuHWND)) {
1079 	    Tcl_CreateTimerHandler(200, UpdateEmbeddedMenu,
1080 		    (ClientData) lParam);
1081 	    nIdles++;
1082 	}
1083 	break;
1084 
1085     case WM_INITMENUPOPUP:
1086 	nIdles = 0;
1087 	break;
1088 
1089     case WM_SETTINGCHANGE:
1090 	if (wParam == SPI_SETNONCLIENTMETRICS
1091 		|| wParam == SPI_SETKEYBOARDCUES) {
1092 	    SetDefaults(0);
1093 	}
1094 	break;
1095 
1096     case WM_INITMENU:
1097     case WM_SYSCOMMAND:
1098     case WM_COMMAND:
1099     case WM_MENUCHAR:
1100     case WM_MEASUREITEM:
1101     case WM_DRAWITEM:
1102     case WM_MENUSELECT:
1103 	lResult = TkWinHandleMenuEvent(&hwnd, &message, &wParam, &lParam,
1104 		&lResult);
1105 	if (lResult || (GetCapture() != hwnd)) {
1106 	    break;
1107 	}
1108 	/* FALLTHRU */
1109     default:
1110 	lResult = DefWindowProcW(hwnd, message, wParam, lParam);
1111 	break;
1112     }
1113     return lResult;
1114 }
1115 
1116 /*
1117  *----------------------------------------------------------------------
1118  *
1119  * TkWinHandleMenuEvent --
1120  *
1121  *	Filters out menu messages from messages passed to a top-level. Will
1122  *	respond appropriately to WM_COMMAND, WM_MENUSELECT, WM_MEASUREITEM,
1123  *	WM_DRAWITEM
1124  *
1125  * Result:
1126  *	Returns 1 if this handled the message; 0 if it did not.
1127  *
1128  * Side effects:
1129  *	All of the parameters may be modified so that the caller can think it
1130  *	is getting a different message. plResult points to the result that
1131  *	should be returned to windows from this message.
1132  *
1133  *----------------------------------------------------------------------
1134  */
1135 
1136 int
TkWinHandleMenuEvent(HWND * phwnd,UINT * pMessage,WPARAM * pwParam,LPARAM * plParam,LRESULT * plResult)1137 TkWinHandleMenuEvent(
1138     HWND *phwnd,
1139     UINT *pMessage,
1140     WPARAM *pwParam,
1141     LPARAM *plParam,
1142     LRESULT *plResult)
1143 {
1144     Tcl_HashEntry *hashEntryPtr;
1145     int returnResult = 0;
1146     TkMenu *menuPtr;
1147     TkMenuEntry *mePtr;
1148     ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
1149 	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
1150     (void)phwnd;
1151 
1152     switch (*pMessage) {
1153     case WM_UNINITMENUPOPUP:
1154 	hashEntryPtr = Tcl_FindHashEntry(&tsdPtr->winMenuTable,
1155 		*pwParam);
1156 	if (hashEntryPtr != NULL) {
1157 	    menuPtr = (TkMenu *)Tcl_GetHashValue(hashEntryPtr);
1158 	    if ((menuPtr->menuRefPtr != NULL)
1159 		    && (menuPtr->menuRefPtr->parentEntryPtr != NULL)) {
1160 		TkPostSubmenu(menuPtr->interp,
1161 			menuPtr->menuRefPtr->parentEntryPtr->menuPtr, NULL);
1162 	    }
1163 	}
1164 	break;
1165 
1166     case WM_INITMENU:
1167 	TkMenuInit();
1168 	hashEntryPtr = Tcl_FindHashEntry(&tsdPtr->winMenuTable,
1169 		*pwParam);
1170 	if (hashEntryPtr != NULL) {
1171 	    tsdPtr->oldServiceMode = Tcl_SetServiceMode(TCL_SERVICE_ALL);
1172 	    menuPtr = (TkMenu *)Tcl_GetHashValue(hashEntryPtr);
1173 	    tsdPtr->modalMenuPtr = menuPtr;
1174 	    CallPendingReconfigureImmediately(menuPtr);
1175 	    RecursivelyClearActiveMenu(menuPtr);
1176 	    if (!tsdPtr->inPostMenu) {
1177 		Tcl_Interp *interp = menuPtr->interp;
1178 		int code;
1179 
1180 		Tcl_Preserve(interp);
1181 		code = TkPreprocessMenu(menuPtr);
1182 		if ((code != TCL_OK) && (code != TCL_CONTINUE)
1183 			&& (code != TCL_BREAK)) {
1184 		    Tcl_AddErrorInfo(interp, "\n    (menu preprocess)");
1185 		    Tcl_BackgroundException(interp, code);
1186 		}
1187 		Tcl_Release(interp);
1188 	    }
1189 	    TkActivateMenuEntry(menuPtr, -1);
1190 	    *plResult = 0;
1191 	    returnResult = 1;
1192 	} else {
1193 	    tsdPtr->modalMenuPtr = NULL;
1194 	}
1195 	break;
1196 
1197     case WM_SYSCOMMAND:
1198     case WM_COMMAND:
1199 	TkMenuInit();
1200 	if (HIWORD(*pwParam) != 0) {
1201 	    break;
1202 	}
1203 	hashEntryPtr = Tcl_FindHashEntry(&tsdPtr->commandTable,
1204 		INT2PTR(LOWORD(*pwParam)));
1205 	if (hashEntryPtr == NULL) {
1206 	    break;
1207 	}
1208 	mePtr = (TkMenuEntry *)Tcl_GetHashValue(hashEntryPtr);
1209 	if (mePtr != NULL) {
1210 	    TkMenuReferences *menuRefPtr;
1211 	    TkMenuEntry *parentEntryPtr;
1212 	    Tcl_Interp *interp;
1213 	    int code;
1214 
1215 	    /*
1216 	     * We have to set the parent of this menu to be active if this is
1217 	     * a submenu so that tearoffs will get the correct title.
1218 	     */
1219 
1220 	    menuPtr = mePtr->menuPtr;
1221 	    menuRefPtr = TkFindMenuReferences(menuPtr->interp,
1222 		    Tk_PathName(menuPtr->tkwin));
1223 	    if ((menuRefPtr != NULL) && (menuRefPtr->parentEntryPtr != NULL)) {
1224 		for (parentEntryPtr = menuRefPtr->parentEntryPtr ; ;
1225 			parentEntryPtr = parentEntryPtr->nextCascadePtr) {
1226 		    const char *name = Tcl_GetString(parentEntryPtr->namePtr);
1227 
1228 		    if (strcmp(name, Tk_PathName(menuPtr->tkwin)) == 0) {
1229 			break;
1230 		    }
1231 		}
1232 		if (parentEntryPtr->menuPtr->entries[parentEntryPtr->index]
1233 			->state != ENTRY_DISABLED) {
1234 		    TkActivateMenuEntry(parentEntryPtr->menuPtr,
1235 			    parentEntryPtr->index);
1236 		}
1237 	    }
1238 
1239 	    interp = menuPtr->interp;
1240 	    Tcl_Preserve(interp);
1241 	    code = TkInvokeMenu(interp, menuPtr, mePtr->index);
1242 	    if (code != TCL_OK && code != TCL_CONTINUE && code != TCL_BREAK) {
1243 		Tcl_AddErrorInfo(interp, "\n    (menu invoke)");
1244 		Tcl_BackgroundException(interp, code);
1245 	    }
1246 	    Tcl_Release(interp);
1247 	    *plResult = 0;
1248 	    returnResult = 1;
1249 	}
1250 	break;
1251 
1252     case WM_MENUCHAR: {
1253 	hashEntryPtr = Tcl_FindHashEntry(&tsdPtr->winMenuTable,
1254 		*plParam);
1255 	if (hashEntryPtr != NULL) {
1256 	    int i, len, underline;
1257 	    Tcl_Obj *labelPtr;
1258 	    WCHAR *wlabel;
1259 	    int menuChar;
1260 	    Tcl_DString ds;
1261 
1262 	    *plResult = 0;
1263 	    menuPtr = (TkMenu *)Tcl_GetHashValue(hashEntryPtr);
1264 	    /*
1265 	     * Assume we have something directly convertable to Tcl_UniChar.
1266 	     * True at least for wide systems.
1267 	     */
1268 	    menuChar = Tcl_UniCharToUpper(LOWORD(*pwParam));
1269 
1270 	    Tcl_DStringInit(&ds);
1271 	    for (i = 0; i < menuPtr->numEntries; i++) {
1272 		underline = menuPtr->entries[i]->underline;
1273 		labelPtr = menuPtr->entries[i]->labelPtr;
1274 		if ((underline >= 0) && (labelPtr != NULL)) {
1275 		    /*
1276 		     * Ensure we don't exceed the label length, then check
1277 		     */
1278 		    const char *src = Tcl_GetStringFromObj(labelPtr, &len);
1279 
1280 		    Tcl_DStringFree(&ds);
1281 		    Tcl_DStringInit(&ds);
1282 		    wlabel = Tcl_UtfToWCharDString(src, len, &ds);
1283 		    if ((underline + 1 < len + 1) && (menuChar ==
1284 				Tcl_UniCharToUpper(wlabel[underline]))) {
1285 			*plResult = (2 << 16) | i;
1286 			returnResult = 1;
1287 			break;
1288 		    }
1289 		}
1290 	    }
1291 	    Tcl_DStringFree(&ds);
1292 	}
1293 	break;
1294     }
1295 
1296     case WM_MEASUREITEM: {
1297 	LPMEASUREITEMSTRUCT itemPtr = (LPMEASUREITEMSTRUCT) *plParam;
1298 
1299 	if (itemPtr != NULL && tsdPtr->modalMenuPtr != NULL) {
1300 	    mePtr = (TkMenuEntry *) itemPtr->itemData;
1301 	    menuPtr = mePtr->menuPtr;
1302 
1303 	    TkRecomputeMenu(menuPtr);
1304 	    itemPtr->itemHeight = mePtr->height;
1305 	    itemPtr->itemWidth = mePtr->width;
1306 	    if (mePtr->hideMargin) {
1307 		itemPtr->itemWidth += 2 - indicatorDimensions[1];
1308 	    } else {
1309 		int activeBorderWidth;
1310 
1311 		Tk_GetPixelsFromObj(menuPtr->interp, menuPtr->tkwin,
1312 			menuPtr->activeBorderWidthPtr, &activeBorderWidth);
1313 		itemPtr->itemWidth += 2 * activeBorderWidth;
1314 	    }
1315 	    *plResult = 1;
1316 	    returnResult = 1;
1317 	}
1318 	break;
1319     }
1320 
1321     case WM_DRAWITEM: {
1322 	TkWinDrawable *twdPtr;
1323 	LPDRAWITEMSTRUCT itemPtr = (LPDRAWITEMSTRUCT) *plParam;
1324 	Tk_FontMetrics fontMetrics;
1325 	int drawingParameters = 0;
1326 
1327 	if (itemPtr != NULL && tsdPtr->modalMenuPtr != NULL) {
1328 	    Tk_Font tkfont;
1329 
1330 	    if (itemPtr->itemState & ODS_NOACCEL && !showMenuAccelerators) {
1331 		drawingParameters |= DRAW_MENU_ENTRY_NOUNDERLINE;
1332 	    }
1333 	    mePtr = (TkMenuEntry *) itemPtr->itemData;
1334 	    menuPtr = mePtr->menuPtr;
1335 	    twdPtr = (TkWinDrawable *)ckalloc(sizeof(TkWinDrawable));
1336 	    twdPtr->type = TWD_WINDC;
1337 	    twdPtr->winDC.hdc = itemPtr->hDC;
1338 
1339 	    if (mePtr->state != ENTRY_DISABLED) {
1340 		if (itemPtr->itemState & ODS_SELECTED) {
1341 		    TkActivateMenuEntry(menuPtr, mePtr->index);
1342 		} else {
1343 		    TkActivateMenuEntry(menuPtr, -1);
1344 		}
1345 	    } else {
1346 		/*
1347 		 * On windows, menu entries should highlight even if they are
1348 		 * disabled. (I know this seems dumb, but it is the way native
1349 		 * windows menus works so we ought to mimic it.) The
1350 		 * ENTRY_PLATFORM_FLAG1 flag will indicate that the entry
1351 		 * should be highlighted even though it is disabled.
1352 		 */
1353 
1354 		if (itemPtr->itemState & ODS_SELECTED) {
1355 		    mePtr->entryFlags |= ENTRY_PLATFORM_FLAG1;
1356 		} else {
1357 		    mePtr->entryFlags &= ~ENTRY_PLATFORM_FLAG1;
1358 		}
1359 
1360 		/*
1361 		 * Also, set the DRAW_MENU_ENTRY_ARROW flag for a disabled
1362 		 * cascade menu since we need to draw the arrow ourselves.
1363 		 */
1364 
1365 		if (mePtr->type == CASCADE_ENTRY) {
1366 		    drawingParameters |= DRAW_MENU_ENTRY_ARROW;
1367 		}
1368 	    }
1369 
1370 	    tkfont = Tk_GetFontFromObj(menuPtr->tkwin, menuPtr->fontPtr);
1371 	    Tk_GetFontMetrics(tkfont, &fontMetrics);
1372 	    TkpDrawMenuEntry(mePtr, (Drawable) twdPtr, tkfont, &fontMetrics,
1373 		    itemPtr->rcItem.left, itemPtr->rcItem.top,
1374 		    itemPtr->rcItem.right - itemPtr->rcItem.left,
1375 		    itemPtr->rcItem.bottom - itemPtr->rcItem.top,
1376 		    0, drawingParameters);
1377 
1378 	    ckfree(twdPtr);
1379 	}
1380 	*plResult = 1;
1381 	returnResult = 1;
1382 	break;
1383     }
1384 
1385     case WM_MENUSELECT: {
1386 	UINT flags = HIWORD(*pwParam);
1387 
1388 	TkMenuInit();
1389 
1390 	if ((flags == 0xFFFF) && (*plParam == 0)) {
1391 	    if (tsdPtr->modalMenuPtr != NULL) {
1392 		Tcl_SetServiceMode(tsdPtr->oldServiceMode);
1393 		RecursivelyClearActiveMenu(tsdPtr->modalMenuPtr);
1394 	    }
1395 	} else {
1396 	    menuPtr = NULL;
1397 	    if (*plParam != 0) {
1398 		hashEntryPtr = Tcl_FindHashEntry(&tsdPtr->winMenuTable,
1399 			*plParam);
1400 		if (hashEntryPtr != NULL) {
1401 		    menuPtr = (TkMenu *)Tcl_GetHashValue(hashEntryPtr);
1402 		}
1403 	    }
1404 
1405 	    if (menuPtr != NULL) {
1406 		long entryIndex = LOWORD(*pwParam);
1407 
1408                 if ((menuPtr->menuType == MENUBAR) && menuPtr->tearoff) {
1409                     /*
1410                      * Windows passes the entry index starting at 0 for
1411                      * the first menu entry. However this entry #0 is the
1412                      * tearoff entry for Tk (the menu has -tearoff 1),
1413                      * which is ignored for MENUBAR menues on Windows.
1414                      */
1415 
1416                     entryIndex++;
1417                 }
1418                 mePtr = NULL;
1419 		if (flags != 0xFFFF) {
1420 		    if ((flags&MF_POPUP) && (entryIndex<menuPtr->numEntries)) {
1421 			mePtr = menuPtr->entries[entryIndex];
1422 		    } else {
1423 			hashEntryPtr = Tcl_FindHashEntry(&tsdPtr->commandTable,
1424 				INT2PTR(entryIndex));
1425 			if (hashEntryPtr != NULL) {
1426 			    mePtr = (TkMenuEntry *)Tcl_GetHashValue(hashEntryPtr);
1427 			}
1428 		    }
1429 		}
1430 
1431 		if ((mePtr == NULL) || (mePtr->state == ENTRY_DISABLED)) {
1432 		    TkActivateMenuEntry(menuPtr, -1);
1433 		} else {
1434 		    if (mePtr->index >= menuPtr->numEntries) {
1435 			Tcl_Panic("Trying to activate an entry which doesn't exist");
1436 		    }
1437 		    TkActivateMenuEntry(menuPtr, mePtr->index);
1438 		}
1439 		MenuSelectEvent(menuPtr);
1440 		Tcl_ServiceAll();
1441 		*plResult = 0;
1442 		returnResult = 1;
1443 	    }
1444 	}
1445 	break;
1446     }
1447     }
1448     return returnResult;
1449 }
1450 
1451 /*
1452  *----------------------------------------------------------------------
1453  *
1454  * RecursivelyClearActiveMenu --
1455  *
1456  *	Recursively clears the active entry in the menu's cascade hierarchy.
1457  *
1458  * Results:
1459  *	None.
1460  *
1461  * Side effects:
1462  *	Generates <<MenuSelect>> virtual events.
1463  *
1464  *----------------------------------------------------------------------
1465  */
1466 
1467 void
RecursivelyClearActiveMenu(TkMenu * menuPtr)1468 RecursivelyClearActiveMenu(
1469     TkMenu *menuPtr)		/* The menu to reset. */
1470 {
1471     int i;
1472     TkMenuEntry *mePtr;
1473 
1474     TkActivateMenuEntry(menuPtr, -1);
1475     MenuSelectEvent(menuPtr);
1476     for (i = 0; i < menuPtr->numEntries; i++) {
1477     	mePtr = menuPtr->entries[i];
1478 	if (mePtr->state == ENTRY_ACTIVE) {
1479 	    mePtr->state = ENTRY_NORMAL;
1480 	}
1481 	mePtr->entryFlags &= ~ENTRY_PLATFORM_FLAG1;
1482     	if (mePtr->type == CASCADE_ENTRY) {
1483     	    if ((mePtr->childMenuRefPtr != NULL)
1484     	    	    && (mePtr->childMenuRefPtr->menuPtr != NULL)) {
1485     	    	RecursivelyClearActiveMenu(mePtr->childMenuRefPtr->menuPtr);
1486     	    }
1487     	}
1488     }
1489 }
1490 
1491 /*
1492  *----------------------------------------------------------------------
1493  *
1494  * TkpSetWindowMenuBar --
1495  *
1496  *	Associates a given menu with a window.
1497  *
1498  * Results:
1499  *	None.
1500  *
1501  * Side effects:
1502  *	On Windows and UNIX, associates the platform menu with the
1503  *	platform window.
1504  *
1505  *----------------------------------------------------------------------
1506  */
1507 
1508 void
TkpSetWindowMenuBar(Tk_Window tkwin,TkMenu * menuPtr)1509 TkpSetWindowMenuBar(
1510     Tk_Window tkwin,		/* The window we are putting the menubar
1511 				 * into.*/
1512     TkMenu *menuPtr)		/* The menu we are inserting */
1513 {
1514     HMENU winMenuHdl;
1515     ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
1516 	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
1517 
1518     if (menuPtr != NULL) {
1519 	Tcl_HashEntry *hashEntryPtr;
1520 	int newEntry;
1521 
1522 	winMenuHdl = (HMENU) menuPtr->platformData;
1523 	hashEntryPtr = Tcl_FindHashEntry(&tsdPtr->winMenuTable,
1524 		winMenuHdl);
1525 	Tcl_DeleteHashEntry(hashEntryPtr);
1526 	DestroyMenu(winMenuHdl);
1527 	winMenuHdl = CreateMenu();
1528 	hashEntryPtr = Tcl_CreateHashEntry(&tsdPtr->winMenuTable,
1529 		(char *) winMenuHdl, &newEntry);
1530 	Tcl_SetHashValue(hashEntryPtr, menuPtr);
1531 	menuPtr->platformData = (TkMenuPlatformData) winMenuHdl;
1532 	TkWinSetMenu(tkwin, winMenuHdl);
1533 	ScheduleMenuReconfigure(menuPtr);
1534     } else {
1535 	TkWinSetMenu(tkwin, NULL);
1536     }
1537 }
1538 
1539 /*
1540  *----------------------------------------------------------------------
1541  *
1542  * TkpSetMainMenubar --
1543  *
1544  *	Puts the menu associated with a window into the menubar. Should only
1545  *	be called when the window is in front.
1546  *
1547  * Results:
1548  *	None.
1549  *
1550  * Side effects:
1551  *	The menubar is changed.
1552  *
1553  *----------------------------------------------------------------------
1554  */
1555 
1556 void
TkpSetMainMenubar(Tcl_Interp * interp,Tk_Window tkwin,const char * menuName)1557 TkpSetMainMenubar(
1558     Tcl_Interp *interp,		/* The interpreter of the application */
1559     Tk_Window tkwin,		/* The frame we are setting up */
1560     const char *menuName)	/* The name of the menu to put in front. If
1561     				 * NULL, use the default menu bar. */
1562 {
1563     (void)interp;
1564     (void)tkwin;
1565     (void)menuName;
1566 
1567     /*
1568      * Nothing to do.
1569      */
1570 }
1571 
1572 /*
1573  *----------------------------------------------------------------------
1574  *
1575  * GetMenuIndicatorGeometry --
1576  *
1577  *	Gets the width and height of the indicator area of a menu.
1578  *
1579  * Results:
1580  *	widthPtr and heightPtr are set.
1581  *
1582  * Side effects:
1583  *	None.
1584  *
1585  *----------------------------------------------------------------------
1586  */
1587 
1588 void
GetMenuIndicatorGeometry(TkMenu * menuPtr,TkMenuEntry * mePtr,Tk_Font tkfont,const Tk_FontMetrics * fmPtr,int * widthPtr,int * heightPtr)1589 GetMenuIndicatorGeometry(
1590     TkMenu *menuPtr,		/* The menu we are measuring */
1591     TkMenuEntry *mePtr,		/* The entry we are measuring */
1592     Tk_Font tkfont,		/* Precalculated font */
1593     const Tk_FontMetrics *fmPtr,/* Precalculated font metrics */
1594     int *widthPtr,		/* The resulting width */
1595     int *heightPtr)		/* The resulting height */
1596 {
1597     (void)menuPtr;
1598     (void)tkfont;
1599     (void)fmPtr;
1600 
1601     *heightPtr = indicatorDimensions[0];
1602     if (mePtr->hideMargin) {
1603 	*widthPtr = 0;
1604     } else {
1605 	int borderWidth;
1606 
1607 	Tk_GetPixelsFromObj(menuPtr->interp, menuPtr->tkwin,
1608 		menuPtr->borderWidthPtr, &borderWidth);
1609 	*widthPtr = indicatorDimensions[1] - borderWidth;
1610 
1611         /*
1612          * Quite dubious about the above (why would borderWidth play a role?)
1613          * and about how indicatorDimensions[1] is obtained in SetDefaults().
1614          * At least don't let the result be negative!
1615          */
1616         if (*widthPtr < 0) {
1617             *widthPtr = 0;
1618         }
1619     }
1620 }
1621 
1622 /*
1623  *----------------------------------------------------------------------
1624  *
1625  * GetMenuAccelGeometry --
1626  *
1627  *	Gets the width and height of the indicator area of a menu.
1628  *
1629  * Results:
1630  *	widthPtr and heightPtr are set.
1631  *
1632  * Side effects:
1633  *	None.
1634  *
1635  *----------------------------------------------------------------------
1636  */
1637 
1638 void
GetMenuAccelGeometry(TkMenu * menuPtr,TkMenuEntry * mePtr,Tk_Font tkfont,const Tk_FontMetrics * fmPtr,int * widthPtr,int * heightPtr)1639 GetMenuAccelGeometry(
1640     TkMenu *menuPtr,		/* The menu we are measuring */
1641     TkMenuEntry *mePtr,		/* The entry we are measuring */
1642     Tk_Font tkfont,		/* The precalculated font */
1643     const Tk_FontMetrics *fmPtr,/* The precalculated font metrics */
1644     int *widthPtr,		/* The resulting width */
1645     int *heightPtr)		/* The resulting height */
1646 {
1647     *heightPtr = fmPtr->linespace;
1648     if (mePtr->type == CASCADE_ENTRY) {
1649         /*
1650          * Cascade entries have no accelerator but do show an arrow. Set
1651          * this field width to the width of the OBM_MNARROW system bitmap
1652          * used to display the arrow. I couldn't find how to query the
1653          * system for this value, therefore I resort to hardcoding.
1654          */
1655 	*widthPtr = CASCADE_ARROW_WIDTH;
1656     } else if ((menuPtr->menuType != MENUBAR) && (mePtr->accelPtr != NULL)) {
1657 	const char *accel = Tcl_GetString(mePtr->accelPtr);
1658 
1659 	*widthPtr = Tk_TextWidth(tkfont, accel, mePtr->accelLength);
1660     } else {
1661     	*widthPtr = 0;
1662     }
1663 }
1664 
1665 /*
1666  *----------------------------------------------------------------------
1667  *
1668  * GetTearoffEntryGeometry --
1669  *
1670  *	Gets the width and height of the indicator area of a menu.
1671  *
1672  * Results:
1673  *	widthPtr and heightPtr are set.
1674  *
1675  * Side effects:
1676  *	None.
1677  *
1678  *----------------------------------------------------------------------
1679  */
1680 
1681 void
GetTearoffEntryGeometry(TkMenu * menuPtr,TkMenuEntry * mePtr,Tk_Font tkfont,const Tk_FontMetrics * fmPtr,int * widthPtr,int * heightPtr)1682 GetTearoffEntryGeometry(
1683     TkMenu *menuPtr,		/* The menu we are measuring */
1684     TkMenuEntry *mePtr,		/* The entry we are measuring */
1685     Tk_Font tkfont,		/* The precalculated font */
1686     const Tk_FontMetrics *fmPtr,/* The precalculated font metrics */
1687     int *widthPtr,		/* The resulting width */
1688     int *heightPtr)		/* The resulting height */
1689 {
1690     (void)mePtr;
1691     (void)tkfont;
1692 
1693     if (menuPtr->menuType != MAIN_MENU) {
1694 	*heightPtr = 0;
1695     } else {
1696 	*heightPtr = fmPtr->linespace;
1697     }
1698     *widthPtr = 0;
1699 }
1700 
1701 /*
1702  *----------------------------------------------------------------------
1703  *
1704  * GetMenuSeparatorGeometry --
1705  *
1706  *	Gets the width and height of the indicator area of a menu.
1707  *
1708  * Results:
1709  *	widthPtr and heightPtr are set.
1710  *
1711  * Side effects:
1712  *	None.
1713  *
1714  *----------------------------------------------------------------------
1715  */
1716 
1717 void
GetMenuSeparatorGeometry(TkMenu * menuPtr,TkMenuEntry * mePtr,Tk_Font tkfont,const Tk_FontMetrics * fmPtr,int * widthPtr,int * heightPtr)1718 GetMenuSeparatorGeometry(
1719     TkMenu *menuPtr,		/* The menu we are measuring */
1720     TkMenuEntry *mePtr,		/* The entry we are measuring */
1721     Tk_Font tkfont,		/* The precalculated font */
1722     const Tk_FontMetrics *fmPtr,/* The precalcualted font metrics */
1723     int *widthPtr,		/* The resulting width */
1724     int *heightPtr)		/* The resulting height */
1725 {
1726     (void)menuPtr;
1727     (void)mePtr;
1728     (void)tkfont;
1729 
1730     *widthPtr = 0;
1731     *heightPtr = fmPtr->linespace - (2 * fmPtr->descent);
1732 }
1733 
1734 /*
1735  *----------------------------------------------------------------------
1736  *
1737  * DrawWindowsSystemBitmap --
1738  *
1739  *	Draws the windows system bitmap given by bitmapID into the rect given
1740  *	by rectPtr in the drawable. The bitmap is centered in the rectangle.
1741  *	It is not clipped, so if the bitmap is bigger than the rect it will
1742  *	bleed.
1743  *
1744  * Results:
1745  *	None.
1746  *
1747  * Side effects:
1748  *	Drawing occurs. Some storage is allocated and released.
1749  *
1750  *----------------------------------------------------------------------
1751  */
1752 
1753 static void
DrawWindowsSystemBitmap(Display * display,Drawable drawable,GC gc,const RECT * rectPtr,int bitmapID,int alignFlags)1754 DrawWindowsSystemBitmap(
1755     Display *display,		/* The display we are drawing into */
1756     Drawable drawable,		/* The drawable we are working with */
1757     GC gc,			/* The GC to draw with */
1758     const RECT *rectPtr,	/* The rectangle to draw into */
1759     int bitmapID,		/* The windows id of the system bitmap to
1760 				 * draw. */
1761     int alignFlags)		/* How to align the bitmap inside the
1762 				 * rectangle. */
1763 {
1764     TkWinDCState state;
1765     HDC hdc = TkWinGetDrawableDC(display, drawable, &state);
1766     HDC scratchDC;
1767     HBITMAP bitmap;
1768     BITMAP bm;
1769     POINT ptSize;
1770     POINT ptOrg;
1771     int topOffset, leftOffset;
1772 
1773     SetBkColor(hdc, gc->background);
1774     SetTextColor(hdc, gc->foreground);
1775 
1776     scratchDC = CreateCompatibleDC(hdc);
1777     bitmap = LoadBitmapW(NULL, (LPCWSTR)MAKEINTRESOURCE(bitmapID));
1778 
1779     SelectObject(scratchDC, bitmap);
1780     SetMapMode(scratchDC, GetMapMode(hdc));
1781     GetObjectA(bitmap, sizeof(BITMAP), &bm);
1782     ptSize.x = bm.bmWidth;
1783     ptSize.y = bm.bmHeight;
1784     DPtoLP(scratchDC, &ptSize, 1);
1785 
1786     ptOrg.y = ptOrg.x = 0;
1787     DPtoLP(scratchDC, &ptOrg, 1);
1788 
1789     if (alignFlags & ALIGN_BITMAP_TOP) {
1790 	topOffset = 0;
1791     } else if (alignFlags & ALIGN_BITMAP_BOTTOM) {
1792 	topOffset = (rectPtr->bottom - rectPtr->top) - ptSize.y;
1793     } else {
1794 	topOffset = (rectPtr->bottom - rectPtr->top) / 2 - (ptSize.y / 2);
1795     }
1796 
1797     if (alignFlags & ALIGN_BITMAP_LEFT) {
1798 	leftOffset = 0;
1799     } else if (alignFlags & ALIGN_BITMAP_RIGHT) {
1800 	leftOffset = (rectPtr->right - rectPtr->left) - ptSize.x;
1801     } else {
1802 	leftOffset = (rectPtr->right - rectPtr->left) / 2 - (ptSize.x / 2);
1803     }
1804 
1805     BitBlt(hdc, rectPtr->left + leftOffset, rectPtr->top + topOffset, ptSize.x,
1806 	    ptSize.y, scratchDC, ptOrg.x, ptOrg.y, SRCCOPY);
1807     DeleteDC(scratchDC);
1808     DeleteObject(bitmap);
1809 
1810     TkWinReleaseDrawableDC(drawable, hdc, &state);
1811 }
1812 
1813 /*
1814  *----------------------------------------------------------------------
1815  *
1816  * DrawMenuEntryIndicator --
1817  *
1818  *	This function draws the indicator part of a menu.
1819  *
1820  * Results:
1821  *	None.
1822  *
1823  * Side effects:
1824  *	Commands are output to X to display the menu in its current mode.
1825  *
1826  *----------------------------------------------------------------------
1827  */
1828 
1829 void
DrawMenuEntryIndicator(TkMenu * menuPtr,TkMenuEntry * mePtr,Drawable d,GC gc,GC indicatorGC,Tk_Font tkfont,const Tk_FontMetrics * fmPtr,int x,int y,int width,int height)1830 DrawMenuEntryIndicator(
1831     TkMenu *menuPtr,		/* The menu we are drawing */
1832     TkMenuEntry *mePtr,		/* The entry we are drawing */
1833     Drawable d,			/* What we are drawing into */
1834     GC gc,			/* The gc we are drawing with */
1835     GC indicatorGC,		/* The gc for indicator objects */
1836     Tk_Font tkfont,		/* The precalculated font */
1837     const Tk_FontMetrics *fmPtr,/* The precalculated font metrics */
1838     int x,			/* Left edge */
1839     int y,			/* Top edge */
1840     int width,
1841     int height)
1842 {
1843     (void)tkfont;
1844     (void)fmPtr;
1845     (void)width;
1846     (void)height;
1847 
1848     if ((mePtr->type == CHECK_BUTTON_ENTRY)
1849 	    || (mePtr->type == RADIO_BUTTON_ENTRY)) {
1850     	if (mePtr->indicatorOn && (mePtr->entryFlags & ENTRY_SELECTED)) {
1851 	    RECT rect;
1852 	    GC whichGC;
1853 	    int borderWidth, activeBorderWidth;
1854 
1855 	    if (mePtr->state != ENTRY_NORMAL) {
1856 		whichGC = gc;
1857 	    } else {
1858 		whichGC = indicatorGC;
1859 	    }
1860 
1861 	    rect.top = y;
1862 	    rect.bottom = y + mePtr->height;
1863 	    Tk_GetPixelsFromObj(menuPtr->interp, menuPtr->tkwin,
1864 		    menuPtr->borderWidthPtr, &borderWidth);
1865 	    Tk_GetPixelsFromObj(menuPtr->interp, menuPtr->tkwin,
1866 		    menuPtr->activeBorderWidthPtr, &activeBorderWidth);
1867 	    rect.left = borderWidth + activeBorderWidth + x;
1868 	    rect.right = mePtr->indicatorSpace + x;
1869 
1870 	    if ((mePtr->state == ENTRY_DISABLED)
1871 		    && (menuPtr->disabledFgPtr != NULL)) {
1872 		RECT hilightRect;
1873 		COLORREF oldFgColor = whichGC->foreground;
1874 
1875 		whichGC->foreground = GetSysColor(COLOR_3DHILIGHT);
1876 		hilightRect.top = rect.top + 1;
1877 		hilightRect.bottom = rect.bottom + 1;
1878 		hilightRect.left = rect.left + 1;
1879 		hilightRect.right = rect.right + 1;
1880 		DrawWindowsSystemBitmap(menuPtr->display, d, whichGC,
1881 			&hilightRect, OBM_CHECK, 0);
1882 		whichGC->foreground = oldFgColor;
1883 	    }
1884 
1885 	    DrawWindowsSystemBitmap(menuPtr->display, d, whichGC, &rect,
1886 		    OBM_CHECK, 0);
1887 	}
1888     }
1889 }
1890 
1891 /*
1892  *----------------------------------------------------------------------
1893  *
1894  * DrawMenuEntryAccelerator --
1895  *
1896  *	This function draws the accelerator part of a menu. For example, the
1897  *	string "CTRL-Z" could be drawn to to the right of the label text for
1898  *	an Undo menu entry. Need to decide what to draw here. Should we
1899  *	replace strings like "Control", "Command", etc?
1900  *
1901  * Results:
1902  *	None.
1903  *
1904  * Side effects:
1905  *	Commands are output to display the menu in its
1906  *	current mode.
1907  *
1908  *----------------------------------------------------------------------
1909  */
1910 
1911 void
DrawMenuEntryAccelerator(TkMenu * menuPtr,TkMenuEntry * mePtr,Drawable d,GC gc,Tk_Font tkfont,const Tk_FontMetrics * fmPtr,Tk_3DBorder activeBorder,int x,int y,int width,int height)1912 DrawMenuEntryAccelerator(
1913     TkMenu *menuPtr,		/* The menu we are drawing */
1914     TkMenuEntry *mePtr,		/* The entry we are drawing */
1915     Drawable d,			/* What we are drawing into */
1916     GC gc,			/* The gc we are drawing with */
1917     Tk_Font tkfont,		/* The precalculated font */
1918     const Tk_FontMetrics *fmPtr,/* The precalculated font metrics */
1919     Tk_3DBorder activeBorder,	/* The border when an item is active */
1920     int x,			/* left edge */
1921     int y,			/* top edge */
1922     int width,			/* Width of menu entry */
1923     int height)			/* Height of menu entry */
1924 {
1925     int baseline;
1926     int leftEdge = x + mePtr->indicatorSpace + mePtr->labelWidth;
1927     const char *accel;
1928     (void)activeBorder;
1929     (void)width;
1930     (void)height;
1931 
1932     if (menuPtr->menuType == MENUBAR) {
1933         return;
1934     }
1935 
1936     if (mePtr->accelPtr != NULL) {
1937 	accel = Tcl_GetString(mePtr->accelPtr);
1938     } else {
1939 	accel = NULL;
1940     }
1941 
1942     baseline = y + (height + fmPtr->ascent - fmPtr->descent) / 2;
1943 
1944     /*
1945      * Draw disabled 3D text highlight only with the Win95/98 look.
1946      */
1947 
1948     if (TkWinGetPlatformTheme() != TK_THEME_WIN_XP) {
1949 	if ((mePtr->state == ENTRY_DISABLED)
1950 		&& (menuPtr->disabledFgPtr != NULL) && (accel != NULL)) {
1951 	    COLORREF oldFgColor = gc->foreground;
1952 
1953 	    gc->foreground = GetSysColor(COLOR_3DHILIGHT);
1954 	    if (!(mePtr->entryFlags & ENTRY_PLATFORM_FLAG1)) {
1955 		Tk_DrawChars(menuPtr->display, d, gc, tkfont, accel,
1956 			mePtr->accelLength, leftEdge + 1, baseline + 1);
1957 	    }
1958 	    gc->foreground = oldFgColor;
1959 	}
1960     }
1961 
1962     if (accel != NULL) {
1963 	Tk_DrawChars(menuPtr->display, d, gc, tkfont, accel,
1964 		mePtr->accelLength, leftEdge, baseline);
1965     }
1966 }
1967 
1968 /*
1969  *----------------------------------------------------------------------
1970  *
1971  * DrawMenuEntryArrow --
1972  *
1973  *	This function draws the arrow bitmap on the right side of a menu
1974  *	entry. This function is only used when drawing the arrow for:
1975  *	 - a disabled cascade item
1976  *	 - a cascade item in any state in a torn-off menu
1977  *
1978  * Results:
1979  *	None.
1980  *
1981  * Side effects:
1982  *	None.
1983  *
1984  *----------------------------------------------------------------------
1985  */
1986 
1987 void
DrawMenuEntryArrow(TkMenu * menuPtr,TkMenuEntry * mePtr,Drawable d,GC gc,Tk_3DBorder activeBorder,int x,int y,int width,int height,int drawArrow)1988 DrawMenuEntryArrow(
1989     TkMenu *menuPtr,		/* The menu we are drawing */
1990     TkMenuEntry *mePtr,		/* The entry we are drawing */
1991     Drawable d,			/* What we are drawing into */
1992     GC gc,			/* The gc we are drawing with */
1993     Tk_3DBorder activeBorder,	/* The border when an item is active */
1994     int x,			/* left edge */
1995     int y,			/* top edge */
1996     int width,			/* Width of menu entry */
1997     int height,			/* Height of menu entry */
1998     int drawArrow)		/* For cascade menus, whether of not to draw
1999 				 * the arrow. I cannot figure out Windows'
2000 				 * algorithm for where to draw this. */
2001 {
2002     COLORREF oldFgColor;
2003     COLORREF oldBgColor;
2004     RECT rect;
2005     (void)gc;
2006     (void)activeBorder;
2007 
2008     if (!drawArrow || (mePtr->type != CASCADE_ENTRY)) {
2009 	return;
2010     }
2011 
2012     /*
2013      * Don't draw the arrow if a submenu is not attached to this
2014      * cascade entry.
2015      */
2016 
2017     if ((mePtr->childMenuRefPtr == NULL)
2018            || (mePtr->childMenuRefPtr->menuPtr == NULL)) {
2019         return;
2020     }
2021 
2022     oldFgColor = gc->foreground;
2023     oldBgColor = gc->background;
2024 
2025     /*
2026      * Set bitmap bg to highlight color if the menu is highlighted.
2027      */
2028 
2029     if (mePtr->entryFlags & ENTRY_PLATFORM_FLAG1) {
2030 	XColor *activeBgColor = Tk_3DBorderColor(Tk_Get3DBorderFromObj(
2031 		mePtr->menuPtr->tkwin, (mePtr->activeBorderPtr == NULL)
2032 		? mePtr->menuPtr->activeBorderPtr
2033 		: mePtr->activeBorderPtr));
2034 
2035 	gc->background = activeBgColor->pixel;
2036     }
2037 
2038     gc->foreground = GetSysColor((mePtr->state == ENTRY_DISABLED)
2039 	? COLOR_GRAYTEXT
2040 		: ((mePtr->state == ENTRY_ACTIVE)
2041 		? COLOR_HIGHLIGHTTEXT : COLOR_MENUTEXT));
2042 
2043     rect.top = y + GetSystemMetrics(SM_CYBORDER);
2044     rect.bottom = y + height - GetSystemMetrics(SM_CYBORDER);
2045     rect.left = x + mePtr->indicatorSpace + mePtr->labelWidth;
2046     rect.right = x + width;
2047 
2048     DrawWindowsSystemBitmap(menuPtr->display, d, gc, &rect, OBM_MNARROW,
2049 	    ALIGN_BITMAP_RIGHT);
2050 
2051     gc->foreground = oldFgColor;
2052     gc->background = oldBgColor;
2053     return;
2054 }
2055 
2056 /*
2057  *----------------------------------------------------------------------
2058  *
2059  * DrawMenuSeparator --
2060  *
2061  *	The menu separator is drawn.
2062  *
2063  * Results:
2064  *	None.
2065  *
2066  * Side effects:
2067  *	Commands are output to X to display the menu in its current mode.
2068  *
2069  *----------------------------------------------------------------------
2070  */
2071 
2072 void
DrawMenuSeparator(TkMenu * menuPtr,TkMenuEntry * mePtr,Drawable d,GC gc,Tk_Font tkfont,const Tk_FontMetrics * fmPtr,int x,int y,int width,int height)2073 DrawMenuSeparator(
2074     TkMenu *menuPtr,		/* The menu we are drawing */
2075     TkMenuEntry *mePtr,		/* The entry we are drawing */
2076     Drawable d,			/* What we are drawing into */
2077     GC gc,			/* The gc we are drawing with */
2078     Tk_Font tkfont,		/* The precalculated font */
2079     const Tk_FontMetrics *fmPtr,/* The precalculated font metrics */
2080     int x,			/* left edge */
2081     int y,			/* top edge */
2082     int width,			/* width of item */
2083     int height)			/* height of item */
2084 {
2085     XPoint points[2];
2086     Tk_3DBorder border;
2087     (void)mePtr;
2088     (void)gc;
2089     (void)tkfont;
2090     (void)fmPtr;
2091 
2092     points[0].x = x;
2093     points[0].y = y + height / 2;
2094     points[1].x = x + width - 1;
2095     points[1].y = points[0].y;
2096     border = Tk_Get3DBorderFromObj(menuPtr->tkwin, menuPtr->borderPtr);
2097     Tk_Draw3DPolygon(menuPtr->tkwin, d, border, points, 2, 1,
2098 	    TK_RELIEF_RAISED);
2099 }
2100 
2101 /*
2102  *----------------------------------------------------------------------
2103  *
2104  * DrawMenuUnderline --
2105  *
2106  *	On appropriate platforms, draw the underline character for the menu.
2107  *
2108  * Results:
2109  *	None.
2110  *
2111  * Side effects:
2112  *	Commands are output to X to display the menu in its current mode.
2113  *
2114  *----------------------------------------------------------------------
2115  */
2116 
2117 static void
DrawMenuUnderline(TkMenu * menuPtr,TkMenuEntry * mePtr,Drawable d,GC gc,Tk_Font tkfont,const Tk_FontMetrics * fmPtr,int x,int y,int width,int height)2118 DrawMenuUnderline(
2119     TkMenu *menuPtr,		/* The menu to draw into */
2120     TkMenuEntry *mePtr,		/* The entry we are drawing */
2121     Drawable d,			/* What we are drawing into */
2122     GC gc,			/* The gc to draw into */
2123     Tk_Font tkfont,		/* The precalculated font */
2124     const Tk_FontMetrics *fmPtr,/* The precalculated font metrics */
2125     int x,			/* Left Edge */
2126     int y,			/* Top Edge */
2127     int width,			/* Width of entry */
2128     int height)			/* Height of entry */
2129 {
2130     (void)fmPtr;
2131     (void)width;
2132 
2133     if ((mePtr->underline >= 0) && (mePtr->labelPtr != NULL)) {
2134 	int len;
2135 
2136 	len = Tcl_GetCharLength(mePtr->labelPtr);
2137 	if (mePtr->underline < len) {
2138 	    const char *label, *start, *end;
2139 	    int ch;
2140 
2141 	    label = Tcl_GetString(mePtr->labelPtr);
2142 	    start = TkUtfAtIndex(label, mePtr->underline);
2143 	    end = start + TkUtfToUniChar(start, &ch);
2144 	    Tk_UnderlineChars(menuPtr->display, d,
2145 		    gc, tkfont, label, x + mePtr->indicatorSpace,
2146 		    y + (height + fmPtr->ascent - fmPtr->descent) / 2,
2147 		    (int) (start - label), (int) (end - label));
2148 	}
2149     }
2150 }
2151 
2152 /*
2153  *--------------------------------------------------------------
2154  *
2155  * TkWinMenuKeyObjCmd --
2156  *
2157  *	This function is invoked when keys related to pulling down menus is
2158  *	pressed. The corresponding Windows events are generated and passed to
2159  *	DefWindowProcW if appropriate. This cmd is registered as tk::WinMenuKey
2160  *	in the interp.
2161  *
2162  * Results:
2163  *	Always returns TCL_OK.
2164  *
2165  * Side effects:
2166  *	The menu system may take over and process user events for menu input.
2167  *
2168  *--------------------------------------------------------------
2169  */
2170 
2171 static int
TkWinMenuKeyObjCmd(ClientData dummy,Tcl_Interp * interp,int objc,Tcl_Obj * const objv[])2172 TkWinMenuKeyObjCmd(
2173     ClientData dummy,	/* Unused. */
2174     Tcl_Interp *interp,		/* Current interpreter. */
2175     int objc,			/* Number of arguments. */
2176     Tcl_Obj *const objv[])	/* Argument objects. */
2177 {
2178     UINT scanCode;
2179     UINT virtualKey;
2180     XEvent *eventPtr;
2181     Tk_Window tkwin;
2182     TkWindow *winPtr;
2183     KeySym keySym;
2184     int i;
2185     (void)dummy;
2186 
2187     if (objc != 3) {
2188 	Tcl_WrongNumArgs(interp, 1, objv, "window keySym");
2189 	return TCL_ERROR;
2190     }
2191 
2192     tkwin = Tk_NameToWindow(interp, Tcl_GetString(objv[1]),
2193 	    Tk_MainWindow(interp));
2194 
2195     if (tkwin == NULL) {
2196 	/*
2197 	 * If we don't find the key, just return, as the window may have
2198 	 * been destroyed in the binding. [Bug 1236306]
2199 	 */
2200 	return TCL_OK;
2201     }
2202 
2203     eventPtr = TkpGetBindingXEvent(interp);
2204 
2205     winPtr = (TkWindow *)tkwin;
2206 
2207     if (Tcl_GetIntFromObj(interp, objv[2], &i) != TCL_OK) {
2208 	return TCL_ERROR;
2209     }
2210     keySym = i;
2211 
2212     if (eventPtr->type == KeyPress) {
2213 	switch (keySym) {
2214 	case XK_Alt_L:
2215 	    scanCode = MapVirtualKeyW(VK_LMENU, 0);
2216 	    CallWindowProcW(DefWindowProcW, Tk_GetHWND(Tk_WindowId(tkwin)),
2217 		    WM_SYSKEYDOWN, VK_MENU,
2218 		    (int) (scanCode << 16) | (1 << 29));
2219 	    break;
2220 	case XK_Alt_R:
2221 	    scanCode = MapVirtualKeyW(VK_RMENU, 0);
2222 	    CallWindowProcW(DefWindowProcW, Tk_GetHWND(Tk_WindowId(tkwin)),
2223 		    WM_SYSKEYDOWN, VK_MENU,
2224 		    (int) (scanCode << 16) | (1 << 29) | (1 << 24));
2225 	    break;
2226 	case XK_F10:
2227 	    scanCode = MapVirtualKeyW(VK_F10, 0);
2228 	    CallWindowProcW(DefWindowProcW, Tk_GetHWND(Tk_WindowId(tkwin)),
2229 		    WM_SYSKEYDOWN, VK_F10, (int) (scanCode << 16));
2230 	    break;
2231 	default:
2232 	    virtualKey = XKeysymToKeycode(winPtr->display, keySym);
2233 	    scanCode = MapVirtualKeyW(virtualKey, 0);
2234 	    if (0 != scanCode) {
2235 		TkKeyEvent xkey;
2236 		memcpy(&xkey, eventPtr, sizeof(xkey));
2237 		CallWindowProcW(DefWindowProcW, Tk_GetHWND(Tk_WindowId(tkwin)),
2238 			WM_SYSKEYDOWN, virtualKey,
2239 			(int) ((scanCode << 16) | (1 << 29)));
2240 		for (i = 0; i < xkey.nbytes; i++) {
2241 		    CallWindowProcW(DefWindowProcW,
2242 			    Tk_GetHWND(Tk_WindowId(tkwin)), WM_SYSCHAR,
2243 			    xkey.trans_chars[i],
2244 			    (int) ((scanCode << 16) | (1 << 29)));
2245 		}
2246 	    }
2247 	}
2248     } else if (eventPtr->type == KeyRelease) {
2249 	switch (keySym) {
2250 	case XK_Alt_L:
2251 	    scanCode = MapVirtualKeyW(VK_LMENU, 0);
2252 	    CallWindowProcW(DefWindowProcW, Tk_GetHWND(Tk_WindowId(tkwin)),
2253 		    WM_SYSKEYUP, VK_MENU, (int) (scanCode << 16)
2254 		    | (1 << 29) | (1 << 30) | (1 << 31));
2255 	    break;
2256 	case XK_Alt_R:
2257 	    scanCode = MapVirtualKeyW(VK_RMENU, 0);
2258 	    CallWindowProcW(DefWindowProcW, Tk_GetHWND(Tk_WindowId(tkwin)),
2259 		    WM_SYSKEYUP, VK_MENU, (int) (scanCode << 16) | (1 << 24)
2260 		    | (1 << 29) | (1 << 30) | (1 << 31));
2261 	    break;
2262 	case XK_F10:
2263 	    scanCode = MapVirtualKeyW(VK_F10, 0);
2264 	    CallWindowProcW(DefWindowProcW, Tk_GetHWND(Tk_WindowId(tkwin)),
2265 		    WM_SYSKEYUP, VK_F10,
2266 		    (int) (scanCode << 16) | (1 << 30) | (1 << 31));
2267 	    break;
2268 	default:
2269 	    virtualKey = XKeysymToKeycode(winPtr->display, keySym);
2270 	    scanCode = MapVirtualKeyW(virtualKey, 0);
2271 	    if (0 != scanCode) {
2272 		CallWindowProcW(DefWindowProcW, Tk_GetHWND(Tk_WindowId(tkwin)),
2273 			WM_SYSKEYUP, virtualKey, (int) ((scanCode << 16)
2274 			| (1 << 29) | (1 << 30) | (1 << 31)));
2275 	    }
2276 	}
2277     }
2278     return TCL_OK;
2279 }
2280 
2281 /*
2282  *--------------------------------------------------------------
2283  *
2284  * TkpInitializeMenuBindings --
2285  *
2286  *	For every interp, initializes the bindings for Windows menus. Does
2287  *	nothing on Mac or XWindows.
2288  *
2289  * Results:
2290  *	None.
2291  *
2292  * Side effects:
2293  *	bindings are setup for the interp which will handle Alt-key sequences
2294  *	for menus without beeping or interfering with user-defined Alt-key
2295  *	bindings.
2296  *
2297  *--------------------------------------------------------------
2298  */
2299 
2300 void
TkpInitializeMenuBindings(Tcl_Interp * interp,Tk_BindingTable bindingTable)2301 TkpInitializeMenuBindings(
2302     Tcl_Interp *interp,		/* The interpreter to set. */
2303     Tk_BindingTable bindingTable)
2304 				/* The table to add to. */
2305 {
2306     Tk_Uid uid = Tk_GetUid("all");
2307 
2308     /*
2309      * We need to set up the bindings for menubars. These have to recreate
2310      * windows events, so we need to invoke C code to generate the
2311      * WM_SYSKEYDOWNS and WM_SYSKEYUPs appropriately. Trick is, we can't
2312      * create a C level binding directly since we may want to modify the
2313      * binding in Tcl code.
2314      */
2315 
2316     (void) Tcl_CreateObjCommand(interp, "tk::WinMenuKey",
2317 	    TkWinMenuKeyObjCmd, Tk_MainWindow(interp), NULL);
2318 
2319     (void) Tk_CreateBinding(interp, bindingTable, (ClientData) uid,
2320 	    "<Alt_L>", "tk::WinMenuKey %W %N", 0);
2321 
2322     (void) Tk_CreateBinding(interp, bindingTable, (ClientData) uid,
2323 	    "<KeyRelease-Alt_L>", "tk::WinMenuKey %W %N", 0);
2324 
2325     (void) Tk_CreateBinding(interp, bindingTable, (ClientData) uid,
2326 	    "<Alt_R>", "tk::WinMenuKey %W %N", 0);
2327 
2328     (void) Tk_CreateBinding(interp, bindingTable, (ClientData) uid,
2329 	    "<KeyRelease-Alt_R>", "tk::WinMenuKey %W %N", 0);
2330 
2331     (void) Tk_CreateBinding(interp, bindingTable, (ClientData) uid,
2332 	    "<Alt-KeyPress>", "tk::WinMenuKey %W %N", 0);
2333 
2334     (void) Tk_CreateBinding(interp, bindingTable, (ClientData) uid,
2335 	    "<Alt-KeyRelease>", "tk::WinMenuKey %W %N", 0);
2336 
2337     (void) Tk_CreateBinding(interp, bindingTable, (ClientData) uid,
2338 	    "<KeyPress-F10>", "tk::WinMenuKey %W %N", 0);
2339 
2340     (void) Tk_CreateBinding(interp, bindingTable, (ClientData) uid,
2341 	    "<KeyRelease-F10>", "tk::WinMenuKey %W %N", 0);
2342 }
2343 
2344 /*
2345  *----------------------------------------------------------------------
2346  *
2347  * DrawMenuEntryLabel --
2348  *
2349  *	This function draws the label part of a menu.
2350  *
2351  * Results:
2352  *	None.
2353  *
2354  * Side effects:
2355  *	Commands are output to X to display the menu in its
2356  *	current mode.
2357  *
2358  *----------------------------------------------------------------------
2359  */
2360 
2361 static void
DrawMenuEntryLabel(TkMenu * menuPtr,TkMenuEntry * mePtr,Drawable d,GC gc,Tk_Font tkfont,const Tk_FontMetrics * fmPtr,int x,int y,int width,int height,int underline)2362 DrawMenuEntryLabel(
2363     TkMenu *menuPtr,		/* The menu we are drawing */
2364     TkMenuEntry *mePtr,		/* The entry we are drawing */
2365     Drawable d,			/* What we are drawing into */
2366     GC gc,			/* The gc we are drawing into */
2367     Tk_Font tkfont,		/* The precalculated font */
2368     const Tk_FontMetrics *fmPtr,/* The precalculated font metrics */
2369     int x,			/* left edge */
2370     int y,			/* right edge */
2371     int width,			/* width of entry */
2372     int height,			/* height of entry */
2373     int underline)		/* accelerator cue should be drawn */
2374 {
2375     int indicatorSpace = mePtr->indicatorSpace;
2376     int activeBorderWidth;
2377     int leftEdge;
2378     int imageHeight, imageWidth;
2379     int textHeight = 0, textWidth = 0;
2380     int haveImage = 0, haveText = 0;
2381     int imageXOffset = 0, imageYOffset = 0;
2382     int textXOffset = 0, textYOffset = 0;
2383 
2384     Tk_GetPixelsFromObj(menuPtr->interp, menuPtr->tkwin,
2385 	    menuPtr->activeBorderWidthPtr, &activeBorderWidth);
2386     leftEdge = x + indicatorSpace + activeBorderWidth;
2387 
2388     /*
2389      * Work out what we will need to draw first.
2390      */
2391 
2392     if (mePtr->image != NULL) {
2393     	Tk_SizeOfImage(mePtr->image, &imageWidth, &imageHeight);
2394 	haveImage = 1;
2395     } else if (mePtr->bitmapPtr != NULL) {
2396 	Pixmap bitmap = Tk_GetBitmapFromObj(menuPtr->tkwin, mePtr->bitmapPtr);
2397 
2398 	Tk_SizeOfBitmap(menuPtr->display, bitmap, &imageWidth, &imageHeight);
2399 	haveImage = 1;
2400     }
2401     if (!haveImage || (mePtr->compound != COMPOUND_NONE)) {
2402 	if (mePtr->labelLength > 0) {
2403 	    const char *label = Tcl_GetString(mePtr->labelPtr);
2404 
2405 	    textWidth = Tk_TextWidth(tkfont, label, mePtr->labelLength);
2406 	    textHeight = fmPtr->linespace;
2407 	    haveText = 1;
2408 	}
2409     }
2410 
2411     /*
2412      * Now work out what the relative positions are.
2413      */
2414 
2415     if (haveImage && haveText) {
2416 	int fullWidth = (imageWidth > textWidth ? imageWidth : textWidth);
2417 	switch ((enum compound) mePtr->compound) {
2418 	case COMPOUND_TOP:
2419 	    textXOffset = (fullWidth - textWidth)/2;
2420 	    textYOffset = imageHeight/2 + 2;
2421 	    imageXOffset = (fullWidth - imageWidth)/2;
2422 	    imageYOffset = -textHeight/2;
2423 	    break;
2424 	case COMPOUND_BOTTOM:
2425 	    textXOffset = (fullWidth - textWidth)/2;
2426 	    textYOffset = -imageHeight/2;
2427 	    imageXOffset = (fullWidth - imageWidth)/2;
2428 	    imageYOffset = textHeight/2 + 2;
2429 	    break;
2430 	case COMPOUND_LEFT:
2431 	    /*
2432 	     * The standard image position on Windows is in the indicator
2433 	     * space to the left of the entries, unless this entry is a
2434 	     * radio|check button because then the indicator space will be
2435 	     * used.
2436 	     */
2437 
2438 	    textXOffset = imageWidth + 2;
2439 	    textYOffset = 0;
2440 	    imageXOffset = 0;
2441 	    imageYOffset = 0;
2442 	    if ((mePtr->type != CHECK_BUTTON_ENTRY)
2443 		    && (mePtr->type != RADIO_BUTTON_ENTRY)) {
2444 		textXOffset -= indicatorSpace;
2445 		if (textXOffset < 0) {
2446 		    textXOffset = 0;
2447 		}
2448 		imageXOffset = -indicatorSpace;
2449 	    }
2450 	    break;
2451 	case COMPOUND_RIGHT:
2452 	    textXOffset = 0;
2453 	    textYOffset = 0;
2454 	    imageXOffset = textWidth + 2;
2455 	    imageYOffset = 0;
2456 	    break;
2457 	case COMPOUND_CENTER:
2458 	    textXOffset = (fullWidth - textWidth)/2;
2459 	    textYOffset = 0;
2460 	    imageXOffset = (fullWidth - imageWidth)/2;
2461 	    imageYOffset = 0;
2462 	    break;
2463 	case COMPOUND_NONE:
2464 	    break;
2465 	}
2466     } else {
2467 	textXOffset = 0;
2468 	textYOffset = 0;
2469 	imageXOffset = 0;
2470 	imageYOffset = 0;
2471     }
2472 
2473     /*
2474      * Draw label and/or bitmap or image for entry.
2475      */
2476 
2477     if (mePtr->image != NULL) {
2478     	if ((mePtr->selectImage != NULL)
2479 	    	&& (mePtr->entryFlags & ENTRY_SELECTED)) {
2480 	    Tk_RedrawImage(mePtr->selectImage, 0, 0,
2481 		    imageWidth, imageHeight, d, leftEdge + imageXOffset,
2482 		    (int) (y + (mePtr->height-imageHeight)/2 + imageYOffset));
2483     	} else {
2484 	    Tk_RedrawImage(mePtr->image, 0, 0, imageWidth,
2485 		    imageHeight, d, leftEdge + imageXOffset,
2486 		    (int) (y + (mePtr->height-imageHeight)/2 + imageYOffset));
2487     	}
2488     } else if (mePtr->bitmapPtr != NULL) {
2489 	Pixmap bitmap = Tk_GetBitmapFromObj(menuPtr->tkwin, mePtr->bitmapPtr);
2490     	XCopyPlane(menuPtr->display, bitmap, d,	gc, 0, 0,
2491 		(unsigned) imageWidth, (unsigned) imageHeight,
2492 		leftEdge + imageXOffset,
2493 		(int) (y + (mePtr->height - imageHeight)/2 + imageYOffset), 1);
2494     }
2495     if ((mePtr->compound != COMPOUND_NONE) || !haveImage) {
2496     	if (mePtr->labelLength > 0) {
2497 	    int baseline = y + (height + fmPtr->ascent - fmPtr->descent) / 2;
2498 	    const char *label = Tcl_GetString(mePtr->labelPtr);
2499 
2500 	    if (TkWinGetPlatformTheme() != TK_THEME_WIN_XP) {
2501 		/*
2502 		 * Win 95/98 systems draw disabled menu text with a 3D
2503 		 * highlight, unless the menu item is highlighted,
2504 		 */
2505 
2506 		if ((mePtr->state == ENTRY_DISABLED) &&
2507 			!(mePtr->entryFlags & ENTRY_PLATFORM_FLAG1)) {
2508 		    COLORREF oldFgColor = gc->foreground;
2509 
2510 		    gc->foreground = GetSysColor(COLOR_3DHILIGHT);
2511 		    Tk_DrawChars(menuPtr->display, d, gc, tkfont, label,
2512 			    mePtr->labelLength, leftEdge + textXOffset + 1,
2513 			    baseline + textYOffset + 1);
2514 		    gc->foreground = oldFgColor;
2515 		}
2516 	    }
2517 	    Tk_DrawChars(menuPtr->display, d, gc, tkfont, label,
2518 		    mePtr->labelLength, leftEdge + textXOffset,
2519 		    baseline + textYOffset);
2520 	    if (underline) {
2521 		DrawMenuUnderline(menuPtr, mePtr, d, gc, tkfont, fmPtr,
2522 			x + textXOffset, y + textYOffset, width, height);
2523 	    }
2524 	}
2525     }
2526 
2527     if (mePtr->state == ENTRY_DISABLED) {
2528 	if (menuPtr->disabledFgPtr == NULL) {
2529 	    XFillRectangle(menuPtr->display, d, menuPtr->disabledGC, x, y,
2530 		    (unsigned) width, (unsigned) height);
2531 	} else if ((mePtr->image != NULL)
2532 		&& menuPtr->disabledImageGC) {
2533 	    XFillRectangle(menuPtr->display, d, menuPtr->disabledImageGC,
2534 		    leftEdge + imageXOffset,
2535 		    (int) (y + (mePtr->height - imageHeight)/2 + imageYOffset),
2536 		    (unsigned) imageWidth, (unsigned) imageHeight);
2537 	}
2538     }
2539 }
2540 
2541 /*
2542  *--------------------------------------------------------------
2543  *
2544  * TkpComputeMenubarGeometry --
2545  *
2546  *	This function is invoked to recompute the size and layout of a menu
2547  *	that is a menubar clone.
2548  *
2549  * Results:
2550  *	None.
2551  *
2552  * Side effects:
2553  *	Fields of menu entries are changed to reflect their current positions,
2554  *	and the size of the menu window itself may be changed.
2555  *
2556  *--------------------------------------------------------------
2557  */
2558 
2559 void
TkpComputeMenubarGeometry(TkMenu * menuPtr)2560 TkpComputeMenubarGeometry(
2561     TkMenu *menuPtr)		/* Structure describing menu. */
2562 {
2563     TkpComputeStandardMenuGeometry(menuPtr);
2564 }
2565 
2566 /*
2567  *----------------------------------------------------------------------
2568  *
2569  * DrawTearoffEntry --
2570  *
2571  *	This function draws the background part of a menu.
2572  *
2573  * Results:
2574  *	None.
2575  *
2576  * Side effects:
2577  *	Commands are output to X to display the menu in its current mode.
2578  *
2579  *----------------------------------------------------------------------
2580  */
2581 
2582 void
DrawTearoffEntry(TkMenu * menuPtr,TkMenuEntry * mePtr,Drawable d,GC gc,Tk_Font tkfont,const Tk_FontMetrics * fmPtr,int x,int y,int width,int height)2583 DrawTearoffEntry(
2584     TkMenu *menuPtr,		/* The menu we are drawing */
2585     TkMenuEntry *mePtr,		/* The entry we are drawing */
2586     Drawable d,			/* The drawable we are drawing into */
2587     GC gc,			/* The gc we are drawing with */
2588     Tk_Font tkfont,		/* The font we are drawing with */
2589     const Tk_FontMetrics *fmPtr,/* The metrics we are drawing with */
2590     int x, int y,
2591     int width, int height)
2592 {
2593     XPoint points[2];
2594     int segmentWidth, maxX;
2595     Tk_3DBorder border;
2596     (void)mePtr;
2597     (void)gc;
2598     (void)tkfont;
2599     (void)fmPtr;
2600 
2601     if (menuPtr->menuType != MAIN_MENU) {
2602 	return;
2603     }
2604 
2605     points[0].x = x;
2606     points[0].y = y + height/2;
2607     points[1].y = points[0].y;
2608     segmentWidth = 6;
2609     maxX = x + width - 1;
2610     border = Tk_Get3DBorderFromObj(menuPtr->tkwin, menuPtr->borderPtr);
2611 
2612     while (points[0].x < maxX) {
2613 	points[1].x = points[0].x + segmentWidth;
2614 	if (points[1].x > maxX) {
2615 	    points[1].x = maxX;
2616 	}
2617 	Tk_Draw3DPolygon(menuPtr->tkwin, d, border, points, 2, 1,
2618 		TK_RELIEF_RAISED);
2619 	points[0].x += 2*segmentWidth;
2620     }
2621 }
2622 
2623 /*
2624  *----------------------------------------------------------------------
2625  *
2626  * TkpConfigureMenuEntry --
2627  *
2628  *	Processes configurations for menu entries.
2629  *
2630  * Results:
2631  *	Returns standard TCL result. If TCL_ERROR is returned, then the
2632  *	interp's result contains an error message.
2633  *
2634  * Side effects:
2635  *	Configuration information get set for mePtr; old resources get freed,
2636  *	if any need it.
2637  *
2638  *----------------------------------------------------------------------
2639  */
2640 
2641 int
TkpConfigureMenuEntry(TkMenuEntry * mePtr)2642 TkpConfigureMenuEntry(
2643     TkMenuEntry *mePtr)/* Information about menu entry; may or may
2644 				 * not already have values for some fields. */
2645 {
2646     ScheduleMenuReconfigure(mePtr->menuPtr);
2647     return TCL_OK;
2648 }
2649 
2650 /*
2651  *----------------------------------------------------------------------
2652  *
2653  * TkpDrawMenuEntry --
2654  *
2655  *	Draws the given menu entry at the given coordinates with the given
2656  *	attributes.
2657  *
2658  * Results:
2659  *	None.
2660  *
2661  * Side effects:
2662  *	X Server commands are executed to display the menu entry.
2663  *
2664  *----------------------------------------------------------------------
2665  */
2666 
2667 void
TkpDrawMenuEntry(TkMenuEntry * mePtr,Drawable menuDrawable,Tk_Font tkfont,const Tk_FontMetrics * menuMetricsPtr,int x,int y,int width,int height,int strictMotif,int drawingParameters)2668 TkpDrawMenuEntry(
2669     TkMenuEntry *mePtr,		/* The entry to draw */
2670     Drawable menuDrawable,	/* Menu to draw into */
2671     Tk_Font tkfont,		/* Precalculated font for menu */
2672     const Tk_FontMetrics *menuMetricsPtr,
2673 				/* Precalculated metrics for menu */
2674     int x,			/* X-coordinate of topleft of entry */
2675     int y,			/* Y-coordinate of topleft of entry */
2676     int width,			/* Width of the entry rectangle */
2677     int height,			/* Height of the current rectangle */
2678     int strictMotif,		/* Boolean flag */
2679     int drawingParameters)	/* Whether or not to draw the cascade arrow
2680 				 * for cascade items and accelerator
2681 				 * cues. */
2682 {
2683     GC gc, indicatorGC;
2684     TkMenu *menuPtr = mePtr->menuPtr;
2685     Tk_3DBorder bgBorder, activeBorder;
2686     const Tk_FontMetrics *fmPtr;
2687     Tk_FontMetrics entryMetrics;
2688     int padY = (menuPtr->menuType == MENUBAR) ? 3 : 0;
2689     int adjustedX, adjustedY;
2690     int adjustedHeight = height - 2 * padY;
2691     TkWinDrawable memWinDraw;
2692     TkWinDCState dcState;
2693     HBITMAP oldBitmap = NULL;
2694     Drawable d;
2695     HDC memDc = NULL, menuDc = NULL;
2696 
2697     /*
2698      * If the menu entry includes an image then draw the entry into a
2699      * compatible bitmap first.  This avoids problems with clipping on
2700      * animated menus.  [Bug 1329198]
2701      */
2702 
2703     if (mePtr->image != NULL) {
2704 	menuDc = TkWinGetDrawableDC(menuPtr->display, menuDrawable, &dcState);
2705 
2706 	memDc = CreateCompatibleDC(menuDc);
2707 	oldBitmap = (HBITMAP)SelectObject(memDc,
2708     			CreateCompatibleBitmap(menuDc, width, height) );
2709 
2710 	memWinDraw.type = TWD_WINDC;
2711 	memWinDraw.winDC.hdc = memDc;
2712 	d = (Drawable)&memWinDraw;
2713 	adjustedX = 0;
2714 	adjustedY = padY;
2715 
2716     } else {
2717 	d = menuDrawable;
2718 	adjustedX = x;
2719 	adjustedY = y + padY;
2720     }
2721 
2722     /*
2723      * Choose the gc for drawing the foreground part of the entry.
2724      */
2725 
2726     if ((mePtr->state == ENTRY_ACTIVE) && !strictMotif) {
2727 	gc = mePtr->activeGC;
2728 	if (gc == NULL) {
2729 	    gc = menuPtr->activeGC;
2730 	}
2731     } else {
2732     	TkMenuEntry *cascadeEntryPtr;
2733     	int parentDisabled = 0;
2734     	const char *name;
2735 
2736     	for (cascadeEntryPtr = menuPtr->menuRefPtr->parentEntryPtr;
2737     		cascadeEntryPtr != NULL;
2738     		cascadeEntryPtr = cascadeEntryPtr->nextCascadePtr) {
2739 	    name = Tcl_GetString(cascadeEntryPtr->namePtr);
2740     	    if (strcmp(name, Tk_PathName(menuPtr->tkwin)) == 0) {
2741     	    	if (mePtr->state == ENTRY_DISABLED) {
2742     	    	    parentDisabled = 1;
2743     	    	}
2744     	    	break;
2745     	    }
2746     	}
2747 
2748 	if (((parentDisabled || (mePtr->state == ENTRY_DISABLED)))
2749 		&& (menuPtr->disabledFgPtr != NULL)) {
2750 	    gc = mePtr->disabledGC;
2751 	    if (gc == NULL) {
2752 		gc = menuPtr->disabledGC;
2753 	    }
2754 	} else {
2755 	    gc = mePtr->textGC;
2756 	    if (gc == NULL) {
2757 		gc = menuPtr->textGC;
2758 	    }
2759 	}
2760     }
2761     indicatorGC = mePtr->indicatorGC;
2762     if (indicatorGC == NULL) {
2763 	indicatorGC = menuPtr->indicatorGC;
2764     }
2765 
2766     bgBorder = Tk_Get3DBorderFromObj(menuPtr->tkwin,
2767 	    (mePtr->borderPtr == NULL) ? menuPtr->borderPtr
2768 	    : mePtr->borderPtr);
2769     if (strictMotif) {
2770 	activeBorder = bgBorder;
2771     } else {
2772 	activeBorder = Tk_Get3DBorderFromObj(menuPtr->tkwin,
2773 	    (mePtr->activeBorderPtr == NULL) ? menuPtr->activeBorderPtr
2774 	    : mePtr->activeBorderPtr);
2775     }
2776 
2777     if (mePtr->fontPtr == NULL) {
2778 	fmPtr = menuMetricsPtr;
2779     } else {
2780 	tkfont = Tk_GetFontFromObj(menuPtr->tkwin, mePtr->fontPtr);
2781 	Tk_GetFontMetrics(tkfont, &entryMetrics);
2782 	fmPtr = &entryMetrics;
2783     }
2784 
2785     /*
2786      * Need to draw the entire background, including padding. On Unix, for
2787      * menubars, we have to draw the rest of the entry taking into account the
2788      * padding.
2789      */
2790 
2791     DrawMenuEntryBackground(menuPtr, mePtr, d, activeBorder,
2792 	    bgBorder, adjustedX, adjustedY-padY, width, height);
2793 
2794     if (mePtr->type == SEPARATOR_ENTRY) {
2795 	DrawMenuSeparator(menuPtr, mePtr, d, gc, tkfont,
2796 		fmPtr, adjustedX, adjustedY, width, adjustedHeight);
2797     } else if (mePtr->type == TEAROFF_ENTRY) {
2798 	DrawTearoffEntry(menuPtr, mePtr, d, gc, tkfont, fmPtr,
2799 		adjustedX, adjustedY, width, adjustedHeight);
2800     } else {
2801 	DrawMenuEntryLabel(menuPtr, mePtr, d, gc, tkfont, fmPtr,
2802 		adjustedX, adjustedY, width, adjustedHeight,
2803 		(drawingParameters & DRAW_MENU_ENTRY_NOUNDERLINE)?0:1);
2804 	DrawMenuEntryAccelerator(menuPtr, mePtr, d, gc, tkfont, fmPtr,
2805 		activeBorder, adjustedX, adjustedY, width, adjustedHeight);
2806 	DrawMenuEntryArrow(menuPtr, mePtr, d, gc,
2807 		activeBorder, adjustedX, adjustedY, width, adjustedHeight,
2808 		(drawingParameters & DRAW_MENU_ENTRY_ARROW)?1:0);
2809 	if (!mePtr->hideMargin) {
2810 	    DrawMenuEntryIndicator(menuPtr, mePtr, d, gc, indicatorGC, tkfont,
2811 		    fmPtr, adjustedX, adjustedY, width, adjustedHeight);
2812 	}
2813     }
2814 
2815     /*
2816      * Copy the entry contents from the temporary bitmap to the menu.
2817      */
2818 
2819     if (mePtr->image != NULL) {
2820 	BitBlt(menuDc, x, y, width, height, memDc, 0, 0, SRCCOPY);
2821 	DeleteObject(SelectObject(memDc, oldBitmap));
2822 	DeleteDC(memDc);
2823 
2824 	TkWinReleaseDrawableDC(menuDrawable, menuDc, &dcState);
2825     }
2826 }
2827 
2828 /*
2829  *----------------------------------------------------------------------
2830  *
2831  * GetMenuLabelGeometry --
2832  *
2833  *	Figures out the size of the label portion of a menu item.
2834  *
2835  * Results:
2836  *	widthPtr and heightPtr are filled in with the correct geometry
2837  *	information.
2838  *
2839  * Side effects:
2840  *	None.
2841  *
2842  *----------------------------------------------------------------------
2843  */
2844 
2845 static void
GetMenuLabelGeometry(TkMenuEntry * mePtr,Tk_Font tkfont,const Tk_FontMetrics * fmPtr,int * widthPtr,int * heightPtr)2846 GetMenuLabelGeometry(
2847     TkMenuEntry *mePtr,		/* The entry we are computing */
2848     Tk_Font tkfont,		/* The precalculated font */
2849     const Tk_FontMetrics *fmPtr,/* The precalculated metrics */
2850     int *widthPtr,		/* The resulting width of the label portion */
2851     int *heightPtr)		/* The resulting height of the label
2852 				 * portion */
2853 {
2854     TkMenu *menuPtr = mePtr->menuPtr;
2855     int haveImage = 0;
2856 
2857     if (mePtr->image != NULL) {
2858     	Tk_SizeOfImage(mePtr->image, widthPtr, heightPtr);
2859 	haveImage = 1;
2860     } else if (mePtr->bitmapPtr != NULL) {
2861 	Pixmap bitmap = Tk_GetBitmapFromObj(menuPtr->tkwin, mePtr->bitmapPtr);
2862 
2863     	Tk_SizeOfBitmap(menuPtr->display, bitmap, widthPtr, heightPtr);
2864 	haveImage = 1;
2865     } else {
2866 	*heightPtr = 0;
2867 	*widthPtr = 0;
2868     }
2869 
2870     if (haveImage && (mePtr->compound == COMPOUND_NONE)) {
2871 	/*
2872 	 * We don't care about the text in this case.
2873 	 */
2874     } else {
2875 	/*
2876 	 * Either it is compound or we don't have an image,
2877 	 */
2878 
2879     	if (mePtr->labelPtr != NULL) {
2880 	    int textWidth;
2881 	    const char *label = Tcl_GetString(mePtr->labelPtr);
2882 
2883 	    textWidth = Tk_TextWidth(tkfont, label, mePtr->labelLength);
2884 
2885 	    if ((mePtr->compound != COMPOUND_NONE) && haveImage) {
2886 		switch ((enum compound) mePtr->compound) {
2887 		case COMPOUND_TOP:
2888 		case COMPOUND_BOTTOM:
2889 		    if (textWidth > *widthPtr) {
2890 			*widthPtr = textWidth;
2891 		    }
2892 
2893 		    /*
2894 		     * Add text and padding.
2895 		     */
2896 
2897 		    *heightPtr += fmPtr->linespace + 2;
2898 		    break;
2899 		case COMPOUND_LEFT:
2900 		case COMPOUND_RIGHT:
2901 		    if (fmPtr->linespace > *heightPtr) {
2902 			*heightPtr = fmPtr->linespace;
2903 		    }
2904 
2905 		    /*
2906 		     * Add text and padding.
2907 		     */
2908 
2909 		    *widthPtr += textWidth + 2;
2910 		    break;
2911 		case COMPOUND_CENTER:
2912 		    if (fmPtr->linespace > *heightPtr) {
2913 			*heightPtr = fmPtr->linespace;
2914 		    }
2915 		    if (textWidth > *widthPtr) {
2916 			*widthPtr = textWidth;
2917 		    }
2918 		    break;
2919 		case COMPOUND_NONE:
2920 		    break;
2921 		}
2922 	    } else {
2923 		/*
2924 		 * We don't have an image or we're not compound.
2925 		 */
2926 
2927 		*heightPtr = fmPtr->linespace;
2928 		*widthPtr = textWidth;
2929 	    }
2930 	} else {
2931 	    /*
2932 	     * An empty entry still has this height.
2933 	     */
2934 
2935 	    *heightPtr = fmPtr->linespace;
2936     	}
2937     }
2938     *heightPtr += 1;
2939 }
2940 
2941 /*
2942  *----------------------------------------------------------------------
2943  *
2944  * DrawMenuEntryBackground --
2945  *
2946  *	This function draws the background part of a menu.
2947  *
2948  * Results:
2949  *	None.
2950  *
2951  * Side effects:
2952  *	Commands are output to X to display the menu in its current mode.
2953  *
2954  *----------------------------------------------------------------------
2955  */
2956 
2957 static void
DrawMenuEntryBackground(TkMenu * menuPtr,TkMenuEntry * mePtr,Drawable d,Tk_3DBorder activeBorder,Tk_3DBorder bgBorder,int x,int y,int width,int height)2958 DrawMenuEntryBackground(
2959     TkMenu *menuPtr,		/* The menu we are drawing. */
2960     TkMenuEntry *mePtr,		/* The entry we are drawing. */
2961     Drawable d,			/* What we are drawing into */
2962     Tk_3DBorder activeBorder,	/* Border for active items */
2963     Tk_3DBorder bgBorder,	/* Border for the background */
2964     int x,			/* left edge */
2965     int y,			/* top edge */
2966     int width,			/* width of rectangle to draw */
2967     int height)			/* height of rectangle to draw */
2968 {
2969     if (mePtr->state == ENTRY_ACTIVE
2970 		|| (mePtr->entryFlags & ENTRY_PLATFORM_FLAG1)!=0 ) {
2971 	bgBorder = activeBorder;
2972     }
2973     Tk_Fill3DRectangle(menuPtr->tkwin, d, bgBorder, x, y, width, height, 0,
2974 	    TK_RELIEF_FLAT);
2975 }
2976 
2977 /*
2978  *--------------------------------------------------------------
2979  *
2980  * TkpComputeStandardMenuGeometry --
2981  *
2982  *	This function is invoked to recompute the size and layout of a menu
2983  *	that is not a menubar clone.
2984  *
2985  * Results:
2986  *	None.
2987  *
2988  * Side effects:
2989  *	Fields of menu entries are changed to reflect their current positions,
2990  *	and the size of the menu window itself may be changed.
2991  *
2992  *--------------------------------------------------------------
2993  */
2994 
2995 void
TkpComputeStandardMenuGeometry(TkMenu * menuPtr)2996 TkpComputeStandardMenuGeometry(
2997     TkMenu *menuPtr)		/* Structure describing menu. */
2998 {
2999     Tk_Font menuFont, tkfont;
3000     Tk_FontMetrics menuMetrics, entryMetrics, *fmPtr;
3001     int x, y, height, width, indicatorSpace, labelWidth, accelWidth;
3002     int windowWidth, windowHeight, accelSpace;
3003     int i, j, lastColumnBreak = 0;
3004     int activeBorderWidth, borderWidth;
3005 
3006     if (menuPtr->tkwin == NULL) {
3007 	return;
3008     }
3009 
3010     Tk_GetPixelsFromObj(menuPtr->interp, menuPtr->tkwin,
3011 	    menuPtr->borderWidthPtr, &borderWidth);
3012     x = y = borderWidth;
3013     indicatorSpace = labelWidth = accelWidth = 0;
3014     windowHeight = 0;
3015 
3016     /*
3017      * On the Mac especially, getting font metrics can be quite slow, so we
3018      * want to do it intelligently. We are going to precalculate them and pass
3019      * them down to all of the measuring and drawing routines. We will measure
3020      * the font metrics of the menu once. If an entry does not have its own
3021      * font set, then we give the geometry/drawing routines the menu's font
3022      * and metrics. If an entry has its own font, we will measure that font
3023      * and give all of the geometry/drawing the entry's font and metrics.
3024      */
3025 
3026     menuFont = Tk_GetFontFromObj(menuPtr->tkwin, menuPtr->fontPtr);
3027     Tk_GetFontMetrics(menuFont, &menuMetrics);
3028     accelSpace = Tk_TextWidth(menuFont, "M", 1);
3029     Tk_GetPixelsFromObj(menuPtr->interp, menuPtr->tkwin,
3030 	    menuPtr->activeBorderWidthPtr, &activeBorderWidth);
3031 
3032     for (i = 0; i < menuPtr->numEntries; i++) {
3033 	if (menuPtr->entries[i]->fontPtr == NULL) {
3034 	    tkfont = menuFont;
3035 	    fmPtr = &menuMetrics;
3036 	} else {
3037 	    tkfont = Tk_GetFontFromObj(menuPtr->tkwin,
3038 		    menuPtr->entries[i]->fontPtr);
3039     	    Tk_GetFontMetrics(tkfont, &entryMetrics);
3040     	    fmPtr = &entryMetrics;
3041     	}
3042 	if ((i > 0) && menuPtr->entries[i]->columnBreak) {
3043 	    if (accelWidth != 0) {
3044 		labelWidth += accelSpace;
3045 	    }
3046 	    for (j = lastColumnBreak; j < i; j++) {
3047 		menuPtr->entries[j]->indicatorSpace = indicatorSpace;
3048 		menuPtr->entries[j]->labelWidth = labelWidth;
3049 		menuPtr->entries[j]->width = indicatorSpace + labelWidth
3050 			+ accelWidth + 2 * activeBorderWidth;
3051 		menuPtr->entries[j]->x = x;
3052 		menuPtr->entries[j]->entryFlags &= ~ENTRY_LAST_COLUMN;
3053 	    }
3054 	    x += indicatorSpace + labelWidth + accelWidth
3055 		    + 2 * activeBorderWidth;
3056 	    indicatorSpace = labelWidth = accelWidth = 0;
3057 	    lastColumnBreak = i;
3058 	    y = borderWidth;
3059 	}
3060 
3061 	if (menuPtr->entries[i]->type == SEPARATOR_ENTRY) {
3062 	    GetMenuSeparatorGeometry(menuPtr, menuPtr->entries[i], tkfont,
3063 	    	    fmPtr, &width, &height);
3064 	    menuPtr->entries[i]->height = height;
3065 	} else if (menuPtr->entries[i]->type == TEAROFF_ENTRY) {
3066 	    GetTearoffEntryGeometry(menuPtr, menuPtr->entries[i], tkfont,
3067 	    	    fmPtr, &width, &height);
3068 	    menuPtr->entries[i]->height = height;
3069 	} else {
3070 	    /*
3071 	     * For each entry, compute the height required by that particular
3072 	     * entry, plus three widths: the width of the label, the width to
3073 	     * allow for an indicator to be displayed to the left of the label
3074 	     * (if any), and the width of the accelerator to be displayed to
3075 	     * the right of the label (if any). These sizes depend, of course,
3076 	     * on the type of the entry.
3077 	     */
3078 
3079 	    GetMenuLabelGeometry(menuPtr->entries[i], tkfont, fmPtr, &width,
3080 	    	    &height);
3081 	    menuPtr->entries[i]->height = height;
3082 	    if (width > labelWidth) {
3083 	    	labelWidth = width;
3084 	    }
3085 
3086 	    GetMenuAccelGeometry(menuPtr, menuPtr->entries[i], tkfont,
3087 		    fmPtr, &width, &height);
3088 	    if (height > menuPtr->entries[i]->height) {
3089 	    	menuPtr->entries[i]->height = height;
3090 	    }
3091 	    if (width > accelWidth) {
3092 	    	accelWidth = width;
3093 	    }
3094 
3095 	    GetMenuIndicatorGeometry(menuPtr, menuPtr->entries[i], tkfont,
3096 	    	    fmPtr, &width, &height);
3097 	    if (height > menuPtr->entries[i]->height) {
3098 	    	menuPtr->entries[i]->height = height;
3099 	    }
3100 	    if (width > indicatorSpace) {
3101 	    	indicatorSpace = width;
3102 	    }
3103 
3104 	    menuPtr->entries[i]->height += 2 * activeBorderWidth + 1;
3105     	}
3106 	menuPtr->entries[i]->y = y;
3107 	y += menuPtr->entries[i]->height;
3108 	if (y > windowHeight) {
3109 	    windowHeight = y;
3110 	}
3111     }
3112 
3113     if (accelWidth != 0) {
3114 	labelWidth += accelSpace;
3115     }
3116     for (j = lastColumnBreak; j < menuPtr->numEntries; j++) {
3117 	menuPtr->entries[j]->indicatorSpace = indicatorSpace;
3118 	menuPtr->entries[j]->labelWidth = labelWidth;
3119 	menuPtr->entries[j]->width = indicatorSpace + labelWidth
3120 		+ accelWidth + 2 * activeBorderWidth;
3121 	menuPtr->entries[j]->x = x;
3122 	menuPtr->entries[j]->entryFlags |= ENTRY_LAST_COLUMN;
3123     }
3124     windowWidth = x + indicatorSpace + labelWidth + accelWidth
3125 	    + 2 * activeBorderWidth + borderWidth;
3126     windowHeight += borderWidth;
3127 
3128     /*
3129      * The X server doesn't like zero dimensions, so round up to at least 1 (a
3130      * zero-sized menu should never really occur, anyway).
3131      */
3132 
3133     if (windowWidth <= 0) {
3134 	windowWidth = 1;
3135     }
3136     if (windowHeight <= 0) {
3137 	windowHeight = 1;
3138     }
3139     menuPtr->totalWidth = windowWidth;
3140     menuPtr->totalHeight = windowHeight;
3141 }
3142 
3143 /*
3144  *----------------------------------------------------------------------
3145  *
3146  * MenuSelectEvent --
3147  *
3148  *	Generates a "MenuSelect" virtual event. This can be used to do
3149  *	context-sensitive menu help.
3150  *
3151  * Results:
3152  *	None.
3153  *
3154  * Side effects:
3155  *	Places a virtual event on the event queue.
3156  *
3157  *----------------------------------------------------------------------
3158  */
3159 
3160 static void
MenuSelectEvent(TkMenu * menuPtr)3161 MenuSelectEvent(
3162     TkMenu *menuPtr)		/* the menu we have selected. */
3163 {
3164     union {XEvent general; XVirtualEvent virt;} event;
3165     union {DWORD msgpos; POINTS point;} root;
3166 
3167     memset(&event, 0, sizeof(event));
3168     event.virt.type = VirtualEvent;
3169     event.virt.serial = menuPtr->display->request;
3170     event.virt.send_event = 0;
3171     event.virt.display = menuPtr->display;
3172     Tk_MakeWindowExist(menuPtr->tkwin);
3173     event.virt.event = Tk_WindowId(menuPtr->tkwin);
3174     event.virt.root = XRootWindow(menuPtr->display, 0);
3175     event.virt.subwindow = None;
3176     event.virt.time = TkpGetMS();
3177 
3178     root.msgpos = GetMessagePos();
3179     event.virt.x_root = root.point.x;
3180     event.virt.y_root = root.point.y;
3181     event.virt.state = TkWinGetModifierState();
3182     event.virt.same_screen = 1;
3183     event.virt.name = Tk_GetUid("MenuSelect");
3184     event.virt.user_data = NULL;
3185     Tk_QueueWindowEvent(&event.general, TCL_QUEUE_TAIL);
3186 }
3187 
3188 /*
3189  *----------------------------------------------------------------------
3190  *
3191  * TkpMenuNotifyToplevelCreate --
3192  *
3193  *	This routine reconfigures the menu and the clones indicated by
3194  *	menuName becuase a toplevel has been created and any system menus need
3195  *	to be created.
3196  *
3197  * Results:
3198  *	None.
3199  *
3200  * Side effects:
3201  *	An idle handler is set up to do the reconfiguration.
3202  *
3203  *----------------------------------------------------------------------
3204  */
3205 
3206 void
TkpMenuNotifyToplevelCreate(Tcl_Interp * interp,const char * menuName)3207 TkpMenuNotifyToplevelCreate(
3208     Tcl_Interp *interp,		/* The interp the menu lives in. */
3209     const char *menuName)	/* The name of the menu to reconfigure. */
3210 {
3211     TkMenuReferences *menuRefPtr;
3212     TkMenu *menuPtr;
3213 
3214     if ((menuName != NULL) && (menuName[0] != '\0')) {
3215 	menuRefPtr = TkFindMenuReferences(interp, menuName);
3216 	if ((menuRefPtr != NULL) && (menuRefPtr->menuPtr != NULL)) {
3217 	    for (menuPtr = menuRefPtr->menuPtr->masterMenuPtr; menuPtr != NULL;
3218 		    menuPtr = menuPtr->nextInstancePtr) {
3219 		if (menuPtr->menuType == MENUBAR) {
3220 		    ScheduleMenuReconfigure(menuPtr);
3221 		}
3222 	    }
3223 	}
3224     }
3225 }
3226 
3227 /*
3228  *----------------------------------------------------------------------
3229  *
3230  * Tk_GetMenuHWND --
3231  *
3232  *	This function returns the HWND of a hidden menu Window that processes
3233  *	messages of a popup menu. This hidden menu window is used to handle
3234  *	either a dynamic popup menu in the same process or a pull-down menu of
3235  *	an embedded window in a different process.
3236  *
3237  * Results:
3238  *	Returns the HWND of the hidden menu Window.
3239  *
3240  * Side effects:
3241  *	None.
3242  *
3243  *----------------------------------------------------------------------
3244  */
3245 
3246 HWND
Tk_GetMenuHWND(Tk_Window tkwin)3247 Tk_GetMenuHWND(
3248     Tk_Window tkwin)
3249 {
3250     ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
3251 	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
3252     (void)tkwin;
3253 
3254     TkMenuInit();
3255     return tsdPtr->embeddedMenuHWND;
3256 }
3257 
3258 /*
3259  *----------------------------------------------------------------------
3260  *
3261  * MenuExitHandler --
3262  *
3263  *	Unregisters the class of utility windows.
3264  *
3265  * Results:
3266  *	None.
3267  *
3268  * Side effects:
3269  *	Menus have to be reinitialized next time.
3270  *
3271  *----------------------------------------------------------------------
3272  */
3273 
3274 static void
MenuExitHandler(ClientData dummy)3275 MenuExitHandler(
3276     ClientData dummy)	    /* Not used */
3277 {
3278     (void)dummy;
3279 
3280     UnregisterClassW(MENU_CLASS_NAME, Tk_GetHINSTANCE());
3281     UnregisterClassW(EMBEDDED_MENU_CLASS_NAME, Tk_GetHINSTANCE());
3282 }
3283 
3284 /*
3285  *----------------------------------------------------------------------
3286  *
3287  * MenuExitHandler --
3288  *
3289  *	Throws away the utility window needed for menus and delete hash
3290  *	tables.
3291  *
3292  * Results:
3293  *	None.
3294  *
3295  * Side effects:
3296  *	Menus have to be reinitialized next time.
3297  *
3298  *----------------------------------------------------------------------
3299  */
3300 
3301 static void
MenuThreadExitHandler(ClientData dummy)3302 MenuThreadExitHandler(
3303     ClientData dummy)	    /* Not used */
3304 {
3305     ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
3306 	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
3307     (void)dummy;
3308 
3309     DestroyWindow(tsdPtr->menuHWND);
3310     DestroyWindow(tsdPtr->embeddedMenuHWND);
3311     tsdPtr->menuHWND = NULL;
3312     tsdPtr->embeddedMenuHWND = NULL;
3313 
3314     Tcl_DeleteHashTable(&tsdPtr->winMenuTable);
3315     Tcl_DeleteHashTable(&tsdPtr->commandTable);
3316 }
3317 
3318 /*
3319  *----------------------------------------------------------------------
3320  *
3321  * TkWinGetMenuSystemDefault --
3322  *
3323  *	Gets the Windows specific default value for a given X resource
3324  *	database name.
3325  *
3326  * Results:
3327  *	Returns a Tcl_Obj* with the default value. If there is no
3328  *	Windows-specific default for this attribute, returns NULL. This object
3329  *	has a ref count of 0.
3330  *
3331  * Side effects:
3332  *	Storage is allocated.
3333  *
3334  *----------------------------------------------------------------------
3335  */
3336 
3337 Tcl_Obj *
TkWinGetMenuSystemDefault(Tk_Window tkwin,const char * dbName,const char * className)3338 TkWinGetMenuSystemDefault(
3339     Tk_Window tkwin,		/* A window to use. */
3340     const char *dbName,		/* The option database name. */
3341     const char *className)	/* The name of the option class. */
3342 {
3343     Tcl_Obj *valuePtr = NULL;
3344     (void)tkwin;
3345     (void)className;
3346 
3347     if ((strcmp(dbName, "activeBorderWidth") == 0) ||
3348 	    (strcmp(dbName, "borderWidth") == 0)) {
3349 	valuePtr = Tcl_NewIntObj(defaultBorderWidth);
3350     } else if (strcmp(dbName, "font") == 0) {
3351 	valuePtr = Tcl_NewStringObj(Tcl_DStringValue(&menuFontDString), -1);
3352     }
3353 
3354     return valuePtr;
3355 }
3356 
3357 /*
3358  *----------------------------------------------------------------------
3359  *
3360  * SetDefaults --
3361  *
3362  *	Read system menu settings (font, sizes of items, use of accelerators)
3363  *	This is called if the UI theme or settings are changed.
3364  *
3365  * Results:
3366  *	None.
3367  *
3368  * Side effects:
3369  *	May result in menu items being redrawn with different appearance.
3370  *
3371  *----------------------------------------------------------------------
3372  */
3373 
3374 static void
SetDefaults(int firstTime)3375 SetDefaults(
3376     int firstTime)		/* Is this the first time this has been
3377 				 * called? */
3378 {
3379     char sizeString[TCL_INTEGER_SPACE];
3380     char faceName[LF_FACESIZE];
3381     HDC scratchDC;
3382     int bold = 0;
3383     int italic = 0;
3384     TEXTMETRICW tm;
3385     int pointSize;
3386     HFONT menuFont;
3387     /* See: [Bug #3239768] tk8.4.19 (and later) WIN32 menu font support */
3388     struct {
3389         NONCLIENTMETRICSW metrics;
3390 #if (WINVER < 0x0600)
3391         int padding;
3392 #endif
3393     } nc;
3394 
3395     /*
3396      * Set all of the default options. The loop will terminate when we run out
3397      * of options via a break statement.
3398      */
3399 
3400     defaultBorderWidth = GetSystemMetrics(SM_CXBORDER);
3401     if (GetSystemMetrics(SM_CYBORDER) > defaultBorderWidth) {
3402 	defaultBorderWidth = GetSystemMetrics(SM_CYBORDER);
3403     }
3404 
3405     scratchDC = CreateDCW(L"DISPLAY", NULL, NULL, NULL);
3406     if (!firstTime) {
3407 	Tcl_DStringFree(&menuFontDString);
3408     }
3409     Tcl_DStringInit(&menuFontDString);
3410 
3411     nc.metrics.cbSize = sizeof(nc);
3412 
3413     if (TkWinGetPlatformTheme() != TK_THEME_WIN_VISTA) {
3414 	nc.metrics.cbSize -= sizeof(int);
3415     }
3416 
3417     SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, nc.metrics.cbSize,
3418 	    &nc.metrics, 0);
3419     menuFont = CreateFontIndirectW(&nc.metrics.lfMenuFont);
3420     SelectObject(scratchDC, menuFont);
3421     GetTextMetricsW(scratchDC, &tm);
3422     GetTextFaceA(scratchDC, LF_FACESIZE, faceName);
3423     pointSize = MulDiv(tm.tmHeight - tm.tmInternalLeading,
3424 	    72, GetDeviceCaps(scratchDC, LOGPIXELSY));
3425     if (tm.tmWeight >= 700) {
3426 	bold = 1;
3427     }
3428     if (tm.tmItalic) {
3429 	italic = 1;
3430     }
3431 
3432     SelectObject(scratchDC, GetStockObject(SYSTEM_FONT));
3433     DeleteDC(scratchDC);
3434 
3435     DeleteObject(menuFont);
3436 
3437     Tcl_DStringAppendElement(&menuFontDString, faceName);
3438     sprintf(sizeString, "%d", pointSize);
3439     Tcl_DStringAppendElement(&menuFontDString, sizeString);
3440 
3441     if (bold || italic) {
3442 	Tcl_DString boldItalicDString;
3443 
3444 	Tcl_DStringInit(&boldItalicDString);
3445 	if (bold) {
3446 	    Tcl_DStringAppendElement(&boldItalicDString, "bold");
3447 	}
3448 	if (italic) {
3449 	    Tcl_DStringAppendElement(&boldItalicDString, "italic");
3450 	}
3451 	Tcl_DStringAppendElement(&menuFontDString,
3452 		Tcl_DStringValue(&boldItalicDString));
3453 	Tcl_DStringFree(&boldItalicDString);
3454     }
3455 
3456     /*
3457      * Now we go ahead and get the dimensions of the check mark and the
3458      * appropriate margins. Since this is fairly hairy, we do it here to save
3459      * time when traversing large sets of menu items.
3460      *
3461      * The code below was given to me by Microsoft over the phone. It is the
3462      * only way to ensure menu items line up, and is not documented.
3463      * How strange the calculation of indicatorDimensions[1] is...!
3464      */
3465 
3466     indicatorDimensions[0] = GetSystemMetrics(SM_CYMENUCHECK);
3467     indicatorDimensions[1] = ((GetSystemMetrics(SM_CXFIXEDFRAME) +
3468 	    GetSystemMetrics(SM_CXBORDER)
3469 	    + GetSystemMetrics(SM_CXMENUCHECK) + 7) & 0xFFF8)
3470 	    - GetSystemMetrics(SM_CXFIXEDFRAME);
3471 
3472     /*
3473      * Accelerators used to be always underlines until Win2K when a system
3474      * parameter was introduced to hide them unless Alt is pressed.
3475      */
3476 
3477     showMenuAccelerators = TRUE;
3478     SystemParametersInfoW(SPI_GETKEYBOARDCUES, 0, &showMenuAccelerators, 0);
3479 }
3480 
3481 /*
3482  *----------------------------------------------------------------------
3483  *
3484  * TkpMenuInit --
3485  *
3486  *	Sets up the process-wide variables used by the menu package.
3487  *
3488  * Results:
3489  *	None.
3490  *
3491  * Side effects:
3492  *	lastMenuID gets initialized.
3493  *
3494  *----------------------------------------------------------------------
3495  */
3496 
3497 void
TkpMenuInit(void)3498 TkpMenuInit(void)
3499 {
3500     WNDCLASSW wndClass;
3501 
3502     wndClass.style = CS_OWNDC;
3503     wndClass.lpfnWndProc = TkWinMenuProc;
3504     wndClass.cbClsExtra = 0;
3505     wndClass.cbWndExtra = 0;
3506     wndClass.hInstance = Tk_GetHINSTANCE();
3507     wndClass.hIcon = NULL;
3508     wndClass.hCursor = NULL;
3509     wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
3510     wndClass.lpszMenuName = NULL;
3511     wndClass.lpszClassName = MENU_CLASS_NAME;
3512     if (!RegisterClassW(&wndClass)) {
3513 	Tcl_Panic("Failed to register menu window class");
3514     }
3515 
3516     wndClass.lpfnWndProc = TkWinEmbeddedMenuProc;
3517     wndClass.lpszClassName = EMBEDDED_MENU_CLASS_NAME;
3518     if (!RegisterClassW(&wndClass)) {
3519 	Tcl_Panic("Failed to register embedded menu window class");
3520     }
3521 
3522     TkCreateExitHandler(MenuExitHandler, NULL);
3523     SetDefaults(1);
3524 }
3525 
3526 /*
3527  *----------------------------------------------------------------------
3528  *
3529  * TkpMenuThreadInit --
3530  *
3531  *	Sets up the thread-local hash tables used by the menu module. Assumes
3532  *	that TkpMenuInit has been called.
3533  *
3534  * Results:
3535  *	None.
3536  *
3537  * Side effects:
3538  *	Hash tables winMenuTable and commandTable are initialized.
3539  *
3540  *----------------------------------------------------------------------
3541  */
3542 
3543 void
TkpMenuThreadInit(void)3544 TkpMenuThreadInit(void)
3545 {
3546     ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
3547 	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
3548 
3549     tsdPtr->menuHWND = CreateWindowW(MENU_CLASS_NAME, L"MenuWindow", WS_POPUP,
3550 	    0, 0, 10, 10, NULL, NULL, Tk_GetHINSTANCE(), NULL);
3551 
3552     if (!tsdPtr->menuHWND) {
3553 	Tcl_Panic("Failed to create the menu window");
3554     }
3555 
3556     tsdPtr->embeddedMenuHWND =
3557 	    CreateWindowW(EMBEDDED_MENU_CLASS_NAME, L"EmbeddedMenuWindow",
3558 	    WS_POPUP, 0, 0, 10, 10, NULL, NULL, Tk_GetHINSTANCE(), NULL);
3559 
3560     if (!tsdPtr->embeddedMenuHWND) {
3561 	Tcl_Panic("Failed to create the embedded menu window");
3562     }
3563 
3564     Tcl_InitHashTable(&tsdPtr->winMenuTable, TCL_ONE_WORD_KEYS);
3565     Tcl_InitHashTable(&tsdPtr->commandTable, TCL_ONE_WORD_KEYS);
3566 
3567     TkCreateThreadExitHandler(MenuThreadExitHandler, NULL);
3568 }
3569 
3570 /*
3571  * Local Variables:
3572  * mode: c
3573  * c-basic-offset: 4
3574  * fill-column: 78
3575  * End:
3576  */
3577