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 - 2, 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 menuBar->SetStickyMode(data.sticky); 386 387 int32 action; 388 menuBar->Track(&action, data.menuIndex, data.showMenu); 389 390 menuBar->fTracking = false; 391 392 // Sends a _MENUS_DONE_ message to the BWindow. 393 // Weird: There is a _MENUS_DONE_ message but not a 394 // _MENUS_BEGINNING_ message, in fact the MenusBeginning() 395 // hook function is called directly. 396 BWindow *window = menuBar->Window(); 397 window->PostMessage(_MENUS_DONE_); 398 399 _set_menu_sem_(window, B_BAD_SEM_ID); 400 delete_sem(menuBar->fMenuSem); 401 menuBar->fMenuSem = B_BAD_SEM_ID; 402 403 return 0; 404 } 405 406 407 BMenuItem * 408 BMenuBar::Track(int32 *action, int32 startIndex, bool showMenu) 409 { 410 // TODO: Cleanup, merge some "if" blocks if possible 411 fChosenItem = NULL; 412 413 BWindow *window = Window(); 414 fState = MENU_STATE_TRACKING; 415 416 if (startIndex != -1) { 417 be_app->ObscureCursor(); 418 window->Lock(); 419 _SelectItem(ItemAt(startIndex), true, true); 420 window->Unlock(); 421 } 422 while (true) { 423 bigtime_t snoozeAmount = 40000; 424 bool locked = window->Lock();//WithTimeout(200000) 425 if (!locked) 426 break; 427 428 BPoint where; 429 ulong buttons; 430 GetMouse(&where, &buttons, true); 431 432 window->UpdateIfNeeded(); 433 BMenuItem *menuItem = HitTestItems(where, B_ORIGIN); 434 if (menuItem != NULL) { 435 // Select item if: 436 // - no previous selection 437 // - nonsticky mode and different selection, 438 // - clicked in sticky mode 439 if (fSelected == NULL 440 || (!IsStickyMode() && menuItem != fSelected) 441 || (buttons != 0 && IsStickyMode())) { 442 if (menuItem->Submenu() != NULL) { 443 if (menuItem->Submenu()->Window() == NULL) { 444 // open the menu if it's not opened yet 445 _SelectItem(menuItem); 446 if (IsStickyMode()) 447 SetStickyMode(false); 448 } else { 449 // Menu was already opened, close it and bail 450 _SelectItem(NULL); 451 fState = MENU_STATE_CLOSED; 452 fChosenItem = NULL; 453 } 454 } else { 455 // No submenu, just select the item 456 _SelectItem(menuItem); 457 } 458 } 459 } 460 461 if (fSelected != NULL && OverSubmenu(fSelected, ConvertToScreen(where))) { 462 // call _track() from the selected sub-menu when the mouse cursor 463 // is over its window 464 BMenu *menu = fSelected->Submenu(); 465 if (menu != NULL) { 466 window->Unlock(); 467 locked = false; 468 snoozeAmount = 30000; 469 if (IsStickyMode()) 470 menu->SetStickyMode(true); 471 int localAction; 472 fChosenItem = menu->_track(&localAction, system_time()); 473 //menu->Window()->Activate(); 474 if (localAction == MENU_STATE_CLOSED) 475 fState = MENU_STATE_CLOSED; 476 } 477 } else if (menuItem == NULL && !IsStickyMode() 478 && fState != MENU_STATE_TRACKING_SUBMENU) { 479 _SelectItem(NULL); 480 fState = MENU_STATE_TRACKING; 481 } 482 483 if (locked) 484 window->Unlock(); 485 486 if (fState == MENU_STATE_CLOSED 487 || (buttons != 0 && IsStickyMode() && menuItem == NULL)) 488 break; 489 else if (buttons == 0 && !IsStickyMode()) { 490 if ((fSelected != NULL && fSelected->Submenu() == NULL) || menuItem == NULL) { 491 fChosenItem = fSelected; 492 break; 493 } else 494 SetStickyMode(true); 495 } 496 497 if (snoozeAmount > 0) 498 snooze(snoozeAmount); 499 } 500 501 if (window->Lock()) { 502 if (fSelected != NULL) 503 _SelectItem(NULL); 504 if (fChosenItem != NULL) 505 fChosenItem->Invoke(); 506 RestoreFocus(); 507 window->Unlock(); 508 } 509 510 if (IsStickyMode()) 511 SetStickyMode(false); 512 513 DeleteMenuWindow(); 514 515 if (action != NULL) 516 *action = fState; 517 518 return fChosenItem; 519 } 520 521 522 void 523 BMenuBar::StealFocus() 524 { 525 // We already stole the focus, don't do anything 526 if (fPrevFocusToken != -1) 527 return; 528 529 BWindow *window = Window(); 530 if (window != NULL && window->Lock()) { 531 BView *focus = window->CurrentFocus(); 532 if (focus != NULL && focus != this) 533 fPrevFocusToken = _get_object_token_(focus); 534 MakeFocus(); 535 window->Unlock(); 536 } 537 } 538 539 540 void 541 BMenuBar::RestoreFocus() 542 { 543 BWindow *window = Window(); 544 if (window != NULL && window->Lock()) { 545 BHandler *handler = NULL; 546 if (fPrevFocusToken != -1 547 && gDefaultTokens.GetToken(fPrevFocusToken, B_HANDLER_TOKEN, (void **)&handler) == B_OK) { 548 BView *view = dynamic_cast<BView *>(handler); 549 if (view != NULL && view->Window() == window) 550 view->MakeFocus(); 551 552 } else if (IsFocus()) 553 MakeFocus(false); 554 555 fPrevFocusToken = -1; 556 window->Unlock(); 557 } 558 } 559 560 561 void 562 BMenuBar::InitData(menu_layout layout) 563 { 564 fLastBounds = new BRect(Bounds()); 565 SetItemMargins(8, 2, 8, 2); 566 SetIgnoreHidden(true); 567 } 568