1 /*
2 * Copyright 2001-2018 Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT license.
4 *
5 * Authors:
6 * Stephan Aßmus, superstippi@gmx.de
7 * Stefano Ceccherini, stefano.ceccherini@gmail.com
8 * Adrien Destugues, pulkomandy@pulkomandy.tk
9 * Marc Flerackers, mflerackers@androme.be
10 * Rene Gollent, anevilyak@gmail.com
11 * John Scipione, jscipione@gmail.com
12 */
13
14
15 #include <Menu.h>
16
17 #include <algorithm>
18 #include <new>
19 #include <set>
20
21 #include <ctype.h>
22 #include <string.h>
23
24 #include <Application.h>
25 #include <Bitmap.h>
26 #include <ControlLook.h>
27 #include <Debug.h>
28 #include <File.h>
29 #include <FindDirectory.h>
30 #include <Layout.h>
31 #include <LayoutUtils.h>
32 #include <MenuBar.h>
33 #include <MenuItem.h>
34 #include <Messenger.h>
35 #include <Path.h>
36 #include <PropertyInfo.h>
37 #include <Screen.h>
38 #include <ScrollBar.h>
39 #include <SystemCatalog.h>
40 #include <UnicodeChar.h>
41 #include <Window.h>
42
43 #include <AppServerLink.h>
44 #include <AutoDeleter.h>
45 #include <binary_compatibility/Interface.h>
46 #include <BMCPrivate.h>
47 #include <MenuPrivate.h>
48 #include <MenuWindow.h>
49 #include <ServerProtocol.h>
50
51 #include "utf8_functions.h"
52
53
54 #define USE_CACHED_MENUWINDOW 1
55
56 using BPrivate::gSystemCatalog;
57
58 #undef B_TRANSLATION_CONTEXT
59 #define B_TRANSLATION_CONTEXT "Menu"
60
61 #undef B_TRANSLATE
62 #define B_TRANSLATE(str) \
63 gSystemCatalog.GetString(B_TRANSLATE_MARK(str), "Menu")
64
65
66 using std::nothrow;
67 using BPrivate::BMenuWindow;
68
69 namespace BPrivate {
70
71 class TriggerList {
72 public:
TriggerList()73 TriggerList() {}
~TriggerList()74 ~TriggerList() {}
75
HasTrigger(uint32 c)76 bool HasTrigger(uint32 c)
77 { return fList.find(BUnicodeChar::ToLower(c)) != fList.end(); }
78
AddTrigger(uint32 c)79 bool AddTrigger(uint32 c)
80 {
81 fList.insert(BUnicodeChar::ToLower(c));
82 return true;
83 }
84
85 private:
86 std::set<uint32> fList;
87 };
88
89
90 class ExtraMenuData {
91 public:
92 menu_tracking_hook trackingHook;
93 void* trackingState;
94
95 // Used to track when the menu would be drawn offscreen and instead gets
96 // shifted back on the screen towards the left. This information
97 // allows us to draw submenus in the same direction as their parents.
98 bool frameShiftedLeft;
99
ExtraMenuData()100 ExtraMenuData()
101 {
102 trackingHook = NULL;
103 trackingState = NULL;
104 frameShiftedLeft = false;
105 }
106 };
107
108
109 typedef int (*compare_func)(const BMenuItem*, const BMenuItem*);
110
111 struct MenuItemComparator
112 {
MenuItemComparatorBPrivate::MenuItemComparator113 MenuItemComparator(compare_func compareFunc)
114 :
115 fCompareFunc(compareFunc)
116 {
117 }
118
operator ()BPrivate::MenuItemComparator119 bool operator () (const BMenuItem* item1, const BMenuItem* item2) {
120 return fCompareFunc(item1, item2) < 0;
121 }
122
123 private:
124 compare_func fCompareFunc;
125 };
126
127
128 } // namespace BPrivate
129
130
131 menu_info BMenu::sMenuInfo;
132
133 uint32 BMenu::sShiftKey;
134 uint32 BMenu::sControlKey;
135 uint32 BMenu::sOptionKey;
136 uint32 BMenu::sCommandKey;
137 uint32 BMenu::sMenuKey;
138
139 static property_info sPropList[] = {
140 { "Enabled", { B_GET_PROPERTY, 0 },
141 { B_DIRECT_SPECIFIER, 0 }, "Returns true if menu or menu item is "
142 "enabled; false otherwise.",
143 0, { B_BOOL_TYPE }
144 },
145
146 { "Enabled", { B_SET_PROPERTY, 0 },
147 { B_DIRECT_SPECIFIER, 0 }, "Enables or disables menu or menu item.",
148 0, { B_BOOL_TYPE }
149 },
150
151 { "Label", { B_GET_PROPERTY, 0 },
152 { B_DIRECT_SPECIFIER, 0 }, "Returns the string label of the menu or "
153 "menu item.",
154 0, { B_STRING_TYPE }
155 },
156
157 { "Label", { B_SET_PROPERTY, 0 },
158 { B_DIRECT_SPECIFIER, 0 }, "Sets the string label of the menu or menu "
159 "item.",
160 0, { B_STRING_TYPE }
161 },
162
163 { "Mark", { B_GET_PROPERTY, 0 },
164 { B_DIRECT_SPECIFIER, 0 }, "Returns true if the menu item or the "
165 "menu's superitem is marked; false otherwise.",
166 0, { B_BOOL_TYPE }
167 },
168
169 { "Mark", { B_SET_PROPERTY, 0 },
170 { B_DIRECT_SPECIFIER, 0 }, "Marks or unmarks the menu item or the "
171 "menu's superitem.",
172 0, { B_BOOL_TYPE }
173 },
174
175 { "Menu", { B_CREATE_PROPERTY, 0 },
176 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
177 "Adds a new menu item at the specified index with the text label "
178 "found in \"data\" and the int32 command found in \"what\" (used as "
179 "the what field in the BMessage sent by the item)." , 0, {},
180 { {{{"data", B_STRING_TYPE}}}
181 }
182 },
183
184 { "Menu", { B_DELETE_PROPERTY, 0 },
185 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
186 "Removes the selected menu or menus.", 0, {}
187 },
188
189 { "Menu", { },
190 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
191 "Directs scripting message to the specified menu, first popping the "
192 "current specifier off the stack.", 0, {}
193 },
194
195 { "MenuItem", { B_COUNT_PROPERTIES, 0 },
196 { B_DIRECT_SPECIFIER, 0 }, "Counts the number of menu items in the "
197 "specified menu.",
198 0, { B_INT32_TYPE }
199 },
200
201 { "MenuItem", { B_CREATE_PROPERTY, 0 },
202 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
203 "Adds a new menu item at the specified index with the text label "
204 "found in \"data\" and the int32 command found in \"what\" (used as "
205 "the what field in the BMessage sent by the item).", 0, {},
206 { { {{"data", B_STRING_TYPE },
207 {"be:invoke_message", B_MESSAGE_TYPE},
208 {"what", B_INT32_TYPE},
209 {"be:target", B_MESSENGER_TYPE}} }
210 }
211 },
212
213 { "MenuItem", { B_DELETE_PROPERTY, 0 },
214 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
215 "Removes the specified menu item from its parent menu."
216 },
217
218 { "MenuItem", { B_EXECUTE_PROPERTY, 0 },
219 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
220 "Invokes the specified menu item."
221 },
222
223 { "MenuItem", { },
224 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
225 "Directs scripting message to the specified menu, first popping the "
226 "current specifier off the stack."
227 },
228
229 { 0 }
230 };
231
232
233 // note: this is redefined to localized one in BMenu::_InitData
234 const char* BPrivate::kEmptyMenuLabel = "<empty>";
235
236
237 struct BMenu::LayoutData {
238 BSize preferred;
239 uint32 lastResizingMode;
240 };
241
242
243 // #pragma mark - BMenu
244
245
BMenu(const char * name,menu_layout layout)246 BMenu::BMenu(const char* name, menu_layout layout)
247 :
248 BView(BRect(0, 0, 0, 0), name, 0, B_WILL_DRAW),
249 fChosenItem(NULL),
250 fSelected(NULL),
251 fCachedMenuWindow(NULL),
252 fSuper(NULL),
253 fSuperitem(NULL),
254 fAscent(-1.0f),
255 fDescent(-1.0f),
256 fFontHeight(-1.0f),
257 fState(MENU_STATE_CLOSED),
258 fLayout(layout),
259 fExtraRect(NULL),
260 fMaxContentWidth(0.0f),
261 fInitMatrixSize(NULL),
262 fExtraMenuData(NULL),
263 fTrigger(0),
264 fResizeToFit(true),
265 fUseCachedMenuLayout(false),
266 fEnabled(true),
267 fDynamicName(false),
268 fRadioMode(false),
269 fTrackNewBounds(false),
270 fStickyMode(false),
271 fIgnoreHidden(true),
272 fTriggerEnabled(true),
273 fHasSubmenus(false),
274 fAttachAborted(false)
275 {
276 _InitData(NULL);
277 }
278
279
BMenu(const char * name,float width,float height)280 BMenu::BMenu(const char* name, float width, float height)
281 :
282 BView(BRect(0.0f, 0.0f, 0.0f, 0.0f), name, 0, B_WILL_DRAW),
283 fChosenItem(NULL),
284 fSelected(NULL),
285 fCachedMenuWindow(NULL),
286 fSuper(NULL),
287 fSuperitem(NULL),
288 fAscent(-1.0f),
289 fDescent(-1.0f),
290 fFontHeight(-1.0f),
291 fState(0),
292 fLayout(B_ITEMS_IN_MATRIX),
293 fExtraRect(NULL),
294 fMaxContentWidth(0.0f),
295 fInitMatrixSize(NULL),
296 fExtraMenuData(NULL),
297 fTrigger(0),
298 fResizeToFit(true),
299 fUseCachedMenuLayout(false),
300 fEnabled(true),
301 fDynamicName(false),
302 fRadioMode(false),
303 fTrackNewBounds(false),
304 fStickyMode(false),
305 fIgnoreHidden(true),
306 fTriggerEnabled(true),
307 fHasSubmenus(false),
308 fAttachAborted(false)
309 {
310 _InitData(NULL);
311 }
312
313
BMenu(BMessage * archive)314 BMenu::BMenu(BMessage* archive)
315 :
316 BView(archive),
317 fChosenItem(NULL),
318 fSelected(NULL),
319 fCachedMenuWindow(NULL),
320 fSuper(NULL),
321 fSuperitem(NULL),
322 fAscent(-1.0f),
323 fDescent(-1.0f),
324 fFontHeight(-1.0f),
325 fState(MENU_STATE_CLOSED),
326 fLayout(B_ITEMS_IN_ROW),
327 fExtraRect(NULL),
328 fMaxContentWidth(0.0f),
329 fInitMatrixSize(NULL),
330 fExtraMenuData(NULL),
331 fTrigger(0),
332 fResizeToFit(true),
333 fUseCachedMenuLayout(false),
334 fEnabled(true),
335 fDynamicName(false),
336 fRadioMode(false),
337 fTrackNewBounds(false),
338 fStickyMode(false),
339 fIgnoreHidden(true),
340 fTriggerEnabled(true),
341 fHasSubmenus(false),
342 fAttachAborted(false)
343 {
344 _InitData(archive);
345 }
346
347
~BMenu()348 BMenu::~BMenu()
349 {
350 _DeleteMenuWindow();
351
352 RemoveItems(0, CountItems(), true);
353
354 delete fInitMatrixSize;
355 delete fExtraMenuData;
356 delete fLayoutData;
357 }
358
359
360 BArchivable*
Instantiate(BMessage * archive)361 BMenu::Instantiate(BMessage* archive)
362 {
363 if (validate_instantiation(archive, "BMenu"))
364 return new (nothrow) BMenu(archive);
365
366 return NULL;
367 }
368
369
370 status_t
Archive(BMessage * data,bool deep) const371 BMenu::Archive(BMessage* data, bool deep) const
372 {
373 status_t err = BView::Archive(data, deep);
374
375 if (err == B_OK && Layout() != B_ITEMS_IN_ROW)
376 err = data->AddInt32("_layout", Layout());
377 if (err == B_OK)
378 err = data->AddBool("_rsize_to_fit", fResizeToFit);
379 if (err == B_OK)
380 err = data->AddBool("_disable", !IsEnabled());
381 if (err == B_OK)
382 err = data->AddBool("_radio", IsRadioMode());
383 if (err == B_OK)
384 err = data->AddBool("_trig_disabled", AreTriggersEnabled());
385 if (err == B_OK)
386 err = data->AddBool("_dyn_label", fDynamicName);
387 if (err == B_OK)
388 err = data->AddFloat("_maxwidth", fMaxContentWidth);
389 if (err == B_OK && deep) {
390 BMenuItem* item = NULL;
391 int32 index = 0;
392 while ((item = ItemAt(index++)) != NULL) {
393 BMessage itemData;
394 item->Archive(&itemData, deep);
395 err = data->AddMessage("_items", &itemData);
396 if (err != B_OK)
397 break;
398 if (fLayout == B_ITEMS_IN_MATRIX) {
399 err = data->AddRect("_i_frames", item->fBounds);
400 }
401 }
402 }
403
404 return err;
405 }
406
407
408 void
AttachedToWindow()409 BMenu::AttachedToWindow()
410 {
411 BView::AttachedToWindow();
412
413 _GetShiftKey(sShiftKey);
414 _GetControlKey(sControlKey);
415 _GetCommandKey(sCommandKey);
416 _GetOptionKey(sOptionKey);
417 _GetMenuKey(sMenuKey);
418
419 // The menu should be added to the menu hierarchy and made visible if:
420 // * the mouse is over the menu,
421 // * the user has requested the menu via the keyboard.
422 // So if we don't pass keydown in here, keyboard navigation breaks since
423 // fAttachAborted will return false if the mouse isn't over the menu
424 bool keyDown = Supermenu() != NULL
425 ? Supermenu()->fState == MENU_STATE_KEY_TO_SUBMENU : false;
426 fAttachAborted = _AddDynamicItems(keyDown);
427
428 if (!fAttachAborted) {
429 _CacheFontInfo();
430 _LayoutItems(0);
431 _UpdateWindowViewSize(false);
432 }
433 }
434
435
436 void
DetachedFromWindow()437 BMenu::DetachedFromWindow()
438 {
439 BView::DetachedFromWindow();
440 }
441
442
443 void
AllAttached()444 BMenu::AllAttached()
445 {
446 BView::AllAttached();
447 }
448
449
450 void
AllDetached()451 BMenu::AllDetached()
452 {
453 BView::AllDetached();
454 }
455
456
457 void
Draw(BRect updateRect)458 BMenu::Draw(BRect updateRect)
459 {
460 if (_RelayoutIfNeeded()) {
461 Invalidate();
462 return;
463 }
464
465 DrawBackground(updateRect);
466 DrawItems(updateRect);
467 }
468
469
470 void
MessageReceived(BMessage * message)471 BMenu::MessageReceived(BMessage* message)
472 {
473 if (message->HasSpecifiers())
474 return _ScriptReceived(message);
475
476 switch (message->what) {
477 case B_MOUSE_WHEEL_CHANGED:
478 {
479 float deltaY = 0;
480 message->FindFloat("be:wheel_delta_y", &deltaY);
481 if (deltaY == 0)
482 return;
483
484 BMenuWindow* window = dynamic_cast<BMenuWindow*>(Window());
485 if (window == NULL)
486 return;
487
488 float largeStep;
489 float smallStep;
490 window->GetSteps(&smallStep, &largeStep);
491
492 // pressing the shift key scrolls faster
493 if ((modifiers() & B_SHIFT_KEY) != 0)
494 deltaY *= largeStep;
495 else
496 deltaY *= smallStep;
497
498 window->TryScrollBy(deltaY);
499 break;
500 }
501
502 default:
503 BView::MessageReceived(message);
504 break;
505 }
506 }
507
508
509 void
KeyDown(const char * bytes,int32 numBytes)510 BMenu::KeyDown(const char* bytes, int32 numBytes)
511 {
512 // TODO: Test how it works on BeOS R5 and implement this correctly
513 switch (bytes[0]) {
514 case B_UP_ARROW:
515 case B_DOWN_ARROW:
516 {
517 BMenuBar* bar = dynamic_cast<BMenuBar*>(Supermenu());
518 if (bar != NULL && fState == MENU_STATE_CLOSED) {
519 // tell MenuBar's _Track:
520 bar->fState = MENU_STATE_KEY_TO_SUBMENU;
521 }
522 if (fLayout == B_ITEMS_IN_COLUMN)
523 _SelectNextItem(fSelected, bytes[0] == B_DOWN_ARROW);
524 break;
525 }
526
527 case B_LEFT_ARROW:
528 if (fLayout == B_ITEMS_IN_ROW)
529 _SelectNextItem(fSelected, false);
530 else {
531 // this case has to be handled a bit specially.
532 BMenuItem* item = Superitem();
533 if (item) {
534 if (dynamic_cast<BMenuBar*>(Supermenu())) {
535 // If we're at the top menu below the menu bar, pass
536 // the keypress to the menu bar so we can move to
537 // another top level menu.
538 BMessenger messenger(Supermenu());
539 messenger.SendMessage(Window()->CurrentMessage());
540 } else {
541 // tell _Track
542 fState = MENU_STATE_KEY_LEAVE_SUBMENU;
543 }
544 }
545 }
546 break;
547
548 case B_RIGHT_ARROW:
549 if (fLayout == B_ITEMS_IN_ROW)
550 _SelectNextItem(fSelected, true);
551 else {
552 if (fSelected != NULL && fSelected->Submenu() != NULL) {
553 fSelected->Submenu()->_SetStickyMode(true);
554 // fix me: this shouldn't be needed but dynamic menus
555 // aren't getting it set correctly when keyboard
556 // navigating, which aborts the attach
557 fState = MENU_STATE_KEY_TO_SUBMENU;
558 _SelectItem(fSelected, true, true, true);
559 } else if (dynamic_cast<BMenuBar*>(Supermenu())) {
560 // if we have no submenu and we're an
561 // item in the top menu below the menubar,
562 // pass the keypress to the menubar
563 // so you can use the keypress to switch menus.
564 BMessenger messenger(Supermenu());
565 messenger.SendMessage(Window()->CurrentMessage());
566 }
567 }
568 break;
569
570 case B_PAGE_UP:
571 case B_PAGE_DOWN:
572 {
573 BMenuWindow* window = dynamic_cast<BMenuWindow*>(Window());
574 if (window == NULL || !window->HasScrollers())
575 break;
576
577 int32 deltaY = bytes[0] == B_PAGE_UP ? -1 : 1;
578
579 float largeStep;
580 window->GetSteps(NULL, &largeStep);
581 window->TryScrollBy(deltaY * largeStep);
582 break;
583 }
584
585 case B_ENTER:
586 case B_SPACE:
587 if (fSelected != NULL) {
588 fChosenItem = fSelected;
589 // preserve for exit handling
590 _QuitTracking(false);
591 }
592 break;
593
594 case B_ESCAPE:
595 _SelectItem(NULL);
596 if (fState == MENU_STATE_CLOSED
597 && dynamic_cast<BMenuBar*>(Supermenu())) {
598 // Keyboard may show menu without tracking it
599 BMessenger messenger(Supermenu());
600 messenger.SendMessage(Window()->CurrentMessage());
601 } else
602 _QuitTracking(false);
603 break;
604
605 default:
606 {
607 if (AreTriggersEnabled()) {
608 uint32 trigger = BUnicodeChar::FromUTF8(&bytes);
609
610 for (uint32 i = CountItems(); i-- > 0;) {
611 BMenuItem* item = ItemAt(i);
612 if (item->fTriggerIndex < 0 || item->fTrigger != trigger)
613 continue;
614
615 _InvokeItem(item);
616 _QuitTracking(false);
617 break;
618 }
619 }
620 break;
621 }
622 }
623 }
624
625
626 BSize
MinSize()627 BMenu::MinSize()
628 {
629 _ValidatePreferredSize();
630
631 BSize size = (GetLayout() != NULL ? GetLayout()->MinSize()
632 : fLayoutData->preferred);
633
634 return BLayoutUtils::ComposeSize(ExplicitMinSize(), size);
635 }
636
637
638 BSize
MaxSize()639 BMenu::MaxSize()
640 {
641 _ValidatePreferredSize();
642
643 BSize size = (GetLayout() != NULL ? GetLayout()->MaxSize()
644 : fLayoutData->preferred);
645
646 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size);
647 }
648
649
650 BSize
PreferredSize()651 BMenu::PreferredSize()
652 {
653 _ValidatePreferredSize();
654
655 BSize size = (GetLayout() != NULL ? GetLayout()->PreferredSize()
656 : fLayoutData->preferred);
657
658 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), size);
659 }
660
661
662 void
GetPreferredSize(float * _width,float * _height)663 BMenu::GetPreferredSize(float* _width, float* _height)
664 {
665 _ValidatePreferredSize();
666
667 if (_width)
668 *_width = fLayoutData->preferred.width;
669
670 if (_height)
671 *_height = fLayoutData->preferred.height;
672 }
673
674
675 void
ResizeToPreferred()676 BMenu::ResizeToPreferred()
677 {
678 BView::ResizeToPreferred();
679 }
680
681
682 void
DoLayout()683 BMenu::DoLayout()
684 {
685 // If the user set a layout, we let the base class version call its
686 // hook.
687 if (GetLayout() != NULL) {
688 BView::DoLayout();
689 return;
690 }
691
692 if (_RelayoutIfNeeded())
693 Invalidate();
694 }
695
696
697 void
FrameMoved(BPoint where)698 BMenu::FrameMoved(BPoint where)
699 {
700 BView::FrameMoved(where);
701 }
702
703
704 void
FrameResized(float width,float height)705 BMenu::FrameResized(float width, float height)
706 {
707 BView::FrameResized(width, height);
708 }
709
710
711 void
InvalidateLayout()712 BMenu::InvalidateLayout()
713 {
714 fUseCachedMenuLayout = false;
715 // This method exits for backwards compatibility reasons, it is used to
716 // invalidate the menu layout, but we also use call
717 // BView::InvalidateLayout() for good measure. Don't delete this method!
718 BView::InvalidateLayout(false);
719 }
720
721
722 void
MakeFocus(bool focused)723 BMenu::MakeFocus(bool focused)
724 {
725 BView::MakeFocus(focused);
726 }
727
728
729 bool
AddItem(BMenuItem * item)730 BMenu::AddItem(BMenuItem* item)
731 {
732 return AddItem(item, CountItems());
733 }
734
735
736 bool
AddItem(BMenuItem * item,int32 index)737 BMenu::AddItem(BMenuItem* item, int32 index)
738 {
739 if (fLayout == B_ITEMS_IN_MATRIX) {
740 debugger("BMenu::AddItem(BMenuItem*, int32) this method can only "
741 "be called if the menu layout is not B_ITEMS_IN_MATRIX");
742 }
743
744 if (item == NULL)
745 return false;
746
747 const bool locked = LockLooper();
748
749 if (!_AddItem(item, index)) {
750 if (locked)
751 UnlockLooper();
752 return false;
753 }
754
755 InvalidateLayout();
756 if (locked) {
757 if (!Window()->IsHidden()) {
758 _LayoutItems(index);
759 _UpdateWindowViewSize(false);
760 Invalidate();
761 }
762 UnlockLooper();
763 }
764
765 return true;
766 }
767
768
769 bool
AddItem(BMenuItem * item,BRect frame)770 BMenu::AddItem(BMenuItem* item, BRect frame)
771 {
772 if (fLayout != B_ITEMS_IN_MATRIX) {
773 debugger("BMenu::AddItem(BMenuItem*, BRect) this method can only "
774 "be called if the menu layout is B_ITEMS_IN_MATRIX");
775 }
776
777 if (item == NULL)
778 return false;
779
780 const bool locked = LockLooper();
781
782 item->fBounds = frame;
783
784 int32 index = CountItems();
785 if (!_AddItem(item, index)) {
786 if (locked)
787 UnlockLooper();
788 return false;
789 }
790
791 if (locked) {
792 if (!Window()->IsHidden()) {
793 _LayoutItems(index);
794 Invalidate();
795 }
796 UnlockLooper();
797 }
798
799 return true;
800 }
801
802
803 bool
AddItem(BMenu * submenu)804 BMenu::AddItem(BMenu* submenu)
805 {
806 BMenuItem* item = new (nothrow) BMenuItem(submenu);
807 if (item == NULL)
808 return false;
809
810 if (!AddItem(item, CountItems())) {
811 item->fSubmenu = NULL;
812 delete item;
813 return false;
814 }
815
816 return true;
817 }
818
819
820 bool
AddItem(BMenu * submenu,int32 index)821 BMenu::AddItem(BMenu* submenu, int32 index)
822 {
823 if (fLayout == B_ITEMS_IN_MATRIX) {
824 debugger("BMenu::AddItem(BMenuItem*, int32) this method can only "
825 "be called if the menu layout is not B_ITEMS_IN_MATRIX");
826 }
827
828 BMenuItem* item = new (nothrow) BMenuItem(submenu);
829 if (item == NULL)
830 return false;
831
832 if (!AddItem(item, index)) {
833 item->fSubmenu = NULL;
834 delete item;
835 return false;
836 }
837
838 return true;
839 }
840
841
842 bool
AddItem(BMenu * submenu,BRect frame)843 BMenu::AddItem(BMenu* submenu, BRect frame)
844 {
845 if (fLayout != B_ITEMS_IN_MATRIX) {
846 debugger("BMenu::AddItem(BMenu*, BRect) this method can only "
847 "be called if the menu layout is B_ITEMS_IN_MATRIX");
848 }
849
850 BMenuItem* item = new (nothrow) BMenuItem(submenu);
851 if (item == NULL)
852 return false;
853
854 if (!AddItem(item, frame)) {
855 item->fSubmenu = NULL;
856 delete item;
857 return false;
858 }
859
860 return true;
861 }
862
863
864 bool
AddList(BList * list,int32 index)865 BMenu::AddList(BList* list, int32 index)
866 {
867 // TODO: test this function, it's not documented in the bebook.
868 if (list == NULL)
869 return false;
870
871 bool locked = LockLooper();
872
873 int32 numItems = list->CountItems();
874 for (int32 i = 0; i < numItems; i++) {
875 BMenuItem* item = static_cast<BMenuItem*>(list->ItemAt(i));
876 if (item != NULL) {
877 if (!_AddItem(item, index + i))
878 break;
879 }
880 }
881
882 InvalidateLayout();
883 if (locked && Window() != NULL && !Window()->IsHidden()) {
884 // Make sure we update the layout if needed.
885 _LayoutItems(index);
886 _UpdateWindowViewSize(false);
887 Invalidate();
888 }
889
890 if (locked)
891 UnlockLooper();
892
893 return true;
894 }
895
896
897 bool
AddSeparatorItem()898 BMenu::AddSeparatorItem()
899 {
900 BMenuItem* item = new (nothrow) BSeparatorItem();
901 if (!item || !AddItem(item, CountItems())) {
902 delete item;
903 return false;
904 }
905
906 return true;
907 }
908
909
910 bool
RemoveItem(BMenuItem * item)911 BMenu::RemoveItem(BMenuItem* item)
912 {
913 return _RemoveItems(0, 0, item, false);
914 }
915
916
917 BMenuItem*
RemoveItem(int32 index)918 BMenu::RemoveItem(int32 index)
919 {
920 BMenuItem* item = ItemAt(index);
921 if (item != NULL)
922 _RemoveItems(index, 1, NULL, false);
923 return item;
924 }
925
926
927 bool
RemoveItems(int32 index,int32 count,bool deleteItems)928 BMenu::RemoveItems(int32 index, int32 count, bool deleteItems)
929 {
930 return _RemoveItems(index, count, NULL, deleteItems);
931 }
932
933
934 bool
RemoveItem(BMenu * submenu)935 BMenu::RemoveItem(BMenu* submenu)
936 {
937 for (int32 i = 0; i < fItems.CountItems(); i++) {
938 if (static_cast<BMenuItem*>(fItems.ItemAtFast(i))->Submenu()
939 == submenu) {
940 return _RemoveItems(i, 1, NULL, false);
941 }
942 }
943
944 return false;
945 }
946
947
948 int32
CountItems() const949 BMenu::CountItems() const
950 {
951 return fItems.CountItems();
952 }
953
954
955 BMenuItem*
ItemAt(int32 index) const956 BMenu::ItemAt(int32 index) const
957 {
958 return static_cast<BMenuItem*>(fItems.ItemAt(index));
959 }
960
961
962 BMenu*
SubmenuAt(int32 index) const963 BMenu::SubmenuAt(int32 index) const
964 {
965 BMenuItem* item = static_cast<BMenuItem*>(fItems.ItemAt(index));
966 return item != NULL ? item->Submenu() : NULL;
967 }
968
969
970 int32
IndexOf(BMenuItem * item) const971 BMenu::IndexOf(BMenuItem* item) const
972 {
973 return fItems.IndexOf(item);
974 }
975
976
977 int32
IndexOf(BMenu * submenu) const978 BMenu::IndexOf(BMenu* submenu) const
979 {
980 for (int32 i = 0; i < fItems.CountItems(); i++) {
981 if (ItemAt(i)->Submenu() == submenu)
982 return i;
983 }
984
985 return -1;
986 }
987
988
989 BMenuItem*
FindItem(const char * label) const990 BMenu::FindItem(const char* label) const
991 {
992 BMenuItem* item = NULL;
993
994 for (int32 i = 0; i < CountItems(); i++) {
995 item = ItemAt(i);
996
997 if (item->Label() && strcmp(item->Label(), label) == 0)
998 return item;
999
1000 if (item->Submenu() != NULL) {
1001 item = item->Submenu()->FindItem(label);
1002 if (item != NULL)
1003 return item;
1004 }
1005 }
1006
1007 return NULL;
1008 }
1009
1010
1011 BMenuItem*
FindItem(uint32 command) const1012 BMenu::FindItem(uint32 command) const
1013 {
1014 BMenuItem* item = NULL;
1015
1016 for (int32 i = 0; i < CountItems(); i++) {
1017 item = ItemAt(i);
1018
1019 if (item->Command() == command)
1020 return item;
1021
1022 if (item->Submenu() != NULL) {
1023 item = item->Submenu()->FindItem(command);
1024 if (item != NULL)
1025 return item;
1026 }
1027 }
1028
1029 return NULL;
1030 }
1031
1032
1033 status_t
SetTargetForItems(BHandler * handler)1034 BMenu::SetTargetForItems(BHandler* handler)
1035 {
1036 status_t status = B_OK;
1037 for (int32 i = 0; i < fItems.CountItems(); i++) {
1038 status = ItemAt(i)->SetTarget(handler);
1039 if (status < B_OK)
1040 break;
1041 }
1042
1043 return status;
1044 }
1045
1046
1047 status_t
SetTargetForItems(BMessenger messenger)1048 BMenu::SetTargetForItems(BMessenger messenger)
1049 {
1050 status_t status = B_OK;
1051 for (int32 i = 0; i < fItems.CountItems(); i++) {
1052 status = ItemAt(i)->SetTarget(messenger);
1053 if (status < B_OK)
1054 break;
1055 }
1056
1057 return status;
1058 }
1059
1060
1061 void
SetEnabled(bool enable)1062 BMenu::SetEnabled(bool enable)
1063 {
1064 if (fEnabled == enable)
1065 return;
1066
1067 fEnabled = enable;
1068
1069 if (dynamic_cast<_BMCMenuBar_*>(Supermenu()) != NULL)
1070 Supermenu()->SetEnabled(enable);
1071
1072 if (fSuperitem)
1073 fSuperitem->SetEnabled(enable);
1074 }
1075
1076
1077 void
SetRadioMode(bool on)1078 BMenu::SetRadioMode(bool on)
1079 {
1080 fRadioMode = on;
1081 if (!on)
1082 SetLabelFromMarked(false);
1083 }
1084
1085
1086 void
SetTriggersEnabled(bool enable)1087 BMenu::SetTriggersEnabled(bool enable)
1088 {
1089 fTriggerEnabled = enable;
1090 }
1091
1092
1093 void
SetMaxContentWidth(float width)1094 BMenu::SetMaxContentWidth(float width)
1095 {
1096 fMaxContentWidth = width;
1097 }
1098
1099
1100 void
SetLabelFromMarked(bool on)1101 BMenu::SetLabelFromMarked(bool on)
1102 {
1103 fDynamicName = on;
1104 if (on)
1105 SetRadioMode(true);
1106 }
1107
1108
1109 bool
IsLabelFromMarked()1110 BMenu::IsLabelFromMarked()
1111 {
1112 return fDynamicName;
1113 }
1114
1115
1116 bool
IsEnabled() const1117 BMenu::IsEnabled() const
1118 {
1119 if (!fEnabled)
1120 return false;
1121
1122 return fSuper ? fSuper->IsEnabled() : true ;
1123 }
1124
1125
1126 bool
IsRadioMode() const1127 BMenu::IsRadioMode() const
1128 {
1129 return fRadioMode;
1130 }
1131
1132
1133 bool
AreTriggersEnabled() const1134 BMenu::AreTriggersEnabled() const
1135 {
1136 return fTriggerEnabled;
1137 }
1138
1139
1140 bool
IsRedrawAfterSticky() const1141 BMenu::IsRedrawAfterSticky() const
1142 {
1143 return false;
1144 }
1145
1146
1147 float
MaxContentWidth() const1148 BMenu::MaxContentWidth() const
1149 {
1150 return fMaxContentWidth;
1151 }
1152
1153
1154 BMenuItem*
FindMarked()1155 BMenu::FindMarked()
1156 {
1157 for (int32 i = 0; i < fItems.CountItems(); i++) {
1158 BMenuItem* item = ItemAt(i);
1159
1160 if (item->IsMarked())
1161 return item;
1162 }
1163
1164 return NULL;
1165 }
1166
1167
1168 int32
FindMarkedIndex()1169 BMenu::FindMarkedIndex()
1170 {
1171 for (int32 i = 0; i < fItems.CountItems(); i++) {
1172 BMenuItem* item = ItemAt(i);
1173
1174 if (item->IsMarked())
1175 return i;
1176 }
1177
1178 return -1;
1179 }
1180
1181
1182 BMenu*
Supermenu() const1183 BMenu::Supermenu() const
1184 {
1185 return fSuper;
1186 }
1187
1188
1189 BMenuItem*
Superitem() const1190 BMenu::Superitem() const
1191 {
1192 return fSuperitem;
1193 }
1194
1195
1196 BHandler*
ResolveSpecifier(BMessage * msg,int32 index,BMessage * specifier,int32 form,const char * property)1197 BMenu::ResolveSpecifier(BMessage* msg, int32 index, BMessage* specifier,
1198 int32 form, const char* property)
1199 {
1200 BPropertyInfo propInfo(sPropList);
1201 BHandler* target = NULL;
1202
1203 if (propInfo.FindMatch(msg, index, specifier, form, property) >= B_OK) {
1204 target = this;
1205 }
1206
1207 if (!target)
1208 target = BView::ResolveSpecifier(msg, index, specifier, form,
1209 property);
1210
1211 return target;
1212 }
1213
1214
1215 status_t
GetSupportedSuites(BMessage * data)1216 BMenu::GetSupportedSuites(BMessage* data)
1217 {
1218 if (data == NULL)
1219 return B_BAD_VALUE;
1220
1221 status_t err = data->AddString("suites", "suite/vnd.Be-menu");
1222
1223 if (err < B_OK)
1224 return err;
1225
1226 BPropertyInfo propertyInfo(sPropList);
1227 err = data->AddFlat("messages", &propertyInfo);
1228
1229 if (err < B_OK)
1230 return err;
1231
1232 return BView::GetSupportedSuites(data);
1233 }
1234
1235
1236 status_t
Perform(perform_code code,void * _data)1237 BMenu::Perform(perform_code code, void* _data)
1238 {
1239 switch (code) {
1240 case PERFORM_CODE_MIN_SIZE:
1241 ((perform_data_min_size*)_data)->return_value
1242 = BMenu::MinSize();
1243 return B_OK;
1244
1245 case PERFORM_CODE_MAX_SIZE:
1246 ((perform_data_max_size*)_data)->return_value
1247 = BMenu::MaxSize();
1248 return B_OK;
1249
1250 case PERFORM_CODE_PREFERRED_SIZE:
1251 ((perform_data_preferred_size*)_data)->return_value
1252 = BMenu::PreferredSize();
1253 return B_OK;
1254
1255 case PERFORM_CODE_LAYOUT_ALIGNMENT:
1256 ((perform_data_layout_alignment*)_data)->return_value
1257 = BMenu::LayoutAlignment();
1258 return B_OK;
1259
1260 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
1261 ((perform_data_has_height_for_width*)_data)->return_value
1262 = BMenu::HasHeightForWidth();
1263 return B_OK;
1264
1265 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
1266 {
1267 perform_data_get_height_for_width* data
1268 = (perform_data_get_height_for_width*)_data;
1269 BMenu::GetHeightForWidth(data->width, &data->min, &data->max,
1270 &data->preferred);
1271 return B_OK;
1272 }
1273
1274 case PERFORM_CODE_SET_LAYOUT:
1275 {
1276 perform_data_set_layout* data = (perform_data_set_layout*)_data;
1277 BMenu::SetLayout(data->layout);
1278 return B_OK;
1279 }
1280
1281 case PERFORM_CODE_LAYOUT_INVALIDATED:
1282 {
1283 perform_data_layout_invalidated* data
1284 = (perform_data_layout_invalidated*)_data;
1285 BMenu::LayoutInvalidated(data->descendants);
1286 return B_OK;
1287 }
1288
1289 case PERFORM_CODE_DO_LAYOUT:
1290 {
1291 BMenu::DoLayout();
1292 return B_OK;
1293 }
1294 }
1295
1296 return BView::Perform(code, _data);
1297 }
1298
1299
1300 // #pragma mark - BMenu protected methods
1301
1302
BMenu(BRect frame,const char * name,uint32 resizingMode,uint32 flags,menu_layout layout,bool resizeToFit)1303 BMenu::BMenu(BRect frame, const char* name, uint32 resizingMode, uint32 flags,
1304 menu_layout layout, bool resizeToFit)
1305 :
1306 BView(frame, name, resizingMode, flags),
1307 fChosenItem(NULL),
1308 fSelected(NULL),
1309 fCachedMenuWindow(NULL),
1310 fSuper(NULL),
1311 fSuperitem(NULL),
1312 fAscent(-1.0f),
1313 fDescent(-1.0f),
1314 fFontHeight(-1.0f),
1315 fState(MENU_STATE_CLOSED),
1316 fLayout(layout),
1317 fExtraRect(NULL),
1318 fMaxContentWidth(0.0f),
1319 fInitMatrixSize(NULL),
1320 fExtraMenuData(NULL),
1321 fTrigger(0),
1322 fResizeToFit(resizeToFit),
1323 fUseCachedMenuLayout(false),
1324 fEnabled(true),
1325 fDynamicName(false),
1326 fRadioMode(false),
1327 fTrackNewBounds(false),
1328 fStickyMode(false),
1329 fIgnoreHidden(true),
1330 fTriggerEnabled(true),
1331 fHasSubmenus(false),
1332 fAttachAborted(false)
1333 {
1334 _InitData(NULL);
1335 }
1336
1337
1338 void
SetItemMargins(float left,float top,float right,float bottom)1339 BMenu::SetItemMargins(float left, float top, float right, float bottom)
1340 {
1341 fPad.Set(left, top, right, bottom);
1342 }
1343
1344
1345 void
GetItemMargins(float * _left,float * _top,float * _right,float * _bottom) const1346 BMenu::GetItemMargins(float* _left, float* _top, float* _right,
1347 float* _bottom) const
1348 {
1349 if (_left != NULL)
1350 *_left = fPad.left;
1351
1352 if (_top != NULL)
1353 *_top = fPad.top;
1354
1355 if (_right != NULL)
1356 *_right = fPad.right;
1357
1358 if (_bottom != NULL)
1359 *_bottom = fPad.bottom;
1360 }
1361
1362
1363 menu_layout
Layout() const1364 BMenu::Layout() const
1365 {
1366 return fLayout;
1367 }
1368
1369
1370 void
Show()1371 BMenu::Show()
1372 {
1373 Show(false);
1374 }
1375
1376
1377 void
Show(bool selectFirst)1378 BMenu::Show(bool selectFirst)
1379 {
1380 _Install(NULL);
1381 _Show(selectFirst);
1382 }
1383
1384
1385 void
Hide()1386 BMenu::Hide()
1387 {
1388 _Hide();
1389 _Uninstall();
1390 }
1391
1392
1393 BMenuItem*
Track(bool sticky,BRect * clickToOpenRect)1394 BMenu::Track(bool sticky, BRect* clickToOpenRect)
1395 {
1396 if (sticky && LockLooper()) {
1397 //RedrawAfterSticky(Bounds());
1398 // the call above didn't do anything, so I've removed it for now
1399 UnlockLooper();
1400 }
1401
1402 if (clickToOpenRect != NULL && LockLooper()) {
1403 fExtraRect = clickToOpenRect;
1404 ConvertFromScreen(fExtraRect);
1405 UnlockLooper();
1406 }
1407
1408 _SetStickyMode(sticky);
1409
1410 int action;
1411 BMenuItem* menuItem = _Track(&action);
1412
1413 fExtraRect = NULL;
1414
1415 return menuItem;
1416 }
1417
1418
1419 // #pragma mark - BMenu private methods
1420
1421
1422 bool
AddDynamicItem(add_state state)1423 BMenu::AddDynamicItem(add_state state)
1424 {
1425 // Implemented in subclasses
1426 return false;
1427 }
1428
1429
1430 void
DrawBackground(BRect updateRect)1431 BMenu::DrawBackground(BRect updateRect)
1432 {
1433 rgb_color base = ui_color(B_MENU_BACKGROUND_COLOR);
1434 uint32 flags = 0;
1435 if (!IsEnabled())
1436 flags |= BControlLook::B_DISABLED;
1437
1438 if (IsFocus())
1439 flags |= BControlLook::B_FOCUSED;
1440
1441 BRect rect = Bounds();
1442 uint32 borders = BControlLook::B_LEFT_BORDER
1443 | BControlLook::B_RIGHT_BORDER;
1444 if (Window() != NULL && Parent() != NULL) {
1445 if (Parent()->Frame().top == Window()->Bounds().top)
1446 borders |= BControlLook::B_TOP_BORDER;
1447
1448 if (Parent()->Frame().bottom == Window()->Bounds().bottom)
1449 borders |= BControlLook::B_BOTTOM_BORDER;
1450 } else {
1451 borders |= BControlLook::B_TOP_BORDER
1452 | BControlLook::B_BOTTOM_BORDER;
1453 }
1454 be_control_look->DrawMenuBackground(this, rect, updateRect, base, flags,
1455 borders);
1456 }
1457
1458
1459 void
SetTrackingHook(menu_tracking_hook func,void * state)1460 BMenu::SetTrackingHook(menu_tracking_hook func, void* state)
1461 {
1462 fExtraMenuData->trackingHook = func;
1463 fExtraMenuData->trackingState = state;
1464 }
1465
1466
1467 // #pragma mark - Reorder item methods
1468
1469
1470 void
SortItems(int (* compare)(const BMenuItem *,const BMenuItem *))1471 BMenu::SortItems(int (*compare)(const BMenuItem*, const BMenuItem*))
1472 {
1473 BMenuItem** begin = (BMenuItem**)fItems.Items();
1474 BMenuItem** end = begin + fItems.CountItems();
1475
1476 std::stable_sort(begin, end, BPrivate::MenuItemComparator(compare));
1477
1478 InvalidateLayout();
1479 if (Window() != NULL && !Window()->IsHidden() && LockLooper()) {
1480 _LayoutItems(0);
1481 Invalidate();
1482 UnlockLooper();
1483 }
1484 }
1485
1486
1487 bool
SwapItems(int32 indexA,int32 indexB)1488 BMenu::SwapItems(int32 indexA, int32 indexB)
1489 {
1490 bool swapped = fItems.SwapItems(indexA, indexB);
1491 if (swapped) {
1492 InvalidateLayout();
1493 if (Window() != NULL && !Window()->IsHidden() && LockLooper()) {
1494 _LayoutItems(std::min(indexA, indexB));
1495 Invalidate();
1496 UnlockLooper();
1497 }
1498 }
1499
1500 return swapped;
1501 }
1502
1503
1504 bool
MoveItem(int32 indexFrom,int32 indexTo)1505 BMenu::MoveItem(int32 indexFrom, int32 indexTo)
1506 {
1507 bool moved = fItems.MoveItem(indexFrom, indexTo);
1508 if (moved) {
1509 InvalidateLayout();
1510 if (Window() != NULL && !Window()->IsHidden() && LockLooper()) {
1511 _LayoutItems(std::min(indexFrom, indexTo));
1512 Invalidate();
1513 UnlockLooper();
1514 }
1515 }
1516
1517 return moved;
1518 }
1519
1520
_ReservedMenu3()1521 void BMenu::_ReservedMenu3() {}
_ReservedMenu4()1522 void BMenu::_ReservedMenu4() {}
_ReservedMenu5()1523 void BMenu::_ReservedMenu5() {}
_ReservedMenu6()1524 void BMenu::_ReservedMenu6() {}
1525
1526
1527 void
_InitData(BMessage * archive)1528 BMenu::_InitData(BMessage* archive)
1529 {
1530 BPrivate::kEmptyMenuLabel = B_TRANSLATE("<empty>");
1531
1532 // TODO: Get _color, _fname, _fflt from the message, if present
1533 BFont font;
1534 font.SetFamilyAndStyle(sMenuInfo.f_family, sMenuInfo.f_style);
1535 font.SetSize(sMenuInfo.font_size);
1536 SetFont(&font, B_FONT_FAMILY_AND_STYLE | B_FONT_SIZE);
1537
1538 fExtraMenuData = new (nothrow) BPrivate::ExtraMenuData();
1539
1540 const float labelSpacing = be_control_look->DefaultLabelSpacing();
1541 fPad = BRect(ceilf(labelSpacing * 2.3f), ceilf(labelSpacing / 3.0f),
1542 ceilf((labelSpacing / 3.0f) * 10.0f), 0.0f);
1543
1544 fLayoutData = new LayoutData;
1545 fLayoutData->lastResizingMode = ResizingMode();
1546
1547 SetLowUIColor(B_MENU_BACKGROUND_COLOR);
1548 SetViewColor(B_TRANSPARENT_COLOR);
1549
1550 fTriggerEnabled = sMenuInfo.triggers_always_shown;
1551
1552 if (archive != NULL) {
1553 archive->FindInt32("_layout", (int32*)&fLayout);
1554 archive->FindBool("_rsize_to_fit", &fResizeToFit);
1555 bool disabled;
1556 if (archive->FindBool("_disable", &disabled) == B_OK)
1557 fEnabled = !disabled;
1558 archive->FindBool("_radio", &fRadioMode);
1559
1560 bool disableTrigger = false;
1561 archive->FindBool("_trig_disabled", &disableTrigger);
1562 fTriggerEnabled = !disableTrigger;
1563
1564 archive->FindBool("_dyn_label", &fDynamicName);
1565 archive->FindFloat("_maxwidth", &fMaxContentWidth);
1566
1567 BMessage msg;
1568 for (int32 i = 0; archive->FindMessage("_items", i, &msg) == B_OK; i++) {
1569 BArchivable* object = instantiate_object(&msg);
1570 if (BMenuItem* item = dynamic_cast<BMenuItem*>(object)) {
1571 BRect bounds;
1572 if (fLayout == B_ITEMS_IN_MATRIX
1573 && archive->FindRect("_i_frames", i, &bounds) == B_OK)
1574 AddItem(item, bounds);
1575 else
1576 AddItem(item);
1577 }
1578 }
1579 }
1580 }
1581
1582
1583 bool
_Show(bool selectFirstItem,bool keyDown)1584 BMenu::_Show(bool selectFirstItem, bool keyDown)
1585 {
1586 if (Window() != NULL)
1587 return false;
1588
1589 // See if the supermenu has a cached menuwindow,
1590 // and use that one if possible.
1591 BMenuWindow* window = NULL;
1592 bool ourWindow = false;
1593 if (fSuper != NULL) {
1594 fSuperbounds = fSuper->ConvertToScreen(fSuper->Bounds());
1595 window = fSuper->_MenuWindow();
1596 }
1597
1598 // Otherwise, create a new one
1599 // This happens for "stand alone" BPopUpMenus
1600 // (i.e. not within a BMenuField)
1601 if (window == NULL) {
1602 // Menu windows get the BMenu's handler name
1603 window = new (nothrow) BMenuWindow(Name());
1604 ourWindow = true;
1605 }
1606
1607 if (window == NULL)
1608 return false;
1609
1610 if (window->Lock()) {
1611 bool addAborted = false;
1612 if (keyDown)
1613 addAborted = _AddDynamicItems(keyDown);
1614
1615 if (addAborted) {
1616 if (ourWindow)
1617 window->Quit();
1618 else
1619 window->Unlock();
1620 return false;
1621 }
1622 fAttachAborted = false;
1623
1624 window->AttachMenu(this);
1625
1626 if (ItemAt(0) != NULL) {
1627 float width, height;
1628 ItemAt(0)->GetContentSize(&width, &height);
1629
1630 window->SetSmallStep(ceilf(height));
1631 }
1632
1633 // Menu didn't have the time to add its items: aborting...
1634 if (fAttachAborted) {
1635 window->DetachMenu();
1636 // TODO: Probably not needed, we can just let _hide() quit the
1637 // window.
1638 if (ourWindow)
1639 window->Quit();
1640 else
1641 window->Unlock();
1642 return false;
1643 }
1644
1645 _UpdateWindowViewSize(true);
1646 window->Show();
1647
1648 if (selectFirstItem)
1649 _SelectItem(ItemAt(0), false);
1650
1651 window->Unlock();
1652 }
1653
1654 return true;
1655 }
1656
1657
1658 void
_Hide()1659 BMenu::_Hide()
1660 {
1661 BMenuWindow* window = dynamic_cast<BMenuWindow*>(Window());
1662 if (window == NULL || !window->Lock())
1663 return;
1664
1665 if (fSelected != NULL)
1666 _SelectItem(NULL);
1667
1668 window->Hide();
1669 window->DetachMenu();
1670 // we don't want to be deleted when the window is removed
1671
1672 #if USE_CACHED_MENUWINDOW
1673 if (fSuper != NULL)
1674 window->Unlock();
1675 else
1676 #endif
1677 window->Quit();
1678 // it's our window, quit it
1679
1680 _DeleteMenuWindow();
1681 // Delete the menu window used by our submenus
1682 }
1683
1684
_ScriptReceived(BMessage * message)1685 void BMenu::_ScriptReceived(BMessage* message)
1686 {
1687 BMessage replyMsg(B_REPLY);
1688 status_t err = B_BAD_SCRIPT_SYNTAX;
1689 int32 index;
1690 BMessage specifier;
1691 int32 what;
1692 const char* property;
1693
1694 if (message->GetCurrentSpecifier(&index, &specifier, &what, &property)
1695 != B_OK) {
1696 return BView::MessageReceived(message);
1697 }
1698
1699 BPropertyInfo propertyInfo(sPropList);
1700 switch (propertyInfo.FindMatch(message, index, &specifier, what,
1701 property)) {
1702 case 0: // Enabled: GET
1703 if (message->what == B_GET_PROPERTY)
1704 err = replyMsg.AddBool("result", IsEnabled());
1705 break;
1706 case 1: // Enabled: SET
1707 if (message->what == B_SET_PROPERTY) {
1708 bool isEnabled;
1709 err = message->FindBool("data", &isEnabled);
1710 if (err >= B_OK)
1711 SetEnabled(isEnabled);
1712 }
1713 break;
1714 case 2: // Label: GET
1715 case 3: // Label: SET
1716 case 4: // Mark: GET
1717 case 5: { // Mark: SET
1718 BMenuItem *item = Superitem();
1719 if (item != NULL)
1720 return Supermenu()->_ItemScriptReceived(message, item);
1721
1722 break;
1723 }
1724 case 6: // Menu: CREATE
1725 if (message->what == B_CREATE_PROPERTY) {
1726 const char *label;
1727 ObjectDeleter<BMessage> invokeMessage(new BMessage());
1728 BMessenger target;
1729 ObjectDeleter<BMenuItem> item;
1730 err = message->FindString("data", &label);
1731 if (err >= B_OK) {
1732 invokeMessage.SetTo(new BMessage());
1733 err = message->FindInt32("what",
1734 (int32*)&invokeMessage->what);
1735 if (err == B_NAME_NOT_FOUND) {
1736 invokeMessage.Unset();
1737 err = B_OK;
1738 }
1739 }
1740 if (err >= B_OK) {
1741 item.SetTo(new BMenuItem(new BMenu(label),
1742 invokeMessage.Detach()));
1743 }
1744 if (err >= B_OK) {
1745 err = _InsertItemAtSpecifier(specifier, what, item.Get());
1746 }
1747 if (err >= B_OK)
1748 item.Detach();
1749 }
1750 break;
1751 case 7: { // Menu: DELETE
1752 if (message->what == B_DELETE_PROPERTY) {
1753 BMenuItem *item = NULL;
1754 int32 index;
1755 err = _ResolveItemSpecifier(specifier, what, item, &index);
1756 if (err >= B_OK) {
1757 if (item->Submenu() == NULL)
1758 err = B_BAD_VALUE;
1759 else {
1760 if (index >= 0)
1761 RemoveItem(index);
1762 else
1763 RemoveItem(item);
1764 }
1765 }
1766 }
1767 break;
1768 }
1769 case 8: { // Menu: *
1770 // TODO: check that submenu looper is running and handle it
1771 // correctly
1772 BMenu *submenu = NULL;
1773 BMenuItem *item;
1774 err = _ResolveItemSpecifier(specifier, what, item);
1775 if (err >= B_OK)
1776 submenu = item->Submenu();
1777 if (submenu != NULL) {
1778 message->PopSpecifier();
1779 return submenu->_ScriptReceived(message);
1780 }
1781 break;
1782 }
1783 case 9: // MenuItem: COUNT
1784 if (message->what == B_COUNT_PROPERTIES)
1785 err = replyMsg.AddInt32("result", CountItems());
1786 break;
1787 case 10: // MenuItem: CREATE
1788 if (message->what == B_CREATE_PROPERTY) {
1789 const char *label;
1790 ObjectDeleter<BMessage> invokeMessage(new BMessage());
1791 bool targetPresent = true;
1792 BMessenger target;
1793 ObjectDeleter<BMenuItem> item;
1794 err = message->FindString("data", &label);
1795 if (err >= B_OK) {
1796 err = message->FindMessage("be:invoke_message",
1797 invokeMessage.Get());
1798 if (err == B_NAME_NOT_FOUND) {
1799 err = message->FindInt32("what",
1800 (int32*)&invokeMessage->what);
1801 if (err == B_NAME_NOT_FOUND) {
1802 invokeMessage.Unset();
1803 err = B_OK;
1804 }
1805 }
1806 }
1807 if (err >= B_OK) {
1808 err = message->FindMessenger("be:target", &target);
1809 if (err == B_NAME_NOT_FOUND) {
1810 targetPresent = false;
1811 err = B_OK;
1812 }
1813 }
1814 if (err >= B_OK) {
1815 item.SetTo(new BMenuItem(label, invokeMessage.Detach()));
1816 if (targetPresent)
1817 err = item->SetTarget(target);
1818 }
1819 if (err >= B_OK) {
1820 err = _InsertItemAtSpecifier(specifier, what, item.Get());
1821 }
1822 if (err >= B_OK)
1823 item.Detach();
1824 }
1825 break;
1826 case 11: // MenuItem: DELETE
1827 if (message->what == B_DELETE_PROPERTY) {
1828 BMenuItem *item = NULL;
1829 int32 index;
1830 err = _ResolveItemSpecifier(specifier, what, item, &index);
1831 if (err >= B_OK) {
1832 if (index >= 0)
1833 RemoveItem(index);
1834 else
1835 RemoveItem(item);
1836 }
1837 }
1838 break;
1839 case 12: { // MenuItem: EXECUTE
1840 if (message->what == B_EXECUTE_PROPERTY) {
1841 BMenuItem *item = NULL;
1842 err = _ResolveItemSpecifier(specifier, what, item);
1843 if (err >= B_OK) {
1844 if (!item->IsEnabled())
1845 err = B_NOT_ALLOWED;
1846 else
1847 err = item->Invoke();
1848 }
1849 }
1850 break;
1851 }
1852 case 13: { // MenuItem: *
1853 BMenuItem *item = NULL;
1854 err = _ResolveItemSpecifier(specifier, what, item);
1855 if (err >= B_OK) {
1856 message->PopSpecifier();
1857 return _ItemScriptReceived(message, item);
1858 }
1859 break;
1860 }
1861 default:
1862 return BView::MessageReceived(message);
1863 }
1864
1865 if (err != B_OK) {
1866 replyMsg.what = B_MESSAGE_NOT_UNDERSTOOD;
1867
1868 if (err == B_BAD_SCRIPT_SYNTAX)
1869 replyMsg.AddString("message", "Didn't understand the specifier(s)");
1870 else
1871 replyMsg.AddString("message", strerror(err));
1872 }
1873
1874 replyMsg.AddInt32("error", err);
1875 message->SendReply(&replyMsg);
1876 }
1877
1878
_ItemScriptReceived(BMessage * message,BMenuItem * item)1879 void BMenu::_ItemScriptReceived(BMessage* message, BMenuItem* item)
1880 {
1881 BMessage replyMsg(B_REPLY);
1882 status_t err = B_BAD_SCRIPT_SYNTAX;
1883 int32 index;
1884 BMessage specifier;
1885 int32 what;
1886 const char* property;
1887
1888 if (message->GetCurrentSpecifier(&index, &specifier, &what, &property)
1889 != B_OK) {
1890 return BView::MessageReceived(message);
1891 }
1892
1893 BPropertyInfo propertyInfo(sPropList);
1894 switch (propertyInfo.FindMatch(message, index, &specifier, what,
1895 property)) {
1896 case 0: // Enabled: GET
1897 if (message->what == B_GET_PROPERTY)
1898 err = replyMsg.AddBool("result", item->IsEnabled());
1899 break;
1900 case 1: // Enabled: SET
1901 if (message->what == B_SET_PROPERTY) {
1902 bool isEnabled;
1903 err = message->FindBool("data", &isEnabled);
1904 if (err >= B_OK)
1905 item->SetEnabled(isEnabled);
1906 }
1907 break;
1908 case 2: // Label: GET
1909 if (message->what == B_GET_PROPERTY)
1910 err = replyMsg.AddString("result", item->Label());
1911 break;
1912 case 3: // Label: SET
1913 if (message->what == B_SET_PROPERTY) {
1914 const char *label;
1915 err = message->FindString("data", &label);
1916 if (err >= B_OK)
1917 item->SetLabel(label);
1918 }
1919 case 4: // Mark: GET
1920 if (message->what == B_GET_PROPERTY)
1921 err = replyMsg.AddBool("result", item->IsMarked());
1922 break;
1923 case 5: // Mark: SET
1924 if (message->what == B_SET_PROPERTY) {
1925 bool isMarked;
1926 err = message->FindBool("data", &isMarked);
1927 if (err >= B_OK)
1928 item->SetMarked(isMarked);
1929 }
1930 break;
1931 case 6: // Menu: CREATE
1932 case 7: // Menu: DELETE
1933 case 8: // Menu: *
1934 case 9: // MenuItem: COUNT
1935 case 10: // MenuItem: CREATE
1936 case 11: // MenuItem: DELETE
1937 case 12: // MenuItem: EXECUTE
1938 case 13: // MenuItem: *
1939 break;
1940 default:
1941 return BView::MessageReceived(message);
1942 }
1943
1944 if (err != B_OK) {
1945 replyMsg.what = B_MESSAGE_NOT_UNDERSTOOD;
1946 replyMsg.AddString("message", strerror(err));
1947 }
1948
1949 replyMsg.AddInt32("error", err);
1950 message->SendReply(&replyMsg);
1951 }
1952
1953
_ResolveItemSpecifier(const BMessage & specifier,int32 what,BMenuItem * & item,int32 * _index)1954 status_t BMenu::_ResolveItemSpecifier(const BMessage& specifier, int32 what,
1955 BMenuItem*& item, int32 *_index)
1956 {
1957 status_t err;
1958 item = NULL;
1959 int32 index = -1;
1960 switch (what) {
1961 case B_INDEX_SPECIFIER:
1962 case B_REVERSE_INDEX_SPECIFIER: {
1963 err = specifier.FindInt32("index", &index);
1964 if (err < B_OK)
1965 return err;
1966 if (what == B_REVERSE_INDEX_SPECIFIER)
1967 index = CountItems() - index;
1968 item = ItemAt(index);
1969 break;
1970 }
1971 case B_NAME_SPECIFIER: {
1972 const char* name;
1973 err = specifier.FindString("name", &name);
1974 if (err < B_OK)
1975 return err;
1976 item = FindItem(name);
1977 break;
1978 }
1979 }
1980 if (item == NULL)
1981 return B_BAD_INDEX;
1982
1983 if (_index != NULL)
1984 *_index = index;
1985
1986 return B_OK;
1987 }
1988
1989
_InsertItemAtSpecifier(const BMessage & specifier,int32 what,BMenuItem * item)1990 status_t BMenu::_InsertItemAtSpecifier(const BMessage& specifier, int32 what,
1991 BMenuItem* item)
1992 {
1993 status_t err;
1994 switch (what) {
1995 case B_INDEX_SPECIFIER:
1996 case B_REVERSE_INDEX_SPECIFIER: {
1997 int32 index;
1998 err = specifier.FindInt32("index", &index);
1999 if (err < B_OK) return err;
2000 if (what == B_REVERSE_INDEX_SPECIFIER)
2001 index = CountItems() - index;
2002 if (!AddItem(item, index))
2003 return B_BAD_INDEX;
2004 break;
2005 }
2006 case B_NAME_SPECIFIER:
2007 return B_NOT_SUPPORTED;
2008 break;
2009 }
2010
2011 return B_OK;
2012 }
2013
2014
2015 // #pragma mark - mouse tracking
2016
2017
2018 const static bigtime_t kOpenSubmenuDelay = 0;
2019 const static bigtime_t kNavigationAreaTimeout = 1000000;
2020
2021
2022 BMenuItem*
_Track(int * action,long start)2023 BMenu::_Track(int* action, long start)
2024 {
2025 // TODO: cleanup
2026 BMenuItem* item = NULL;
2027 BRect navAreaRectAbove;
2028 BRect navAreaRectBelow;
2029 bigtime_t selectedTime = system_time();
2030 bigtime_t navigationAreaTime = 0;
2031
2032 fState = MENU_STATE_TRACKING;
2033 fChosenItem = NULL;
2034 // we will use this for keyboard selection
2035
2036 BPoint location;
2037 uint32 buttons = 0;
2038 if (LockLooper()) {
2039 GetMouse(&location, &buttons);
2040 UnlockLooper();
2041 }
2042
2043 bool releasedOnce = buttons == 0;
2044 while (fState != MENU_STATE_CLOSED) {
2045 if (_CustomTrackingWantsToQuit())
2046 break;
2047
2048 if (!LockLooper())
2049 break;
2050
2051 BMenuWindow* window = static_cast<BMenuWindow*>(Window());
2052 BPoint screenLocation = ConvertToScreen(location);
2053 if (window->CheckForScrolling(screenLocation)) {
2054 UnlockLooper();
2055 continue;
2056 }
2057
2058 // The order of the checks is important
2059 // to be able to handle overlapping menus:
2060 // first we check if mouse is inside a submenu,
2061 // then if the mouse is inside this menu,
2062 // then if it's over a super menu.
2063 if (_OverSubmenu(fSelected, screenLocation)
2064 || fState == MENU_STATE_KEY_TO_SUBMENU) {
2065 if (fState == MENU_STATE_TRACKING) {
2066 // not if from R.Arrow
2067 fState = MENU_STATE_TRACKING_SUBMENU;
2068 }
2069 navAreaRectAbove = BRect();
2070 navAreaRectBelow = BRect();
2071
2072 // Since the submenu has its own looper,
2073 // we can unlock ours. Doing so also make sure
2074 // that our window gets any update message to
2075 // redraw itself
2076 UnlockLooper();
2077
2078 // To prevent NULL access violation, ensure a menu has actually
2079 // been selected and that it has a submenu. Because keyboard and
2080 // mouse interactions set selected items differently, the menu
2081 // tracking thread needs to be careful in triggering the navigation
2082 // to the submenu.
2083 if (fSelected != NULL) {
2084 BMenu* submenu = fSelected->Submenu();
2085 int submenuAction = MENU_STATE_TRACKING;
2086 if (submenu != NULL) {
2087 submenu->_SetStickyMode(_IsStickyMode());
2088
2089 // The following call blocks until the submenu
2090 // gives control back to us, either because the mouse
2091 // pointer goes out of the submenu's bounds, or because
2092 // the user closes the menu
2093 BMenuItem* submenuItem = submenu->_Track(&submenuAction);
2094 if (submenuAction == MENU_STATE_CLOSED) {
2095 item = submenuItem;
2096 fState = MENU_STATE_CLOSED;
2097 } else if (submenuAction == MENU_STATE_KEY_LEAVE_SUBMENU) {
2098 if (LockLooper()) {
2099 BMenuItem* temp = fSelected;
2100 // close the submenu:
2101 _SelectItem(NULL);
2102 // but reselect the item itself for user:
2103 _SelectItem(temp, false);
2104 UnlockLooper();
2105 }
2106 // cancel key-nav state
2107 fState = MENU_STATE_TRACKING;
2108 } else
2109 fState = MENU_STATE_TRACKING;
2110 }
2111 }
2112 if (!LockLooper())
2113 break;
2114 } else if ((item = _HitTestItems(location, B_ORIGIN)) != NULL) {
2115 _UpdateStateOpenSelect(item, location, navAreaRectAbove,
2116 navAreaRectBelow, selectedTime, navigationAreaTime);
2117 releasedOnce = true;
2118 } else if (_OverSuper(screenLocation)
2119 && fSuper->fState != MENU_STATE_KEY_TO_SUBMENU) {
2120 fState = MENU_STATE_TRACKING;
2121 UnlockLooper();
2122 break;
2123 } else if (fState == MENU_STATE_KEY_LEAVE_SUBMENU) {
2124 UnlockLooper();
2125 break;
2126 } else if (fSuper == NULL
2127 || fSuper->fState != MENU_STATE_KEY_TO_SUBMENU) {
2128 // Mouse pointer outside menu:
2129 // If there's no other submenu opened,
2130 // deselect the current selected item
2131 if (fSelected != NULL
2132 && (fSelected->Submenu() == NULL
2133 || fSelected->Submenu()->Window() == NULL)) {
2134 _SelectItem(NULL);
2135 fState = MENU_STATE_TRACKING;
2136 }
2137
2138 if (fSuper != NULL) {
2139 // Give supermenu the chance to continue tracking
2140 *action = fState;
2141 UnlockLooper();
2142 return NULL;
2143 }
2144 }
2145
2146 UnlockLooper();
2147
2148 if (releasedOnce)
2149 _UpdateStateClose(item, location, buttons);
2150
2151 if (fState != MENU_STATE_CLOSED) {
2152 bigtime_t snoozeAmount = 50000;
2153
2154 BPoint newLocation = location;
2155 uint32 newButtons = buttons;
2156
2157 // If user doesn't move the mouse, loop here,
2158 // so we don't interfere with keyboard menu navigation
2159 do {
2160 snooze(snoozeAmount);
2161 if (!LockLooper())
2162 break;
2163 GetMouse(&newLocation, &newButtons, true);
2164 UnlockLooper();
2165 } while (newLocation == location && newButtons == buttons
2166 && !(item != NULL && item->Submenu() != NULL
2167 && item->Submenu()->Window() == NULL)
2168 && fState == MENU_STATE_TRACKING);
2169
2170 if (newLocation != location || newButtons != buttons) {
2171 if (!releasedOnce && newButtons == 0 && buttons != 0)
2172 releasedOnce = true;
2173 location = newLocation;
2174 buttons = newButtons;
2175 }
2176
2177 if (releasedOnce)
2178 _UpdateStateClose(item, location, buttons);
2179 }
2180 }
2181
2182 if (action != NULL)
2183 *action = fState;
2184
2185 // keyboard Enter will set this
2186 if (fChosenItem != NULL)
2187 item = fChosenItem;
2188 else if (fSelected == NULL) {
2189 // needed to cover (rare) mouse/ESC combination
2190 item = NULL;
2191 }
2192
2193 if (fSelected != NULL && LockLooper()) {
2194 _SelectItem(NULL);
2195 UnlockLooper();
2196 }
2197
2198 // delete the menu window recycled for all the child menus
2199 _DeleteMenuWindow();
2200
2201 return item;
2202 }
2203
2204
2205 void
_UpdateNavigationArea(BPoint position,BRect & navAreaRectAbove,BRect & navAreaRectBelow)2206 BMenu::_UpdateNavigationArea(BPoint position, BRect& navAreaRectAbove,
2207 BRect& navAreaRectBelow)
2208 {
2209 #define NAV_AREA_THRESHOLD 8
2210
2211 // The navigation area is a region in which mouse-overs won't select
2212 // the item under the cursor. This makes it easier to navigate to
2213 // submenus, as the cursor can be moved to submenu items directly instead
2214 // of having to move it horizontally into the submenu first. The concept
2215 // is illustrated below:
2216 //
2217 // +-------+----+---------+
2218 // | | /| |
2219 // | | /*| |
2220 // |[2]--> | /**| |
2221 // | |/[4]| |
2222 // |------------| |
2223 // | [1] | [6] |
2224 // |------------| |
2225 // | |\[5]| |
2226 // |[3]--> | \**| |
2227 // | | \*| |
2228 // | | \| |
2229 // | +----|---------+
2230 // | |
2231 // +------------+
2232 //
2233 // [1] Selected item, cursor position ('position')
2234 // [2] Upper navigation area rectangle ('navAreaRectAbove')
2235 // [3] Lower navigation area rectangle ('navAreaRectBelow')
2236 // [4] Upper navigation area
2237 // [5] Lower navigation area
2238 // [6] Submenu
2239 //
2240 // The rectangles are used to calculate if the cursor is in the actual
2241 // navigation area (see _UpdateStateOpenSelect()).
2242
2243 if (fSelected == NULL)
2244 return;
2245
2246 BMenu* submenu = fSelected->Submenu();
2247
2248 if (submenu != NULL) {
2249 BRect menuBounds = ConvertToScreen(Bounds());
2250
2251 BRect submenuBounds;
2252 if (fSelected->Submenu()->LockLooper()) {
2253 submenuBounds = fSelected->Submenu()->ConvertToScreen(
2254 fSelected->Submenu()->Bounds());
2255 fSelected->Submenu()->UnlockLooper();
2256 }
2257
2258 if (menuBounds.left < submenuBounds.left) {
2259 navAreaRectAbove.Set(position.x + NAV_AREA_THRESHOLD,
2260 submenuBounds.top, menuBounds.right,
2261 position.y);
2262 navAreaRectBelow.Set(position.x + NAV_AREA_THRESHOLD,
2263 position.y, menuBounds.right,
2264 submenuBounds.bottom);
2265 } else {
2266 navAreaRectAbove.Set(menuBounds.left,
2267 submenuBounds.top, position.x - NAV_AREA_THRESHOLD,
2268 position.y);
2269 navAreaRectBelow.Set(menuBounds.left,
2270 position.y, position.x - NAV_AREA_THRESHOLD,
2271 submenuBounds.bottom);
2272 }
2273 } else {
2274 navAreaRectAbove = BRect();
2275 navAreaRectBelow = BRect();
2276 }
2277 }
2278
2279
2280 void
_UpdateStateOpenSelect(BMenuItem * item,BPoint position,BRect & navAreaRectAbove,BRect & navAreaRectBelow,bigtime_t & selectedTime,bigtime_t & navigationAreaTime)2281 BMenu::_UpdateStateOpenSelect(BMenuItem* item, BPoint position,
2282 BRect& navAreaRectAbove, BRect& navAreaRectBelow, bigtime_t& selectedTime,
2283 bigtime_t& navigationAreaTime)
2284 {
2285 if (fState == MENU_STATE_CLOSED)
2286 return;
2287
2288 if (item != fSelected) {
2289 if (navigationAreaTime == 0)
2290 navigationAreaTime = system_time();
2291
2292 position = ConvertToScreen(position);
2293
2294 bool inNavAreaRectAbove = navAreaRectAbove.Contains(position);
2295 bool inNavAreaRectBelow = navAreaRectBelow.Contains(position);
2296
2297 if (fSelected == NULL
2298 || (!inNavAreaRectAbove && !inNavAreaRectBelow)) {
2299 _SelectItem(item, false);
2300 navAreaRectAbove = BRect();
2301 navAreaRectBelow = BRect();
2302 selectedTime = system_time();
2303 navigationAreaTime = 0;
2304 return;
2305 }
2306
2307 bool isLeft = ConvertFromScreen(navAreaRectAbove).left == 0;
2308 BPoint p1, p2;
2309
2310 if (inNavAreaRectAbove) {
2311 if (!isLeft) {
2312 p1 = navAreaRectAbove.LeftBottom();
2313 p2 = navAreaRectAbove.RightTop();
2314 } else {
2315 p2 = navAreaRectAbove.RightBottom();
2316 p1 = navAreaRectAbove.LeftTop();
2317 }
2318 } else {
2319 if (!isLeft) {
2320 p2 = navAreaRectBelow.LeftTop();
2321 p1 = navAreaRectBelow.RightBottom();
2322 } else {
2323 p1 = navAreaRectBelow.RightTop();
2324 p2 = navAreaRectBelow.LeftBottom();
2325 }
2326 }
2327 bool inNavArea =
2328 (p1.y - p2.y) * position.x + (p2.x - p1.x) * position.y
2329 + (p1.x - p2.x) * p1.y + (p2.y - p1.y) * p1.x >= 0;
2330
2331 bigtime_t systime = system_time();
2332
2333 if (!inNavArea || (navigationAreaTime > 0 && systime -
2334 navigationAreaTime > kNavigationAreaTimeout)) {
2335 // Don't delay opening of submenu if the user had
2336 // to wait for the navigation area timeout anyway
2337 _SelectItem(item, inNavArea);
2338
2339 if (inNavArea) {
2340 _UpdateNavigationArea(position, navAreaRectAbove,
2341 navAreaRectBelow);
2342 } else {
2343 navAreaRectAbove = BRect();
2344 navAreaRectBelow = BRect();
2345 }
2346
2347 selectedTime = system_time();
2348 navigationAreaTime = 0;
2349 }
2350 } else if (fSelected->Submenu() != NULL &&
2351 system_time() - selectedTime > kOpenSubmenuDelay) {
2352 _SelectItem(fSelected, true);
2353
2354 if (!navAreaRectAbove.IsValid() && !navAreaRectBelow.IsValid()) {
2355 position = ConvertToScreen(position);
2356 _UpdateNavigationArea(position, navAreaRectAbove,
2357 navAreaRectBelow);
2358 }
2359 }
2360
2361 if (fState != MENU_STATE_TRACKING)
2362 fState = MENU_STATE_TRACKING;
2363 }
2364
2365
2366 void
_UpdateStateClose(BMenuItem * item,const BPoint & where,const uint32 & buttons)2367 BMenu::_UpdateStateClose(BMenuItem* item, const BPoint& where,
2368 const uint32& buttons)
2369 {
2370 if (fState == MENU_STATE_CLOSED)
2371 return;
2372
2373 if (buttons != 0 && _IsStickyMode()) {
2374 if (item == NULL) {
2375 if (item != fSelected && LockLooper()) {
2376 _SelectItem(item, false);
2377 UnlockLooper();
2378 }
2379 fState = MENU_STATE_CLOSED;
2380 } else
2381 _SetStickyMode(false);
2382 } else if (buttons == 0 && !_IsStickyMode()) {
2383 if (fExtraRect != NULL && fExtraRect->Contains(where)) {
2384 _SetStickyMode(true);
2385 fExtraRect = NULL;
2386 // Setting this to NULL will prevent this code
2387 // to be executed next time
2388 } else {
2389 if (item != fSelected && LockLooper()) {
2390 _SelectItem(item, false);
2391 UnlockLooper();
2392 }
2393 fState = MENU_STATE_CLOSED;
2394 }
2395 }
2396 }
2397
2398
2399 bool
_AddItem(BMenuItem * item,int32 index)2400 BMenu::_AddItem(BMenuItem* item, int32 index)
2401 {
2402 ASSERT(item != NULL);
2403 if (index < 0 || index > fItems.CountItems())
2404 return false;
2405
2406 if (item->IsMarked())
2407 _ItemMarked(item);
2408
2409 if (!fItems.AddItem(item, index))
2410 return false;
2411
2412 // install the item on the supermenu's window
2413 // or onto our window, if we are a root menu
2414 BWindow* window = NULL;
2415 if (Superitem() != NULL)
2416 window = Superitem()->fWindow;
2417 else
2418 window = Window();
2419 if (window != NULL)
2420 item->Install(window);
2421
2422 item->SetSuper(this);
2423 return true;
2424 }
2425
2426
2427 bool
_RemoveItems(int32 index,int32 count,BMenuItem * item,bool deleteItems)2428 BMenu::_RemoveItems(int32 index, int32 count, BMenuItem* item,
2429 bool deleteItems)
2430 {
2431 bool success = false;
2432 bool invalidateLayout = false;
2433
2434 bool locked = LockLooper();
2435 BWindow* window = Window();
2436
2437 // The plan is simple: If we're given a BMenuItem directly, we use it
2438 // and ignore index and count. Otherwise, we use them instead.
2439 if (item != NULL) {
2440 if (fItems.RemoveItem(item)) {
2441 if (item == fSelected && window != NULL)
2442 _SelectItem(NULL);
2443 item->Uninstall();
2444 item->SetSuper(NULL);
2445 if (deleteItems)
2446 delete item;
2447 success = invalidateLayout = true;
2448 }
2449 } else {
2450 // We iterate backwards because it's simpler
2451 int32 i = std::min(index + count - 1, fItems.CountItems() - 1);
2452 // NOTE: the range check for "index" is done after
2453 // calculating the last index to be removed, so
2454 // that the range is not "shifted" unintentionally
2455 index = std::max((int32)0, index);
2456 for (; i >= index; i--) {
2457 item = static_cast<BMenuItem*>(fItems.ItemAt(i));
2458 if (item != NULL) {
2459 if (fItems.RemoveItem(i)) {
2460 if (item == fSelected && window != NULL)
2461 _SelectItem(NULL);
2462 item->Uninstall();
2463 item->SetSuper(NULL);
2464 if (deleteItems)
2465 delete item;
2466 success = true;
2467 invalidateLayout = true;
2468 } else {
2469 // operation not entirely successful
2470 success = false;
2471 break;
2472 }
2473 }
2474 }
2475 }
2476
2477 if (invalidateLayout) {
2478 InvalidateLayout();
2479 if (locked && window != NULL) {
2480 _LayoutItems(0);
2481 _UpdateWindowViewSize(false);
2482 Invalidate();
2483 }
2484 }
2485
2486 if (locked)
2487 UnlockLooper();
2488
2489 return success;
2490 }
2491
2492
2493 bool
_RelayoutIfNeeded()2494 BMenu::_RelayoutIfNeeded()
2495 {
2496 if (!fUseCachedMenuLayout) {
2497 fUseCachedMenuLayout = true;
2498 _CacheFontInfo();
2499 _LayoutItems(0);
2500 _UpdateWindowViewSize(false);
2501 return true;
2502 }
2503 return false;
2504 }
2505
2506
2507 void
_LayoutItems(int32 index)2508 BMenu::_LayoutItems(int32 index)
2509 {
2510 _CalcTriggers();
2511
2512 float width;
2513 float height;
2514 _ComputeLayout(index, fResizeToFit, true, &width, &height);
2515
2516 if (fResizeToFit)
2517 ResizeTo(width, height);
2518 }
2519
2520
2521 BSize
_ValidatePreferredSize()2522 BMenu::_ValidatePreferredSize()
2523 {
2524 if (!fLayoutData->preferred.IsWidthSet() || ResizingMode()
2525 != fLayoutData->lastResizingMode) {
2526 _ComputeLayout(0, true, false, NULL, NULL);
2527 ResetLayoutInvalidation();
2528 }
2529
2530 return fLayoutData->preferred;
2531 }
2532
2533
2534 void
_ComputeLayout(int32 index,bool bestFit,bool moveItems,float * _width,float * _height)2535 BMenu::_ComputeLayout(int32 index, bool bestFit, bool moveItems,
2536 float* _width, float* _height)
2537 {
2538 // TODO: Take "bestFit", "moveItems", "index" into account,
2539 // Recalculate only the needed items,
2540 // not the whole layout every time
2541
2542 fLayoutData->lastResizingMode = ResizingMode();
2543
2544 BRect frame;
2545 switch (fLayout) {
2546 case B_ITEMS_IN_COLUMN:
2547 {
2548 BRect parentFrame;
2549 BRect* overrideFrame = NULL;
2550 if (dynamic_cast<_BMCMenuBar_*>(Supermenu()) != NULL) {
2551 // When the menu is modified while it's open, we get here in a
2552 // situation where trying to lock the looper would deadlock
2553 // (the window is locked waiting for the menu to terminate).
2554 // In that case, just give up on getting the supermenu bounds
2555 // and keep the menu at the current width and position.
2556 if (Supermenu()->LockLooperWithTimeout(0) == B_OK) {
2557 parentFrame = Supermenu()->Bounds();
2558 Supermenu()->UnlockLooper();
2559 overrideFrame = &parentFrame;
2560 }
2561 }
2562
2563 _ComputeColumnLayout(index, bestFit, moveItems, overrideFrame,
2564 frame);
2565 break;
2566 }
2567
2568 case B_ITEMS_IN_ROW:
2569 _ComputeRowLayout(index, bestFit, moveItems, frame);
2570 break;
2571
2572 case B_ITEMS_IN_MATRIX:
2573 _ComputeMatrixLayout(frame);
2574 break;
2575 }
2576
2577 // change width depending on resize mode
2578 BSize size;
2579 if ((ResizingMode() & B_FOLLOW_LEFT_RIGHT) == B_FOLLOW_LEFT_RIGHT) {
2580 if (dynamic_cast<_BMCMenuBar_*>(this) != NULL)
2581 size.width = Bounds().Width() - fPad.right;
2582 else if (Parent() != NULL)
2583 size.width = Parent()->Frame().Width();
2584 else if (Window() != NULL)
2585 size.width = Window()->Frame().Width();
2586 else
2587 size.width = Bounds().Width();
2588 } else
2589 size.width = frame.Width();
2590
2591 size.height = frame.Height();
2592
2593 if (_width)
2594 *_width = size.width;
2595
2596 if (_height)
2597 *_height = size.height;
2598
2599 if (bestFit)
2600 fLayoutData->preferred = size;
2601
2602 if (moveItems)
2603 fUseCachedMenuLayout = true;
2604 }
2605
2606
2607 void
_ComputeColumnLayout(int32 index,bool bestFit,bool moveItems,BRect * overrideFrame,BRect & frame)2608 BMenu::_ComputeColumnLayout(int32 index, bool bestFit, bool moveItems,
2609 BRect* overrideFrame, BRect& frame)
2610 {
2611 bool command = false;
2612 bool control = false;
2613 bool shift = false;
2614 bool option = false;
2615 bool submenu = false;
2616
2617 if (index > 0)
2618 frame = ItemAt(index - 1)->Frame();
2619 else if (overrideFrame != NULL)
2620 frame.Set(0, 0, overrideFrame->right, -1);
2621 else
2622 frame.Set(0, 0, 0, -1);
2623
2624 BFont font;
2625 GetFont(&font);
2626
2627 // Loop over all items to set their top, bottom and left coordinates,
2628 // all while computing the width of the menu
2629 for (; index < fItems.CountItems(); index++) {
2630 BMenuItem* item = ItemAt(index);
2631
2632 float width;
2633 float height;
2634 item->GetContentSize(&width, &height);
2635
2636 if (item->fModifiers && item->fShortcutChar) {
2637 width += font.Size();
2638 if ((item->fModifiers & B_COMMAND_KEY) != 0)
2639 command = true;
2640
2641 if ((item->fModifiers & B_CONTROL_KEY) != 0)
2642 control = true;
2643
2644 if ((item->fModifiers & B_SHIFT_KEY) != 0)
2645 shift = true;
2646
2647 if ((item->fModifiers & B_OPTION_KEY) != 0)
2648 option = true;
2649 }
2650
2651 item->fBounds.left = 0.0f;
2652 item->fBounds.top = frame.bottom + 1.0f;
2653 item->fBounds.bottom = item->fBounds.top + height + fPad.top
2654 + fPad.bottom;
2655
2656 if (item->fSubmenu != NULL)
2657 submenu = true;
2658
2659 frame.right = std::max(frame.right, width + fPad.left + fPad.right);
2660 frame.bottom = item->fBounds.bottom;
2661 }
2662
2663 // Compute the extra space needed for shortcuts and submenus
2664 if (command) {
2665 frame.right
2666 += BPrivate::MenuPrivate::MenuItemCommand()->Bounds().Width() + 1;
2667 }
2668 if (control) {
2669 frame.right
2670 += BPrivate::MenuPrivate::MenuItemControl()->Bounds().Width() + 1;
2671 }
2672 if (option) {
2673 frame.right
2674 += BPrivate::MenuPrivate::MenuItemOption()->Bounds().Width() + 1;
2675 }
2676 if (shift) {
2677 frame.right
2678 += BPrivate::MenuPrivate::MenuItemShift()->Bounds().Width() + 1;
2679 }
2680 if (submenu) {
2681 frame.right += ItemAt(0)->Frame().Height() / 2;
2682 fHasSubmenus = true;
2683 } else {
2684 fHasSubmenus = false;
2685 }
2686
2687 if (fMaxContentWidth > 0)
2688 frame.right = std::min(frame.right, fMaxContentWidth);
2689
2690 frame.top = 0;
2691 frame.right = ceilf(frame.right);
2692
2693 // Finally update the "right" coordinate of all items
2694 if (moveItems) {
2695 for (int32 i = 0; i < fItems.CountItems(); i++)
2696 ItemAt(i)->fBounds.right = frame.right;
2697 }
2698 }
2699
2700
2701 void
_ComputeRowLayout(int32 index,bool bestFit,bool moveItems,BRect & frame)2702 BMenu::_ComputeRowLayout(int32 index, bool bestFit, bool moveItems,
2703 BRect& frame)
2704 {
2705 font_height fh;
2706 GetFontHeight(&fh);
2707 frame.Set(0.0f, 0.0f, 0.0f, ceilf(fh.ascent + fh.descent + fPad.top
2708 + fPad.bottom));
2709
2710 for (int32 i = 0; i < fItems.CountItems(); i++) {
2711 BMenuItem* item = ItemAt(i);
2712
2713 float width, height;
2714 item->GetContentSize(&width, &height);
2715
2716 item->fBounds.left = frame.right;
2717 item->fBounds.top = 0.0f;
2718 item->fBounds.right = item->fBounds.left + width + fPad.left
2719 + fPad.right;
2720
2721 frame.right = item->Frame().right + 1.0f;
2722 frame.bottom = std::max(frame.bottom, height + fPad.top + fPad.bottom);
2723 }
2724
2725 if (moveItems) {
2726 for (int32 i = 0; i < fItems.CountItems(); i++)
2727 ItemAt(i)->fBounds.bottom = frame.bottom;
2728 }
2729
2730 if (bestFit)
2731 frame.right = ceilf(frame.right);
2732 else
2733 frame.right = Bounds().right;
2734 }
2735
2736
2737 void
_ComputeMatrixLayout(BRect & frame)2738 BMenu::_ComputeMatrixLayout(BRect &frame)
2739 {
2740 frame.Set(0, 0, 0, 0);
2741 for (int32 i = 0; i < CountItems(); i++) {
2742 BMenuItem* item = ItemAt(i);
2743 if (item != NULL) {
2744 frame.left = std::min(frame.left, item->Frame().left);
2745 frame.right = std::max(frame.right, item->Frame().right);
2746 frame.top = std::min(frame.top, item->Frame().top);
2747 frame.bottom = std::max(frame.bottom, item->Frame().bottom);
2748 }
2749 }
2750 }
2751
2752
2753 void
LayoutInvalidated(bool descendants)2754 BMenu::LayoutInvalidated(bool descendants)
2755 {
2756 fUseCachedMenuLayout = false;
2757 fLayoutData->preferred.Set(B_SIZE_UNSET, B_SIZE_UNSET);
2758 }
2759
2760
2761 // Assumes the SuperMenu to be locked (due to calling ConvertToScreen())
2762 BPoint
ScreenLocation()2763 BMenu::ScreenLocation()
2764 {
2765 BMenu* superMenu = Supermenu();
2766 BMenuItem* superItem = Superitem();
2767
2768 if (superMenu == NULL || superItem == NULL) {
2769 debugger("BMenu can't determine where to draw."
2770 "Override BMenu::ScreenLocation() to determine location.");
2771 }
2772
2773 BPoint point;
2774 if (superMenu->Layout() == B_ITEMS_IN_COLUMN)
2775 point = superItem->Frame().RightTop() + BPoint(1.0f, 1.0f);
2776 else
2777 point = superItem->Frame().LeftBottom() + BPoint(1.0f, 1.0f);
2778
2779 superMenu->ConvertToScreen(&point);
2780
2781 return point;
2782 }
2783
2784
2785 BRect
_CalcFrame(BPoint where,bool * scrollOn)2786 BMenu::_CalcFrame(BPoint where, bool* scrollOn)
2787 {
2788 // TODO: Improve me
2789 BRect bounds = Bounds();
2790 BRect frame = bounds.OffsetToCopy(where);
2791
2792 BScreen screen(Window());
2793 BRect screenFrame = screen.Frame();
2794
2795 BMenu* superMenu = Supermenu();
2796 BMenuItem* superItem = Superitem();
2797
2798 // Reset frame shifted state since this menu is being redrawn
2799 fExtraMenuData->frameShiftedLeft = false;
2800
2801 // TODO: Horrible hack:
2802 // When added to a BMenuField, a BPopUpMenu is the child of
2803 // a _BMCMenuBar_ to "fake" the menu hierarchy
2804 bool inMenuField = dynamic_cast<_BMCMenuBar_*>(superMenu) != NULL;
2805
2806 // Offset the menu field menu window left by the width of the checkmark
2807 // so that the text when the menu is closed lines up with the text when
2808 // the menu is open.
2809 if (inMenuField)
2810 frame.OffsetBy(-8.0f, 0.0f);
2811
2812 if (superMenu == NULL || superItem == NULL || inMenuField) {
2813 // just move the window on screen
2814 if (frame.bottom > screenFrame.bottom)
2815 frame.OffsetBy(0, screenFrame.bottom - frame.bottom);
2816 else if (frame.top < screenFrame.top)
2817 frame.OffsetBy(0, -frame.top);
2818
2819 if (frame.right > screenFrame.right) {
2820 frame.OffsetBy(screenFrame.right - frame.right, 0);
2821 fExtraMenuData->frameShiftedLeft = true;
2822 }
2823 else if (frame.left < screenFrame.left)
2824 frame.OffsetBy(-frame.left, 0);
2825 } else if (superMenu->Layout() == B_ITEMS_IN_COLUMN) {
2826 if (frame.right > screenFrame.right
2827 || superMenu->fExtraMenuData->frameShiftedLeft) {
2828 frame.OffsetBy(-superItem->Frame().Width() - frame.Width() - 2, 0);
2829 fExtraMenuData->frameShiftedLeft = true;
2830 }
2831
2832 if (frame.left < 0)
2833 frame.OffsetBy(-frame.left + 6, 0);
2834
2835 if (frame.bottom > screenFrame.bottom)
2836 frame.OffsetBy(0, screenFrame.bottom - frame.bottom);
2837 } else {
2838 if (frame.bottom > screenFrame.bottom) {
2839 float spaceBelow = screenFrame.bottom - frame.top;
2840 float spaceOver = frame.top - screenFrame.top
2841 - superItem->Frame().Height();
2842 if (spaceOver > spaceBelow) {
2843 frame.OffsetBy(0, -superItem->Frame().Height()
2844 - frame.Height() - 3);
2845 }
2846 }
2847
2848 if (frame.right > screenFrame.right)
2849 frame.OffsetBy(screenFrame.right - frame.right, 0);
2850 }
2851
2852 if (scrollOn != NULL) {
2853 // basically, if this returns false, it means
2854 // that the menu frame won't fit completely inside the screen
2855 // TODO: Scrolling will currently only work up/down,
2856 // not left/right
2857 *scrollOn = screenFrame.top > frame.top
2858 || screenFrame.bottom < frame.bottom;
2859 }
2860
2861 return frame;
2862 }
2863
2864
2865 void
DrawItems(BRect updateRect)2866 BMenu::DrawItems(BRect updateRect)
2867 {
2868 int32 itemCount = fItems.CountItems();
2869 for (int32 i = 0; i < itemCount; i++) {
2870 BMenuItem* item = ItemAt(i);
2871 if (item->Frame().Intersects(updateRect))
2872 item->Draw();
2873 }
2874 }
2875
2876
2877 int
_State(BMenuItem ** item) const2878 BMenu::_State(BMenuItem** item) const
2879 {
2880 if (fState == MENU_STATE_TRACKING || fState == MENU_STATE_CLOSED)
2881 return fState;
2882
2883 if (fSelected != NULL && fSelected->Submenu() != NULL)
2884 return fSelected->Submenu()->_State(item);
2885
2886 return fState;
2887 }
2888
2889
2890 void
_InvokeItem(BMenuItem * item,bool now)2891 BMenu::_InvokeItem(BMenuItem* item, bool now)
2892 {
2893 if (!item->IsEnabled())
2894 return;
2895
2896 // Do the "selected" animation
2897 // TODO: Doesn't work. This is supposed to highlight
2898 // and dehighlight the item, works on beos but not on haiku.
2899 if (!item->Submenu() && LockLooper()) {
2900 snooze(50000);
2901 item->Select(true);
2902 Window()->UpdateIfNeeded();
2903 snooze(50000);
2904 item->Select(false);
2905 Window()->UpdateIfNeeded();
2906 snooze(50000);
2907 item->Select(true);
2908 Window()->UpdateIfNeeded();
2909 snooze(50000);
2910 item->Select(false);
2911 Window()->UpdateIfNeeded();
2912 UnlockLooper();
2913 }
2914
2915 // Lock the root menu window before calling BMenuItem::Invoke()
2916 BMenu* parent = this;
2917 BMenu* rootMenu = NULL;
2918 do {
2919 rootMenu = parent;
2920 parent = rootMenu->Supermenu();
2921 } while (parent != NULL);
2922
2923 if (rootMenu->LockLooper()) {
2924 item->Invoke();
2925 rootMenu->UnlockLooper();
2926 }
2927 }
2928
2929
2930 bool
_OverSuper(BPoint location)2931 BMenu::_OverSuper(BPoint location)
2932 {
2933 if (!Supermenu())
2934 return false;
2935
2936 return fSuperbounds.Contains(location);
2937 }
2938
2939
2940 bool
_OverSubmenu(BMenuItem * item,BPoint loc)2941 BMenu::_OverSubmenu(BMenuItem* item, BPoint loc)
2942 {
2943 if (item == NULL)
2944 return false;
2945
2946 BMenu* subMenu = item->Submenu();
2947 if (subMenu == NULL || subMenu->Window() == NULL)
2948 return false;
2949
2950 // assume that loc is in screen coordinates
2951 if (subMenu->Window()->Frame().Contains(loc))
2952 return true;
2953
2954 return subMenu->_OverSubmenu(subMenu->fSelected, loc);
2955 }
2956
2957
2958 BMenuWindow*
_MenuWindow()2959 BMenu::_MenuWindow()
2960 {
2961 #if USE_CACHED_MENUWINDOW
2962 if (fCachedMenuWindow == NULL) {
2963 char windowName[64];
2964 snprintf(windowName, 64, "%s cached menu", Name());
2965 fCachedMenuWindow = new (nothrow) BMenuWindow(windowName);
2966 }
2967 #endif
2968 return fCachedMenuWindow;
2969 }
2970
2971
2972 void
_DeleteMenuWindow()2973 BMenu::_DeleteMenuWindow()
2974 {
2975 if (fCachedMenuWindow != NULL) {
2976 fCachedMenuWindow->Lock();
2977 fCachedMenuWindow->Quit();
2978 fCachedMenuWindow = NULL;
2979 }
2980 }
2981
2982
2983 BMenuItem*
_HitTestItems(BPoint where,BPoint slop) const2984 BMenu::_HitTestItems(BPoint where, BPoint slop) const
2985 {
2986 // TODO: Take "slop" into account ?
2987
2988 // if the point doesn't lie within the menu's
2989 // bounds, bail out immediately
2990 if (!Bounds().Contains(where))
2991 return NULL;
2992
2993 int32 itemCount = CountItems();
2994 for (int32 i = 0; i < itemCount; i++) {
2995 BMenuItem* item = ItemAt(i);
2996 if (item->Frame().Contains(where)
2997 && dynamic_cast<BSeparatorItem*>(item) == NULL) {
2998 return item;
2999 }
3000 }
3001
3002 return NULL;
3003 }
3004
3005
3006 BRect
_Superbounds() const3007 BMenu::_Superbounds() const
3008 {
3009 return fSuperbounds;
3010 }
3011
3012
3013 void
_CacheFontInfo()3014 BMenu::_CacheFontInfo()
3015 {
3016 font_height fh;
3017 GetFontHeight(&fh);
3018 fAscent = fh.ascent;
3019 fDescent = fh.descent;
3020 fFontHeight = ceilf(fh.ascent + fh.descent + fh.leading);
3021 }
3022
3023
3024 void
_ItemMarked(BMenuItem * item)3025 BMenu::_ItemMarked(BMenuItem* item)
3026 {
3027 if (IsRadioMode()) {
3028 for (int32 i = 0; i < CountItems(); i++) {
3029 if (ItemAt(i) != item)
3030 ItemAt(i)->SetMarked(false);
3031 }
3032 }
3033
3034 if (IsLabelFromMarked() && Superitem() != NULL)
3035 Superitem()->SetLabel(item->Label());
3036 }
3037
3038
3039 void
_Install(BWindow * target)3040 BMenu::_Install(BWindow* target)
3041 {
3042 for (int32 i = 0; i < CountItems(); i++)
3043 ItemAt(i)->Install(target);
3044 }
3045
3046
3047 void
_Uninstall()3048 BMenu::_Uninstall()
3049 {
3050 for (int32 i = 0; i < CountItems(); i++)
3051 ItemAt(i)->Uninstall();
3052 }
3053
3054
3055 void
_SelectItem(BMenuItem * item,bool showSubmenu,bool selectFirstItem,bool keyDown)3056 BMenu::_SelectItem(BMenuItem* item, bool showSubmenu, bool selectFirstItem,
3057 bool keyDown)
3058 {
3059 // Avoid deselecting and then reselecting the same item
3060 // which would cause flickering
3061 if (item != fSelected) {
3062 if (fSelected != NULL) {
3063 fSelected->Select(false);
3064 BMenu* subMenu = fSelected->Submenu();
3065 if (subMenu != NULL && subMenu->Window() != NULL)
3066 subMenu->_Hide();
3067 }
3068
3069 fSelected = item;
3070 if (fSelected != NULL)
3071 fSelected->Select(true);
3072 }
3073
3074 if (fSelected != NULL && showSubmenu) {
3075 BMenu* subMenu = fSelected->Submenu();
3076 if (subMenu != NULL && subMenu->Window() == NULL) {
3077 if (!subMenu->_Show(selectFirstItem, keyDown)) {
3078 // something went wrong, deselect the item
3079 fSelected->Select(false);
3080 fSelected = NULL;
3081 }
3082 }
3083 }
3084 }
3085
3086
3087 bool
_SelectNextItem(BMenuItem * item,bool forward)3088 BMenu::_SelectNextItem(BMenuItem* item, bool forward)
3089 {
3090 if (CountItems() == 0) // cannot select next item in an empty menu
3091 return false;
3092
3093 BMenuItem* nextItem = _NextItem(item, forward);
3094 if (nextItem == NULL)
3095 return false;
3096
3097 _SelectItem(nextItem, dynamic_cast<BMenuBar*>(this) != NULL);
3098
3099 if (LockLooper()) {
3100 be_app->ObscureCursor();
3101 UnlockLooper();
3102 }
3103
3104 return true;
3105 }
3106
3107
3108 BMenuItem*
_NextItem(BMenuItem * item,bool forward) const3109 BMenu::_NextItem(BMenuItem* item, bool forward) const
3110 {
3111 const int32 numItems = fItems.CountItems();
3112 if (numItems == 0)
3113 return NULL;
3114
3115 int32 index = fItems.IndexOf(item);
3116 int32 loopCount = numItems;
3117 while (--loopCount) {
3118 // Cycle through menu items in the given direction...
3119 if (forward)
3120 index++;
3121 else
3122 index--;
3123
3124 // ... wrap around...
3125 if (index < 0)
3126 index = numItems - 1;
3127 else if (index >= numItems)
3128 index = 0;
3129
3130 // ... and return the first suitable item found.
3131 BMenuItem* nextItem = ItemAt(index);
3132 if (nextItem->IsEnabled())
3133 return nextItem;
3134 }
3135
3136 // If no other suitable item was found, return NULL.
3137 return NULL;
3138 }
3139
3140
3141 void
_SetStickyMode(bool sticky)3142 BMenu::_SetStickyMode(bool sticky)
3143 {
3144 if (fStickyMode == sticky)
3145 return;
3146
3147 fStickyMode = sticky;
3148
3149 if (fSuper != NULL) {
3150 // propagate the status to the super menu
3151 fSuper->_SetStickyMode(sticky);
3152 } else {
3153 // TODO: Ugly hack, but it needs to be done in this method
3154 BMenuBar* menuBar = dynamic_cast<BMenuBar*>(this);
3155 if (sticky && menuBar != NULL && menuBar->LockLooper()) {
3156 // If we are switching to sticky mode,
3157 // steal the focus from the current focus view
3158 // (needed to handle keyboard navigation)
3159 menuBar->_StealFocus();
3160 menuBar->UnlockLooper();
3161 }
3162 }
3163 }
3164
3165
3166 bool
_IsStickyMode() const3167 BMenu::_IsStickyMode() const
3168 {
3169 return fStickyMode;
3170 }
3171
3172
3173 void
_GetShiftKey(uint32 & value) const3174 BMenu::_GetShiftKey(uint32 &value) const
3175 {
3176 // TODO: Move into init_interface_kit().
3177 // Currently we can't do that, as get_modifier_key() blocks forever
3178 // when called on input_server initialization, since it tries
3179 // to send a synchronous message to itself (input_server is
3180 // a BApplication)
3181
3182 if (get_modifier_key(B_LEFT_SHIFT_KEY, &value) != B_OK)
3183 value = 0x4b;
3184 }
3185
3186
3187 void
_GetControlKey(uint32 & value) const3188 BMenu::_GetControlKey(uint32 &value) const
3189 {
3190 // TODO: Move into init_interface_kit().
3191 // Currently we can't do that, as get_modifier_key() blocks forever
3192 // when called on input_server initialization, since it tries
3193 // to send a synchronous message to itself (input_server is
3194 // a BApplication)
3195
3196 if (get_modifier_key(B_LEFT_CONTROL_KEY, &value) != B_OK)
3197 value = 0x5c;
3198 }
3199
3200
3201 void
_GetCommandKey(uint32 & value) const3202 BMenu::_GetCommandKey(uint32 &value) const
3203 {
3204 // TODO: Move into init_interface_kit().
3205 // Currently we can't do that, as get_modifier_key() blocks forever
3206 // when called on input_server initialization, since it tries
3207 // to send a synchronous message to itself (input_server is
3208 // a BApplication)
3209
3210 if (get_modifier_key(B_LEFT_COMMAND_KEY, &value) != B_OK)
3211 value = 0x66;
3212 }
3213
3214
3215 void
_GetOptionKey(uint32 & value) const3216 BMenu::_GetOptionKey(uint32 &value) const
3217 {
3218 // TODO: Move into init_interface_kit().
3219 // Currently we can't do that, as get_modifier_key() blocks forever
3220 // when called on input_server initialization, since it tries
3221 // to send a synchronous message to itself (input_server is
3222 // a BApplication)
3223
3224 if (get_modifier_key(B_LEFT_OPTION_KEY, &value) != B_OK)
3225 value = 0x5d;
3226 }
3227
3228
3229 void
_GetMenuKey(uint32 & value) const3230 BMenu::_GetMenuKey(uint32 &value) const
3231 {
3232 // TODO: Move into init_interface_kit().
3233 // Currently we can't do that, as get_modifier_key() blocks forever
3234 // when called on input_server initialization, since it tries
3235 // to send a synchronous message to itself (input_server is
3236 // a BApplication)
3237
3238 if (get_modifier_key(B_MENU_KEY, &value) != B_OK)
3239 value = 0x68;
3240 }
3241
3242
3243 void
_CalcTriggers()3244 BMenu::_CalcTriggers()
3245 {
3246 BPrivate::TriggerList triggerList;
3247
3248 // Gathers the existing triggers set by the user
3249 for (int32 i = 0; i < CountItems(); i++) {
3250 char trigger = ItemAt(i)->Trigger();
3251 if (trigger != 0)
3252 triggerList.AddTrigger(trigger);
3253 }
3254
3255 // Set triggers for items which don't have one yet
3256 for (int32 i = 0; i < CountItems(); i++) {
3257 BMenuItem* item = ItemAt(i);
3258 if (item->Trigger() == 0) {
3259 uint32 trigger;
3260 int32 index;
3261 if (_ChooseTrigger(item->Label(), index, trigger, triggerList))
3262 item->SetAutomaticTrigger(index, trigger);
3263 }
3264 }
3265 }
3266
3267
3268 bool
_ChooseTrigger(const char * title,int32 & index,uint32 & trigger,BPrivate::TriggerList & triggers)3269 BMenu::_ChooseTrigger(const char* title, int32& index, uint32& trigger,
3270 BPrivate::TriggerList& triggers)
3271 {
3272 if (title == NULL)
3273 return false;
3274
3275 index = 0;
3276 uint32 c;
3277 const char* nextCharacter, *character;
3278
3279 // two runs: first we look out for alphanumeric ASCII characters
3280 nextCharacter = title;
3281 character = nextCharacter;
3282 while ((c = BUnicodeChar::FromUTF8(&nextCharacter)) != 0) {
3283 if (!(c < 128 && BUnicodeChar::IsAlNum(c)) || triggers.HasTrigger(c)) {
3284 character = nextCharacter;
3285 continue;
3286 }
3287 trigger = BUnicodeChar::ToLower(c);
3288 index = (int32)(character - title);
3289 return triggers.AddTrigger(c);
3290 }
3291
3292 // then, if we still haven't found something, we accept anything
3293 nextCharacter = title;
3294 character = nextCharacter;
3295 while ((c = BUnicodeChar::FromUTF8(&nextCharacter)) != 0) {
3296 if (BUnicodeChar::IsSpace(c) || triggers.HasTrigger(c)) {
3297 character = nextCharacter;
3298 continue;
3299 }
3300 trigger = BUnicodeChar::ToLower(c);
3301 index = (int32)(character - title);
3302 return triggers.AddTrigger(c);
3303 }
3304
3305 return false;
3306 }
3307
3308
3309 void
_UpdateWindowViewSize(const bool & move)3310 BMenu::_UpdateWindowViewSize(const bool &move)
3311 {
3312 BMenuWindow* window = static_cast<BMenuWindow*>(Window());
3313 if (window == NULL)
3314 return;
3315
3316 if (dynamic_cast<BMenuBar*>(this) != NULL)
3317 return;
3318
3319 if (!fResizeToFit)
3320 return;
3321
3322 bool scroll = false;
3323 const BPoint screenLocation = move ? ScreenLocation()
3324 : window->Frame().LeftTop();
3325 BRect frame = _CalcFrame(screenLocation, &scroll);
3326 ResizeTo(frame.Width(), frame.Height());
3327
3328 if (fItems.CountItems() > 0) {
3329 if (!scroll) {
3330 if (fLayout == B_ITEMS_IN_COLUMN)
3331 window->DetachScrollers();
3332
3333 window->ResizeTo(Bounds().Width(), Bounds().Height());
3334 } else {
3335
3336 // Resize the window to fit the screen without overflowing the
3337 // frame, and attach scrollers to our cached BMenuWindow.
3338 BScreen screen(window);
3339 frame = frame & screen.Frame();
3340 window->ResizeTo(Bounds().Width(), frame.Height());
3341
3342 // we currently only support scrolling for B_ITEMS_IN_COLUMN
3343 if (fLayout == B_ITEMS_IN_COLUMN) {
3344 window->AttachScrollers();
3345
3346 BMenuItem* selectedItem = FindMarked();
3347 if (selectedItem != NULL) {
3348 // scroll to the selected item
3349 if (Supermenu() == NULL) {
3350 window->TryScrollTo(selectedItem->Frame().top);
3351 } else {
3352 BPoint point = selectedItem->Frame().LeftTop();
3353 BPoint superPoint = Superitem()->Frame().LeftTop();
3354 Supermenu()->ConvertToScreen(&superPoint);
3355 ConvertToScreen(&point);
3356 window->TryScrollTo(point.y - superPoint.y);
3357 }
3358 }
3359 }
3360 }
3361 } else {
3362 _CacheFontInfo();
3363 window->ResizeTo(StringWidth(BPrivate::kEmptyMenuLabel)
3364 + fPad.left + fPad.right,
3365 fFontHeight + fPad.top + fPad.bottom);
3366 }
3367
3368 if (move)
3369 window->MoveTo(frame.LeftTop());
3370 }
3371
3372
3373 bool
_AddDynamicItems(bool keyDown)3374 BMenu::_AddDynamicItems(bool keyDown)
3375 {
3376 bool addAborted = false;
3377 if (AddDynamicItem(B_INITIAL_ADD)) {
3378 BMenuItem* superItem = Superitem();
3379 BMenu* superMenu = Supermenu();
3380 do {
3381 if (superMenu != NULL
3382 && !superMenu->_OkToProceed(superItem, keyDown)) {
3383 AddDynamicItem(B_ABORT);
3384 addAborted = true;
3385 break;
3386 }
3387 } while (AddDynamicItem(B_PROCESSING));
3388 }
3389
3390 return addAborted;
3391 }
3392
3393
3394 bool
_OkToProceed(BMenuItem * item,bool keyDown)3395 BMenu::_OkToProceed(BMenuItem* item, bool keyDown)
3396 {
3397 BPoint where;
3398 uint32 buttons;
3399 GetMouse(&where, &buttons, false);
3400 bool stickyMode = _IsStickyMode();
3401 // Quit if user clicks the mouse button in sticky mode
3402 // or releases the mouse button in nonsticky mode
3403 // or moves the pointer over another item
3404 // TODO: I added the check for BMenuBar to solve a problem with Deskbar.
3405 // BeOS seems to do something similar. This could also be a bug in
3406 // Deskbar, though.
3407 if ((buttons != 0 && stickyMode)
3408 || ((dynamic_cast<BMenuBar*>(this) == NULL
3409 && (buttons == 0 && !stickyMode))
3410 || ((_HitTestItems(where) != item) && !keyDown))) {
3411 return false;
3412 }
3413
3414 return true;
3415 }
3416
3417
3418 bool
_CustomTrackingWantsToQuit()3419 BMenu::_CustomTrackingWantsToQuit()
3420 {
3421 if (fExtraMenuData != NULL && fExtraMenuData->trackingHook != NULL
3422 && fExtraMenuData->trackingState != NULL) {
3423 return fExtraMenuData->trackingHook(this,
3424 fExtraMenuData->trackingState);
3425 }
3426
3427 return false;
3428 }
3429
3430
3431 void
_QuitTracking(bool onlyThis)3432 BMenu::_QuitTracking(bool onlyThis)
3433 {
3434 _SelectItem(NULL);
3435 if (BMenuBar* menuBar = dynamic_cast<BMenuBar*>(this))
3436 menuBar->_RestoreFocus();
3437
3438 fState = MENU_STATE_CLOSED;
3439
3440 if (!onlyThis) {
3441 // Close the whole menu hierarchy
3442 if (Supermenu() != NULL)
3443 Supermenu()->fState = MENU_STATE_CLOSED;
3444
3445 if (_IsStickyMode())
3446 _SetStickyMode(false);
3447
3448 if (LockLooper()) {
3449 be_app->ShowCursor();
3450 UnlockLooper();
3451 }
3452 }
3453
3454 _Hide();
3455 }
3456
3457
3458 // #pragma mark - menu_info functions
3459
3460
3461 // TODO: Maybe the following two methods would fit better into
3462 // InterfaceDefs.cpp
3463 // In R5, they do all the work client side, we let the app_server handle the
3464 // details.
3465 status_t
set_menu_info(menu_info * info)3466 set_menu_info(menu_info* info)
3467 {
3468 if (!info)
3469 return B_BAD_VALUE;
3470
3471 BPrivate::AppServerLink link;
3472 link.StartMessage(AS_SET_MENU_INFO);
3473 link.Attach<menu_info>(*info);
3474
3475 status_t status = B_ERROR;
3476 if (link.FlushWithReply(status) == B_OK && status == B_OK)
3477 BMenu::sMenuInfo = *info;
3478 // Update also the local copy, in case anyone relies on it
3479
3480 return status;
3481 }
3482
3483
3484 status_t
get_menu_info(menu_info * info)3485 get_menu_info(menu_info* info)
3486 {
3487 if (!info)
3488 return B_BAD_VALUE;
3489
3490 BPrivate::AppServerLink link;
3491 link.StartMessage(AS_GET_MENU_INFO);
3492
3493 status_t status = B_ERROR;
3494 if (link.FlushWithReply(status) == B_OK && status == B_OK)
3495 link.Read<menu_info>(info);
3496
3497 return status;
3498 }
3499
3500
3501 extern "C" void
B_IF_GCC_2(InvalidateLayout__5BMenub,_ZN5BMenu16InvalidateLayoutEb)3502 B_IF_GCC_2(InvalidateLayout__5BMenub,_ZN5BMenu16InvalidateLayoutEb)(
3503 BMenu* menu, bool descendants)
3504 {
3505 menu->InvalidateLayout();
3506 }
3507