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