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