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 SetMaxContentWidth(itemWidth); 475 } else { 476 itemWidth = iconSize; 477 if (!settings->hideLabels) 478 itemWidth += gMinimumWindowWidth - kMinimumIconSize; 479 else 480 itemWidth += kIconPadding * 2; 481 } 482 float itemHeight = -1.0f; 483 484 TeamMenuItemMap items; 485 int32 itemCount = CountItems(); 486 BList itemList(itemCount); 487 for (int32 i = 0; i < itemCount; i++) { 488 BMenuItem* menuItem = RemoveItem((int32)0); 489 itemList.AddItem(menuItem); 490 TTeamMenuItem* item = dynamic_cast<TTeamMenuItem*>(menuItem); 491 if (item != NULL) 492 items[BString(item->Signature()).ToLower()] = item; 493 } 494 495 if (settings->sortRunningApps) 496 fTeamList.SortItems(TTeamMenu::CompareByName); 497 498 int32 teamCount = fTeamList.CountItems(); 499 for (int32 i = 0; i < teamCount; i++) { 500 BarTeamInfo* barInfo = (BarTeamInfo*)fTeamList.ItemAt(i); 501 TeamMenuItemMap::const_iterator iter 502 = items.find(BString(barInfo->sig).ToLower()); 503 if (iter == items.end()) { 504 // new team 505 TTeamMenuItem* item = new TTeamMenuItem(barInfo->teams, 506 barInfo->icon, barInfo->name, barInfo->sig, itemWidth, 507 itemHeight); 508 509 if (settings->trackerAlwaysFirst 510 && strcasecmp(barInfo->sig, kTrackerSignature) == 0) { 511 AddItem(item, 0); 512 } else 513 AddItem(item); 514 515 if (fFirstBuild && fVertical && settings->expandNewTeams) 516 item->ToggleExpandState(true); 517 } else { 518 // existing team, update info and add it 519 TTeamMenuItem* item = iter->second; 520 item->SetIcon(barInfo->icon); 521 item->SetOverrideWidth(itemWidth); 522 item->SetOverrideHeight(itemHeight); 523 524 if (settings->trackerAlwaysFirst 525 && strcasecmp(barInfo->sig, kTrackerSignature) == 0) { 526 AddItem(item, 0); 527 } else 528 AddItem(item); 529 530 // add window items back 531 int32 index = itemList.IndexOf(item); 532 TWindowMenuItem* windowItem; 533 TWindowMenu* submenu = dynamic_cast<TWindowMenu*>(item->Submenu()); 534 bool hasWindowItems = false; 535 while ((windowItem = dynamic_cast<TWindowMenuItem*>( 536 (BMenuItem*)(itemList.ItemAt(++index)))) != NULL) { 537 if (fVertical) 538 AddItem(windowItem); 539 else { 540 delete windowItem; 541 hasWindowItems = submenu != NULL; 542 } 543 } 544 545 // unexpand if turn off show team expander 546 if (fVertical && !settings->superExpando && item->IsExpanded()) 547 item->ToggleExpandState(false); 548 549 if (hasWindowItems) { 550 // add (new) window items in submenu 551 submenu->SetExpanded(false, 0); 552 submenu->AttachedToWindow(); 553 } 554 } 555 } 556 557 if (CountItems() == 0) { 558 // If we're empty, BMenuBar::AttachedToWindow() resizes us to some 559 // weird value - we just override it again 560 ResizeTo(itemWidth, 0); 561 } 562 563 fFirstBuild = false; 564 } 565 566 567 bool 568 TExpandoMenuBar::InDeskbarMenu(BPoint loc) const 569 { 570 TBarWindow* window = dynamic_cast<TBarWindow*>(Window()); 571 if (window != NULL) { 572 if (TDeskbarMenu* bemenu = window->DeskbarMenu()) { 573 bool inDeskbarMenu = false; 574 if (bemenu->LockLooper()) { 575 inDeskbarMenu = bemenu->Frame().Contains(loc); 576 bemenu->UnlockLooper(); 577 } 578 return inDeskbarMenu; 579 } 580 } 581 582 return false; 583 } 584 585 586 /*! Returns the team menu item that belongs to the item under the 587 specified \a point. 588 If \a _item is given, it will return the exact menu item under 589 that point (which might be a window item when the expander is on). 590 */ 591 TTeamMenuItem* 592 TExpandoMenuBar::TeamItemAtPoint(BPoint point, BMenuItem** _item) 593 { 594 TTeamMenuItem* lastApp = NULL; 595 int32 count = CountItems(); 596 597 for (int32 i = 0; i < count; i++) { 598 BMenuItem* item = ItemAt(i); 599 600 if (dynamic_cast<TTeamMenuItem*>(item) != NULL) 601 lastApp = (TTeamMenuItem*)item; 602 603 if (item && item->Frame().Contains(point)) { 604 if (_item != NULL) 605 *_item = item; 606 607 return lastApp; 608 } 609 } 610 611 // no item found 612 613 if (_item != NULL) 614 *_item = NULL; 615 616 return NULL; 617 } 618 619 620 void 621 TExpandoMenuBar::AddTeam(BList* team, BBitmap* icon, char* name, 622 char* signature) 623 { 624 int32 iconSize = static_cast<TBarApp*>(be_app)->IconSize(); 625 desk_settings* settings = static_cast<TBarApp*>(be_app)->Settings(); 626 627 float itemWidth = -1.0f; 628 if (fVertical) 629 itemWidth = fBarView->Bounds().Width(); 630 else { 631 itemWidth = iconSize; 632 if (!settings->hideLabels) 633 itemWidth += gMinimumWindowWidth - kMinimumIconSize; 634 else 635 itemWidth += kIconPadding * 2; 636 } 637 float itemHeight = -1.0f; 638 639 TTeamMenuItem* item = new TTeamMenuItem(team, icon, name, signature, 640 itemWidth, itemHeight); 641 642 if (settings->trackerAlwaysFirst 643 && strcasecmp(signature, kTrackerSignature) == 0) { 644 AddItem(item, 0); 645 } else if (settings->sortRunningApps) { 646 TTeamMenuItem* teamItem = dynamic_cast<TTeamMenuItem*>(ItemAt(0)); 647 int32 firstApp = 0; 648 649 // if Tracker should always be the first item, we need to skip it 650 // when sorting in the current item 651 if (settings->trackerAlwaysFirst && teamItem != NULL 652 && strcasecmp(teamItem->Signature(), kTrackerSignature) == 0) { 653 firstApp++; 654 } 655 656 BCollator collator; 657 BLocale::Default()->GetCollator(&collator); 658 659 int32 i = firstApp; 660 int32 itemCount = CountItems(); 661 while (i < itemCount) { 662 teamItem = dynamic_cast<TTeamMenuItem*>(ItemAt(i)); 663 if (teamItem != NULL && collator.Compare(teamItem->Label(), name) 664 > 0) { 665 AddItem(item, i); 666 break; 667 } 668 i++; 669 } 670 // was the item added to the list yet? 671 if (i == itemCount) 672 AddItem(item); 673 } else 674 AddItem(item); 675 676 if (fVertical && settings->superExpando && settings->expandNewTeams) 677 item->ToggleExpandState(false); 678 679 SizeWindow(1); 680 Window()->UpdateIfNeeded(); 681 } 682 683 684 void 685 TExpandoMenuBar::AddTeam(team_id team, const char* signature) 686 { 687 int32 itemCount = CountItems(); 688 for (int32 i = 0; i < itemCount; i++) { 689 // Only add to team menu items 690 TTeamMenuItem* item = dynamic_cast<TTeamMenuItem*>(ItemAt(i)); 691 if (item != NULL && strcasecmp(item->Signature(), signature) == 0 692 && !(item->Teams()->HasItem((void*)(addr_t)team))) { 693 item->Teams()->AddItem((void*)(addr_t)team); 694 break; 695 } 696 } 697 } 698 699 700 void 701 TExpandoMenuBar::RemoveTeam(team_id team, bool partial) 702 { 703 TWindowMenuItem* windowItem = NULL; 704 705 for (int32 i = CountItems() - 1; i >= 0; i--) { 706 TTeamMenuItem* item = dynamic_cast<TTeamMenuItem*>(ItemAt(i)); 707 if (item != NULL && item->Teams()->HasItem((void*)(addr_t)team)) { 708 item->Teams()->RemoveItem(team); 709 if (partial) 710 return; 711 712 BAutolock locker(sMonLocker); 713 // make the update thread wait 714 RemoveItem(i); 715 if (item == fPreviousDragTargetItem) 716 fPreviousDragTargetItem = NULL; 717 718 if (item == fLastMousedOverItem) 719 fLastMousedOverItem = NULL; 720 721 if (item == fLastClickedItem) 722 fLastClickedItem = NULL; 723 724 delete item; 725 while ((windowItem = dynamic_cast<TWindowMenuItem*>( 726 ItemAt(i))) != NULL) { 727 // Also remove window items (if there are any) 728 RemoveItem(i); 729 if (windowItem == fLastMousedOverItem) 730 fLastMousedOverItem = NULL; 731 732 if (windowItem == fLastClickedItem) 733 fLastClickedItem = NULL; 734 735 delete windowItem; 736 } 737 SizeWindow(-1); 738 Window()->UpdateIfNeeded(); 739 return; 740 } 741 } 742 } 743 744 745 void 746 TExpandoMenuBar::CheckItemSizes(int32 delta) 747 { 748 if (fBarView->Vertical()) 749 return; 750 751 bool drawLabels = !static_cast<TBarApp*>(be_app)->Settings()->hideLabels; 752 753 float maxWidth = fBarView->DragRegion()->Frame().left 754 - fDeskbarMenuWidth - kSepItemWidth; 755 int32 iconSize = static_cast<TBarApp*>(be_app)->IconSize(); 756 float iconOnlyWidth = kIconPadding + iconSize + kIconPadding; 757 float minItemWidth = drawLabels 758 ? iconOnlyWidth + kMinMenuItemWidth 759 : iconOnlyWidth - kIconPadding; 760 float maxItemWidth = drawLabels 761 ? gMinimumWindowWidth + iconSize - kMinimumIconSize 762 : iconOnlyWidth; 763 float menuWidth = maxItemWidth * CountItems() + fDeskbarMenuWidth 764 + kSepItemWidth; 765 766 bool reset = false; 767 float newWidth = -1.0f; 768 769 if (delta >= 0 && menuWidth > maxWidth) { 770 fOverflow = true; 771 reset = true; 772 newWidth = floorf(maxWidth / CountItems()); 773 } else if (delta < 0 && fOverflow) { 774 reset = true; 775 if (menuWidth > maxWidth) 776 newWidth = floorf(maxWidth / CountItems()); 777 else 778 newWidth = maxItemWidth; 779 } 780 781 if (reset) { 782 if (newWidth > maxItemWidth) 783 newWidth = maxItemWidth; 784 else if (newWidth < minItemWidth) 785 newWidth = minItemWidth; 786 787 SetMaxContentWidth(newWidth); 788 if (newWidth == maxItemWidth) 789 fOverflow = false; 790 791 InvalidateLayout(); 792 793 for (int32 index = 0; ; index++) { 794 TTeamMenuItem* item = (TTeamMenuItem*)ItemAt(index); 795 if (item == NULL) 796 break; 797 798 item->SetOverrideWidth(newWidth); 799 } 800 801 Invalidate(); 802 Window()->UpdateIfNeeded(); 803 fBarView->CheckForScrolling(); 804 } 805 } 806 807 808 menu_layout 809 TExpandoMenuBar::MenuLayout() const 810 { 811 return Layout(); 812 } 813 814 815 void 816 TExpandoMenuBar::SetMenuLayout(menu_layout layout) 817 { 818 fVertical = layout == B_ITEMS_IN_COLUMN; 819 BPrivate::MenuPrivate(this).SetLayout(layout); 820 SetMaxItemWidth(); 821 // when the menu layout changes, make sure to set the max width 822 } 823 824 825 void 826 TExpandoMenuBar::Draw(BRect updateRect) 827 { 828 BMenu::Draw(updateRect); 829 } 830 831 832 void 833 TExpandoMenuBar::DrawBackground(BRect updateRect) 834 { 835 if (fVertical) 836 return; 837 838 BRect bounds(Bounds()); 839 rgb_color menuColor = ui_color(B_MENU_BACKGROUND_COLOR); 840 rgb_color hilite = tint_color(menuColor, B_DARKEN_1_TINT); 841 rgb_color vlight = tint_color(menuColor, B_LIGHTEN_2_TINT); 842 843 int32 count = CountItems() - 1; 844 if (count >= 0) 845 bounds.left = ItemAt(count)->Frame().right + 1; 846 else 847 bounds.left = 0; 848 849 if (be_control_look != NULL) { 850 SetHighColor(tint_color(menuColor, 1.22)); 851 StrokeLine(bounds.LeftTop(), bounds.LeftBottom()); 852 bounds.left++; 853 uint32 borders = BControlLook::B_TOP_BORDER 854 | BControlLook::B_BOTTOM_BORDER | BControlLook::B_RIGHT_BORDER; 855 856 be_control_look->DrawButtonBackground(this, bounds, bounds, menuColor, 857 0, borders); 858 } else { 859 SetHighColor(vlight); 860 StrokeLine(bounds.LeftTop(), bounds.RightTop()); 861 StrokeLine(BPoint(bounds.left, bounds.top + 1), bounds.LeftBottom()); 862 SetHighColor(hilite); 863 StrokeLine(BPoint(bounds.left + 1, bounds.bottom), 864 bounds.RightBottom()); 865 } 866 } 867 868 869 /*! Something to help determine if we are showing too many apps 870 need to add in scrolling functionality. 871 */ 872 bool 873 TExpandoMenuBar::CheckForSizeOverrun() 874 { 875 if (fVertical) { 876 if (Window() == NULL) 877 return false; 878 879 BRect screenFrame = (BScreen(Window())).Frame(); 880 return Window()->Frame().bottom > screenFrame.bottom; 881 } 882 883 // horizontal 884 int32 count = CountItems() - 1; 885 if (count < 0) 886 return false; 887 888 int32 iconSize = static_cast<TBarApp*>(be_app)->IconSize(); 889 float iconOnlyWidth = kIconPadding + iconSize + kIconPadding; 890 float minItemWidth = !static_cast<TBarApp*>(be_app)->Settings()->hideLabels 891 ? iconOnlyWidth + kMinMenuItemWidth 892 : iconOnlyWidth - kIconPadding; 893 float menuWidth = minItemWidth * CountItems() + fDeskbarMenuWidth 894 + kSepItemWidth; 895 float maxWidth = fBarView->DragRegion()->Frame().left 896 - fDeskbarMenuWidth - kSepItemWidth; 897 898 return menuWidth > maxWidth; 899 } 900 901 902 void 903 TExpandoMenuBar::SetMaxItemWidth() 904 { 905 if (fVertical) 906 SetMaxContentWidth(static_cast<TBarApp*>(be_app)->Settings()->width); 907 else { 908 // Make more room for the icon in horizontal mode 909 int32 iconSize = static_cast<TBarApp*>(be_app)->IconSize(); 910 SetMaxContentWidth(gMinimumWindowWidth + iconSize 911 - kMinimumIconSize); 912 } 913 } 914 915 916 void 917 TExpandoMenuBar::SizeWindow(int32 delta) 918 { 919 // instead of resizing the window here and there in the 920 // code the resize method will be centered in one place 921 // thus, the same behavior (good or bad) will be used 922 // wherever window sizing is done 923 if (fVertical) { 924 BRect screenFrame = (BScreen(Window())).Frame(); 925 fBarView->SizeWindow(screenFrame); 926 fBarView->PositionWindow(screenFrame); 927 fBarView->CheckForScrolling(); 928 } else 929 CheckItemSizes(delta); 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) > 0) 1030 item->SetLabel(windowName); 1031 1032 if (item->Modified()) 1033 itemModified = true; 1034 } else if (teamItem->IsExpanded()) { 1035 // Add the item 1036 item = new TWindowMenuItem(windowName, 1037 wInfo->server_token, wInfo->is_mini, 1038 ((1 << current_workspace()) 1039 & wInfo->workspaces) != 0, false); 1040 item->SetExpanded(true); 1041 teamMenu->AddItem(item, 1042 TWindowMenuItem::InsertIndexFor( 1043 teamMenu, i + 1, item)); 1044 resize = true; 1045 } 1046 } 1047 free(wInfo); 1048 } 1049 free(tokens); 1050 } 1051 } 1052 } 1053 1054 // Remove any remaining items which require an update. 1055 for (int32 i = 0; i < totalItems; i++) { 1056 if (!teamMenu->SubmenuAt(i)) { 1057 item = static_cast<TWindowMenuItem*>(teamMenu->ItemAt(i)); 1058 if (item && item->RequiresUpdate()) { 1059 item = static_cast<TWindowMenuItem*> 1060 (teamMenu->RemoveItem(i)); 1061 delete item; 1062 totalItems--; 1063 1064 resize = true; 1065 } 1066 } 1067 } 1068 1069 // If any of the WindowMenuItems changed state, we need to force a 1070 // repaint. 1071 if (itemModified || resize) { 1072 teamMenu->Invalidate(); 1073 if (resize) 1074 teamMenu->SizeWindow(1); 1075 } 1076 1077 teamMenu->Window()->Unlock(); 1078 } 1079 1080 sMonLocker.Unlock(); 1081 1082 // sleep for a bit... 1083 snooze(150000); 1084 } 1085 return B_OK; 1086 } 1087 1088 1089 void 1090 TExpandoMenuBar::_FinishedDrag(bool invoke) 1091 { 1092 if (fPreviousDragTargetItem != NULL) { 1093 if (invoke) 1094 fPreviousDragTargetItem->Invoke(); 1095 1096 fPreviousDragTargetItem->SetOverrideSelected(false); 1097 fPreviousDragTargetItem = NULL; 1098 } 1099 1100 if (!invoke && fBarView->Dragging()) 1101 fBarView->DragStop(true); 1102 } 1103 1104 1105 void 1106 TExpandoMenuBar::_DoneTracking(BPoint where) 1107 { 1108 TTeamMenuItem* lastItem = dynamic_cast<TTeamMenuItem*>(fLastClickedItem); 1109 if (lastItem == NULL) 1110 return; 1111 1112 if (!lastItem->ExpanderBounds().Contains(where)) 1113 return; 1114 1115 lastItem->ToggleExpandState(true); 1116 lastItem->SetArrowDirection(lastItem->IsExpanded() 1117 ? BControlLook::B_DOWN_ARROW 1118 : BControlLook::B_RIGHT_ARROW); 1119 1120 Invalidate(lastItem->ExpanderBounds()); 1121 } 1122 1123 1124 void 1125 TExpandoMenuBar::_Track(BPoint where, uint32) 1126 { 1127 TTeamMenuItem* lastItem = dynamic_cast<TTeamMenuItem*>(fLastClickedItem); 1128 if (lastItem == NULL) 1129 return; 1130 1131 if (lastItem->ExpanderBounds().Contains(where)) 1132 lastItem->SetArrowDirection(BControlLook::B_RIGHT_DOWN_ARROW); 1133 else { 1134 lastItem->SetArrowDirection(lastItem->IsExpanded() 1135 ? BControlLook::B_DOWN_ARROW 1136 : BControlLook::B_RIGHT_ARROW); 1137 } 1138 1139 Invalidate(lastItem->ExpanderBounds()); 1140 } 1141