1 /*
2 * Copyright 2001-2015, Haiku Inc. All rights reserved.
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 * Stephan Aßmus <superstippi@gmx.de>
9 */
10
11
12 #include <MenuBar.h>
13
14 #include <math.h>
15
16 #include <Application.h>
17 #include <Autolock.h>
18 #include <ControlLook.h>
19 #include <LayoutUtils.h>
20 #include <MenuItem.h>
21 #include <Window.h>
22
23 #include <AppMisc.h>
24 #include <binary_compatibility/Interface.h>
25 #include <MenuPrivate.h>
26 #include <TokenSpace.h>
27 #include <InterfaceDefs.h>
28
29 #include "BMCPrivate.h"
30
31
32 using BPrivate::gDefaultTokens;
33
34
35 struct menubar_data {
36 BMenuBar* menuBar;
37 int32 menuIndex;
38
39 bool sticky;
40 bool showMenu;
41
42 bool useRect;
43 BRect rect;
44 };
45
46
BMenuBar(BRect frame,const char * name,uint32 resizingMode,menu_layout layout,bool resizeToFit)47 BMenuBar::BMenuBar(BRect frame, const char* name, uint32 resizingMode,
48 menu_layout layout, bool resizeToFit)
49 :
50 BMenu(frame, name, resizingMode, B_WILL_DRAW | B_FRAME_EVENTS
51 | B_FULL_UPDATE_ON_RESIZE, layout, resizeToFit),
52 fBorder(B_BORDER_FRAME),
53 fTrackingPID(-1),
54 fPrevFocusToken(-1),
55 fMenuSem(-1),
56 fLastBounds(NULL),
57 fTracking(false)
58 {
59 _InitData(layout);
60 }
61
62
BMenuBar(const char * name,menu_layout layout,uint32 flags)63 BMenuBar::BMenuBar(const char* name, menu_layout layout, uint32 flags)
64 :
65 BMenu(BRect(), name, B_FOLLOW_NONE,
66 flags | B_WILL_DRAW | B_FRAME_EVENTS | B_SUPPORTS_LAYOUT,
67 layout, false),
68 fBorder(B_BORDER_FRAME),
69 fTrackingPID(-1),
70 fPrevFocusToken(-1),
71 fMenuSem(-1),
72 fLastBounds(NULL),
73 fTracking(false)
74 {
75 _InitData(layout);
76 }
77
78
BMenuBar(BMessage * archive)79 BMenuBar::BMenuBar(BMessage* archive)
80 :
81 BMenu(archive),
82 fBorder(B_BORDER_FRAME),
83 fTrackingPID(-1),
84 fPrevFocusToken(-1),
85 fMenuSem(-1),
86 fLastBounds(NULL),
87 fTracking(false)
88 {
89 int32 border;
90
91 if (archive->FindInt32("_border", &border) == B_OK)
92 SetBorder((menu_bar_border)border);
93
94 menu_layout layout = B_ITEMS_IN_COLUMN;
95 archive->FindInt32("_layout", (int32*)&layout);
96
97 _InitData(layout);
98 }
99
100
~BMenuBar()101 BMenuBar::~BMenuBar()
102 {
103 if (fTracking) {
104 status_t dummy;
105 wait_for_thread(fTrackingPID, &dummy);
106 }
107
108 delete fLastBounds;
109 }
110
111
112 BArchivable*
Instantiate(BMessage * data)113 BMenuBar::Instantiate(BMessage* data)
114 {
115 if (validate_instantiation(data, "BMenuBar"))
116 return new BMenuBar(data);
117
118 return NULL;
119 }
120
121
122 status_t
Archive(BMessage * data,bool deep) const123 BMenuBar::Archive(BMessage* data, bool deep) const
124 {
125 status_t err = BMenu::Archive(data, deep);
126
127 if (err < B_OK)
128 return err;
129
130 if (Border() != B_BORDER_FRAME)
131 err = data->AddInt32("_border", Border());
132
133 return err;
134 }
135
136
137 // #pragma mark -
138
139
140 void
AttachedToWindow()141 BMenuBar::AttachedToWindow()
142 {
143 _Install(Window());
144 Window()->SetKeyMenuBar(this);
145
146 BMenu::AttachedToWindow();
147
148 *fLastBounds = Bounds();
149 }
150
151
152 void
DetachedFromWindow()153 BMenuBar::DetachedFromWindow()
154 {
155 BMenu::DetachedFromWindow();
156 }
157
158
159 void
AllAttached()160 BMenuBar::AllAttached()
161 {
162 BMenu::AllAttached();
163 }
164
165
166 void
AllDetached()167 BMenuBar::AllDetached()
168 {
169 BMenu::AllDetached();
170 }
171
172
173 void
WindowActivated(bool state)174 BMenuBar::WindowActivated(bool state)
175 {
176 BView::WindowActivated(state);
177 }
178
179
180 void
MakeFocus(bool state)181 BMenuBar::MakeFocus(bool state)
182 {
183 BMenu::MakeFocus(state);
184 }
185
186
187 // #pragma mark -
188
189
190 void
ResizeToPreferred()191 BMenuBar::ResizeToPreferred()
192 {
193 BMenu::ResizeToPreferred();
194 }
195
196
197 void
GetPreferredSize(float * width,float * height)198 BMenuBar::GetPreferredSize(float* width, float* height)
199 {
200 BMenu::GetPreferredSize(width, height);
201 }
202
203
204 BSize
MinSize()205 BMenuBar::MinSize()
206 {
207 return BMenu::MinSize();
208 }
209
210
211 BSize
MaxSize()212 BMenuBar::MaxSize()
213 {
214 BSize size = BMenu::MaxSize();
215 return BLayoutUtils::ComposeSize(ExplicitMaxSize(),
216 BSize(B_SIZE_UNLIMITED, size.height));
217 }
218
219
220 BSize
PreferredSize()221 BMenuBar::PreferredSize()
222 {
223 return BMenu::PreferredSize();
224 }
225
226
227 void
FrameMoved(BPoint newPosition)228 BMenuBar::FrameMoved(BPoint newPosition)
229 {
230 BMenu::FrameMoved(newPosition);
231 }
232
233
234 void
FrameResized(float newWidth,float newHeight)235 BMenuBar::FrameResized(float newWidth, float newHeight)
236 {
237 // invalidate right border
238 if (newWidth != fLastBounds->Width()) {
239 BRect rect(min_c(fLastBounds->right, newWidth), 0,
240 max_c(fLastBounds->right, newWidth), newHeight);
241 Invalidate(rect);
242 }
243
244 // invalidate bottom border
245 if (newHeight != fLastBounds->Height()) {
246 BRect rect(0, min_c(fLastBounds->bottom, newHeight) - 1,
247 newWidth, max_c(fLastBounds->bottom, newHeight));
248 Invalidate(rect);
249 }
250
251 fLastBounds->Set(0, 0, newWidth, newHeight);
252
253 BMenu::FrameResized(newWidth, newHeight);
254 }
255
256
257 // #pragma mark -
258
259
260 void
Show()261 BMenuBar::Show()
262 {
263 BView::Show();
264 }
265
266
267 void
Hide()268 BMenuBar::Hide()
269 {
270 BView::Hide();
271 }
272
273
274 void
Draw(BRect updateRect)275 BMenuBar::Draw(BRect updateRect)
276 {
277 if (_RelayoutIfNeeded()) {
278 Invalidate();
279 return;
280 }
281
282 BRect rect(Bounds());
283 rgb_color base = LowColor();
284 uint32 flags = 0;
285
286 be_control_look->DrawBorder(this, rect, updateRect, base,
287 B_PLAIN_BORDER, flags, BControlLook::B_BOTTOM_BORDER);
288
289 be_control_look->DrawMenuBarBackground(this, rect, updateRect, base,
290 0, fBorders);
291
292 DrawItems(updateRect);
293 }
294
295
296 // #pragma mark -
297
298
299 void
MessageReceived(BMessage * message)300 BMenuBar::MessageReceived(BMessage* message)
301 {
302 BMenu::MessageReceived(message);
303 }
304
305
306 void
MouseDown(BPoint where)307 BMenuBar::MouseDown(BPoint where)
308 {
309 if (fTracking)
310 return;
311
312 uint32 buttons;
313 GetMouse(&where, &buttons);
314
315 BWindow* window = Window();
316 if (!window->IsActive() || !window->IsFront()) {
317 if ((mouse_mode() == B_FOCUS_FOLLOWS_MOUSE)
318 || ((mouse_mode() == B_CLICK_TO_FOCUS_MOUSE)
319 && ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0))) {
320 // right-click to bring-to-front and send-to-back
321 // (might cause some regressions in FFM)
322 window->Activate();
323 window->UpdateIfNeeded();
324 }
325 }
326
327 StartMenuBar(-1, false, false);
328 }
329
330
331 void
MouseUp(BPoint where)332 BMenuBar::MouseUp(BPoint where)
333 {
334 BView::MouseUp(where);
335 }
336
337
338 // #pragma mark -
339
340
341 BHandler*
ResolveSpecifier(BMessage * msg,int32 index,BMessage * specifier,int32 form,const char * property)342 BMenuBar::ResolveSpecifier(BMessage* msg, int32 index, BMessage* specifier,
343 int32 form, const char* property)
344 {
345 return BMenu::ResolveSpecifier(msg, index, specifier, form, property);
346 }
347
348
349 status_t
GetSupportedSuites(BMessage * data)350 BMenuBar::GetSupportedSuites(BMessage* data)
351 {
352 return BMenu::GetSupportedSuites(data);
353 }
354
355
356 // #pragma mark -
357
358
359 void
SetBorder(menu_bar_border border)360 BMenuBar::SetBorder(menu_bar_border border)
361 {
362 fBorder = border;
363 }
364
365
366 menu_bar_border
Border() const367 BMenuBar::Border() const
368 {
369 return fBorder;
370 }
371
372
373 void
SetBorders(uint32 borders)374 BMenuBar::SetBorders(uint32 borders)
375 {
376 fBorders = borders;
377 }
378
379
380 uint32
Borders() const381 BMenuBar::Borders() const
382 {
383 return fBorders;
384 }
385
386
387 // #pragma mark -
388
389
390 status_t
Perform(perform_code code,void * _data)391 BMenuBar::Perform(perform_code code, void* _data)
392 {
393 switch (code) {
394 case PERFORM_CODE_MIN_SIZE:
395 ((perform_data_min_size*)_data)->return_value
396 = BMenuBar::MinSize();
397 return B_OK;
398
399 case PERFORM_CODE_MAX_SIZE:
400 ((perform_data_max_size*)_data)->return_value
401 = BMenuBar::MaxSize();
402 return B_OK;
403
404 case PERFORM_CODE_PREFERRED_SIZE:
405 ((perform_data_preferred_size*)_data)->return_value
406 = BMenuBar::PreferredSize();
407 return B_OK;
408
409 case PERFORM_CODE_LAYOUT_ALIGNMENT:
410 ((perform_data_layout_alignment*)_data)->return_value
411 = BMenuBar::LayoutAlignment();
412 return B_OK;
413
414 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
415 ((perform_data_has_height_for_width*)_data)->return_value
416 = BMenuBar::HasHeightForWidth();
417 return B_OK;
418
419 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
420 {
421 perform_data_get_height_for_width* data
422 = (perform_data_get_height_for_width*)_data;
423 BMenuBar::GetHeightForWidth(data->width, &data->min, &data->max,
424 &data->preferred);
425 return B_OK;
426 }
427
428 case PERFORM_CODE_SET_LAYOUT:
429 {
430 perform_data_set_layout* data = (perform_data_set_layout*)_data;
431 BMenuBar::SetLayout(data->layout);
432 return B_OK;
433 }
434
435 case PERFORM_CODE_LAYOUT_INVALIDATED:
436 {
437 perform_data_layout_invalidated* data
438 = (perform_data_layout_invalidated*)_data;
439 BMenuBar::LayoutInvalidated(data->descendants);
440 return B_OK;
441 }
442
443 case PERFORM_CODE_DO_LAYOUT:
444 {
445 BMenuBar::DoLayout();
446 return B_OK;
447 }
448 }
449
450 return BMenu::Perform(code, _data);
451 }
452
453
454 // #pragma mark -
455
456
_ReservedMenuBar1()457 void BMenuBar::_ReservedMenuBar1() {}
_ReservedMenuBar2()458 void BMenuBar::_ReservedMenuBar2() {}
_ReservedMenuBar3()459 void BMenuBar::_ReservedMenuBar3() {}
_ReservedMenuBar4()460 void BMenuBar::_ReservedMenuBar4() {}
461
462
463 BMenuBar&
operator =(const BMenuBar &)464 BMenuBar::operator=(const BMenuBar &)
465 {
466 return *this;
467 }
468
469
470 // #pragma mark -
471
472
473 void
StartMenuBar(int32 menuIndex,bool sticky,bool showMenu,BRect * specialRect)474 BMenuBar::StartMenuBar(int32 menuIndex, bool sticky, bool showMenu,
475 BRect* specialRect)
476 {
477 if (fTracking)
478 return;
479
480 BWindow* window = Window();
481 if (window == NULL)
482 debugger("MenuBar must be added to a window before it can be used.");
483
484 BAutolock lock(window);
485 if (!lock.IsLocked())
486 return;
487
488 fPrevFocusToken = -1;
489 fTracking = true;
490
491 // We are called from the window's thread,
492 // so let's call MenusBeginning() directly
493 window->MenusBeginning();
494
495 fMenuSem = create_sem(0, "window close sem");
496 _set_menu_sem_(window, fMenuSem);
497
498 fTrackingPID = spawn_thread(_TrackTask, "menu_tracking",
499 B_DISPLAY_PRIORITY, NULL);
500 if (fTrackingPID >= 0) {
501 menubar_data data;
502 data.menuBar = this;
503 data.menuIndex = menuIndex;
504 data.sticky = sticky;
505 data.showMenu = showMenu;
506 data.useRect = specialRect != NULL;
507 if (data.useRect)
508 data.rect = *specialRect;
509
510 resume_thread(fTrackingPID);
511 send_data(fTrackingPID, 0, &data, sizeof(data));
512 } else {
513 fTracking = false;
514 _set_menu_sem_(window, B_NO_MORE_SEMS);
515 delete_sem(fMenuSem);
516 }
517 }
518
519
520 /*static*/ int32
_TrackTask(void * arg)521 BMenuBar::_TrackTask(void* arg)
522 {
523 menubar_data data;
524 thread_id id;
525 receive_data(&id, &data, sizeof(data));
526
527 BMenuBar* menuBar = data.menuBar;
528 if (data.useRect)
529 menuBar->fExtraRect = &data.rect;
530 menuBar->_SetStickyMode(data.sticky);
531
532 int32 action;
533 menuBar->_Track(&action, data.menuIndex, data.showMenu);
534
535 menuBar->fTracking = false;
536 menuBar->fExtraRect = NULL;
537
538 // We aren't the BWindow thread, so don't call MenusEnded() directly
539 BWindow* window = menuBar->Window();
540 window->PostMessage(_MENUS_DONE_);
541
542 _set_menu_sem_(window, B_BAD_SEM_ID);
543 delete_sem(menuBar->fMenuSem);
544 menuBar->fMenuSem = B_BAD_SEM_ID;
545
546 return 0;
547 }
548
549
550 BMenuItem*
_Track(int32 * action,int32 startIndex,bool showMenu)551 BMenuBar::_Track(int32* action, int32 startIndex, bool showMenu)
552 {
553 // TODO: Cleanup, merge some "if" blocks if possible
554 BMenuItem* item = NULL;
555 fState = MENU_STATE_TRACKING;
556 fChosenItem = NULL;
557 // we will use this for keyboard selection
558
559 BPoint where;
560 uint32 buttons;
561 if (LockLooper()) {
562 if (startIndex != -1) {
563 be_app->ObscureCursor();
564 _SelectItem(ItemAt(startIndex), true, false);
565 }
566 GetMouse(&where, &buttons);
567 UnlockLooper();
568 }
569
570 while (fState != MENU_STATE_CLOSED) {
571 bigtime_t snoozeAmount = 40000;
572 if (!LockLooper())
573 break;
574
575 item = dynamic_cast<_BMCMenuBar_*>(this) != NULL ? ItemAt(0)
576 : _HitTestItems(where, B_ORIGIN);
577
578 if (_OverSubmenu(fSelected, ConvertToScreen(where))
579 || fState == MENU_STATE_KEY_TO_SUBMENU) {
580 // call _Track() from the selected sub-menu when the mouse cursor
581 // is over its window
582 BMenu* submenu = fSelected->Submenu();
583 UnlockLooper();
584 snoozeAmount = 30000;
585 submenu->_SetStickyMode(_IsStickyMode());
586 int localAction;
587 fChosenItem = submenu->_Track(&localAction);
588
589 // The mouse could have meen moved since the last time we
590 // checked its position, or buttons might have been pressed.
591 // Unfortunately our child menus don't tell
592 // us the new position.
593 // TODO: Maybe have a shared struct between all menus
594 // where to store the current mouse position ?
595 // (Or just use the BView mouse hooks)
596 BPoint newWhere;
597 if (LockLooper()) {
598 GetMouse(&newWhere, &buttons);
599 UnlockLooper();
600 }
601
602 // Needed to make BMenuField child menus "sticky"
603 // (see ticket #953)
604 if (localAction == MENU_STATE_CLOSED) {
605 if (fExtraRect != NULL && fExtraRect->Contains(where)
606 && point_distance(newWhere, where) < 9) {
607 // 9 = 3 pixels ^ 2 (since point_distance() returns the
608 // square of the distance)
609 _SetStickyMode(true);
610 fExtraRect = NULL;
611 } else
612 fState = MENU_STATE_CLOSED;
613 }
614 if (!LockLooper())
615 break;
616 } else if (item != NULL) {
617 if (item->Submenu() != NULL && item != fSelected) {
618 if (item->Submenu()->Window() == NULL) {
619 // open the menu if it's not opened yet
620 _SelectItem(item);
621 } else {
622 // Menu was already opened, close it and bail
623 _SelectItem(NULL);
624 fState = MENU_STATE_CLOSED;
625 fChosenItem = NULL;
626 }
627 } else {
628 // No submenu, just select the item
629 _SelectItem(item);
630 }
631 } else if (item == NULL && fSelected != NULL
632 && !_IsStickyMode() && Bounds().Contains(where)) {
633 _SelectItem(NULL);
634 fState = MENU_STATE_TRACKING;
635 }
636
637 UnlockLooper();
638
639 if (fState != MENU_STATE_CLOSED) {
640 BPoint newWhere = where;
641 uint32 newButtons = buttons;
642
643 do {
644 // If user doesn't move the mouse or change buttons loop
645 // here so that we don't interfere with keyboard menu
646 // navigation
647 snooze(snoozeAmount);
648 if (!LockLooper())
649 break;
650
651 GetMouse(&newWhere, &newButtons);
652 UnlockLooper();
653 } while (newWhere == where && newButtons == buttons
654 && fState == MENU_STATE_TRACKING);
655
656 if (newButtons != 0 && _IsStickyMode()) {
657 if (item == NULL || (item->Submenu() != NULL
658 && item->Submenu()->Window() != NULL)) {
659 // clicked outside the menu bar or on item with already
660 // open sub menu
661 fState = MENU_STATE_CLOSED;
662 } else
663 _SetStickyMode(false);
664 } else if (newButtons == 0 && !_IsStickyMode()) {
665 if ((fSelected != NULL && fSelected->Submenu() == NULL)
666 || item == NULL) {
667 // clicked on an item without a submenu or clicked and
668 // released the mouse button outside the menu bar
669 fChosenItem = fSelected;
670 fState = MENU_STATE_CLOSED;
671 } else
672 _SetStickyMode(true);
673 }
674 where = newWhere;
675 buttons = newButtons;
676 }
677 }
678
679 if (LockLooper()) {
680 if (fSelected != NULL)
681 _SelectItem(NULL);
682
683 if (fChosenItem != NULL)
684 fChosenItem->Invoke();
685
686 _RestoreFocus();
687 UnlockLooper();
688 }
689
690 if (_IsStickyMode())
691 _SetStickyMode(false);
692
693 _DeleteMenuWindow();
694
695 if (action != NULL)
696 *action = fState;
697
698 return fChosenItem;
699 }
700
701
702 void
_StealFocus()703 BMenuBar::_StealFocus()
704 {
705 // We already stole the focus, don't do anything
706 if (fPrevFocusToken != -1)
707 return;
708
709 BWindow* window = Window();
710 if (window != NULL && window->Lock()) {
711 BView* focusView = window->CurrentFocus();
712 if (focusView != NULL && focusView != this)
713 fPrevFocusToken = _get_object_token_(focusView);
714 MakeFocus();
715 window->Unlock();
716 }
717 }
718
719
720 void
_RestoreFocus()721 BMenuBar::_RestoreFocus()
722 {
723 BWindow* window = Window();
724 if (window != NULL && window->Lock()) {
725 BHandler* handler = NULL;
726 if (fPrevFocusToken != -1
727 && gDefaultTokens.GetToken(fPrevFocusToken, B_HANDLER_TOKEN,
728 (void**)&handler) == B_OK) {
729 BView* view = dynamic_cast<BView*>(handler);
730 if (view != NULL && view->Window() == window)
731 view->MakeFocus();
732 } else if (IsFocus())
733 MakeFocus(false);
734
735 fPrevFocusToken = -1;
736 window->Unlock();
737 }
738 }
739
740
741 void
_InitData(menu_layout layout)742 BMenuBar::_InitData(menu_layout layout)
743 {
744 const float fontSize = be_plain_font->Size();
745 float lr = fontSize * 2.0f / 3.0f, tb = fontSize / 6.0f;
746 SetItemMargins(lr, tb, lr, tb);
747
748 fBorders = BControlLook::B_ALL_BORDERS;
749 fLastBounds = new BRect(Bounds());
750 _SetIgnoreHidden(true);
751 SetLowUIColor(B_MENU_BACKGROUND_COLOR);
752 SetViewColor(B_TRANSPARENT_COLOR);
753 }
754