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