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 11 #include <MenuBar.h> 12 13 #include <Application.h> 14 #include <Autolock.h> 15 #include <LayoutUtils.h> 16 #include <MenuItem.h> 17 #include <Window.h> 18 19 #include <AppMisc.h> 20 #include <MenuPrivate.h> 21 #include <TokenSpace.h> 22 23 using BPrivate::gDefaultTokens; 24 25 26 struct menubar_data { 27 BMenuBar *menuBar; 28 int32 menuIndex; 29 30 bool sticky; 31 bool showMenu; 32 33 bool useRect; 34 BRect rect; 35 }; 36 37 38 BMenuBar::BMenuBar(BRect frame, const char *title, uint32 resizeMask, 39 menu_layout layout, bool resizeToFit) 40 : BMenu(frame, title, resizeMask, B_WILL_DRAW | B_FRAME_EVENTS, layout, 41 resizeToFit), 42 fBorder(B_BORDER_FRAME), 43 fTrackingPID(-1), 44 fPrevFocusToken(-1), 45 fMenuSem(-1), 46 fLastBounds(NULL), 47 fTracking(false) 48 { 49 InitData(layout); 50 } 51 52 53 BMenuBar::BMenuBar(BMessage *data) 54 : BMenu(data), 55 fBorder(B_BORDER_FRAME), 56 fTrackingPID(-1), 57 fPrevFocusToken(-1), 58 fMenuSem(-1), 59 fLastBounds(NULL), 60 fTracking(false) 61 { 62 int32 border; 63 64 if (data->FindInt32("_border", &border) == B_OK) 65 SetBorder((menu_bar_border)border); 66 67 menu_layout layout = B_ITEMS_IN_COLUMN; 68 data->FindInt32("_layout", (int32 *)&layout); 69 70 InitData(layout); 71 } 72 73 74 BMenuBar::~BMenuBar() 75 { 76 if (fTracking) { 77 status_t dummy; 78 wait_for_thread(fTrackingPID, &dummy); 79 } 80 81 delete fLastBounds; 82 } 83 84 85 BArchivable * 86 BMenuBar::Instantiate(BMessage *data) 87 { 88 if (validate_instantiation(data, "BMenuBar")) 89 return new BMenuBar(data); 90 91 return NULL; 92 } 93 94 95 status_t 96 BMenuBar::Archive(BMessage *data, bool deep) const 97 { 98 status_t err = BMenu::Archive(data, deep); 99 100 if (err < B_OK) 101 return err; 102 103 if (Border() != B_BORDER_FRAME) 104 err = data->AddInt32("_border", Border()); 105 106 return err; 107 } 108 109 110 void 111 BMenuBar::SetBorder(menu_bar_border border) 112 { 113 fBorder = border; 114 } 115 116 117 menu_bar_border 118 BMenuBar::Border() const 119 { 120 return fBorder; 121 } 122 123 124 void 125 BMenuBar::Draw(BRect updateRect) 126 { 127 if (RelayoutIfNeeded()) { 128 Invalidate(); 129 return; 130 } 131 132 // TODO: implement additional border styles 133 rgb_color color = HighColor(); 134 135 BRect bounds(Bounds()); 136 // Restore the background of the previously selected menuitem 137 DrawBackground(bounds & updateRect); 138 139 SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_LIGHTEN_2_TINT)); 140 StrokeLine(BPoint(0.0f, bounds.bottom - 2.0f), BPoint(0.0f, 0.0f)); 141 StrokeLine(BPoint(bounds.right, 0.0f)); 142 143 SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_1_TINT)); 144 StrokeLine(BPoint(1.0f, bounds.bottom - 1.0f), 145 BPoint(bounds.right, bounds.bottom - 1.0f)); 146 147 SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_2_TINT)); 148 StrokeLine(BPoint(0.0f, bounds.bottom), BPoint(bounds.right, bounds.bottom)); 149 StrokeLine(BPoint(bounds.right, 0.0f), BPoint(bounds.right, bounds.bottom)); 150 151 SetHighColor(color); 152 // revert to previous used color (cheap PushState()/PopState()) 153 154 DrawItems(updateRect); 155 } 156 157 158 void 159 BMenuBar::AttachedToWindow() 160 { 161 Install(Window()); 162 Window()->SetKeyMenuBar(this); 163 164 BMenu::AttachedToWindow(); 165 166 *fLastBounds = Bounds(); 167 } 168 169 170 void 171 BMenuBar::DetachedFromWindow() 172 { 173 BMenu::DetachedFromWindow(); 174 } 175 176 177 void 178 BMenuBar::MessageReceived(BMessage *msg) 179 { 180 BMenu::MessageReceived(msg); 181 } 182 183 184 void 185 BMenuBar::MouseDown(BPoint where) 186 { 187 if (fTracking) 188 return; 189 190 BWindow *window = Window(); 191 if (!window->IsActive() || !window->IsFront()) { 192 window->Activate(); 193 window->UpdateIfNeeded(); 194 } 195 196 StartMenuBar(-1, false, false); 197 } 198 199 200 void 201 BMenuBar::WindowActivated(bool state) 202 { 203 BView::WindowActivated(state); 204 } 205 206 207 void 208 BMenuBar::MouseUp(BPoint where) 209 { 210 BView::MouseUp(where); 211 } 212 213 214 void 215 BMenuBar::FrameMoved(BPoint newPosition) 216 { 217 BMenu::FrameMoved(newPosition); 218 } 219 220 221 void 222 BMenuBar::FrameResized(float newWidth, float newHeight) 223 { 224 BRect bounds(Bounds()); 225 BRect rect(fLastBounds->right - 12, fLastBounds->top, bounds.right, bounds.bottom); 226 fLastBounds->Set(0, 0, newWidth, newHeight); 227 228 Invalidate(rect); 229 230 BMenu::FrameResized(newWidth, newHeight); 231 } 232 233 234 void 235 BMenuBar::Show() 236 { 237 BView::Show(); 238 } 239 240 241 void 242 BMenuBar::Hide() 243 { 244 BView::Hide(); 245 } 246 247 248 BHandler * 249 BMenuBar::ResolveSpecifier(BMessage *msg, int32 index, BMessage *specifier, int32 form, const char *property) 250 { 251 return BMenu::ResolveSpecifier(msg, index, specifier, form, property); 252 } 253 254 255 status_t 256 BMenuBar::GetSupportedSuites(BMessage *data) 257 { 258 return BMenu::GetSupportedSuites(data); 259 } 260 261 262 void 263 BMenuBar::ResizeToPreferred() 264 { 265 BMenu::ResizeToPreferred(); 266 } 267 268 269 void 270 BMenuBar::GetPreferredSize(float *width, float *height) 271 { 272 BMenu::GetPreferredSize(width, height); 273 } 274 275 276 void 277 BMenuBar::MakeFocus(bool state) 278 { 279 BMenu::MakeFocus(state); 280 } 281 282 283 void 284 BMenuBar::AllAttached() 285 { 286 BMenu::AllAttached(); 287 } 288 289 290 void 291 BMenuBar::AllDetached() 292 { 293 BMenu::AllDetached(); 294 } 295 296 297 status_t 298 BMenuBar::Perform(perform_code d, void *arg) 299 { 300 return BMenu::Perform(d, arg); 301 } 302 303 304 BSize 305 BMenuBar::MaxSize() 306 { 307 // TODO: cache the result 308 float width, height; 309 GetPreferredSize(&width, &height); 310 311 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), 312 BSize(B_SIZE_UNLIMITED, height)); 313 } 314 315 316 // #pragma mark - 317 318 319 void BMenuBar::_ReservedMenuBar1() {} 320 void BMenuBar::_ReservedMenuBar2() {} 321 void BMenuBar::_ReservedMenuBar3() {} 322 void BMenuBar::_ReservedMenuBar4() {} 323 324 325 BMenuBar & 326 BMenuBar::operator=(const BMenuBar &) 327 { 328 return *this; 329 } 330 331 332 void 333 BMenuBar::StartMenuBar(int32 menuIndex, bool sticky, bool showMenu, BRect *specialRect) 334 { 335 if (fTracking) 336 return; 337 338 BWindow *window = Window(); 339 if (window == NULL) 340 debugger("MenuBar must be added to a window before it can be used."); 341 342 BAutolock lock(window); 343 if (!lock.IsLocked()) 344 return; 345 346 fPrevFocusToken = -1; 347 fTracking = true; 348 349 window->MenusBeginning(); 350 351 fMenuSem = create_sem(0, "window close sem"); 352 _set_menu_sem_(window, fMenuSem); 353 354 fTrackingPID = spawn_thread(TrackTask, "menu_tracking", B_DISPLAY_PRIORITY, NULL); 355 if (fTrackingPID >= 0) { 356 menubar_data data; 357 data.menuBar = this; 358 data.menuIndex = menuIndex; 359 data.sticky = sticky; 360 data.showMenu = showMenu; 361 data.useRect = specialRect != NULL; 362 if (data.useRect) 363 data.rect = *specialRect; 364 365 resume_thread(fTrackingPID); 366 send_data(fTrackingPID, 0, &data, sizeof(data)); 367 368 } else { 369 fTracking = false; 370 _set_menu_sem_(window, B_NO_MORE_SEMS); 371 delete_sem(fMenuSem); 372 } 373 } 374 375 376 long 377 BMenuBar::TrackTask(void *arg) 378 { 379 menubar_data data; 380 thread_id id; 381 382 receive_data(&id, &data, sizeof(data)); 383 384 BMenuBar *menuBar = data.menuBar; 385 if (data.useRect) 386 menuBar->fExtraRect = &data.rect; 387 menuBar->SetStickyMode(data.sticky); 388 389 int32 action; 390 menuBar->Track(&action, data.menuIndex, data.showMenu); 391 392 menuBar->fTracking = false; 393 menuBar->fExtraRect = NULL; 394 395 // Sends a _MENUS_DONE_ message to the BWindow. 396 // Weird: There is a _MENUS_DONE_ message but not a 397 // _MENUS_BEGINNING_ message, in fact the MenusBeginning() 398 // hook function is called directly. 399 BWindow *window = menuBar->Window(); 400 window->PostMessage(_MENUS_DONE_); 401 402 _set_menu_sem_(window, B_BAD_SEM_ID); 403 delete_sem(menuBar->fMenuSem); 404 menuBar->fMenuSem = B_BAD_SEM_ID; 405 406 return 0; 407 } 408 409 410 BMenuItem * 411 BMenuBar::Track(int32 *action, int32 startIndex, bool showMenu) 412 { 413 // TODO: Cleanup, merge some "if" blocks if possible 414 fChosenItem = NULL; 415 416 BWindow *window = Window(); 417 fState = MENU_STATE_TRACKING; 418 419 if (startIndex != -1) { 420 be_app->ObscureCursor(); 421 if (window->Lock()) { 422 _SelectItem(ItemAt(startIndex), true, true); 423 window->Unlock(); 424 } 425 } 426 427 while (true) { 428 bigtime_t snoozeAmount = 40000; 429 bool locked = window->Lock();//WithTimeout(200000) 430 if (!locked) 431 break; 432 433 BPoint where; 434 ulong buttons; 435 GetMouse(&where, &buttons, true); 436 437 BMenuItem *menuItem = HitTestItems(where, B_ORIGIN); 438 if (menuItem != NULL) { 439 // Select item if: 440 // - no previous selection 441 // - nonsticky mode and different selection, 442 // - clicked in sticky mode 443 if (fSelected == NULL 444 || (!IsStickyMode() && menuItem != fSelected) 445 || (buttons != 0 && IsStickyMode())) { 446 if (menuItem->Submenu() != NULL) { 447 if (menuItem->Submenu()->Window() == NULL) { 448 // open the menu if it's not opened yet 449 _SelectItem(menuItem); 450 if (IsStickyMode()) 451 SetStickyMode(false); 452 } else { 453 // Menu was already opened, close it and bail 454 _SelectItem(NULL); 455 fState = MENU_STATE_CLOSED; 456 fChosenItem = NULL; 457 } 458 } else { 459 // No submenu, just select the item 460 _SelectItem(menuItem); 461 } 462 } 463 } 464 465 if (OverSubmenu(fSelected, ConvertToScreen(where))) { 466 // call _track() from the selected sub-menu when the mouse cursor 467 // is over its window 468 BMenu *menu = fSelected->Submenu(); 469 window->Unlock(); 470 locked = false; 471 snoozeAmount = 30000; 472 bool wasSticky = IsStickyMode(); 473 if (wasSticky) 474 menu->SetStickyMode(true); 475 int localAction; 476 fChosenItem = menu->_track(&localAction); 477 if (menu->State(NULL) == MENU_STATE_TRACKING && menu->IsStickyMode()) 478 menu->SetStickyMode(false); 479 480 // check if the user started holding down a mouse button in a submenu 481 if (wasSticky && !IsStickyMode()) 482 buttons = 1; 483 // buttons must have been pressed in the meantime 484 485 if (localAction == MENU_STATE_CLOSED) { 486 if (fExtraRect != NULL && fExtraRect->Contains(where)) { 487 SetStickyMode(true); 488 fExtraRect = NULL; 489 } else 490 fState = MENU_STATE_CLOSED; 491 } 492 493 } else if (menuItem == NULL && fSelected != NULL 494 && !IsStickyMode() /*&& Bounds().Contains(where)*/ && fState != MENU_STATE_TRACKING_SUBMENU) { 495 _SelectItem(NULL); 496 fState = MENU_STATE_TRACKING; 497 } 498 499 if (locked) 500 window->Unlock(); 501 502 if (fState == MENU_STATE_CLOSED 503 || (buttons != 0 && IsStickyMode() && menuItem == NULL)) 504 break; 505 else if (buttons == 0 && !IsStickyMode()) { 506 if ((fSelected != NULL && fSelected->Submenu() == NULL) || menuItem == NULL) { 507 fChosenItem = fSelected; 508 break; 509 } else 510 SetStickyMode(true); 511 } 512 513 if (snoozeAmount > 0) 514 snooze(snoozeAmount); 515 } 516 517 if (window->Lock()) { 518 if (fSelected != NULL) 519 _SelectItem(NULL); 520 521 if (fChosenItem != NULL) 522 fChosenItem->Invoke(); 523 RestoreFocus(); 524 window->Unlock(); 525 } 526 527 if (IsStickyMode()) 528 SetStickyMode(false); 529 530 DeleteMenuWindow(); 531 532 if (action != NULL) 533 *action = fState; 534 535 return fChosenItem; 536 } 537 538 539 void 540 BMenuBar::StealFocus() 541 { 542 // We already stole the focus, don't do anything 543 if (fPrevFocusToken != -1) 544 return; 545 546 BWindow *window = Window(); 547 if (window != NULL && window->Lock()) { 548 BView *focus = window->CurrentFocus(); 549 if (focus != NULL && focus != this) 550 fPrevFocusToken = _get_object_token_(focus); 551 MakeFocus(); 552 window->Unlock(); 553 } 554 } 555 556 557 void 558 BMenuBar::RestoreFocus() 559 { 560 BWindow *window = Window(); 561 if (window != NULL && window->Lock()) { 562 BHandler *handler = NULL; 563 if (fPrevFocusToken != -1 564 && gDefaultTokens.GetToken(fPrevFocusToken, B_HANDLER_TOKEN, (void **)&handler) == B_OK) { 565 BView *view = dynamic_cast<BView *>(handler); 566 if (view != NULL && view->Window() == window) 567 view->MakeFocus(); 568 569 } else if (IsFocus()) 570 MakeFocus(false); 571 572 fPrevFocusToken = -1; 573 window->Unlock(); 574 } 575 } 576 577 578 void 579 BMenuBar::InitData(menu_layout layout) 580 { 581 fLastBounds = new BRect(Bounds()); 582 SetItemMargins(8, 2, 8, 2); 583 SetIgnoreHidden(true); 584 } 585