OSDN Git Service

Please enter the commit message for your changes. Lines starting
[eos/base.git] / util / src / TclTk / tk8.6.12 / win / tkWinMenu.c
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 \f
191 static inline void
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
202 CallPendingReconfigureImmediately(
203     TkMenu *menuPtr)
204 {
205     if (menuPtr->menuFlags & MENU_RECONFIGURE_PENDING) {
206         Tcl_CancelIdleCall(ReconfigureWindowsMenu, menuPtr);
207         ReconfigureWindowsMenu(menuPtr);
208     }
209 }
210 \f
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
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 \f
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
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 \f
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
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 \f
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
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 \f
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
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 \f
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 *
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 \f
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
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 \f
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
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 \f
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
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 \f
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
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 \f
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
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 \f
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
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 \f
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
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 \f
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
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 \f
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
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 \f
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
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 \f
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
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 \f
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
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 \f
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
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 \f
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
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 \f
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
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 \f
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
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 \f
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
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 \f
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
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 \f
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
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 \f
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
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 \f
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
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 \f
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
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 \f
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
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 \f
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
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 \f
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
2560 TkpComputeMenubarGeometry(
2561     TkMenu *menuPtr)            /* Structure describing menu. */
2562 {
2563     TkpComputeStandardMenuGeometry(menuPtr);
2564 }
2565 \f
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
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 \f
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
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 \f
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
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 \f
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
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 \f
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
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 \f
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
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 \f
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
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 \f
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
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 \f
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
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 \f
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
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 \f
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
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 \f
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 *
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 \f
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
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 \f
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
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 \f
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
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 \f
3570 /*
3571  * Local Variables:
3572  * mode: c
3573  * c-basic-offset: 4
3574  * fill-column: 78
3575  * End:
3576  */