Ticket #902: Menu.cpp

File Menu.cpp, 42.4 KB (added by sil2100, 18 years ago)

The small fix

Line 
1/*
2 * Copyright 2001-2006, Haiku, Inc.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 * Marc Flerackers (mflerackers@androme.be)
7 * Stefano Ceccherini (burton666@libero.it)
8 */
9
10#include <new>
11#include <string.h>
12
13#include <Debug.h>
14#include <File.h>
15#include <FindDirectory.h>
16#include <Menu.h>
17#include <MenuBar.h>
18#include <MenuItem.h>
19#include <Path.h>
20#include <PropertyInfo.h>
21#include <Screen.h>
22#include <Window.h>
23
24#include <AppServerLink.h>
25#include <BMCPrivate.h>
26#include <MenuPrivate.h>
27#include <MenuWindow.h>
28#include <ServerProtocol.h>
29
30using std::nothrow;
31
32class _ExtraMenuData_ {
33public:
34 menu_tracking_hook trackingHook;
35 void *trackingState;
36
37 _ExtraMenuData_(menu_tracking_hook func, void *state)
38 {
39 trackingHook = func;
40 trackingState = state;
41 };
42};
43
44
45menu_info BMenu::sMenuInfo;
46bool BMenu::sAltAsCommandKey;
47
48
49static property_info
50sPropList[] = {
51 { "Enabled", { B_GET_PROPERTY, 0 },
52 { B_DIRECT_SPECIFIER, 0 }, "Returns true if menu or menu item is enabled; false "
53 "otherwise.",
54 0, { B_BOOL_TYPE }
55 },
56
57 { "Enabled", { B_SET_PROPERTY, 0 },
58 { B_DIRECT_SPECIFIER, 0 }, "Enables or disables menu or menu item.",
59 0, { B_BOOL_TYPE }
60 },
61
62 { "Label", { B_GET_PROPERTY, 0 },
63 { B_DIRECT_SPECIFIER, 0 }, "Returns the string label of the menu or menu item.",
64 0, { B_STRING_TYPE }
65 },
66
67 { "Label", { B_SET_PROPERTY, 0 },
68 { B_DIRECT_SPECIFIER, 0 }, "Sets the string label of the menu or menu item.",
69 0, { B_STRING_TYPE }
70 },
71
72 { "Mark", { B_GET_PROPERTY, 0 },
73 { B_DIRECT_SPECIFIER, 0 }, "Returns true if the menu item or the menu's superitem "
74 "is marked; false otherwise.",
75 0, { B_BOOL_TYPE }
76 },
77
78 { "Mark", { B_SET_PROPERTY, 0 },
79 { B_DIRECT_SPECIFIER, 0 }, "Marks or unmarks the menu item or the menu's superitem.",
80 0, { B_BOOL_TYPE }
81 },
82
83 { "Menu", { B_CREATE_PROPERTY, 0 },
84 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
85 "Adds a new menu item at the specified index with the text label found in \"data\" "
86 "and the int32 command found in \"what\" (used as the what field in the CMessage "
87 "sent by the item)." , 0, {},
88 { {{{"data", B_STRING_TYPE}}}
89 }
90 },
91
92 { "Menu", { B_DELETE_PROPERTY, 0 },
93 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
94 "Removes the selected menu or menus.", 0, {}
95 },
96
97 { "Menu", { },
98 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
99 "Directs scripting message to the specified menu, first popping the current "
100 "specifier off the stack.", 0, {}
101 },
102
103 { "MenuItem", { B_COUNT_PROPERTIES, 0 },
104 { B_DIRECT_SPECIFIER, 0 }, "Counts the number of menu items in the specified menu.",
105 0, { B_INT32_TYPE }
106 },
107
108 { "MenuItem", { B_CREATE_PROPERTY, 0 },
109 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
110 "Adds a new menu item at the specified index with the text label found in \"data\" "
111 "and the int32 command found in \"what\" (used as the what field in the CMessage "
112 "sent by the item).", 0, {},
113 { { {{"data", B_STRING_TYPE },
114 {"be:invoke_message", B_MESSAGE_TYPE},
115 {"what", B_INT32_TYPE},
116 {"be:target", B_MESSENGER_TYPE}} }
117 }
118 },
119
120 { "MenuItem", { B_DELETE_PROPERTY, 0 },
121 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
122 "Removes the specified menu item from its parent menu."
123 },
124
125 { "MenuItem", { B_EXECUTE_PROPERTY, 0 },
126 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
127 "Invokes the specified menu item."
128 },
129
130 { "MenuItem", { },
131 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
132 "Directs scripting message to the specified menu, first popping the current "
133 "specifier off the stack."
134 },
135
136 {}
137};
138
139
140const char *kEmptyMenuLabel = "<empty>";
141
142
143BMenu::BMenu(const char *name, menu_layout layout)
144 : BView(BRect(0, 0, 0, 0), name, 0, B_WILL_DRAW),
145 fChosenItem(NULL),
146 fPad(14.0f, 2.0f, 20.0f, 0.0f),
147 fSelected(NULL),
148 fCachedMenuWindow(NULL),
149 fSuper(NULL),
150 fSuperitem(NULL),
151 fAscent(-1.0f),
152 fDescent(-1.0f),
153 fFontHeight(-1.0f),
154 fState(0),
155 fLayout(layout),
156 fExtraRect(NULL),
157 fMaxContentWidth(0.0f),
158 fInitMatrixSize(NULL),
159 fExtraMenuData(NULL),
160 fTrigger(0),
161 fResizeToFit(true),
162 fUseCachedMenuLayout(false),
163 fEnabled(true),
164 fDynamicName(false),
165 fRadioMode(false),
166 fTrackNewBounds(false),
167 fStickyMode(false),
168 fIgnoreHidden(true),
169 fTriggerEnabled(true),
170 fRedrawAfterSticky(false),
171 fAttachAborted(false)
172{
173 InitData(NULL);
174}
175
176
177BMenu::BMenu(const char *name, float width, float height)
178 : BView(BRect(0.0f, width, 0.0f, height), name, 0, B_WILL_DRAW),
179 fChosenItem(NULL),
180 fSelected(NULL),
181 fCachedMenuWindow(NULL),
182 fSuper(NULL),
183 fSuperitem(NULL),
184 fAscent(-1.0f),
185 fDescent(-1.0f),
186 fFontHeight(-1.0f),
187 fState(0),
188 fLayout(B_ITEMS_IN_MATRIX),
189 fExtraRect(NULL),
190 fMaxContentWidth(0.0f),
191 fInitMatrixSize(NULL),
192 fExtraMenuData(NULL),
193 fTrigger(0),
194 fResizeToFit(true),
195 fUseCachedMenuLayout(false),
196 fEnabled(true),
197 fDynamicName(false),
198 fRadioMode(false),
199 fTrackNewBounds(false),
200 fStickyMode(false),
201 fIgnoreHidden(true),
202 fTriggerEnabled(true),
203 fRedrawAfterSticky(false),
204 fAttachAborted(false)
205{
206 InitData(NULL);
207}
208
209
210BMenu::~BMenu()
211{
212 DeleteMenuWindow();
213
214 RemoveItems(0, CountItems(), true);
215
216 delete fInitMatrixSize;
217 delete fExtraMenuData;
218}
219
220
221BMenu::BMenu(BMessage *archive)
222 : BView(archive),
223 fChosenItem(NULL),
224 fPad(14.0f, 2.0f, 20.0f, 0.0f),
225 fSelected(NULL),
226 fCachedMenuWindow(NULL),
227 fSuper(NULL),
228 fSuperitem(NULL),
229 fAscent(-1.0f),
230 fDescent(-1.0f),
231 fFontHeight(-1.0f),
232 fState(0),
233 fLayout(B_ITEMS_IN_ROW),
234 fExtraRect(NULL),
235 fMaxContentWidth(0.0f),
236 fInitMatrixSize(NULL),
237 fExtraMenuData(NULL),
238 fTrigger(0),
239 fResizeToFit(true),
240 fUseCachedMenuLayout(false),
241 fEnabled(true),
242 fDynamicName(false),
243 fRadioMode(false),
244 fTrackNewBounds(false),
245 fStickyMode(false),
246 fIgnoreHidden(true),
247 fTriggerEnabled(true),
248 fRedrawAfterSticky(false),
249 fAttachAborted(false)
250{
251 InitData(archive);
252}
253
254
255BArchivable *
256BMenu::Instantiate(BMessage *data)
257{
258 if (validate_instantiation(data, "BMenu"))
259 return new (nothrow) BMenu(data);
260
261 return NULL;
262}
263
264
265status_t
266BMenu::Archive(BMessage *data, bool deep) const
267{
268 status_t err = BView::Archive(data, deep);
269
270 if (err == B_OK && Layout() != B_ITEMS_IN_ROW)
271 err = data->AddInt32("_layout", Layout());
272 if (err == B_OK)
273 err = data->AddBool("_rsize_to_fit", fResizeToFit);
274 if (err == B_OK)
275 err = data->AddBool("_disable", !IsEnabled());
276 if (err == B_OK)
277 err = data->AddBool("_radio", IsRadioMode());
278 if (err == B_OK)
279 err = data->AddBool("_trig_disabled", AreTriggersEnabled());
280 if (err == B_OK)
281 err = data->AddBool("_dyn_label", fDynamicName);
282 if (err == B_OK)
283 err = data->AddFloat("_maxwidth", fMaxContentWidth);
284 if (err == B_OK && deep) {
285 BMenuItem *item = NULL;
286 int32 index = 0;
287 while ((item = ItemAt(index++)) != NULL) {
288 BMessage itemData;
289 item->Archive(&itemData, deep);
290 err = data->AddMessage("_items", &itemData);
291 if (err != B_OK)
292 break;
293 if (fLayout == B_ITEMS_IN_MATRIX) {
294 err = data->AddRect("_i_frames", item->fBounds);
295 }
296 }
297 }
298
299 return err;
300}
301
302
303void
304BMenu::AttachedToWindow()
305{
306 BView::AttachedToWindow();
307
308 sAltAsCommandKey = true;
309 key_map *keys = NULL;
310 char *chars = NULL;
311 get_key_map(&keys, &chars);
312 if (keys == NULL || keys->left_command_key != 0x5d || keys->right_command_key != 0x5f)
313 sAltAsCommandKey = false;
314 free(chars);
315 free(keys);
316
317 BMenuItem *superItem = Superitem();
318 BMenu *superMenu = Supermenu();
319 if (AddDynamicItem(B_INITIAL_ADD)) {
320 do {
321 if (superMenu != NULL && !superMenu->OkToProceed(superItem)) {
322 AddDynamicItem(B_ABORT);
323 fAttachAborted = true;
324 break;
325 }
326 } while (AddDynamicItem(B_PROCESSING));
327 }
328
329 if (!fAttachAborted) {
330 CacheFontInfo();
331 LayoutItems(0);
332 //UpdateWindowViewSize();
333 }
334}
335
336
337void
338BMenu::DetachedFromWindow()
339{
340 BView::DetachedFromWindow();
341}
342
343
344bool
345BMenu::AddItem(BMenuItem *item)
346{
347 return AddItem(item, CountItems());
348}
349
350
351bool
352BMenu::AddItem(BMenuItem *item, int32 index)
353{
354 if (fLayout == B_ITEMS_IN_MATRIX)
355 debugger("BMenu::AddItem(BMenuItem *, int32) this method can only "
356 "be called if the menu layout is not B_ITEMS_IN_MATRIX");
357
358 if (!item || !_AddItem(item, index))
359 return false;
360
361 InvalidateLayout();
362 if (LockLooper()) {
363 if (!Window()->IsHidden()) {
364 LayoutItems(index);
365 Invalidate();
366 }
367 UnlockLooper();
368 }
369 return true;
370}
371
372
373bool
374BMenu::AddItem(BMenuItem *item, BRect frame)
375{
376 if (fLayout != B_ITEMS_IN_MATRIX)
377 debugger("BMenu::AddItem(BMenuItem *, BRect) this method can only "
378 "be called if the menu layout is B_ITEMS_IN_MATRIX");
379
380 if (!item)
381 return false;
382
383 item->fBounds = frame;
384
385 int32 index = CountItems();
386 if (!_AddItem(item, index)) {
387 return false;
388 }
389
390 if (LockLooper()) {
391 if (!Window()->IsHidden()) {
392 LayoutItems(index);
393 Invalidate();
394 }
395 UnlockLooper();
396 }
397
398 return true;
399}
400
401
402bool
403BMenu::AddItem(BMenu *submenu)
404{
405 BMenuItem *item = new (nothrow) BMenuItem(submenu);
406 if (!item)
407 return false;
408
409 if (!AddItem(item, CountItems())) {
410 item->fSubmenu = NULL;
411 delete item;
412 return false;
413 }
414
415 return true;
416}
417
418
419bool
420BMenu::AddItem(BMenu *submenu, int32 index)
421{
422 if (fLayout == B_ITEMS_IN_MATRIX)
423 debugger("BMenu::AddItem(BMenuItem *, int32) this method can only "
424 "be called if the menu layout is not B_ITEMS_IN_MATRIX");
425
426 BMenuItem *item = new (nothrow) BMenuItem(submenu);
427 if (!item)
428 return false;
429
430 if (!AddItem(item, index)) {
431 item->fSubmenu = NULL;
432 delete item;
433 return false;
434 }
435
436 return true;
437}
438
439
440bool
441BMenu::AddItem(BMenu *submenu, BRect frame)
442{
443 if (fLayout != B_ITEMS_IN_MATRIX)
444 debugger("BMenu::AddItem(BMenu *, BRect) this method can only "
445 "be called if the menu layout is B_ITEMS_IN_MATRIX");
446
447 BMenuItem *item = new (nothrow) BMenuItem(submenu);
448 if (!item)
449 return false;
450
451 if (!AddItem(item, frame)) {
452 item->fSubmenu = NULL;
453 delete item;
454 return false;
455 }
456
457 return true;
458}
459
460
461bool
462BMenu::AddList(BList *list, int32 index)
463{
464 // TODO: test this function, it's not documented in the bebook.
465 if (list == NULL)
466 return false;
467
468 bool locked = LockLooper();
469
470 int32 numItems = list->CountItems();
471 for (int32 i = 0; i < numItems; i++) {
472 BMenuItem *item = static_cast<BMenuItem *>(list->ItemAt(i));
473 if (item != NULL) {
474 if (!_AddItem(item, index + i))
475 break;
476 }
477 }
478
479 InvalidateLayout();
480 if (locked && Window() != NULL && !Window()->IsHidden()) {
481 // Make sure we update the layout if needed.
482 LayoutItems(index);
483 //UpdateWindowViewSize();
484 Invalidate();
485 }
486
487 if (locked)
488 UnlockLooper();
489
490 return true;
491}
492
493
494bool
495BMenu::AddSeparatorItem()
496{
497 BMenuItem *item = new (nothrow) BSeparatorItem();
498 if (!item || !AddItem(item, CountItems())) {
499 delete item;
500 return false;
501 }
502
503 return true;
504}
505
506
507bool
508BMenu::RemoveItem(BMenuItem *item)
509{
510 // TODO: Check if item is also deleted
511 return RemoveItems(0, 0, item, false);
512}
513
514
515BMenuItem *
516BMenu::RemoveItem(int32 index)
517{
518 BMenuItem *item = ItemAt(index);
519 if (item != NULL)
520 RemoveItems(0, 0, item, false);
521 return item;
522}
523
524
525bool
526BMenu::RemoveItems(int32 index, int32 count, bool del)
527{
528 return RemoveItems(index, count, NULL, del);
529}
530
531
532bool
533BMenu::RemoveItem(BMenu *submenu)
534{
535 for (int32 i = 0; i < fItems.CountItems(); i++) {
536 if (static_cast<BMenuItem *>(fItems.ItemAtFast(i))->Submenu() == submenu)
537 return RemoveItems(i, 1, NULL, false);
538 }
539
540 return false;
541}
542
543
544int32
545BMenu::CountItems() const
546{
547 return fItems.CountItems();
548}
549
550
551BMenuItem *
552BMenu::ItemAt(int32 index) const
553{
554 return static_cast<BMenuItem *>(fItems.ItemAt(index));
555}
556
557
558BMenu *
559BMenu::SubmenuAt(int32 index) const
560{
561 BMenuItem *item = static_cast<BMenuItem *>(fItems.ItemAt(index));
562 return (item != NULL) ? item->Submenu() : NULL;
563}
564
565
566int32
567BMenu::IndexOf(BMenuItem *item) const
568{
569 return fItems.IndexOf(item);
570}
571
572
573int32
574BMenu::IndexOf(BMenu *submenu) const
575{
576 for (int32 i = 0; i < fItems.CountItems(); i++) {
577 if (ItemAt(i)->Submenu() == submenu)
578 return i;
579 }
580
581 return -1;
582}
583
584
585BMenuItem *
586BMenu::FindItem(const char *label) const
587{
588 BMenuItem *item = NULL;
589
590 for (int32 i = 0; i < CountItems(); i++) {
591 item = ItemAt(i);
592
593 if (item->Label() && strcmp(item->Label(), label) == 0)
594 return item;
595
596 if (item->Submenu() != NULL) {
597 item = item->Submenu()->FindItem(label);
598 if (item != NULL)
599 return item;
600 }
601 }
602
603 return NULL;
604}
605
606
607BMenuItem *
608BMenu::FindItem(uint32 command) const
609{
610 BMenuItem *item = NULL;
611
612 for (int32 i = 0; i < CountItems(); i++) {
613 item = ItemAt(i);
614
615 if (item->Command() == command)
616 return item;
617
618 if (item->Submenu() != NULL) {
619 item = item->Submenu()->FindItem(command);
620 if (item != NULL)
621 return item;
622 }
623 }
624
625 return NULL;
626}
627
628
629status_t
630BMenu::SetTargetForItems(BHandler *handler)
631{
632 status_t status = B_OK;
633 for (int32 i = 0; i < fItems.CountItems(); i++) {
634 status = ItemAt(i)->SetTarget(handler);
635 if (status < B_OK)
636 break;
637 }
638
639 return status;
640}
641
642
643status_t
644BMenu::SetTargetForItems(BMessenger messenger)
645{
646 status_t status = B_OK;
647 for (int32 i = 0; i < fItems.CountItems(); i++) {
648 status = ItemAt(i)->SetTarget(messenger);
649 if (status < B_OK)
650 break;
651 }
652
653 return status;
654}
655
656
657void
658BMenu::SetEnabled(bool enabled)
659{
660 if (fEnabled == enabled)
661 return;
662
663 fEnabled = enabled;
664
665 if (fSuperitem)
666 fSuperitem->SetEnabled(enabled);
667}
668
669
670void
671BMenu::SetRadioMode(bool flag)
672{
673 fRadioMode = flag;
674 if (!flag)
675 SetLabelFromMarked(false);
676}
677
678
679void
680BMenu::SetTriggersEnabled(bool flag)
681{
682 fTriggerEnabled = flag;
683}
684
685
686void
687BMenu::SetMaxContentWidth(float width)
688{
689 fMaxContentWidth = width;
690}
691
692
693void
694BMenu::SetLabelFromMarked(bool flag)
695{
696 fDynamicName = flag;
697 if (flag)
698 SetRadioMode(true);
699}
700
701
702bool
703BMenu::IsLabelFromMarked()
704{
705 return fDynamicName;
706}
707
708
709bool
710BMenu::IsEnabled() const
711{
712 if (!fEnabled)
713 return false;
714
715 return fSuper ? fSuper->IsEnabled() : true ;
716}
717
718
719bool
720BMenu::IsRadioMode() const
721{
722 return fRadioMode;
723}
724
725
726bool
727BMenu::AreTriggersEnabled() const
728{
729 return fTriggerEnabled;
730}
731
732
733bool
734BMenu::IsRedrawAfterSticky() const
735{
736 return fRedrawAfterSticky;
737}
738
739
740float
741BMenu::MaxContentWidth() const
742{
743 return fMaxContentWidth;
744}
745
746
747BMenuItem *
748BMenu::FindMarked()
749{
750 for (int32 i = 0; i < fItems.CountItems(); i++) {
751 BMenuItem *item = ItemAt(i);
752 if (item->IsMarked())
753 return item;
754 }
755
756 return NULL;
757}
758
759
760BMenu *
761BMenu::Supermenu() const
762{
763 return fSuper;
764}
765
766
767BMenuItem *
768BMenu::Superitem() const
769{
770 return fSuperitem;
771}
772
773
774void
775BMenu::MessageReceived(BMessage *msg)
776{
777 BView::MessageReceived(msg);
778}
779
780
781void
782BMenu::KeyDown(const char *bytes, int32 numBytes)
783{
784 // TODO: Test how it works on beos and implement it correctly
785 switch (bytes[0]) {
786 case B_UP_ARROW:
787 if (fLayout == B_ITEMS_IN_COLUMN)
788 SelectNextItem(fSelected, false);
789 break;
790
791 case B_DOWN_ARROW:
792 if (fLayout == B_ITEMS_IN_COLUMN)
793 SelectNextItem(fSelected, true);
794 break;
795
796 case B_LEFT_ARROW:
797 if (fLayout == B_ITEMS_IN_ROW)
798 SelectNextItem(fSelected, false);
799 break;
800
801 case B_RIGHT_ARROW:
802 if (fLayout == B_ITEMS_IN_ROW)
803 SelectNextItem(fSelected, true);
804 break;
805
806 case B_ENTER:
807 case B_SPACE:
808 if (fSelected)
809 InvokeItem(fSelected);
810
811 break;
812
813 case B_ESCAPE:
814 QuitTracking();
815 break;
816
817 default:
818 BView::KeyDown(bytes, numBytes);
819 }
820}
821
822
823void
824BMenu::Draw(BRect updateRect)
825{
826 if (RelayoutIfNeeded()) {
827 Invalidate();
828 return;
829 }
830
831 DrawBackground(updateRect);
832 DrawItems(updateRect);
833}
834
835
836void
837BMenu::GetPreferredSize(float *_width, float *_height)
838{
839 ComputeLayout(0, true, false, _width, _height);
840}
841
842
843void
844BMenu::ResizeToPreferred()
845{
846 BView::ResizeToPreferred();
847}
848
849
850void
851BMenu::FrameMoved(BPoint new_position)
852{
853 BView::FrameMoved(new_position);
854}
855
856
857void
858BMenu::FrameResized(float new_width, float new_height)
859{
860 BView::FrameResized(new_width, new_height);
861}
862
863
864void
865BMenu::InvalidateLayout()
866{
867 fUseCachedMenuLayout = false;
868}
869
870
871BHandler *
872BMenu::ResolveSpecifier(BMessage *msg, int32 index, BMessage *specifier,
873 int32 form, const char *property)
874{
875 BPropertyInfo propInfo(sPropList);
876 BHandler *target = NULL;
877
878 switch (propInfo.FindMatch(msg, 0, specifier, form, property)) {
879 case B_ERROR:
880 break;
881
882 case 0:
883 case 1:
884 case 2:
885 case 3:
886 case 4:
887 case 5:
888 case 6:
889 case 7:
890 target = this;
891 break;
892 case 8:
893 // TODO: redirect to menu
894 target = this;
895 break;
896 case 9:
897 case 10:
898 case 11:
899 case 12:
900 target = this;
901 break;
902 case 13:
903 // TODO: redirect to menuitem
904 target = this;
905 break;
906 }
907
908 if (!target)
909 target = BView::ResolveSpecifier(msg, index, specifier, form,
910 property);
911
912 return target;
913}
914
915
916status_t
917BMenu::GetSupportedSuites(BMessage *data)
918{
919 if (data == NULL)
920 return B_BAD_VALUE;
921
922 status_t err = data->AddString("suites", "suite/vnd.Be-menu");
923
924 if (err < B_OK)
925 return err;
926
927 BPropertyInfo propertyInfo(sPropList);
928 err = data->AddFlat("messages", &propertyInfo);
929
930 if (err < B_OK)
931 return err;
932
933 return BView::GetSupportedSuites(data);
934}
935
936
937status_t
938BMenu::Perform(perform_code d, void *arg)
939{
940 return BView::Perform(d, arg);
941}
942
943
944void
945BMenu::MakeFocus(bool focused)
946{
947 BView::MakeFocus(focused);
948}
949
950
951void
952BMenu::AllAttached()
953{
954 BView::AllAttached();
955}
956
957
958void
959BMenu::AllDetached()
960{
961 BView::AllDetached();
962}
963
964
965BMenu::BMenu(BRect frame, const char *name, uint32 resizingMode, uint32 flags,
966 menu_layout layout, bool resizeToFit)
967 : BView(frame, name, resizingMode, flags),
968 fChosenItem(NULL),
969 fSelected(NULL),
970 fCachedMenuWindow(NULL),
971 fSuper(NULL),
972 fSuperitem(NULL),
973 fAscent(-1.0f),
974 fDescent(-1.0f),
975 fFontHeight(-1.0f),
976 fState(0),
977 fLayout(layout),
978 fExtraRect(NULL),
979 fMaxContentWidth(0.0f),
980 fInitMatrixSize(NULL),
981 fExtraMenuData(NULL),
982 fTrigger(0),
983 fResizeToFit(resizeToFit),
984 fUseCachedMenuLayout(false),
985 fEnabled(true),
986 fDynamicName(false),
987 fRadioMode(false),
988 fTrackNewBounds(false),
989 fStickyMode(false),
990 fIgnoreHidden(true),
991 fTriggerEnabled(true),
992 fRedrawAfterSticky(false),
993 fAttachAborted(false)
994{
995 InitData(NULL);
996}
997
998
999void
1000BMenu::SetItemMargins(float left, float top, float right, float bottom)
1001{
1002 fPad.Set(left, top, right, bottom);
1003}
1004
1005
1006void
1007BMenu::GetItemMargins(float *left, float *top, float *right,
1008 float *bottom) const
1009{
1010 if (left != NULL)
1011 *left = fPad.left;
1012 if (top != NULL)
1013 *top = fPad.top;
1014 if (right != NULL)
1015 *right = fPad.right;
1016 if (bottom != NULL)
1017 *bottom = fPad.bottom;
1018}
1019
1020
1021menu_layout
1022BMenu::Layout() const
1023{
1024 return fLayout;
1025}
1026
1027
1028void
1029BMenu::Show()
1030{
1031 Show(false);
1032}
1033
1034
1035void
1036BMenu::Show(bool selectFirst)
1037{
1038 Install(NULL);
1039 _show(selectFirst);
1040}
1041
1042
1043void
1044BMenu::Hide()
1045{
1046 _hide();
1047 Uninstall();
1048}
1049
1050
1051BMenuItem *
1052BMenu::Track(bool sticky, BRect *clickToOpenRect)
1053{
1054 if (sticky && LockLooper()) {
1055 RedrawAfterSticky(Bounds());
1056 UnlockLooper();
1057 }
1058
1059 if (clickToOpenRect != NULL && LockLooper()) {
1060 fExtraRect = clickToOpenRect;
1061 ConvertFromScreen(fExtraRect);
1062 UnlockLooper();
1063 }
1064
1065 // If sticky is false, pass 0 to the tracking function
1066 // so the menu will stay in nonsticky mode
1067 const bigtime_t trackTime = sticky ? system_time() : 0;
1068 int action;
1069 BMenuItem *menuItem = _track(&action, trackTime);
1070
1071 SetStickyMode(false);
1072 fExtraRect = NULL;
1073
1074 return menuItem;
1075}
1076
1077
1078bool
1079BMenu::AddDynamicItem(add_state s)
1080{
1081 // Implemented in subclasses
1082 return false;
1083}
1084
1085
1086void
1087BMenu::DrawBackground(BRect update)
1088{
1089 rgb_color oldColor = HighColor();
1090 SetHighColor(sMenuInfo.background_color);
1091 FillRect(Bounds() & update, B_SOLID_HIGH);
1092 SetHighColor(oldColor);
1093}
1094
1095
1096void
1097BMenu::SetTrackingHook(menu_tracking_hook func, void *state)
1098{
1099 delete fExtraMenuData;
1100 fExtraMenuData = new (nothrow) _ExtraMenuData_(func, state);
1101}
1102
1103
1104void BMenu::_ReservedMenu3() {}
1105void BMenu::_ReservedMenu4() {}
1106void BMenu::_ReservedMenu5() {}
1107void BMenu::_ReservedMenu6() {}
1108
1109
1110BMenu &
1111BMenu::operator=(const BMenu &)
1112{
1113 return *this;
1114}
1115
1116
1117void
1118BMenu::InitData(BMessage *data)
1119{
1120 // TODO: Get _color, _fname, _fflt from the message, if present
1121 BFont font;
1122 font.SetFamilyAndStyle(sMenuInfo.f_family, sMenuInfo.f_style);
1123 font.SetSize(sMenuInfo.font_size);
1124 SetFont(&font, B_FONT_FAMILY_AND_STYLE | B_FONT_SIZE);
1125
1126 SetLowColor(sMenuInfo.background_color);
1127 SetViewColor(sMenuInfo.background_color);
1128
1129 if (data != NULL) {
1130 data->FindInt32("_layout", (int32 *)&fLayout);
1131 data->FindBool("_rsize_to_fit", &fResizeToFit);
1132 bool disabled;
1133 if (data->FindBool("_disable", &disabled) == B_OK)
1134 fEnabled = !disabled;
1135 data->FindBool("_radio", &fRadioMode);
1136
1137 bool disableTrigger = false;
1138 data->FindBool("_trig_disabled", &disableTrigger);
1139 fTriggerEnabled = !disableTrigger;
1140
1141 data->FindBool("_dyn_label", &fDynamicName);
1142 data->FindFloat("_maxwidth", &fMaxContentWidth);
1143
1144 BMessage msg;
1145 for (int32 i = 0; data->FindMessage("_items", i, &msg) == B_OK; i++) {
1146 BArchivable *object = instantiate_object(&msg);
1147 if (BMenuItem *item = dynamic_cast<BMenuItem *>(object)) {
1148 BRect bounds;
1149 if ((fLayout == B_ITEMS_IN_MATRIX)
1150 && (data->FindRect("_i_frames", i, &bounds) == B_OK))
1151 AddItem(item, bounds);
1152 else
1153 AddItem(item);
1154 }
1155 }
1156 }
1157}
1158
1159
1160bool
1161BMenu::_show(bool selectFirstItem)
1162{
1163 // See if the supermenu has a cached menuwindow,
1164 // and use that one if possible.
1165 BMenuWindow *window = NULL;
1166 bool ourWindow = false;
1167 if (fSuper != NULL) {
1168 fSuperbounds = fSuper->ConvertToScreen(fSuper->Bounds());
1169 window = fSuper->MenuWindow();
1170 }
1171
1172 // Otherwise, create a new one
1173 // This happens for "stand alone" BPopUpMenus
1174 // (i.e. not within a BMenuField)
1175 if (window == NULL) {
1176 // Menu windows get the BMenu's handler name
1177 window = new (nothrow) BMenuWindow(Name());
1178 ourWindow = true;
1179 }
1180
1181 if (window == NULL)
1182 return false;
1183
1184 if (window->Lock()) {
1185 fAttachAborted = false;
1186 window->AttachMenu(this);
1187
1188 // Menu didn't have the time to add its items: aborting...
1189 if (fAttachAborted) {
1190 window->DetachMenu();
1191 // TODO: Probably not needed, we can just let _hide() quit the window
1192 if (ourWindow)
1193 window->Quit();
1194 else
1195 window->Unlock();
1196 return false;
1197 }
1198
1199 // Move the BMenu to 1, 1, if it's attached to a BMenuWindow,
1200 // (that means it's a BMenu, BMenuBars are attached to regular BWindows).
1201 // This is needed to be able to draw the frame around the BMenu.
1202 if (dynamic_cast<BMenuWindow *>(window) != NULL)
1203 MoveTo(1, 1);
1204
1205 UpdateWindowViewSize();
1206 window->Show();
1207
1208 if (selectFirstItem)
1209 SelectItem(ItemAt(0));
1210
1211 window->Unlock();
1212 }
1213
1214 return true;
1215}
1216
1217
1218void
1219BMenu::_hide()
1220{
1221 BMenuWindow *window = static_cast<BMenuWindow *>(Window());
1222 if (window == NULL || !window->Lock())
1223 return;
1224
1225 if (fSelected != NULL)
1226 SelectItem(NULL);
1227
1228 window->Hide();
1229 window->DetachMenu();
1230 // we don't want to be deleted when the window is removed
1231
1232 // Delete the menu window used by our submenus
1233 DeleteMenuWindow();
1234
1235 window->Unlock();
1236
1237 if (fSuper == NULL && window->Lock())
1238 window->Quit();
1239}
1240
1241
1242const bigtime_t kHysteresis = 200000; // TODO: Test and reduce if needed.
1243
1244
1245BMenuItem *
1246BMenu::_track(int *action, bigtime_t trackTime, long start)
1247{
1248 // TODO: cleanup
1249 BMenuItem *item = NULL;
1250 bigtime_t openTime = system_time();
1251 bigtime_t closeTime = 0;
1252
1253 fState = MENU_STATE_TRACKING;
1254 if (fSuper != NULL)
1255 fSuper->fState = MENU_STATE_TRACKING_SUBMENU;
1256
1257 while (true) {
1258 if (fExtraMenuData != NULL && fExtraMenuData->trackingHook != NULL
1259 && fExtraMenuData->trackingState != NULL) {
1260 /*bool result =*/ fExtraMenuData->trackingHook(this, fExtraMenuData->trackingState);
1261 //printf("tracking hook returned %s\n", result ? "true" : "false");
1262 }
1263
1264 bool locked = LockLooper();
1265 if (!locked)
1266 break;
1267
1268 bigtime_t snoozeAmount = 50000;
1269 BPoint location;
1270 ulong buttons;
1271 GetMouse(&location, &buttons, true);
1272
1273 Window()->UpdateIfNeeded();
1274 BPoint screenLocation = ConvertToScreen(location);
1275 item = HitTestItems(location, B_ORIGIN);
1276 if (item != NULL) {
1277 if (item != fSelected && system_time() > closeTime + kHysteresis) {
1278 SelectItem(item, -1);
1279 openTime = system_time();
1280 } else if (system_time() > kHysteresis + openTime && item->Submenu() != NULL
1281 && item->Submenu()->Window() == NULL) {
1282 // Open the submenu if it's not opened yet, but only if
1283 // the mouse pointer stayed over there for some time
1284 // (hysteresis)
1285 SelectItem(item);
1286 closeTime = system_time();
1287 }
1288 fState = MENU_STATE_TRACKING;
1289 }
1290
1291 // Track the submenu
1292 if (fSelected != NULL && OverSubmenu(fSelected, screenLocation)) {
1293 UnlockLooper();
1294 locked = false;
1295 int submenuAction = MENU_STATE_TRACKING;
1296 BMenu *submenu = fSelected->Submenu();
1297 if (IsStickyMode())
1298 submenu->SetStickyMode(true);
1299 BMenuItem *submenuItem = submenu->_track(&submenuAction, trackTime);
1300 //submenu->Window()->Activate();
1301 if (submenuAction == MENU_STATE_CLOSED) {
1302 item = submenuItem;
1303 fState = submenuAction;
1304 break;
1305 }
1306
1307 locked = LockLooper();
1308 if (!locked)
1309 break;
1310 }
1311
1312 if (item == NULL) {
1313 if (OverSuper(screenLocation)) {
1314 fState = MENU_STATE_TRACKING;
1315 UnlockLooper();
1316 break;
1317 }
1318
1319 if (fSelected != NULL && !OverSubmenu(fSelected, screenLocation)
1320 && system_time() > closeTime + kHysteresis
1321 && fState != MENU_STATE_TRACKING_SUBMENU) {
1322 SelectItem(NULL);
1323 fState = MENU_STATE_TRACKING;
1324 }
1325
1326 if (fSuper != NULL) {
1327 if (locked)
1328 UnlockLooper();
1329 *action = fState;
1330 return NULL;
1331 }
1332 }
1333
1334 if (locked)
1335 UnlockLooper();
1336
1337 if (buttons != 0 && IsStickyMode())
1338 fState = MENU_STATE_CLOSED;
1339 else if (buttons == 0 && !IsStickyMode()) {
1340 if (system_time() < trackTime + 1000000
1341 || (fExtraRect != NULL && fExtraRect->Contains(location)))
1342 SetStickyMode(true);
1343 else
1344 fState = MENU_STATE_CLOSED;
1345 }
1346
1347 if (fState == MENU_STATE_CLOSED)
1348 break;
1349
1350 snooze(snoozeAmount);
1351 }
1352
1353 if (action != NULL)
1354 *action = fState;
1355
1356 if (fSelected != NULL && LockLooper()) {
1357 SelectItem(NULL);
1358 UnlockLooper();
1359 }
1360
1361 if (IsStickyMode())
1362 SetStickyMode(false);
1363
1364 // delete the menu window recycled for all the child menus
1365 DeleteMenuWindow();
1366
1367 return item;
1368}
1369
1370
1371bool
1372BMenu::_AddItem(BMenuItem *item, int32 index)
1373{
1374 ASSERT(item != NULL);
1375 if (index < 0 || index > fItems.CountItems())
1376 return false;
1377
1378 if (!fItems.AddItem(item, index))
1379 return false;
1380
1381 // install the item on the supermenu's window
1382 // or onto our window, if we are a root menu
1383 BWindow* window = NULL;
1384 if (Superitem() != NULL)
1385 window = Superitem()->fWindow;
1386 else
1387 window = Window();
1388 if (window != NULL)
1389 item->Install(window);
1390
1391 item->SetSuper(this);
1392
1393 return true;
1394}
1395
1396
1397bool
1398BMenu::RemoveItems(int32 index, int32 count, BMenuItem *item, bool deleteItems)
1399{
1400 bool success = false;
1401 bool invalidateLayout = false;
1402
1403 bool locked = LockLooper();
1404 BWindow *window = Window();
1405
1406 // The plan is simple: If we're given a BMenuItem directly, we use it
1407 // and ignore index and count. Otherwise, we use them instead.
1408 if (item != NULL) {
1409 if (fItems.RemoveItem(item)) {
1410 if (item == fSelected && window != NULL)
1411 SelectItem(NULL);
1412 item->Uninstall();
1413 item->SetSuper(NULL);
1414 if (deleteItems)
1415 delete item;
1416 success = invalidateLayout = true;
1417 }
1418 } else {
1419 // We iterate backwards because it's simpler
1420 int32 i = min_c(index + count - 1, fItems.CountItems() - 1);
1421 // NOTE: the range check for "index" is done after
1422 // calculating the last index to be removed, so
1423 // that the range is not "shifted" unintentionally
1424 index = max_c(0, index);
1425 for (; i >= index; i--) {
1426 item = static_cast<BMenuItem*>(fItems.ItemAt(i));
1427 if (item != NULL) {
1428 if (fItems.RemoveItem(item)) {
1429 if (item == fSelected && window != NULL)
1430 SelectItem(NULL);
1431 item->Uninstall();
1432 item->SetSuper(NULL);
1433 if (deleteItems)
1434 delete item;
1435 success = true;
1436 invalidateLayout = true;
1437 } else {
1438 // operation not entirely successful
1439 success = false;
1440 break;
1441 }
1442 }
1443 }
1444 }
1445
1446 if (invalidateLayout && locked && window != NULL) {
1447 LayoutItems(0);
1448 //UpdateWindowViewSize();
1449 Invalidate();
1450 }
1451
1452 if (locked)
1453 UnlockLooper();
1454
1455 return success;
1456}
1457
1458
1459bool
1460BMenu::RelayoutIfNeeded()
1461{
1462 if (!fUseCachedMenuLayout) {
1463 fUseCachedMenuLayout = true;
1464 CacheFontInfo();
1465 LayoutItems(0);
1466 return true;
1467 }
1468 return false;
1469}
1470
1471
1472void
1473BMenu::LayoutItems(int32 index)
1474{
1475 CalcTriggers();
1476
1477 float width, height;
1478 ComputeLayout(index, fResizeToFit, true, &width, &height);
1479
1480 ResizeTo(width, height);
1481}
1482
1483
1484void
1485BMenu::ComputeLayout(int32 index, bool bestFit, bool moveItems,
1486 float* _width, float* _height)
1487{
1488 // TODO: Take "bestFit", "moveItems", "index" into account,
1489 // Recalculate only the needed items,
1490 // not the whole layout every time
1491
1492 BRect frame(0, 0, 0, 0);
1493 float iWidth, iHeight;
1494 BMenuItem *item = NULL;
1495
1496 BFont font;
1497 GetFont(&font);
1498 switch (fLayout) {
1499 case B_ITEMS_IN_COLUMN:
1500 {
1501 for (int32 i = 0; i < fItems.CountItems(); i++) {
1502 item = ItemAt(i);
1503 if (item != NULL) {
1504 item->GetContentSize(&iWidth, &iHeight);
1505
1506 if (item->fModifiers && item->fShortcutChar)
1507 iWidth += 2 * font.Size();
1508 if (item->fSubmenu != NULL)
1509 iWidth += 2 * font.Size();
1510
1511 item->fBounds.left = 0.0f;
1512 item->fBounds.top = frame.bottom;
1513 item->fBounds.bottom = item->fBounds.top + iHeight + fPad.top + fPad.bottom;
1514
1515 frame.right = max_c(frame.right, iWidth + fPad.left + fPad.right);
1516 frame.bottom = item->fBounds.bottom + 1.0f;
1517 }
1518 }
1519 if (fMaxContentWidth > 0)
1520 frame.right = min_c(frame.right, fMaxContentWidth);
1521
1522 if (moveItems) {
1523 for (int32 i = 0; i < fItems.CountItems(); i++)
1524 ItemAt(i)->fBounds.right = frame.right;
1525 }
1526 frame.right = ceilf(frame.right);
1527 frame.bottom--;
1528 break;
1529 }
1530
1531 case B_ITEMS_IN_ROW:
1532 {
1533 font_height fh;
1534 GetFontHeight(&fh);
1535 frame = BRect(0.0f, 0.0f, 0.0f, ceilf(fh.ascent + fh.descent + fPad.top + fPad.bottom));
1536
1537 for (int32 i = 0; i < fItems.CountItems(); i++) {
1538 item = ItemAt(i);
1539 if (item != NULL) {
1540 item->GetContentSize(&iWidth, &iHeight);
1541
1542 item->fBounds.left = frame.right;
1543 item->fBounds.top = 0.0f;
1544 item->fBounds.right = item->fBounds.left + iWidth + fPad.left + fPad.right;
1545
1546 frame.right = item->Frame().right + 1.0f;
1547 frame.bottom = max_c(frame.bottom, iHeight + fPad.top + fPad.bottom);
1548 }
1549 }
1550
1551 if (moveItems) {
1552 for (int32 i = 0; i < fItems.CountItems(); i++)
1553 ItemAt(i)->fBounds.bottom = frame.bottom;
1554 }
1555
1556 if (bestFit)
1557 frame.right = ceilf(frame.right);
1558 else
1559 frame.right = Bounds().right;
1560 break;
1561 }
1562
1563 case B_ITEMS_IN_MATRIX:
1564 {
1565 for (int32 i = 0; i < CountItems(); i++) {
1566 item = ItemAt(i);
1567 if (item != NULL) {
1568 frame.left = min_c(frame.left, item->Frame().left);
1569 frame.right = max_c(frame.right, item->Frame().right);
1570 frame.top = min_c(frame.top, item->Frame().top);
1571 frame.bottom = max_c(frame.bottom, item->Frame().bottom);
1572 }
1573 }
1574 break;
1575 }
1576
1577 default:
1578 break;
1579 }
1580
1581 if (_width) {
1582 // change width depending on resize mode
1583 if ((ResizingMode() & B_FOLLOW_LEFT_RIGHT) == B_FOLLOW_LEFT_RIGHT) {
1584 if (Parent())
1585 *_width = Parent()->Frame().Width() + 1;
1586 else if (Window())
1587 *_width = Window()->Frame().Width() + 1;
1588 else
1589 *_width = Bounds().Width();
1590 } else
1591 *_width = frame.Width();
1592 }
1593
1594 if (_height)
1595 *_height = frame.Height();
1596
1597 if (moveItems)
1598 fUseCachedMenuLayout = true;
1599}
1600
1601
1602BRect
1603BMenu::Bump(BRect current, BPoint extent, int32 index) const
1604{
1605 // ToDo: what's this?
1606 return BRect();
1607}
1608
1609
1610BPoint
1611BMenu::ItemLocInRect(BRect frame) const
1612{
1613 // ToDo: what's this?
1614 return BPoint();
1615}
1616
1617
1618BPoint
1619BMenu::ScreenLocation()
1620{
1621 BMenu *superMenu = Supermenu();
1622 BMenuItem *superItem = Superitem();
1623
1624 if (superMenu == NULL || superItem == NULL) {
1625 debugger("BMenu can't determine where to draw."
1626 "Override BMenu::ScreenLocation() to determine location.");
1627 }
1628
1629 BPoint point;
1630 if (superMenu->Layout() == B_ITEMS_IN_COLUMN)
1631 point = superItem->Frame().RightTop() + BPoint(1.0f, 1.0f);
1632 else
1633 point = superItem->Frame().LeftBottom() + BPoint(1.0f, 1.0f);
1634
1635 superMenu->ConvertToScreen(&point);
1636
1637 return point;
1638}
1639
1640
1641BRect
1642BMenu::CalcFrame(BPoint where, bool *scrollOn)
1643{
1644 // TODO: Improve me
1645 BRect bounds = Bounds();
1646 BRect frame = bounds.OffsetToCopy(where);
1647
1648 BScreen screen(Window());
1649 BRect screenFrame = screen.Frame();
1650
1651 BMenu *superMenu = Supermenu();
1652 BMenuItem *superItem = Superitem();
1653
1654 if (scrollOn != NULL) {
1655 // basically, if this returns false, it means
1656 // that the menu frame won't fit completely inside the screen
1657 *scrollOn = !screenFrame.Contains(bounds);
1658 }
1659
1660 // TODO: Horrible hack:
1661 // When added to a BMenuField, a BPopUpMenu is the child of
1662 // a _BMCMenuBar_ to "fake" the menu hierarchy
1663 if (superMenu == NULL || superItem == NULL
1664 || dynamic_cast<_BMCMenuBar_ *>(superMenu) != NULL) {
1665 // just move the window on screen
1666
1667 if (frame.bottom > screenFrame.bottom)
1668 frame.OffsetBy(0, screenFrame.bottom - frame.bottom);
1669 else if (frame.top < screenFrame.top)
1670 frame.OffsetBy(0, -frame.top);
1671
1672 if (frame.right > screenFrame.right)
1673 frame.OffsetBy(screenFrame.right - frame.right, 0);
1674 else if (frame.left < screenFrame.left)
1675 frame.OffsetBy(-frame.left, 0);
1676
1677 return frame;
1678 }
1679
1680 if (superMenu->Layout() == B_ITEMS_IN_COLUMN) {
1681 if (frame.right > screenFrame.right)
1682 frame.OffsetBy(-superItem->Frame().Width() - frame.Width() - 2, 0);
1683
1684 if (frame.left < 0)
1685 frame.OffsetBy(-frame.left, 0);
1686
1687 if (frame.bottom > screenFrame.bottom)
1688 frame.OffsetBy(0, screenFrame.bottom - frame.bottom);
1689 } else {
1690 if (frame.bottom > screenFrame.bottom)
1691 frame.OffsetBy(0, -superItem->Frame().Height() - frame.Height() - 3);
1692
1693 if (frame.right > screenFrame.right)
1694 frame.OffsetBy(screenFrame.right - frame.right, 0);
1695 }
1696
1697 return frame;
1698}
1699
1700
1701bool
1702BMenu::ScrollMenu(BRect bounds, BPoint loc, bool *fast)
1703{
1704 return false;
1705}
1706
1707
1708void
1709BMenu::ScrollIntoView(BMenuItem *item)
1710{
1711}
1712
1713
1714void
1715BMenu::DrawItems(BRect updateRect)
1716{
1717 int32 itemCount = fItems.CountItems();
1718 for (int32 i = 0; i < itemCount; i++) {
1719 BMenuItem *item = ItemAt(i);
1720 if (item->Frame().Intersects(updateRect))
1721 item->Draw();
1722 }
1723}
1724
1725
1726int
1727BMenu::State(BMenuItem **item) const
1728{
1729 return 0;
1730}
1731
1732
1733void
1734BMenu::InvokeItem(BMenuItem *item, bool now)
1735{
1736 if (!item->IsEnabled())
1737 return;
1738
1739 // Do the "selected" animation
1740 if (!item->Submenu() && LockLooper()) {
1741 snooze(50000);
1742 item->Select(true);
1743 Sync();
1744 snooze(50000);
1745 item->Select(false);
1746 Sync();
1747 snooze(50000);
1748 item->Select(true);
1749 Sync();
1750 snooze(50000);
1751 item->Select(false);
1752 Sync();
1753 UnlockLooper();
1754 }
1755
1756 item->Invoke();
1757}
1758
1759
1760bool
1761BMenu::OverSuper(BPoint location)
1762{
1763 if (!Supermenu())
1764 return false;
1765
1766 return fSuperbounds.Contains(location);
1767}
1768
1769
1770bool
1771BMenu::OverSubmenu(BMenuItem *item, BPoint loc)
1772{
1773 // we assume that loc is in screen coords
1774 BMenu *subMenu = item->Submenu();
1775 if (subMenu == NULL || subMenu->Window() == NULL)
1776 return false;
1777
1778 if (subMenu->Window()->Frame().Contains(loc))
1779 return true;
1780
1781 if (subMenu->fSelected == NULL)
1782 return false;
1783
1784 return subMenu->OverSubmenu(subMenu->fSelected, loc);
1785}
1786
1787
1788BMenuWindow *
1789BMenu::MenuWindow()
1790{
1791 if (fCachedMenuWindow == NULL) {
1792 char windowName[64];
1793 snprintf(windowName, 64, "%s cached menu", Name());
1794 fCachedMenuWindow = new (nothrow) BMenuWindow(windowName);
1795 }
1796
1797 return fCachedMenuWindow;
1798}
1799
1800
1801void
1802BMenu::DeleteMenuWindow()
1803{
1804 if (fCachedMenuWindow != NULL) {
1805 fCachedMenuWindow->Lock();
1806 fCachedMenuWindow->Quit();
1807 fCachedMenuWindow = NULL;
1808 }
1809}
1810
1811
1812BMenuItem *
1813BMenu::HitTestItems(BPoint where, BPoint slop) const
1814{
1815 // TODO: Take "slop" into account ?
1816
1817 // if the point doesn't lie within the menu's
1818 // bounds, bail out immediately
1819 if (!Bounds().Contains(where))
1820 return NULL;
1821
1822 int32 itemCount = CountItems();
1823 for (int32 i = 0; i < itemCount; i++) {
1824 BMenuItem *item = ItemAt(i);
1825 if (item->Frame().Contains(where))
1826 return item;
1827 }
1828
1829 return NULL;
1830}
1831
1832
1833BRect
1834BMenu::Superbounds() const
1835{
1836 return fSuperbounds;
1837}
1838
1839
1840void
1841BMenu::CacheFontInfo()
1842{
1843 font_height fh;
1844 GetFontHeight(&fh);
1845 fAscent = fh.ascent;
1846 fDescent = fh.descent;
1847 fFontHeight = ceilf(fh.ascent + fh.descent + fh.leading);
1848}
1849
1850
1851void
1852BMenu::ItemMarked(BMenuItem *item)
1853{
1854 if (IsRadioMode()) {
1855 for (int32 i = 0; i < CountItems(); i++)
1856 if (ItemAt(i) != item)
1857 ItemAt(i)->SetMarked(false);
1858 InvalidateLayout();
1859 }
1860
1861 if (IsLabelFromMarked() && Superitem())
1862 Superitem()->SetLabel(item->Label());
1863}
1864
1865
1866void
1867BMenu::Install(BWindow *target)
1868{
1869 for (int32 i = 0; i < CountItems(); i++)
1870 ItemAt(i)->Install(target);
1871}
1872
1873
1874void
1875BMenu::Uninstall()
1876{
1877 for (int32 i = 0; i < CountItems(); i++)
1878 ItemAt(i)->Uninstall();
1879}
1880
1881
1882void
1883BMenu::SelectItem(BMenuItem *menuItem, uint32 showSubmenu, bool selectFirstItem)
1884{
1885 // Avoid deselecting and then reselecting the same item
1886 // which would cause flickering
1887 if (menuItem != fSelected) {
1888 if (fSelected != NULL) {
1889 fSelected->Select(false);
1890 BMenu *subMenu = fSelected->Submenu();
1891 if (subMenu != NULL && subMenu->Window() != NULL)
1892 subMenu->_hide();
1893 }
1894
1895 fSelected = menuItem;
1896 if (fSelected != NULL)
1897 fSelected->Select(true);
1898 }
1899
1900 if (fSelected != NULL && showSubmenu == 0) {
1901 BMenu *subMenu = fSelected->Submenu();
1902 if (subMenu != NULL && subMenu->Window() == NULL) {
1903 if (!subMenu->_show(selectFirstItem)) {
1904 // something went wrong, deselect the item
1905 fSelected->Select(false);
1906 fSelected = NULL;
1907 }
1908 //subMenu->Window()->Activate();
1909 }
1910 }
1911}
1912
1913
1914BMenuItem *
1915BMenu::CurrentSelection() const
1916{
1917 return fSelected;
1918}
1919
1920
1921bool
1922BMenu::SelectNextItem(BMenuItem *item, bool forward)
1923{
1924 BMenuItem *nextItem = NextItem(item, forward);
1925 if (nextItem == NULL)
1926 return false;
1927
1928 SelectItem(nextItem);
1929 return true;
1930}
1931
1932
1933BMenuItem *
1934BMenu::NextItem(BMenuItem *item, bool forward) const
1935{
1936 if (item == NULL) {
1937 if (forward)
1938 return ItemAt(CountItems() - 1);
1939 else
1940 return ItemAt(0);
1941 }
1942
1943 int32 index = fItems.IndexOf(item);
1944 if (forward)
1945 index++;
1946 else
1947 index--;
1948
1949 if (index < 0 || index >= fItems.CountItems())
1950 return NULL;
1951
1952 return ItemAt(index);
1953}
1954
1955
1956bool
1957BMenu::IsItemVisible(BMenuItem *item) const
1958{
1959 BRect itemFrame = item->Frame();
1960 ConvertToScreen(&itemFrame);
1961
1962 BRect visibilityFrame = Window()->Frame() & BScreen(Window()).Frame();
1963
1964 return visibilityFrame.Intersects(itemFrame);
1965}
1966
1967
1968void
1969BMenu::SetIgnoreHidden(bool on)
1970{
1971 fIgnoreHidden = on;
1972}
1973
1974
1975void
1976BMenu::SetStickyMode(bool on)
1977{
1978 if (fStickyMode != on) {
1979 // TODO: Ugly hack, but it needs to be done right here in this method
1980 BMenuBar *menuBar = dynamic_cast<BMenuBar *>(this);
1981 if (on && menuBar != NULL && menuBar->LockLooper()) {
1982 // Steal the focus from the current focus view
1983 // (needed to handle keyboard navigation)
1984 menuBar->StealFocus();
1985 menuBar->UnlockLooper();
1986 }
1987
1988 fStickyMode = on;
1989 }
1990
1991 // If we are switching to sticky mode, propagate the status
1992 // back to the super menu
1993 if (on && fSuper != NULL)
1994 fSuper->SetStickyMode(on);
1995}
1996
1997
1998bool
1999BMenu::IsStickyMode() const
2000{
2001 return fStickyMode;
2002}
2003
2004
2005void
2006BMenu::CalcTriggers()
2007{
2008 BList triggersList;
2009
2010 // Gathers the existing triggers
2011 // TODO: Oh great, reinterpret_cast.
2012 for (int32 i = 0; i < CountItems(); i++) {
2013 char trigger = ItemAt(i)->Trigger();
2014 if (trigger != 0)
2015 triggersList.AddItem(reinterpret_cast<void *>((uint32)trigger));
2016 }
2017
2018 // Set triggers for items which don't have one yet
2019 for (int32 i = 0; i < CountItems(); i++) {
2020 BMenuItem *item = ItemAt(i);
2021 if (item->Trigger() == 0) {
2022 const char *newTrigger = ChooseTrigger(item->Label(), &triggersList);
2023 if (newTrigger != NULL)
2024 item->SetAutomaticTrigger(*newTrigger);
2025 }
2026 }
2027}
2028
2029
2030const char *
2031BMenu::ChooseTrigger(const char *title, BList *chars)
2032{
2033 ASSERT(chars != NULL);
2034
2035 if (title == NULL)
2036 return NULL;
2037
2038 char trigger;
2039 // TODO: Oh great, reinterpret_cast all around
2040 while ((trigger = title[0]) != '\0') {
2041 if (!chars->HasItem(reinterpret_cast<void *>((uint32)trigger))) {
2042 chars->AddItem(reinterpret_cast<void *>((uint32)trigger));
2043 return title;
2044 }
2045
2046 title++;
2047 }
2048
2049 return NULL;
2050}
2051
2052
2053void
2054BMenu::UpdateWindowViewSize(bool upWind)
2055{
2056 BWindow *window = Window();
2057 if (window == NULL)
2058 return;
2059
2060 bool scroll;
2061 BRect frame = CalcFrame(ScreenLocation(), &scroll);
2062 ResizeTo(frame.Width(), frame.Height());
2063
2064 if (fItems.CountItems() > 0)
2065 window->ResizeTo(Bounds().Width() + 2, Bounds().Height() + 2);
2066 else {
2067 CacheFontInfo();
2068 window->ResizeTo(StringWidth(kEmptyMenuLabel) + fPad.left + fPad.right,
2069 fFontHeight + fPad.top + fPad.bottom);
2070 }
2071 window->MoveTo(frame.LeftTop());
2072}
2073
2074
2075bool
2076BMenu::IsStickyPrefOn()
2077{
2078 return true;
2079}
2080
2081
2082void
2083BMenu::RedrawAfterSticky(BRect bounds)
2084{
2085}
2086
2087
2088bool
2089BMenu::OkToProceed(BMenuItem* item)
2090{
2091 bool proceed = true;
2092 BPoint where;
2093 ulong buttons;
2094 GetMouse(&where, &buttons, false);
2095 bool stickyMode = IsStickyMode();
2096 // Quit if user clicks the mouse button in sticky mode
2097 // or releases the mouse button in nonsticky mode
2098 // or moves the pointer over another item
2099 // TODO: I added the check for BMenuBar to solve a problem with Deskbar.
2100 // Beos seems to do something similar. This could also be a bug in Deskbar, though.
2101 if ((buttons != 0 && stickyMode)
2102 || (dynamic_cast<BMenuBar *>(this) == NULL && (buttons == 0 && !stickyMode)
2103 || HitTestItems(where) != item))
2104 proceed = false;
2105
2106
2107 return proceed;
2108}
2109
2110
2111void
2112BMenu::QuitTracking()
2113{
2114 SelectItem(NULL);
2115 if (BMenuBar *menuBar = dynamic_cast<BMenuBar *>(this))
2116 menuBar->RestoreFocus();
2117
2118 fChosenItem = NULL;
2119 fState = MENU_STATE_CLOSED;
2120}
2121
2122
2123status_t
2124BMenu::ParseMsg(BMessage *msg, int32 *sindex, BMessage *spec,
2125 int32 *form, const char **prop, BMenu **tmenu,
2126 BMenuItem **titem, int32 *user_data,
2127 BMessage *reply) const
2128{
2129 return B_ERROR;
2130}
2131
2132
2133status_t
2134BMenu::DoMenuMsg(BMenuItem **next, BMenu *menu, BMessage *message,
2135 BMessage *r, BMessage *spec, int32 f) const
2136{
2137 return B_ERROR;
2138}
2139
2140
2141status_t
2142BMenu::DoMenuItemMsg(BMenuItem **next, BMenu *menu, BMessage *message,
2143 BMessage *r, BMessage *spec, int32 f) const
2144{
2145 return B_ERROR;
2146}
2147
2148
2149status_t
2150BMenu::DoEnabledMsg(BMenuItem *ti, BMenu *tm, BMessage *m,
2151 BMessage *r) const
2152{
2153 return B_ERROR;
2154}
2155
2156
2157status_t
2158BMenu::DoLabelMsg(BMenuItem *ti, BMenu *tm, BMessage *m,
2159 BMessage *r) const
2160{
2161 return B_ERROR;
2162}
2163
2164
2165status_t
2166BMenu::DoMarkMsg(BMenuItem *ti, BMenu *tm, BMessage *m,
2167 BMessage *r) const
2168{
2169 return B_ERROR;
2170}
2171
2172
2173status_t
2174BMenu::DoDeleteMsg(BMenuItem *ti, BMenu *tm, BMessage *m,
2175 BMessage *r) const
2176{
2177 return B_ERROR;
2178}
2179
2180
2181status_t
2182BMenu::DoCreateMsg(BMenuItem *ti, BMenu *tm, BMessage *m,
2183 BMessage *r, bool menu) const
2184{
2185 return B_ERROR;
2186}
2187
2188
2189// TODO: Maybe the following two methods would fit better into InterfaceDefs.cpp
2190// In R5, they do all the work client side, we let the app_server handle the details.
2191status_t
2192set_menu_info(menu_info *info)
2193{
2194 if (!info)
2195 return B_BAD_VALUE;
2196
2197 BPrivate::AppServerLink link;
2198 link.StartMessage(AS_SET_MENU_INFO);
2199 link.Attach<menu_info>(*info);
2200
2201 status_t status = B_ERROR;
2202 if (link.FlushWithReply(status) == B_OK && status == B_OK)
2203 BMenu::sMenuInfo = *info;
2204 // Update also the local copy, in case anyone relies on it
2205
2206 return status;
2207}
2208
2209
2210status_t
2211get_menu_info(menu_info *info)
2212{
2213 if (!info)
2214 return B_BAD_VALUE;
2215
2216 BPrivate::AppServerLink link;
2217 link.StartMessage(AS_GET_MENU_INFO);
2218
2219 status_t status = B_ERROR;
2220 if (link.FlushWithReply(status) == B_OK && status == B_OK)
2221 link.Read<menu_info>(info);
2222
2223 return status;
2224}