update for HEAD-2003091401
[reactos.git] / subsys / system / explorer / Seashell / SeaShellExt / UIMenuBar.cpp
1 ////////////////////////////////////////////////////////////////
2 // Copyright 1998 Paul DiLascia
3 // If this code works, it was written by Paul DiLascia.
4 // If not, I don't know who wrote it.
5 //
6 // CMenuBar implements menu bar for MFC. See MenuBar.h for how
7 // to use, and also the MBTest sample application.
8 //
9 #include "StdAfx.h"
10 #include "UIMenuBar.h"
11
12 const UINT MB_SET_MENU_NULL = WM_USER + 1100;
13
14 #ifdef _DEBUG
15 #define new DEBUG_NEW
16 #undef THIS_FILE
17 static char THIS_FILE[] = __FILE__;
18 #endif
19
20 // if you want to see extra TRACE diagnostics, set CMenuBar::bTRACE = TRUE
21 BOOL CMenuBar::bTRACE = FALSE;
22
23 #ifdef _DEBUG
24 #define MBTRACEFN                       \
25         CTraceFn __fooble;      \
26         if (CMenuBar::bTRACE)\
27                 TRACE
28 #define MBTRACE                 \
29         if (CMenuBar::bTRACE)\
30                 TRACE
31 #else
32 #define MBTRACEFN TRACE
33 #define MBTRACE   TRACE
34 #endif
35
36 IMPLEMENT_DYNAMIC(CMenuBar, CFlatToolBar)
37
38 BEGIN_MESSAGE_MAP(CMenuBar, CFlatToolBar)
39         ON_WM_CREATE()
40         ON_WM_LBUTTONDOWN()
41         ON_WM_MOUSEMOVE()
42         ON_WM_SIZE()
43         ON_UPDATE_COMMAND_UI_RANGE(0, 256, OnUpdateMenuButton)
44         ON_MESSAGE(MB_SET_MENU_NULL, OnSetMenuNull)
45 END_MESSAGE_MAP()
46
47 CMenuBar::CMenuBar()
48 {
49         if (iVerComCtl32 <= 470)
50                 AfxMessageBox(_T("Warning: This program requires comctl32.dll version 4.71 or greater."));
51
52         m_iTrackingState = TRACK_NONE;           // initial state: not tracking 
53         m_iPopupTracking = m_iNewPopup = -1; // invalid
54         m_hmenu = NULL;
55         m_hMenuTracking = NULL;
56         m_bAutoRemoveFrameMenu = TRUE;           // set frame's menu to NULL
57 }
58
59 CMenuBar::~CMenuBar()
60 {
61 }
62
63 //////////////////
64 // Menu bar was created: install hook into owner window
65 //
66 int CMenuBar::OnCreate(LPCREATESTRUCT lpCreateStruct)
67 {
68         if (CFlatToolBar::OnCreate(lpCreateStruct)==-1)
69                 return -1;
70         UpdateFont();
71         CWnd* pFrame = GetOwner();
72         ASSERT_VALID(pFrame);
73         m_frameHook.Install(this, *pFrame);
74         return 0; // OK
75 }
76
77 //////////////////
78 // Set menu bar font from current system menu font
79 //
80 void CMenuBar::UpdateFont()
81 {
82         static CFont font;
83         NONCLIENTMETRICS info;
84         info.cbSize = sizeof(info);
85         SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(info), &info, 0);
86         if ((HFONT)font)
87                 font.DeleteObject();
88         VERIFY(font.CreateFontIndirect(&info.lfMenuFont));
89         SetFont(&font);
90 }
91
92 //////////////////
93 // The reason for having this is so MFC won't automatically disable
94 // the menu buttons. Assumes < 256 top-level menu items. The ID of
95 // the ith menu button is i. IOW, the index and ID are the same.
96 //
97 void CMenuBar::OnUpdateMenuButton(CCmdUI* pCmdUI)
98 {
99         ASSERT_VALID(this);
100         if (IsValidButton(pCmdUI->m_nID))
101                 pCmdUI->Enable(TRUE);
102 }
103
104 //////////////////
105 // Recompute layout of menu bar
106 //
107 void CMenuBar::RecomputeMenuLayout()
108 {
109         SetWindowPos(NULL, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
110                 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER);
111 }
112
113 //////////////////
114 // Make frame recalculate control bar sizes after menu change
115 //
116 void CMenuBar::RecomputeToolbarSize()
117 {
118         // Force toolbar to recompute size
119         CFrameWnd* pFrame = (CFrameWnd*)GetOwner();
120         ASSERT_VALID(pFrame);
121         ASSERT(pFrame->IsFrameWnd());
122         pFrame->RecalcLayout();
123
124         // floating frame
125         pFrame = GetParentFrame();
126         if (pFrame->IsKindOf(RUNTIME_CLASS(CMiniFrameWnd)))
127                 pFrame->RecalcLayout(); 
128 }
129
130 //////////////////
131 // Set tracking state: none, button, or popup
132 //
133 void CMenuBar::SetTrackingState(TRACKINGSTATE iState, int iButton)
134 {
135         ASSERT_VALID(this);
136         if (iState != m_iTrackingState) {
137                 if (iState == TRACK_NONE)
138                         iButton = -1;
139
140 #ifdef _DEBUG
141                 static LPCTSTR StateName[] = { _T("NONE"), _T("BUTTON"), _T("POPUP") };
142                 MBTRACE(_T("CMenuBar::SetTrackingState to %s, button=%d\n"),
143                         StateName[iState], iButton);
144 #endif
145
146                 SetHotItem(iButton);                                     // could be none (-1)
147
148                 if (iState==TRACK_POPUP) {
149                         // set related state stuff
150                         m_bEscapeWasPressed = FALSE;     // assume Esc key not pressed
151                         m_bProcessRightArrow =                   // assume left/right arrow..
152                                 m_bProcessLeftArrow = TRUE; // ..will move to prev/next popup
153                         m_iPopupTracking = iButton;      // which popup I'm tracking
154                 }
155                 m_iTrackingState = iState;
156         }
157 }
158
159 //////////////////
160 // Toggle state from home state to button-tracking and back
161 //
162 void CMenuBar::ToggleTrackButtonMode()
163 {
164         ASSERT_VALID(this);
165         if (m_iTrackingState == TRACK_NONE || m_iTrackingState == TRACK_BUTTON) {
166                 SetTrackingState(m_iTrackingState == TRACK_NONE ?
167                         TRACK_BUTTON : TRACK_NONE, 0);
168         }
169 }
170
171 //////////////////
172 // Get button index before/after a given button
173 //
174 int CMenuBar::GetNextOrPrevButton(int iButton, BOOL bPrev)
175 {
176         ASSERT_VALID(this);
177         if (bPrev) {
178                 iButton--;
179                 if (iButton <0)
180                         iButton = GetButtonCount() - 1;
181         } else {
182                 iButton++;
183                 if (iButton >= GetButtonCount())
184                         iButton = 0;
185         }
186         return iButton;
187 }
188
189 /////////////////
190 // This is to correct a bug in the system toolbar control: TB_HITTEST only
191 // looks at the buttons, not the size of the window. So it returns a button
192 // hit even if that button is totally outside the size of the window!
193 //
194 int CMenuBar::HitTest(CPoint p) const
195 {
196         int iHit = CFlatToolBar::HitTest(p);
197         if (iHit>0) {
198                 CRect rc;
199                 GetClientRect(&rc);
200                 if (!rc.PtInRect(p)) // if point is outside window
201                         iHit = -1;                      // can't be a hit!
202         }
203         return iHit;
204 }
205
206 //////////////////
207 // Load a different menu. The HMENU must not belong to any CMenu,
208 // and you must free it when you're done. Returns old menu.
209 //
210 HMENU CMenuBar::LoadMenu(HMENU hmenu)
211 {
212         UINT iPrevID=(UINT)-1;
213         ASSERT(::IsMenu(hmenu));
214         ASSERT_VALID(this);
215
216         if (m_bAutoRemoveFrameMenu) {
217                 CFrameWnd* pFrame = GetParentFrame();
218                 if (::GetMenu(*pFrame)!=NULL) {
219                         // I would like to set the frame's menu to NULL now, but if I do, MFC
220                         // gets all upset: it calls GetMenu and expects to have a real menu.
221                         // So Instead, I post a message to myself. Because the message is
222                         // posted, not sent, I won't process it until MFC is done with all its
223                         // initialization stuff. (MFC needs to set CFrameWnd::m_hMenuDefault
224                         // to the menu, which it gets by calling GetMenu.)
225                         //
226                         PostMessage(MB_SET_MENU_NULL, (WPARAM)pFrame->GetSafeHwnd());
227                 }
228         }
229         HMENU hOldMenu = m_hmenu;
230         m_hmenu = hmenu;
231
232         // delete existing buttons
233         int nCount = GetButtonCount();
234         while (nCount--) {
235                 VERIFY(DeleteButton(0));
236         }
237
238         SetImageList(NULL);
239 //      SetButtonSize(CSize(0,0)); // This barfs in VC 6.0
240
241         DWORD dwStyle = GetStyle();
242         BOOL bModifyStyle = ModifyStyle(0, TBSTYLE_FLAT|TBSTYLE_TRANSPARENT);
243
244         // add text buttons
245         UINT nMenuItems = hmenu ? ::GetMenuItemCount(hmenu) : 0;
246
247         for (UINT i=0; i < nMenuItems; i++) {
248                 TCHAR name[64];
249                 memset(name, 0, sizeof(name)); // guarantees double-0 at end
250                 if (::GetMenuString(hmenu, i, name, countof(name)-1, MF_BYPOSITION)) {
251                         TBBUTTON tbb;
252                         memset(&tbb, 0, sizeof(tbb));
253                         tbb.idCommand = ::GetMenuItemID(hmenu, i);
254
255                         // Because the toolbar is too brain-damaged to know if it already has
256                         // a string, and is also too brain-dead to even let you delete strings,
257                         // I have to determine if each string has been added already. Otherwise
258                         // in a MDI app, as the menus are repeatedly switched between doc and
259                         // no-doc menus, I will keep adding strings until somebody runs out of
260                         // memory. Sheesh!
261                         // 
262                         int iString = -1;
263                         for (int j=0; j<m_arStrings.GetSize(); j++) {
264                                 if (m_arStrings[j] == name) {
265                                         iString = j; // found it
266                                         break;
267                                 }
268                         }
269                         if (iString <0) {
270                                 // string not found: add it
271                                 iString = AddStrings(name);
272                                 m_arStrings.SetAtGrow(iString, name);
273                         }
274
275                         tbb.iString = iString;
276                         tbb.fsState = TBSTATE_ENABLED;
277                         tbb.fsStyle = TBSTYLE_AUTOSIZE;
278                         tbb.iBitmap = -1;
279                         tbb.idCommand = i;
280                         VERIFY(AddButtons(1, &tbb));
281                 }
282         }
283
284         if (bModifyStyle)
285                 SetWindowLong(m_hWnd, GWL_STYLE, dwStyle);
286         
287         if (hmenu) {
288                 AutoSize();                                                              // size buttons
289                 RecomputeToolbarSize();                          // and menubar itself
290         }
291         return hOldMenu;
292 }
293
294 //////////////////
295 // Load menu from resource
296 //
297 HMENU CMenuBar::LoadMenu(LPCTSTR lpszMenuName)
298 {
299         return LoadMenu(::LoadMenu(AfxFindResourceHandle(lpszMenuName,RT_MENU),lpszMenuName));
300 }
301
302 //////////////////
303 // Set the frame's menu to NULL. WPARAM is HWND of frame.
304 //
305 LRESULT CMenuBar::OnSetMenuNull(WPARAM wp, LPARAM lp)
306 {
307         HWND hwnd = (HWND)wp;
308         ASSERT(::IsWindow(hwnd));
309         ::SetMenu(hwnd, NULL);
310         return 0;
311 }
312
313 //////////////////
314 // Handle mouse click: if clicked on button, press it
315 // and go into main menu loop.
316 //
317 void CMenuBar::OnLButtonDown(UINT nFlags, CPoint pt)
318 {
319         ASSERT_VALID(this);
320         int iButton = HitTest(pt);
321         if (iButton >= 0 && iButton<GetButtonCount()) // if mouse is over a button:
322                 TrackPopup(iButton);                                                             //   track it
323         else                                                                                                             // otherwise:
324                 CFlatToolBar::OnLButtonDown(nFlags, pt);         //   pass it on...
325 }
326
327 //////////////////
328 // Handle mouse movement
329 //
330 void CMenuBar::OnMouseMove(UINT nFlags, CPoint pt)
331 {
332         ASSERT_VALID(this);
333
334         if (m_iTrackingState==TRACK_BUTTON) {
335
336                 // In button-tracking state, ignore mouse-over to non-button area.
337                 // Normally, the toolbar would de-select the hot item in this case.
338                 // 
339                 // Only change the hot item if the mouse has actually moved.
340                 // This is necessary to avoid a bug where the user moves to a different
341                 // button from the one the mouse is over, and presses arrow-down to get
342                 // the menu, then Esc to cancel it. Without this code, the button will
343                 // jump to wherever the mouse is--not right.
344
345                 int iHot = HitTest(pt);
346                 if (IsValidButton(iHot) && pt != m_ptMouse)
347                         SetHotItem(iHot);
348                 return;                  // don't let toolbar get it
349         }
350         m_ptMouse = pt; // remember point
351         CFlatToolBar::OnMouseMove(nFlags, pt);
352 }
353
354 //////////////////
355 // Window was resized: need to recompute layout
356 //
357 void CMenuBar::OnSize(UINT nType, int cx, int cy)
358 {
359         CFlatToolBar::OnSize(nType, cx, cy);
360         RecomputeMenuLayout();
361 }
362
363 //////////////////
364 // Bar style changed: eg, moved from left to right dock or floating
365 //
366 void CMenuBar::OnBarStyleChange(DWORD dwOldStyle, DWORD dwNewStyle)
367 {
368         CFlatToolBar::OnBarStyleChange(dwOldStyle, dwNewStyle);
369         RecomputeMenuLayout();
370 }
371
372 /////////////////
373 // When user selects a new menu item, note whether it has a submenu
374 // and/or parent menu, so I know whether right/left arrow should
375 // move to the next popup.
376 //
377 void CMenuBar::OnMenuSelect(HMENU hmenu, UINT iItem)
378 {
379         if (m_iTrackingState > 0) {
380                 // process right-arrow iff item is NOT a submenu
381                 m_bProcessRightArrow = (::GetSubMenu(hmenu, iItem) == NULL);
382                 // process left-arrow iff curent menu is one I'm tracking
383                 m_bProcessLeftArrow = hmenu==m_hMenuTracking;
384         }
385 }
386
387 // globals--yuk! But no other way using windows hooks.
388 //
389 static CMenuBar*        g_pMenuBar = NULL;
390 static HHOOK            g_hMsgHook = NULL;
391
392 ////////////////
393 // Menu filter hook just passes to virtual CMenuBar function
394 //
395 LRESULT CALLBACK
396 CMenuBar::MenuInputFilter(int code, WPARAM wp, LPARAM lp)
397 {
398         return (code==MSGF_MENU && g_pMenuBar &&
399                 g_pMenuBar->OnMenuInput(*((MSG*)lp))) ? TRUE
400                 : CallNextHookEx(g_hMsgHook, code, wp, lp);
401 }
402
403 //////////////////
404 // Handle menu input event: Look for left/right to change popup menu,
405 // mouse movement over over a different menu button for "hot" popup effect.
406 // Returns TRUE if message handled (to eat it).
407 //
408 BOOL CMenuBar::OnMenuInput(MSG& m)
409 {
410         ASSERT_VALID(this);
411         ASSERT(m_iTrackingState == TRACK_POPUP); // sanity check
412         int msg = m.message;
413
414         if (msg==WM_KEYDOWN) {
415                 // handle left/right-arow.
416                 TCHAR vkey = m.wParam;
417                 if (vkey == VK_LEFT)
418                         MBTRACE(_T("CMenuBar::OnMenuInput: handle VK_LEFT - m_bProcessLeftArrow=%d\n"),m_bProcessLeftArrow);
419                 else if (vkey == VK_RIGHT)
420                         MBTRACE(_T("CMenuBar::OnMenuInput: handle RIGHT - m_bProcessRightArrow=%d\n"),m_bProcessRightArrow);
421                 if ((vkey == VK_LEFT && m_bProcessLeftArrow) ||
422                         (vkey == VK_RIGHT && m_bProcessRightArrow)) {
423                         CancelMenuAndTrackNewOne(
424                                 GetNextOrPrevButton(m_iPopupTracking, vkey==VK_LEFT));
425                         return TRUE; // eat it
426
427                 } else if (vkey == VK_ESCAPE) {
428                         m_bEscapeWasPressed = TRUE;      // (menu will abort itself)
429                 }
430
431         } else if (msg==WM_MOUSEMOVE || msg==WM_LBUTTONDOWN) {
432                 // handle mouse move or click
433                 CPoint pt = m.lParam;
434                 ScreenToClient(&pt);
435
436                 if (msg == WM_MOUSEMOVE) {
437                         if (pt != m_ptMouse) {
438                                 int iButton = HitTest(pt);
439                                 if (IsValidButton(iButton) && iButton != m_iPopupTracking) {
440                                         // user moved mouse over a different button: track its popup
441                                         CancelMenuAndTrackNewOne(iButton);
442                                 }
443                                 m_ptMouse = pt;
444                         }
445
446                 } else if (msg == WM_LBUTTONDOWN) {
447                         if (HitTest(pt) == m_iPopupTracking) {
448                                 // user clicked on same button I am tracking: cancel menu
449                                 MBTRACE(_T("CMenuBar:OnMenuInput: handle mouse click to exit popup\n"));
450                                 CancelMenuAndTrackNewOne(-1);
451                                 return TRUE; // eat it
452                         }
453                 }
454         }
455         return FALSE; // not handled
456 }
457
458 //////////////////
459 // Cancel the current popup menu by posting WM_CANCELMODE, and track a new
460 // menu. iNewPopup is which new popup to track (-1 to quit).
461 //
462 void CMenuBar::CancelMenuAndTrackNewOne(int iNewPopup)
463 {
464         MBTRACE(_T("CMenuBar::CancelMenuAndTrackNewOne: %d\n"), iNewPopup);
465         ASSERT_VALID(this);
466         if (iNewPopup != m_iPopupTracking) {
467                 GetOwner()->PostMessage(WM_CANCELMODE); // quit menu loop
468                 m_iNewPopup = iNewPopup;                                         // go to this popup (-1 = quit)
469         }
470 }
471
472 //////////////////
473 // Track the popup submenu associated with the i'th button in the menu bar.
474 // This fn actually goes into a loop, tracking different menus until the user
475 // selects a command or exits the menu.
476 //
477 void CMenuBar::TrackPopup(int iButton)
478 {
479         MBTRACE(_T("CMenuBar::TrackPopup %d\n"), iButton);
480         ASSERT_VALID(this);
481         ASSERT(m_hmenu);
482
483         CMenu menu;
484         menu.Attach(m_hmenu);
485         int nMenuItems = menu.GetMenuItemCount();
486
487         while (iButton >= 0) {                                   // while user selects another menu
488
489                 m_iNewPopup = -1;                                                // assume quit after this
490                 PressButton(iButton, TRUE);              // press the button
491                 UpdateWindow();                                          // and force repaint now
492
493                 // post a simulated arrow-down into the message stream
494                 // so TrackPopupMenu will read it and move to the first item
495                 GetOwner()->PostMessage(WM_KEYDOWN, VK_DOWN, 1);
496                 GetOwner()->PostMessage(WM_KEYUP, VK_DOWN, 1);
497
498                 SetTrackingState(TRACK_POPUP, iButton); // enter tracking state
499
500                 // Need to install a hook to trap menu input in order to make
501                 // left/right-arrow keys and "hot" mouse tracking work.
502                 //
503                 ASSERT(g_pMenuBar == NULL);
504                 g_pMenuBar = this;
505                 ASSERT(g_hMsgHook == NULL);
506                 g_hMsgHook = SetWindowsHookEx(WH_MSGFILTER,
507                         MenuInputFilter, NULL, ::GetCurrentThreadId());
508
509                 // get submenu and display it beneath button
510                 TPMPARAMS tpm;
511                 CRect rcButton;
512                 GetRect(iButton, rcButton);
513                 ClientToScreen(&rcButton);
514                 CPoint pt = ComputeMenuTrackPoint(rcButton, tpm);
515                 HMENU hMenuPopup = ::GetSubMenu(m_hmenu, iButton);
516                 ASSERT(hMenuPopup);
517                 m_hMenuTracking = hMenuPopup;
518                 BOOL bRet = TrackPopupMenuEx(hMenuPopup,
519                         TPM_LEFTALIGN|TPM_LEFTBUTTON|TPM_VERTICAL,
520                         pt.x, pt.y, GetOwner()->GetSafeHwnd(), &tpm);
521
522                 // uninstall hook.
523                 ::UnhookWindowsHookEx(g_hMsgHook);
524                 g_hMsgHook = NULL;
525                 g_pMenuBar = NULL;
526
527                 PressButton(iButton, FALSE);     // un-press button
528                 UpdateWindow();                                  // and force repaint now
529
530                 // If the user exited the menu loop by pressing Escape,
531                 // return to track-button state; otherwise normal non-tracking state.
532                 SetTrackingState(m_bEscapeWasPressed ?
533                         TRACK_BUTTON : TRACK_NONE, iButton);
534
535                 // If the user moved mouse to a new top-level popup (eg from File to
536                 // Edit button), I will have posted a WM_CANCELMODE to quit
537                 // the first popup, and set m_iNewPopup to the new menu to show.
538                 // Otherwise, m_iNewPopup will be -1 as set above.
539                 // So just set iButton to the next popup menu and keep looping...
540                 iButton = m_iNewPopup;
541         }
542         menu.Detach();
543         m_hMenuTracking = m_hmenu;
544 }
545
546 //////////////////
547 // Given button rectangle, compute point and "exclude rect" for
548 // TrackPopupMenu, based on current docking style, so that the menu will
549 // appear always inside the window.
550 //
551 CPoint CMenuBar::ComputeMenuTrackPoint(const CRect& rcButn, TPMPARAMS& tpm)
552 {
553         tpm.cbSize = sizeof(tpm);
554         DWORD dwStyle = m_dwStyle;
555         CPoint pt;
556         CRect& rcExclude = (CRect&)tpm.rcExclude;
557         rcExclude = rcButn;
558         ::GetWindowRect(::GetDesktopWindow(), &rcExclude);
559
560         switch (dwStyle & CBRS_ALIGN_ANY) {
561         case CBRS_ALIGN_BOTTOM:
562                 pt = CPoint(rcButn.left, rcButn.top);
563                 rcExclude.top = rcButn.top;
564                 break;
565
566         case CBRS_ALIGN_LEFT:
567                 pt = CPoint(rcButn.right, rcButn.top);
568                 rcExclude.right = rcButn.right;
569                 break;
570
571         case CBRS_ALIGN_RIGHT:
572                 pt = CPoint(rcButn.left, rcButn.top);
573                 rcExclude.left = rcButn.left;
574                 break;
575
576         default: //     case CBRS_ALIGN_TOP:
577                 pt = CPoint(rcButn.left, rcButn.bottom);
578                 break;
579         }
580         return pt;
581 }
582
583 //////////////////
584 // This function translates special menu keys and mouse actions.
585 // You must call it from your frame's PreTranslateMessage.
586 //
587 BOOL CMenuBar::TranslateFrameMessage(MSG* pMsg)
588 {
589         ASSERT_VALID(this);
590         ASSERT(pMsg);
591         UINT msg = pMsg->message;
592         if (WM_LBUTTONDOWN <= msg && msg <= WM_MOUSELAST) {
593                 if (pMsg->hwnd != m_hWnd && m_iTrackingState > 0) {
594                         // user clicked outside menu bar: exit tracking mode
595                         MBTRACE(_T("CMenuBar::TranslateFrameMessage: user clicked outside menu bar: end tracking\n"));
596                         SetTrackingState(TRACK_NONE);
597                 }
598
599         } else if (msg==WM_SYSKEYDOWN || msg==WM_SYSKEYUP || msg==WM_KEYDOWN) {
600
601                 BOOL bAlt = HIWORD(pMsg->lParam) & KF_ALTDOWN; // Alt key down
602                 TCHAR vkey = pMsg->wParam;                                                        // get virt key
603                 if (vkey==VK_MENU ||
604                         (vkey==VK_F10 && !((GetKeyState(VK_SHIFT) & 0x80000000) ||
605                                            (GetKeyState(VK_CONTROL) & 0x80000000) || bAlt))) {
606
607                         // key is VK_MENU or F10 with no alt/ctrl/shift: toggle menu mode
608                         if (msg==WM_SYSKEYUP) {
609                                 MBTRACE(_T("CMenuBar::TranslateFrameMessage: handle menu key\n"));
610                                 ToggleTrackButtonMode();
611                         }
612                         return TRUE;
613
614                 } else if ((msg==WM_SYSKEYDOWN || msg==WM_KEYDOWN)) {
615                         if (m_iTrackingState == TRACK_BUTTON) {
616                                 // I am tracking: handle left/right/up/down/space/Esc
617                                 switch (vkey) {
618                                 case VK_LEFT:
619                                 case VK_RIGHT:
620                                         // left or right-arrow: change hot button if tracking buttons
621                                         MBTRACE(_T("CMenuBar::TranslateFrameMessage: VK_LEFT/RIGHT\n"));
622                                         SetHotItem(GetNextOrPrevButton(GetHotItem(), vkey==VK_LEFT));
623                                         return TRUE;
624
625                                 case VK_SPACE:  // (personally, I like SPACE to enter menu too)
626                                 case VK_UP:
627                                 case VK_DOWN:
628                                         // up or down-arrow: move into current menu, if any
629                                         MBTRACE(_T("CMenuBar::TranslateFrameMessage: VK_UP/DOWN/SPACE\n"));
630                                         TrackPopup(GetHotItem());
631                                         return TRUE;
632
633                                 case VK_ESCAPE:
634                                         // escape key: exit tracking mode
635                                         MBTRACE(_T("CMenuBar::TranslateFrameMessage: VK_ESCAPE\n"));
636                                         SetTrackingState(TRACK_NONE);
637                                         return TRUE;
638                                 }
639                         }
640
641                         // Handle alphanumeric key: invoke menu. Note that Alt-X
642                         // chars come through as WM_SYSKEYDOWN, plain X as WM_KEYDOWN.
643                         if ((bAlt || m_iTrackingState == TRACK_BUTTON) && isalnum(vkey)) {
644                                 // Alt-X, or else X while in tracking mode
645                                 UINT nID;
646                                 if (MapAccelerator(vkey, nID)) {
647                                         MBTRACE(_T("CMenuBar::TranslateFrameMessage: map acclerator\n"));
648                                         TrackPopup(nID);         // found menu mnemonic: track it
649                                         return TRUE;             // handled
650                                 } else if (m_iTrackingState==TRACK_BUTTON && !bAlt) {
651                                         MessageBeep(0);
652                                         return TRUE;
653                                 }
654                         }
655
656                         // Default for any key not handled so far: return to no-menu state
657                         if (m_iTrackingState > 0) {
658                                 MBTRACE(_T("CMenuBar::TranslateFrameMessage: unknown key, stop tracking\n"));
659                                 SetTrackingState(TRACK_NONE);
660                         }
661                 }
662         }
663         return FALSE; // not handled, pass along
664 }
665
666 #ifdef _DEBUG
667 void CMenuBar::AssertValid() const
668 {
669         CFlatToolBar::AssertValid();
670         ASSERT(m_hmenu==NULL || ::IsMenu(m_hmenu));
671         ASSERT(TRACK_NONE<=m_iTrackingState && m_iTrackingState<=TRACK_POPUP);
672         m_frameHook.AssertValid();
673 }
674
675 void CMenuBar::Dump(CDumpContext& dc) const
676 {
677         CFlatToolBar::Dump(dc);
678 }
679 #endif
680
681 //////////////////////////////////////////////////////////////////
682 // CMenuBarFrameHook is used to trap menu-related messages sent to the owning
683 // frame. The same class is also used to trap messages sent to the MDI client
684 // window in an MDI app. I should really use two classes for this,
685 // but it uses less code to chare the same class. Note however: there
686 // are two different INSTANCES of CMenuBarFrameHook in CMenuBar: one for
687 // the frame and one for the MDI client window.
688 //
689 CMenuBarFrameHook::CMenuBarFrameHook()
690 {
691 }
692
693 CMenuBarFrameHook::~CMenuBarFrameHook()
694 {
695         HookWindow((HWND)NULL); // (unhook)
696 }
697
698 //////////////////
699 // Install hook to trap window messages sent to frame or MDI client.
700 // 
701 BOOL CMenuBarFrameHook::Install(CMenuBar* pMenuBar, HWND hWndToHook)
702 {
703         ASSERT_VALID(pMenuBar);
704         m_pMenuBar = pMenuBar;
705         return HookWindow(hWndToHook);
706 }
707
708 //////////////////////////////////////////////////////////////////
709 // Trap frame/MDI client messages specific to menubar. 
710 //
711 LRESULT CMenuBarFrameHook::WindowProc(UINT msg, WPARAM wp, LPARAM lp)
712 {
713         CMenuBar& mb = *m_pMenuBar;
714
715         switch (msg) {
716         // The following messages are trapped for the frame window
717         case WM_SYSCOLORCHANGE:
718                 mb.UpdateFont();
719                 break;
720
721         case WM_MENUSELECT:
722                 mb.OnMenuSelect((HMENU)lp, (UINT)LOWORD(wp));
723                 break;
724         }
725         return CSubclassWnd::WindowProc(msg, wp, lp);
726 }