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