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