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