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