1 /* 2 Open Tracker License 3 4 Terms and Conditions 5 6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved. 7 8 Permission is hereby granted, free of charge, to any person obtaining a copy of 9 this software and associated documentation files (the "Software"), to deal in 10 the Software without restriction, including without limitation the rights to 11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 12 of the Software, and to permit persons to whom the Software is furnished to do 13 so, subject to the following conditions: 14 15 The above copyright notice and this permission notice applies to all licensees 16 and shall be included in all copies or substantial portions of the Software. 17 18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, 20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 21 BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 22 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION 23 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 25 Except as contained in this notice, the name of Be Incorporated shall not be 26 used in advertising or otherwise to promote the sale, use or other dealings in 27 this Software without prior written authorization from Be Incorporated. 28 29 Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered 30 trademarks of Be Incorporated in the United States and other countries. Other 31 brand product names are registered trademarks or trademarks of their respective 32 holders. 33 All rights reserved. 34 */ 35 36 37 #include "ExpandoMenuBar.h" 38 39 #include <strings.h> 40 41 #include <map> 42 43 #include <Autolock.h> 44 #include <Bitmap.h> 45 #include <Collator.h> 46 #include <ControlLook.h> 47 #include <Debug.h> 48 #include <MenuPrivate.h> 49 #include <MessengerPrivate.h> 50 #include <NodeInfo.h> 51 #include <Roster.h> 52 #include <Screen.h> 53 #include <Thread.h> 54 #include <Window.h> 55 56 #include "icons.h" 57 58 #include "BarApp.h" 59 #include "BarMenuBar.h" 60 #include "BarView.h" 61 #include "BarWindow.h" 62 #include "DeskbarMenu.h" 63 #include "DeskbarUtils.h" 64 #include "InlineScrollView.h" 65 #include "ResourceSet.h" 66 #include "ShowHideMenuItem.h" 67 #include "StatusView.h" 68 #include "TeamMenu.h" 69 #include "TeamMenuItem.h" 70 #include "WindowMenu.h" 71 #include "WindowMenuItem.h" 72 73 74 const uint32 kMinimizeTeam = 'mntm'; 75 const uint32 kBringTeamToFront = 'bftm'; 76 77 bool TExpandoMenuBar::sDoMonitor = false; 78 thread_id TExpandoMenuBar::sMonThread = B_ERROR; 79 BLocker TExpandoMenuBar::sMonLocker("expando monitor"); 80 81 typedef std::map<BString, TTeamMenuItem*> TeamMenuItemMap; 82 83 84 // #pragma mark - TExpandoMenuBar 85 86 87 TExpandoMenuBar::TExpandoMenuBar(menu_layout layout, TBarView* barView) 88 : 89 BMenuBar(BRect(0, 0, 0, 0), "ExpandoMenuBar", B_FOLLOW_NONE, layout), 90 fBarView(barView), 91 fOverflow(false), 92 fUnderflow(false), 93 fFirstBuild(true), 94 fPreviousDragTargetItem(NULL), 95 fLastMousedOverItem(NULL), 96 fLastClickedItem(NULL), 97 fLastClickTime(0) 98 { 99 SetItemMargins(0.0f, 0.0f, 0.0f, 0.0f); 100 SetFont(be_plain_font); 101 } 102 103 104 void 105 TExpandoMenuBar::AllAttached() 106 { 107 BMenuBar::AllAttached(); 108 109 SizeWindow(0); 110 } 111 112 113 void 114 TExpandoMenuBar::AttachedToWindow() 115 { 116 BMenuBar::AttachedToWindow(); 117 118 fTeamList.MakeEmpty(); 119 120 if (Vertical()) 121 StartMonitoringWindows(); 122 } 123 124 125 void 126 TExpandoMenuBar::DetachedFromWindow() 127 { 128 BMenuBar::DetachedFromWindow(); 129 130 StopMonitoringWindows(); 131 132 BMessenger self(this); 133 BMessage message(kUnsubscribe); 134 message.AddMessenger("messenger", self); 135 be_app->PostMessage(&message); 136 137 RemoveItems(0, CountItems(), true); 138 } 139 140 141 void 142 TExpandoMenuBar::MessageReceived(BMessage* message) 143 { 144 int32 index; 145 TTeamMenuItem* item; 146 147 switch (message->what) { 148 case B_SOME_APP_LAUNCHED: 149 { 150 BList* teams = NULL; 151 message->FindPointer("teams", (void**)&teams); 152 153 BBitmap* icon = NULL; 154 message->FindPointer("icon", (void**)&icon); 155 156 const char* signature = NULL; 157 message->FindString("sig", &signature); 158 159 uint32 flags = 0; 160 message->FindInt32("flags", ((int32*) &flags)); 161 162 const char* name = NULL; 163 message->FindString("name", &name); 164 165 AddTeam(teams, icon, strdup(name), strdup(signature)); 166 break; 167 } 168 169 case B_MOUSE_WHEEL_CHANGED: 170 { 171 float deltaY = 0; 172 message->FindFloat("be:wheel_delta_y", &deltaY); 173 if (deltaY == 0) 174 return; 175 176 TInlineScrollView* scrollView 177 = dynamic_cast<TInlineScrollView*>(Parent()); 178 if (scrollView == NULL || !scrollView->HasScrollers()) 179 return; 180 181 float largeStep; 182 float smallStep; 183 scrollView->GetSteps(&smallStep, &largeStep); 184 185 // pressing the option/command/control key scrolls faster 186 if (modifiers() & (B_OPTION_KEY | B_COMMAND_KEY | B_CONTROL_KEY)) 187 deltaY *= largeStep; 188 else 189 deltaY *= smallStep; 190 191 scrollView->ScrollBy(deltaY); 192 break; 193 } 194 195 case kAddTeam: 196 AddTeam(message->FindInt32("team"), message->FindString("sig")); 197 break; 198 199 case kRemoveTeam: 200 { 201 team_id team = -1; 202 message->FindInt32("team", &team); 203 204 RemoveTeam(team, true); 205 break; 206 } 207 208 case B_SOME_APP_QUIT: 209 { 210 team_id team = -1; 211 message->FindInt32("team", &team); 212 213 RemoveTeam(team, false); 214 break; 215 } 216 217 case kMinimizeTeam: 218 { 219 index = message->FindInt32("itemIndex"); 220 item = dynamic_cast<TTeamMenuItem*>(ItemAt(index)); 221 if (item == NULL) 222 break; 223 224 TShowHideMenuItem::TeamShowHideCommon(B_MINIMIZE_WINDOW, 225 item->Teams(), 226 item->Menu()->ConvertToScreen(item->Frame()), 227 true); 228 break; 229 } 230 231 case kBringTeamToFront: 232 { 233 index = message->FindInt32("itemIndex"); 234 item = dynamic_cast<TTeamMenuItem*>(ItemAt(index)); 235 if (item == NULL) 236 break; 237 238 TShowHideMenuItem::TeamShowHideCommon(B_BRING_TO_FRONT, 239 item->Teams(), item->Menu()->ConvertToScreen(item->Frame()), 240 true); 241 break; 242 } 243 244 default: 245 BMenuBar::MessageReceived(message); 246 break; 247 } 248 } 249 250 251 void 252 TExpandoMenuBar::MouseDown(BPoint where) 253 { 254 BMessage* message = Window()->CurrentMessage(); 255 BMenuItem* menuItem; 256 TTeamMenuItem* item = TeamItemAtPoint(where, &menuItem); 257 fLastClickedItem = item; 258 259 if (message == NULL || item == NULL || fBarView == NULL 260 || fBarView->Dragging()) { 261 BMenuBar::MouseDown(where); 262 return; 263 } 264 265 int32 modifiers = 0; 266 int32 buttons = 0; 267 message->FindInt32("modifiers", &modifiers); 268 message->FindInt32("buttons", &buttons); 269 270 // check for three finger salute, a.k.a. Vulcan Death Grip 271 if ((modifiers & B_COMMAND_KEY) != 0 272 && (modifiers & B_CONTROL_KEY) != 0 273 && (modifiers & B_SHIFT_KEY) != 0) { 274 const BList* teams = item->Teams(); 275 int32 teamCount = teams->CountItems(); 276 team_id teamID; 277 for (int32 team = 0; team < teamCount; team++) { 278 teamID = (addr_t)teams->ItemAt(team); 279 kill_team(teamID); 280 RemoveTeam(teamID, false); 281 // remove the team from display immediately 282 } 283 return; 284 // absorb the message 285 } else if (item != NULL 286 && (modifiers & B_SHIFT_KEY) == 0 287 && (buttons & B_TERTIARY_MOUSE_BUTTON) != 0) { 288 be_roster->Launch(item->Signature()); 289 return; 290 // absorb the message 291 } else { 292 TWindowMenuItem* wndItem = dynamic_cast<TWindowMenuItem*>(menuItem); 293 if (wndItem != NULL 294 && (modifiers & B_SHIFT_KEY) != 0 295 && (buttons & B_TERTIARY_MOUSE_BUTTON) != 0) { 296 // close window 297 client_window_info* info; 298 BMessenger wnd; 299 info = get_window_info(wndItem->ID()); 300 if (info == NULL) return; 301 BMessenger::Private(wnd).SetTo( 302 info->team, info->client_port, info->client_token); 303 free(info); info = NULL; 304 wnd.SendMessage(B_QUIT_REQUESTED); 305 return; 306 // absorb the message 307 } 308 } 309 310 // control click - show all/hide all shortcut 311 if ((modifiers & B_CONTROL_KEY) != 0) { 312 // show/hide item's teams 313 BMessage showMessage((modifiers & B_SHIFT_KEY) != 0 314 ? kMinimizeTeam : kBringTeamToFront); 315 showMessage.AddInt32("itemIndex", IndexOf(item)); 316 Window()->PostMessage(&showMessage, this); 317 return; 318 // absorb the message 319 } 320 321 // check if within expander bounds to expand window items 322 if (Vertical() && static_cast<TBarApp*>(be_app)->Settings()->superExpando 323 && item->ExpanderBounds().Contains(where)) { 324 // start the animation here, finish on mouse up 325 item->SetArrowDirection(BControlLook::B_RIGHT_DOWN_ARROW); 326 SetMouseEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY); 327 Invalidate(item->ExpanderBounds()); 328 return; 329 // absorb the message 330 } 331 332 // double-click on an item brings the team to front 333 int32 clicks; 334 bigtime_t clickSpeed = 0; 335 get_click_speed(&clickSpeed); 336 bigtime_t delta = system_time() - fLastClickTime; 337 if (message->FindInt32("clicks", &clicks) == B_OK && clicks > 1 338 && item == menuItem && item == fLastClickedItem 339 && delta <= clickSpeed) { 340 be_roster->ActivateApp((addr_t)item->Teams()->ItemAt(0)); 341 // activate this team 342 return; 343 // absorb the message 344 } 345 346 // Update fLastClickTime only if we are not already triggering the 347 // double-click action. Otherwise the delay is renewed at every subsequent 348 // click and they keep triggering the double click action 349 fLastClickTime = system_time(); 350 351 BMenuBar::MouseDown(where); 352 } 353 354 355 void 356 TExpandoMenuBar::MouseMoved(BPoint where, uint32 code, const BMessage* message) 357 { 358 int32 buttons; 359 BMessage* currentMessage = Window()->CurrentMessage(); 360 if (currentMessage == NULL 361 || currentMessage->FindInt32("buttons", &buttons) != B_OK) { 362 buttons = 0; 363 } 364 365 if (message == NULL) { 366 // force a cleanup 367 _FinishedDrag(); 368 369 if (Vertical() && buttons != 0 370 && static_cast<TBarApp*>(be_app)->Settings()->superExpando) { 371 TTeamMenuItem* lastItem 372 = dynamic_cast<TTeamMenuItem*>(fLastClickedItem); 373 if (lastItem != NULL) { 374 if (lastItem->ExpanderBounds().Contains(where)) 375 lastItem->SetArrowDirection( 376 BControlLook::B_RIGHT_DOWN_ARROW); 377 else { 378 lastItem->SetArrowDirection(lastItem->IsExpanded() 379 ? BControlLook::B_DOWN_ARROW 380 : BControlLook::B_RIGHT_ARROW); 381 } 382 383 Invalidate(lastItem->ExpanderBounds()); 384 } 385 } 386 387 switch (code) { 388 case B_INSIDE_VIEW: 389 { 390 BMenuItem* menuItem; 391 TTeamMenuItem* item = TeamItemAtPoint(where, &menuItem); 392 393 if (item == NULL || menuItem == NULL) { 394 // item is NULL, remove the tooltip and break out 395 fLastMousedOverItem = NULL; 396 SetToolTip((const char*)NULL); 397 break; 398 } 399 400 if (menuItem == fLastMousedOverItem) { 401 // already set the tooltip for this item, break out 402 break; 403 } 404 405 TWindowMenuItem* windowMenuItem 406 = dynamic_cast<TWindowMenuItem*>(menuItem); 407 if (windowMenuItem != NULL && fBarView != NULL && Vertical() 408 && fBarView->ExpandoState() && item->IsExpanded()) { 409 // expando mode window menu item 410 fLastMousedOverItem = menuItem; 411 if (strcasecmp(windowMenuItem->TruncatedLabel(), 412 windowMenuItem->Label()) > 0) { 413 // label is truncated, set tooltip 414 SetToolTip(windowMenuItem->Label()); 415 } else 416 SetToolTip((const char*)NULL); 417 418 break; 419 } 420 421 if (!dynamic_cast<TBarApp*>(be_app)->Settings()->hideLabels) { 422 // item has a visible label, set tool tip if truncated 423 fLastMousedOverItem = menuItem; 424 if (strcasecmp(item->TruncatedLabel(), item->Label()) > 0) { 425 // label is truncated, set tooltip 426 SetToolTip(item->Label()); 427 } else 428 SetToolTip((const char*)NULL); 429 430 break; 431 } 432 433 SetToolTip(item->Label()); 434 // new item, set the tooltip to the item label 435 fLastMousedOverItem = menuItem; 436 // save the current menuitem for the next MouseMoved() call 437 break; 438 } 439 } 440 441 BMenuBar::MouseMoved(where, code, message); 442 return; 443 } 444 445 if (buttons == 0) 446 return; 447 448 switch (code) { 449 case B_ENTERED_VIEW: 450 // fPreviousDragTargetItem should always be NULL here anyways. 451 if (fPreviousDragTargetItem != NULL) 452 _FinishedDrag(); 453 454 fBarView->CacheDragData(message); 455 fPreviousDragTargetItem = NULL; 456 break; 457 458 case B_OUTSIDE_VIEW: 459 // NOTE: Should not be here, but for the sake of defensive 460 // programming... fall-through 461 case B_EXITED_VIEW: 462 _FinishedDrag(); 463 break; 464 465 case B_INSIDE_VIEW: 466 if (fBarView != NULL && fBarView->Dragging()) { 467 TTeamMenuItem* item = NULL; 468 int32 itemCount = CountItems(); 469 for (int32 i = 0; i < itemCount; i++) { 470 BMenuItem* _item = ItemAt(i); 471 if (_item->Frame().Contains(where)) { 472 item = dynamic_cast<TTeamMenuItem*>(_item); 473 break; 474 } 475 } 476 if (item == fPreviousDragTargetItem) 477 break; 478 if (fPreviousDragTargetItem != NULL) 479 fPreviousDragTargetItem->SetOverrideSelected(false); 480 if (item != NULL) 481 item->SetOverrideSelected(true); 482 fPreviousDragTargetItem = item; 483 } 484 break; 485 } 486 } 487 488 489 void 490 TExpandoMenuBar::MouseUp(BPoint where) 491 { 492 TTeamMenuItem* lastItem = dynamic_cast<TTeamMenuItem*>(fLastClickedItem); 493 fLastClickedItem = NULL; 494 495 if (fBarView != NULL && fBarView->Dragging()) { 496 _FinishedDrag(true); 497 return; 498 // absorb the message 499 } 500 501 if (Vertical() && static_cast<TBarApp*>(be_app)->Settings()->superExpando 502 && lastItem != NULL && lastItem->ExpanderBounds().Contains(where)) { 503 lastItem->ToggleExpandState(true); 504 lastItem->SetArrowDirection(lastItem->IsExpanded() 505 ? BControlLook::B_DOWN_ARROW 506 : BControlLook::B_RIGHT_ARROW); 507 508 Invalidate(lastItem->ExpanderBounds()); 509 } 510 BMenuBar::MouseUp(where); 511 } 512 513 514 void 515 TExpandoMenuBar::BuildItems() 516 { 517 BMessenger self(this); 518 TBarApp::Subscribe(self, &fTeamList); 519 520 desk_settings* settings = static_cast<TBarApp*>(be_app)->Settings(); 521 522 float itemWidth = -1.0f; 523 if (Vertical() && (fBarView->ExpandoState() || fBarView->FullState())) { 524 itemWidth = settings->width; 525 } 526 SetMaxContentWidth(itemWidth); 527 528 TeamMenuItemMap items; 529 int32 itemCount = CountItems(); 530 BList itemList(itemCount); 531 for (int32 i = 0; i < itemCount; i++) { 532 BMenuItem* menuItem = RemoveItem((int32)0); 533 itemList.AddItem(menuItem); 534 TTeamMenuItem* item = dynamic_cast<TTeamMenuItem*>(menuItem); 535 if (item != NULL) 536 items[BString(item->Signature()).ToLower()] = item; 537 } 538 539 if (settings->sortRunningApps) 540 fTeamList.SortItems(TTeamMenu::CompareByName); 541 542 int32 teamCount = fTeamList.CountItems(); 543 for (int32 i = 0; i < teamCount; i++) { 544 BarTeamInfo* barInfo = (BarTeamInfo*)fTeamList.ItemAt(i); 545 TeamMenuItemMap::const_iterator iter 546 = items.find(BString(barInfo->sig).ToLower()); 547 if (iter == items.end()) { 548 // new team 549 TTeamMenuItem* item = new TTeamMenuItem(barInfo->teams, 550 barInfo->icon, barInfo->name, barInfo->sig, itemWidth); 551 552 if (settings->trackerAlwaysFirst 553 && strcasecmp(barInfo->sig, kTrackerSignature) == 0) { 554 AddItem(item, 0); 555 } else 556 AddItem(item); 557 558 if (fFirstBuild && Vertical() && settings->expandNewTeams) 559 item->ToggleExpandState(true); 560 } else { 561 // existing team, update info and add it 562 TTeamMenuItem* item = iter->second; 563 item->SetIcon(barInfo->icon); 564 item->SetOverrideWidth(itemWidth); 565 566 if (settings->trackerAlwaysFirst 567 && strcasecmp(barInfo->sig, kTrackerSignature) == 0) { 568 AddItem(item, 0); 569 } else 570 AddItem(item); 571 572 // add window items back 573 int32 index = itemList.IndexOf(item); 574 TWindowMenuItem* windowItem; 575 TWindowMenu* submenu = dynamic_cast<TWindowMenu*>(item->Submenu()); 576 bool hasWindowItems = false; 577 while ((windowItem = dynamic_cast<TWindowMenuItem*>( 578 (BMenuItem*)(itemList.ItemAt(++index)))) != NULL) { 579 if (Vertical()) 580 AddItem(windowItem); 581 else { 582 delete windowItem; 583 hasWindowItems = submenu != NULL; 584 } 585 } 586 587 // unexpand if turn off show team expander 588 if (Vertical() && !settings->superExpando && item->IsExpanded()) 589 item->ToggleExpandState(false); 590 591 if (hasWindowItems) { 592 // add (new) window items in submenu 593 submenu->SetExpanded(false, 0); 594 submenu->AttachedToWindow(); 595 } 596 } 597 } 598 599 if (CountItems() == 0) { 600 // If we're empty, BMenuBar::AttachedToWindow() resizes us to some 601 // weird value - we just override it again 602 ResizeTo(gMinimumWindowWidth, 0); 603 } else { 604 // first build isn't complete until we've gotten here with an item 605 fFirstBuild = false; 606 } 607 } 608 609 610 bool 611 TExpandoMenuBar::InDeskbarMenu(BPoint loc) const 612 { 613 TBarWindow* window = dynamic_cast<TBarWindow*>(Window()); 614 if (window != NULL) { 615 if (TDeskbarMenu* bemenu = window->DeskbarMenu()) { 616 bool inDeskbarMenu = false; 617 if (bemenu->LockLooper()) { 618 inDeskbarMenu = bemenu->Frame().Contains(loc); 619 bemenu->UnlockLooper(); 620 } 621 return inDeskbarMenu; 622 } 623 } 624 625 return false; 626 } 627 628 629 /*! Returns the team menu item that belongs to the item under the 630 specified \a point. 631 If \a _item is given, it will return the exact menu item under 632 that point (which might be a window item when the expander is on). 633 */ 634 TTeamMenuItem* 635 TExpandoMenuBar::TeamItemAtPoint(BPoint point, BMenuItem** _item) 636 { 637 TTeamMenuItem* lastApp = NULL; 638 int32 itemCount = CountItems(); 639 640 for (int32 i = 0; i < itemCount; i++) { 641 BMenuItem* item = ItemAt(i); 642 643 if (dynamic_cast<TTeamMenuItem*>(item) != NULL) 644 lastApp = (TTeamMenuItem*)item; 645 646 if (item && item->Frame().Contains(point)) { 647 if (_item != NULL) 648 *_item = item; 649 650 return lastApp; 651 } 652 } 653 654 // no item found 655 656 if (_item != NULL) 657 *_item = NULL; 658 659 return NULL; 660 } 661 662 663 void 664 TExpandoMenuBar::AddTeam(BList* team, BBitmap* icon, char* name, 665 char* signature) 666 { 667 TTeamMenuItem* item = new TTeamMenuItem(team, icon, name, signature); 668 669 desk_settings* settings = static_cast<TBarApp*>(be_app)->Settings(); 670 if (settings != NULL && settings->trackerAlwaysFirst 671 && strcasecmp(signature, kTrackerSignature) == 0) { 672 AddItem(item, 0); 673 } else if (settings->sortRunningApps) { 674 TTeamMenuItem* teamItem = dynamic_cast<TTeamMenuItem*>(ItemAt(0)); 675 int32 firstApp = 0; 676 677 // if Tracker should always be the first item, we need to skip it 678 // when sorting in the current item 679 if (settings->trackerAlwaysFirst && teamItem != NULL 680 && strcasecmp(teamItem->Signature(), kTrackerSignature) == 0) { 681 firstApp++; 682 } 683 684 BCollator collator; 685 BLocale::Default()->GetCollator(&collator); 686 687 int32 i = firstApp; 688 int32 itemCount = CountItems(); 689 while (i < itemCount) { 690 teamItem = dynamic_cast<TTeamMenuItem*>(ItemAt(i)); 691 if (teamItem != NULL && collator.Compare(teamItem->Label(), name) 692 > 0) { 693 AddItem(item, i); 694 break; 695 } 696 i++; 697 } 698 // was the item added to the list yet? 699 if (i == itemCount) 700 AddItem(item); 701 } else 702 AddItem(item); 703 704 if (Vertical() && settings != NULL && settings->superExpando 705 && settings->expandNewTeams) { 706 item->ToggleExpandState(false); 707 } 708 709 SizeWindow(1); 710 Window()->UpdateIfNeeded(); 711 } 712 713 714 void 715 TExpandoMenuBar::AddTeam(team_id team, const char* signature) 716 { 717 int32 itemCount = CountItems(); 718 for (int32 i = 0; i < itemCount; i++) { 719 // Only add to team menu items 720 TTeamMenuItem* item = dynamic_cast<TTeamMenuItem*>(ItemAt(i)); 721 if (item != NULL && strcasecmp(item->Signature(), signature) == 0 722 && !(item->Teams()->HasItem((void*)(addr_t)team))) { 723 item->Teams()->AddItem((void*)(addr_t)team); 724 break; 725 } 726 } 727 } 728 729 730 void 731 TExpandoMenuBar::RemoveTeam(team_id team, bool partial) 732 { 733 TWindowMenuItem* windowItem = NULL; 734 735 for (int32 i = CountItems() - 1; i >= 0; i--) { 736 TTeamMenuItem* item = dynamic_cast<TTeamMenuItem*>(ItemAt(i)); 737 if (item != NULL && item->Teams()->HasItem((void*)(addr_t)team)) { 738 item->Teams()->RemoveItem(team); 739 if (partial) 740 return; 741 742 BAutolock locker(sMonLocker); 743 // make the update thread wait 744 RemoveItem(i); 745 if (item == fPreviousDragTargetItem) 746 fPreviousDragTargetItem = NULL; 747 748 if (item == fLastMousedOverItem) 749 fLastMousedOverItem = NULL; 750 751 if (item == fLastClickedItem) 752 fLastClickedItem = NULL; 753 754 delete item; 755 while ((windowItem = dynamic_cast<TWindowMenuItem*>( 756 ItemAt(i))) != NULL) { 757 // Also remove window items (if there are any) 758 RemoveItem(i); 759 if (windowItem == fLastMousedOverItem) 760 fLastMousedOverItem = NULL; 761 762 if (windowItem == fLastClickedItem) 763 fLastClickedItem = NULL; 764 765 delete windowItem; 766 } 767 SizeWindow(-1); 768 Window()->UpdateIfNeeded(); 769 return; 770 } 771 } 772 } 773 774 775 void 776 TExpandoMenuBar::CheckItemSizes(int32 delta, bool reset) 777 { 778 // horizontal only 779 if (fBarView == NULL || Vertical()) 780 return; 781 782 // minimum two items before size overrun can occur 783 int32 itemCount = CountItems(); 784 if (itemCount < 2) 785 return; 786 787 float minItemWidth = MinHorizontalItemWidth(); 788 float maxItemWidth = MaxHorizontalItemWidth(); 789 float maxMenuWidth = maxItemWidth * itemCount; 790 float maxWidth = MaxHorizontalWidth(); 791 bool tooWide = maxMenuWidth > maxWidth; 792 793 // start at max width 794 float newItemWidth = maxItemWidth; 795 796 if (delta < 0 && fOverflow) { 797 // removing an item, check if menu is still too wide 798 if (tooWide) 799 newItemWidth = floorf(maxWidth / itemCount); 800 else 801 newItemWidth = maxItemWidth; 802 } else if (tooWide) { 803 fOverflow = true; 804 newItemWidth = std::min(floorf(maxWidth / itemCount), maxItemWidth); 805 } 806 807 // see if we should grow items 808 fUnderflow = delta < 0 && newItemWidth < maxItemWidth; 809 810 if (fOverflow || fUnderflow || fFirstBuild || reset) { 811 // clip within limits 812 if (newItemWidth > maxItemWidth) 813 newItemWidth = maxItemWidth; 814 else if (newItemWidth < minItemWidth) 815 newItemWidth = minItemWidth; 816 817 SetMaxContentWidth(newItemWidth); 818 if (newItemWidth == maxItemWidth) 819 fOverflow = false; 820 821 for (int32 index = 0; ; index++) { 822 TTeamMenuItem* item = (TTeamMenuItem*)ItemAt(index); 823 if (item == NULL) 824 break; 825 826 item->SetOverrideWidth(newItemWidth); 827 } 828 829 InvalidateLayout(); 830 831 ResizeTo(newItemWidth * itemCount, Frame().Height()); 832 } 833 } 834 835 836 float 837 TExpandoMenuBar::MinHorizontalItemWidth() 838 { 839 const int32 iconSize = static_cast<TBarApp*>(be_app)->IconSize(); 840 const float iconPadding = be_control_look->ComposeSpacing(kIconPadding); 841 float iconOnlyWidth = iconSize + iconPadding; 842 843 return static_cast<TBarApp*>(be_app)->Settings()->hideLabels 844 ? iconOnlyWidth 845 : (iconSize - kMinimumIconSize) + gMinimumWindowWidth 846 + (be_plain_font->Size() - 12) * 4; 847 } 848 849 850 float 851 TExpandoMenuBar::MaxHorizontalItemWidth() 852 { 853 const int32 iconSize = static_cast<TBarApp*>(be_app)->IconSize(); 854 const float iconPadding = be_control_look->ComposeSpacing(kIconPadding); 855 float iconOnlyWidth = iconSize + iconPadding; 856 857 // hide labels 858 if (static_cast<TBarApp*>(be_app)->Settings()->hideLabels) 859 return iconOnlyWidth + iconPadding; // add an extra icon padding 860 861 // set max item width to 1.25x min item width 862 return floorf(MinHorizontalItemWidth() * 1.25); 863 } 864 865 866 menu_layout 867 TExpandoMenuBar::MenuLayout() const 868 { 869 return Layout(); 870 } 871 872 873 void 874 TExpandoMenuBar::SetMenuLayout(menu_layout layout) 875 { 876 BPrivate::MenuPrivate(this).SetLayout(layout); 877 InvalidateLayout(); 878 } 879 880 881 void 882 TExpandoMenuBar::Draw(BRect updateRect) 883 { 884 BMenu::Draw(updateRect); 885 } 886 887 888 void 889 TExpandoMenuBar::DrawBackground(BRect updateRect) 890 { 891 if (Vertical()) 892 return; 893 894 SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), 1.22)); 895 StrokeLine(Bounds().RightTop(), Bounds().RightBottom()); 896 } 897 898 899 /*! Some methods to help determine if we are showing too many apps 900 and need to add or remove in scroll arrows. 901 */ 902 bool 903 TExpandoMenuBar::CheckForSizeOverrun() 904 { 905 if (Vertical()) 906 return CheckForSizeOverrunVertical(); 907 else 908 return CheckForSizeOverrunHorizontal(); 909 } 910 911 912 bool 913 TExpandoMenuBar::CheckForSizeOverrunVertical() 914 { 915 if (Window() == NULL || !Vertical()) 916 return false; 917 918 return Window()->Frame().bottom > (BScreen(Window())).Frame().bottom; 919 920 } 921 922 923 bool 924 TExpandoMenuBar::CheckForSizeOverrunHorizontal() 925 { 926 if (fBarView == NULL || Vertical()) 927 return false; 928 929 // minimum two items before size overrun can occur 930 int32 itemCount = CountItems(); 931 if (itemCount < 2) 932 return false; 933 934 float minMenuWidth = MinHorizontalItemWidth() * itemCount; 935 float maxWidth = MaxHorizontalWidth(); 936 937 return minMenuWidth > maxWidth; 938 } 939 940 941 float 942 TExpandoMenuBar::MaxHorizontalWidth() 943 { 944 return (BScreen(Window())).Frame().Width() 945 - fBarView->DragRegion()->Frame().Width() - 1 946 - fBarView->BarMenuBar()->Frame().Width(); 947 } 948 949 950 void 951 TExpandoMenuBar::SizeWindow(int32 delta) 952 { 953 // instead of resizing the window here and there in the 954 // code the resize method will be centered in one place 955 // thus, the same behavior (good or bad) will be used 956 // wherever window sizing is done 957 if (fBarView == NULL || Window() == NULL) 958 return; 959 960 BRect screenFrame = (BScreen(Window())).Frame(); 961 fBarView->SizeWindow(screenFrame); 962 fBarView->PositionWindow(screenFrame); 963 964 if (!Vertical()) 965 CheckItemSizes(delta); 966 967 fBarView->CheckForScrolling(); 968 Window()->UpdateIfNeeded(); 969 Invalidate(); 970 } 971 972 973 void 974 TExpandoMenuBar::StartMonitoringWindows() 975 { 976 if (sMonThread != B_ERROR) 977 return; 978 979 sDoMonitor = true; 980 sMonThread = spawn_thread(monitor_team_windows, 981 "Expando Window Watcher", B_LOW_PRIORITY, this); 982 resume_thread(sMonThread); 983 } 984 985 986 void 987 TExpandoMenuBar::StopMonitoringWindows() 988 { 989 if (sMonThread == B_ERROR) 990 return; 991 992 sDoMonitor = false; 993 status_t returnCode; 994 wait_for_thread(sMonThread, &returnCode); 995 996 sMonThread = B_ERROR; 997 } 998 999 1000 int32 1001 TExpandoMenuBar::monitor_team_windows(void* arg) 1002 { 1003 TExpandoMenuBar* teamMenu = (TExpandoMenuBar*)arg; 1004 1005 while (teamMenu->sDoMonitor) { 1006 sMonLocker.Lock(); 1007 1008 if (teamMenu->Window()->LockWithTimeout(50000) == B_OK) { 1009 int32 totalItems = teamMenu->CountItems(); 1010 1011 // Set all WindowMenuItems to require an update. 1012 TWindowMenuItem* item = NULL; 1013 for (int32 i = 0; i < totalItems; i++) { 1014 if (!teamMenu->SubmenuAt(i)) { 1015 item = static_cast<TWindowMenuItem*>(teamMenu->ItemAt(i)); 1016 item->SetRequireUpdate(true); 1017 } 1018 } 1019 1020 // Perform SetTo() on all the items that still exist as well as add 1021 // new items. 1022 bool itemModified = false; 1023 bool resize = false; 1024 TTeamMenuItem* teamItem = NULL; 1025 1026 for (int32 i = 0; i < totalItems; i++) { 1027 if (teamMenu->SubmenuAt(i) == NULL) 1028 continue; 1029 1030 teamItem = static_cast<TTeamMenuItem*>(teamMenu->ItemAt(i)); 1031 if (teamItem->IsExpanded()) { 1032 int32 teamCount = teamItem->Teams()->CountItems(); 1033 for (int32 j = 0; j < teamCount; j++) { 1034 // The following code is almost a copy/paste from 1035 // WindowMenu.cpp 1036 team_id theTeam = (addr_t)teamItem->Teams()->ItemAt(j); 1037 int32 count = 0; 1038 int32* tokens = get_token_list(theTeam, &count); 1039 1040 for (int32 k = 0; k < count; k++) { 1041 client_window_info* wInfo 1042 = get_window_info(tokens[k]); 1043 if (wInfo == NULL) 1044 continue; 1045 1046 BString windowName(wInfo->name); 1047 1048 BString teamPrefix(teamItem->Label()); 1049 teamPrefix.Append(": "); 1050 1051 BString teamSuffix(" - "); 1052 teamSuffix.Append(teamItem->Label()); 1053 1054 if (windowName.StartsWith(teamPrefix)) 1055 windowName.RemoveFirst(teamPrefix); 1056 if (windowName.EndsWith(teamSuffix)) 1057 windowName.RemoveLast(teamSuffix); 1058 1059 if (TWindowMenu::WindowShouldBeListed(wInfo)) { 1060 // Check if we have a matching window item... 1061 item = teamItem->ExpandedWindowItem( 1062 wInfo->server_token); 1063 if (item != NULL) { 1064 item->SetTo(windowName, 1065 wInfo->server_token, wInfo->is_mini, 1066 ((1 << current_workspace()) 1067 & wInfo->workspaces) != 0); 1068 1069 if (strcasecmp(item->Label(), windowName) 1070 > 0) { 1071 item->SetLabel(windowName); 1072 } 1073 if (item->Modified()) 1074 itemModified = true; 1075 } else if (teamItem->IsExpanded()) { 1076 // Add the item 1077 item = new TWindowMenuItem(windowName, 1078 wInfo->server_token, wInfo->is_mini, 1079 ((1 << current_workspace()) 1080 & wInfo->workspaces) != 0, false); 1081 item->SetExpanded(true); 1082 teamMenu->AddItem(item, 1083 TWindowMenuItem::InsertIndexFor( 1084 teamMenu, i + 1, item)); 1085 resize = true; 1086 } 1087 } 1088 free(wInfo); 1089 } 1090 free(tokens); 1091 } 1092 } 1093 } 1094 1095 // Remove any remaining items which require an update. 1096 for (int32 i = 0; i < totalItems; i++) { 1097 if (!teamMenu->SubmenuAt(i)) { 1098 item = static_cast<TWindowMenuItem*>(teamMenu->ItemAt(i)); 1099 if (item && item->RequiresUpdate()) { 1100 item = static_cast<TWindowMenuItem*> 1101 (teamMenu->RemoveItem(i)); 1102 delete item; 1103 totalItems--; 1104 1105 resize = true; 1106 } 1107 } 1108 } 1109 1110 // If any of the WindowMenuItems changed state, we need to force a 1111 // repaint. 1112 if (itemModified || resize) { 1113 teamMenu->Invalidate(); 1114 if (resize) 1115 teamMenu->SizeWindow(1); 1116 } 1117 1118 teamMenu->Window()->Unlock(); 1119 } 1120 1121 sMonLocker.Unlock(); 1122 1123 // sleep for a bit... 1124 snooze(150000); 1125 } 1126 return B_OK; 1127 } 1128 1129 1130 void 1131 TExpandoMenuBar::_FinishedDrag(bool invoke) 1132 { 1133 if (fPreviousDragTargetItem != NULL) { 1134 if (invoke) 1135 fPreviousDragTargetItem->Invoke(); 1136 1137 fPreviousDragTargetItem->SetOverrideSelected(false); 1138 fPreviousDragTargetItem = NULL; 1139 } 1140 1141 if (!invoke && fBarView != NULL && fBarView->Dragging()) 1142 fBarView->DragStop(true); 1143 } 1144