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 trademarks 30 of Be Incorporated in the United States and other countries. Other brand product 31 names are registered trademarks or trademarks of their respective holders. 32 All rights reserved. 33 */ 34 35 36 #include "tracker_private.h" 37 #include "BarApp.h" 38 #include "Switcher.h" 39 #include "ResourceSet.h" 40 #include "WindowMenuItem.h" 41 #include "icons.h" 42 43 #include <Bitmap.h> 44 #include <Debug.h> 45 #include <Font.h> 46 #include <Mime.h> 47 #include <Node.h> 48 #include <NodeInfo.h> 49 #include <Roster.h> 50 #include <Screen.h> 51 #include <String.h> 52 53 #include <string.h> 54 #include <stdlib.h> 55 #include <float.h> 56 57 58 #define _ALLOW_STICKY_ 0 59 // allows you to press 's' to keep the switcher window on screen 60 61 #if __HAIKU__ 62 static const color_space kIconFormat = B_RGBA32; 63 #else 64 static const color_space kIconFormat = B_CMAP8; 65 #endif 66 67 68 class TTeamGroup { 69 public: 70 TTeamGroup(); 71 TTeamGroup(BList *teams, uint32 flags, char *name, const char *sig); 72 virtual ~TTeamGroup(); 73 74 void Draw(BView *, BRect bounds, bool main); 75 76 BList *TeamList() const 77 { return fTeams; } 78 const char *Name() const 79 { return fName; } 80 const char *Signature() const 81 { return fSignature; } 82 uint32 Flags() const 83 { return fFlags; } 84 const BBitmap *SmallIcon() const 85 { return fSmallIcon; } 86 const BBitmap *LargeIcon() const 87 { return fLargeIcon; } 88 89 private: 90 BList *fTeams; 91 uint32 fFlags; 92 char fSignature[B_MIME_TYPE_LENGTH]; 93 char *fName; 94 BBitmap *fSmallIcon; 95 BBitmap *fLargeIcon; 96 }; 97 98 class TSwitcherWindow : public BWindow { 99 public: 100 TSwitcherWindow(BRect frame, TSwitchManager *mgr); 101 virtual ~TSwitcherWindow(); 102 103 virtual bool QuitRequested(); 104 virtual void MessageReceived(BMessage *message); 105 virtual void Show(); 106 virtual void Hide(); 107 virtual void WindowActivated(bool state); 108 109 void DoKey(uint32 key, uint32 mods); 110 TIconView *IconView(); 111 TWindowView *WindowView(); 112 TBox *TopView(); 113 bool HairTrigger(); 114 void Update(int32 previous, int32 current, int32 prevSlot, 115 int32 currentSlot, bool forward); 116 int32 SlotOf(int32); 117 void Redraw(int32 index); 118 119 private: 120 TSwitchManager *fManager; 121 TIconView *fIconView; 122 TBox *fTopView; 123 TWindowView *fWindowView; 124 bool fHairTrigger; 125 bool fSkipKeyRepeats; 126 }; 127 128 class TWindowView : public BView { 129 public: 130 TWindowView(BRect frame, TSwitchManager *manager, TSwitcherWindow *switcher); 131 132 void UpdateGroup(int32 groupIndex, int32 windowIndex); 133 134 virtual void AttachedToWindow(); 135 virtual void Draw(BRect update); 136 virtual void Pulse(); 137 virtual void GetPreferredSize(float *w, float *h); 138 void ScrollTo(float x, float y) 139 { ScrollTo(BPoint(x,y)); } 140 virtual void ScrollTo(BPoint where); 141 142 void ShowIndex(int32 windex); 143 BRect FrameOf(int32 index) const; 144 145 private: 146 int32 fCurrentToken; 147 float fItemHeight; 148 TSwitcherWindow *fSwitcher; 149 TSwitchManager *fManager; 150 bool fLocal; 151 }; 152 153 class TIconView : public BView { 154 public: 155 TIconView(BRect frame, TSwitchManager *manager, TSwitcherWindow *switcher); 156 ~TIconView(); 157 158 void Showing(); 159 void Hiding(); 160 161 virtual void KeyDown(const char *bytes, int32 numBytes); 162 virtual void Pulse(); 163 virtual void MouseDown(BPoint ); 164 virtual void Draw(BRect ); 165 166 void ScrollTo(float x, float y) 167 { ScrollTo(BPoint(x,y)); } 168 virtual void ScrollTo(BPoint where); 169 void Update(int32 previous, int32 current, int32 previousSlot, 170 int32 currentSlot, bool forward); 171 void DrawTeams(BRect update); 172 int32 SlotOf(int32) const; 173 BRect FrameOf(int32) const; 174 int32 ItemAtPoint(BPoint) const; 175 int32 IndexAt(int32 slot) const; 176 void CenterOn(int32 index); 177 178 private: 179 void CacheIcons(TTeamGroup *); 180 void AnimateIcon(BBitmap *startIcon, BBitmap *endIcon); 181 182 bool fAutoScrolling; 183 bool fCapsState; 184 TSwitcherWindow *fSwitcher; 185 TSwitchManager *fManager; 186 BBitmap *fOffBitmap; 187 BView *fOffView; 188 BBitmap *fCurrentSmall; 189 BBitmap *fCurrentLarge; 190 }; 191 192 class TBox : public BBox { 193 public: 194 TBox(BRect bounds, TSwitchManager *manager, TSwitcherWindow *, TIconView *iconView); 195 196 virtual void Draw(BRect update); 197 virtual void AllAttached(); 198 virtual void DrawIconScrollers(bool force); 199 virtual void DrawWindowScrollers(bool force); 200 virtual void MouseDown(BPoint where); 201 202 private: 203 TSwitchManager *fManager; 204 TSwitcherWindow *fWindow; 205 TIconView *fIconView; 206 BRect fCenter; 207 bool fLeftScroller; 208 bool fRightScroller; 209 bool fUpScroller; 210 bool fDownScroller; 211 }; 212 213 214 const int32 kHorizontalMargin = 11; 215 const int32 kVerticalMargin = 10; 216 217 // SLOT_SIZE must be divisible by 4. That's because of the scrolling 218 // animation. If this needs to change then look at TIconView::Update() 219 220 const int32 kSlotSize = 36; 221 const int32 kScrollStep = kSlotSize / 2; 222 const int32 kNumSlots = 7; 223 const int32 kCenterSlot = 3; 224 225 226 // #pragma mark - 227 228 229 static int32 230 LowBitIndex(uint32 value) 231 { 232 int32 result = 0; 233 int32 bitMask = 1; 234 235 if (value == 0) 236 return -1; 237 238 while (result < 32 && (value & bitMask) == 0) { 239 result++; 240 bitMask = bitMask << 1; 241 } 242 return result; 243 } 244 245 246 inline bool 247 IsVisibleInCurrentWorkspace(const window_info *windowInfo) 248 { 249 /* 250 The window list is always ordered from the top 251 front visible window (the first on the list), going down through all 252 the other visible windows, then all the hidden or non workspace 253 visible window at the end. 254 255 layer > 2 : normal visible window. 256 layer == 2 : reserved for the desktop window (visible also). 257 layer < 2 : hidden (0) and non workspace visible window (1) 258 */ 259 return windowInfo->layer > 2; 260 } 261 262 263 bool 264 IsKeyDown(int32 key) 265 { 266 key_info keyInfo; 267 268 get_key_info(&keyInfo); 269 return (keyInfo.key_states[key >> 3] & (1 << ((7 - key) & 7))) != 0; 270 } 271 272 273 bool 274 IsWindowOK(const window_info *windowInfo) 275 { 276 // is_mini (true means that the window is minimized). 277 // if not, then 278 // show_hide >= 1 means that the window is hidden. 279 // 280 // If the window is both minimized and hidden, then you get : 281 // TWindow->is_mini = false; 282 // TWindow->was_mini = true; 283 // TWindow->show_hide >= 1; 284 285 if (windowInfo->w_type != _STD_W_TYPE_) 286 return false; 287 288 if (windowInfo->is_mini) 289 return true; 290 291 return windowInfo->show_hide_level <= 0; 292 } 293 294 295 bool 296 OKToUse(const TTeamGroup *teamGroup) 297 { 298 if (!teamGroup) 299 return false; 300 301 // skip background applications 302 if ((teamGroup->Flags() & B_BACKGROUND_APP) != 0) 303 return false; 304 305 // skip the Deakbar itself 306 if (strcasecmp(teamGroup->Signature(), kDeskbarSignature) == 0) 307 return false; 308 309 return true; 310 } 311 312 313 int 314 SmartStrcmp(const char *s1, const char *s2) 315 { 316 if (strcasecmp(s1, s2) == 0) 317 return 0; 318 319 // if the strings on differ in spaces or underscores they still match 320 while (*s1 && *s2) { 321 if ((*s1 == ' ') || (*s1 == '_')) { 322 s1++; 323 continue; 324 } 325 if ((*s2 == ' ') || (*s2 == '_')) { 326 s2++; 327 continue; 328 } 329 if (*s1 != *s2) 330 return 1; // they differ 331 s1++; 332 s2++; 333 } 334 335 // if one of the strings ended before the other 336 // ??? could process trailing spaces & underscores! 337 if (*s1) 338 return 1; 339 if (*s2) 340 return 1; 341 342 return 0; 343 } 344 345 346 // #pragma mark - 347 348 349 TTeamGroup::TTeamGroup() 350 : 351 fTeams(NULL), 352 fFlags(0), 353 fName(NULL), 354 fSmallIcon(NULL), 355 fLargeIcon(NULL) 356 { 357 fSignature[0] = '\0'; 358 } 359 360 361 TTeamGroup::TTeamGroup(BList *teams, uint32 flags, char *name, 362 const char *signature) 363 : 364 fTeams(teams), 365 fFlags(flags), 366 fName(name), 367 fSmallIcon(NULL), 368 fLargeIcon(NULL) 369 { 370 strcpy(fSignature, signature); 371 372 fSmallIcon = new BBitmap(BRect(0, 0, 15, 15), kIconFormat); 373 fLargeIcon = new BBitmap(BRect(0, 0, 31, 31), kIconFormat); 374 375 app_info appInfo; 376 if (be_roster->GetAppInfo(signature, &appInfo) == B_OK) { 377 BNode node(&(appInfo.ref)); 378 if (node.InitCheck() == B_OK) { 379 BNodeInfo nodeInfo(&node); 380 if (nodeInfo.InitCheck() == B_OK) { 381 nodeInfo.GetTrackerIcon(fSmallIcon, B_MINI_ICON); 382 nodeInfo.GetTrackerIcon(fLargeIcon, B_LARGE_ICON); 383 } 384 } 385 } 386 } 387 388 389 TTeamGroup::~TTeamGroup() 390 { 391 delete fTeams; 392 free(fName); 393 delete fSmallIcon; 394 delete fLargeIcon; 395 } 396 397 398 void 399 TTeamGroup::Draw(BView *view, BRect bounds, bool main) 400 { 401 BRect rect; 402 if (main) { 403 rect = fLargeIcon->Bounds(); 404 rect.OffsetTo(bounds.LeftTop()); 405 rect.OffsetBy(2, 2); 406 view->DrawBitmap(fLargeIcon, rect); 407 } else { 408 rect = fSmallIcon->Bounds(); 409 rect.OffsetTo(bounds.LeftTop()); 410 rect.OffsetBy(10, 10); 411 view->DrawBitmap(fSmallIcon, rect); 412 } 413 } 414 415 416 // #pragma mark - 417 418 419 TSwitchManager::TSwitchManager(BPoint point) 420 : BHandler("SwitchManager"), 421 fMainMonitor(create_sem(1, "main_monitor")), 422 fBlock(false), 423 fSkipUntil(0), 424 fGroupList(10), 425 fCurrentIndex(0), 426 fCurrentSlot(0), 427 fWindowID(-1) 428 { 429 BRect rect(point.x, point.y, 430 point.x + (kSlotSize * kNumSlots) - 1 + (2 * kHorizontalMargin), 431 point.y + 82); 432 fWindow = new TSwitcherWindow(rect, this); 433 fWindow->AddHandler(this); 434 435 fWindow->Lock(); 436 fWindow->Run(); 437 438 BList tmpList; 439 TBarApp::Subscribe(BMessenger(this), &tmpList); 440 441 for (int32 i = 0; ; i++) { 442 BarTeamInfo *barTeamInfo = (BarTeamInfo *)tmpList.ItemAt(i); 443 if (!barTeamInfo) 444 break; 445 446 TTeamGroup *tinfo = new TTeamGroup(barTeamInfo->teams, barTeamInfo->flags, 447 barTeamInfo->name, barTeamInfo->sig); 448 fGroupList.AddItem(tinfo); 449 450 barTeamInfo->teams = NULL; 451 barTeamInfo->name = NULL; 452 453 delete barTeamInfo; 454 } 455 fWindow->Unlock(); 456 } 457 458 459 TSwitchManager::~TSwitchManager() 460 { 461 for (int32 i = fGroupList.CountItems(); i-- > 0;) { 462 TTeamGroup *teamInfo = static_cast<TTeamGroup *>(fGroupList.ItemAt(i)); 463 delete teamInfo; 464 } 465 } 466 467 468 void 469 TSwitchManager::MessageReceived(BMessage *message) 470 { 471 switch (message->what) { 472 case B_SOME_APP_QUIT: 473 { 474 // This is only sent when last team of a matching set quits 475 team_id teamID; 476 int i = 0; 477 TTeamGroup *tinfo; 478 message->FindInt32("team", &teamID); 479 while ((tinfo = (TTeamGroup *) fGroupList.ItemAt(i)) != NULL) { 480 if (tinfo->TeamList()->HasItem((void *)teamID)) { 481 fGroupList.RemoveItem(i); 482 483 if (OKToUse(tinfo)) { 484 fWindow->Redraw(i); 485 if (i <= fCurrentIndex) { 486 fCurrentIndex--; 487 CycleApp(true); 488 } 489 } 490 delete tinfo; 491 break; 492 } 493 i++; 494 } 495 break; 496 } 497 498 case B_SOME_APP_LAUNCHED: 499 { 500 BList *teams; 501 const char *name; 502 BBitmap *smallIcon; 503 uint32 flags; 504 const char *signature; 505 506 if (message->FindPointer("teams", (void **)&teams) != B_OK) 507 break; 508 509 if (message->FindPointer("icon", (void **)&smallIcon) != B_OK) { 510 delete teams; 511 break; 512 } 513 delete smallIcon; 514 if (message->FindString("sig", &signature) != B_OK) { 515 delete teams; 516 break; 517 } 518 if (message->FindInt32("flags", (int32 *)&flags) != B_OK) { 519 delete teams; 520 break; 521 } 522 if (message->FindString("name", &name) != B_OK) { 523 delete teams; 524 break; 525 } 526 527 TTeamGroup *tinfo = new TTeamGroup(teams, flags, strdup(name), signature); 528 529 fGroupList.AddItem(tinfo); 530 if (OKToUse(tinfo)) 531 fWindow->Redraw(fGroupList.CountItems() - 1); 532 533 break; 534 } 535 536 case msg_AddTeam: 537 { 538 const char *signature = message->FindString("sig"); 539 team_id team = message->FindInt32("team"); 540 541 int32 numItems = fGroupList.CountItems(); 542 for (int32 i = 0; i < numItems; i++) { 543 TTeamGroup *tinfo = (TTeamGroup *)fGroupList.ItemAt(i); 544 if (strcasecmp(tinfo->Signature(), signature) == 0) { 545 if (!(tinfo->TeamList()->HasItem((void *)team))) 546 tinfo->TeamList()->AddItem((void *)team); 547 break; 548 } 549 } 550 break; 551 } 552 553 case msg_RemoveTeam: 554 { 555 team_id team = message->FindInt32("team"); 556 557 int32 numItems = fGroupList.CountItems(); 558 for (int32 i = 0; i < numItems; i++) { 559 TTeamGroup *tinfo = (TTeamGroup *)fGroupList.ItemAt(i); 560 if (tinfo->TeamList()->HasItem((void *)team)) { 561 tinfo->TeamList()->RemoveItem((void *)team); 562 break; 563 } 564 } 565 break; 566 } 567 568 case 'TASK': 569 { 570 // The first TASK message calls MainEntry. Subsequent ones 571 // call Process(). 572 bigtime_t time; 573 message->FindInt64("when", (int64 *)&time); 574 575 // The fSkipUntil stuff can be removed once the new input_server 576 // starts differentiating initial key_downs from KeyDowns generated 577 // by auto-repeat. Until then the fSkipUntil stuff helps, but it 578 // isn't perfect. 579 580 if (time < fSkipUntil) 581 break; 582 583 status_t status = acquire_sem_etc(fMainMonitor, 1, B_TIMEOUT, 0); 584 if (status != B_OK) { 585 if (!fWindow->IsHidden() && !fBlock) { 586 // Want to skip TASK msgs posted before the window 587 // was made visible. Better UI feel if we do this. 588 if (time > fSkipUntil) { 589 uint32 modifiers; 590 message->FindInt32("modifiers", (int32 *)&modifiers); 591 Process((modifiers & B_SHIFT_KEY) == 0, (modifiers & B_OPTION_KEY) != 0); 592 } 593 } 594 } else 595 MainEntry(message); 596 597 break; 598 } 599 600 default: 601 break; 602 } 603 } 604 605 606 void 607 TSwitchManager::MainEntry(BMessage *message) 608 { 609 bigtime_t keyRepeatRate; 610 get_key_repeat_delay(&keyRepeatRate); 611 612 if (keyRepeatRate < 200000) 613 keyRepeatRate = 200000; 614 615 bigtime_t timeout = system_time() + keyRepeatRate; 616 617 app_info appInfo; 618 be_roster->GetActiveAppInfo(&appInfo); 619 620 int32 index; 621 fCurrentIndex = (FindTeam(appInfo.team, &index) != NULL) ? index : 0; 622 int32 key; 623 message->FindInt32("key", (int32 *)&key); 624 625 uint32 modifierKeys = 0; 626 while (system_time() < timeout) { 627 modifierKeys = modifiers(); 628 if (!IsKeyDown(key)) { 629 QuickSwitch(message); 630 return; 631 } 632 if ((modifierKeys & B_CONTROL_KEY) == 0) { 633 QuickSwitch(message); 634 return; 635 } 636 snooze(50000); 637 } 638 639 Process((modifierKeys & B_SHIFT_KEY) == 0, (modifierKeys & B_OPTION_KEY) != 0); 640 } 641 642 643 void 644 TSwitchManager::Stop(bool do_action, uint32) 645 { 646 fWindow->Hide(); 647 if (do_action) 648 ActivateApp(true, true); 649 650 release_sem(fMainMonitor); 651 } 652 653 654 TTeamGroup * 655 TSwitchManager::FindTeam(team_id teamID, int32 *index) 656 { 657 int i = 0; 658 TTeamGroup *tinfo; 659 while ((tinfo = (TTeamGroup *)fGroupList.ItemAt(i)) != NULL) { 660 if (tinfo->TeamList()->HasItem((void*) teamID)) { 661 *index = i; 662 return tinfo; 663 } 664 i++; 665 } 666 667 return NULL; 668 } 669 670 671 void 672 TSwitchManager::Process(bool forward, bool byWindow) 673 { 674 bool hidden = false; 675 if (fWindow->Lock()) { 676 hidden = fWindow->IsHidden(); 677 fWindow->Unlock(); 678 } 679 if (byWindow) { 680 // If hidden we need to get things started by switching to correct app 681 if (hidden) 682 SwitchToApp(fCurrentIndex, fCurrentIndex, forward); 683 CycleWindow(forward, false); 684 } else 685 CycleApp(forward, false); 686 687 if (hidden) { 688 // more auto keyrepeat code 689 // Because of key repeats we don't want to respond to any extraneous 690 // 'TASK' messages until the window is completely shown. So block here. 691 // the WindowActivated hook function will unblock. 692 fBlock = true; 693 694 if (fWindow->Lock()) { 695 BRect screenFrame = BScreen().Frame(); 696 BRect windowFrame = fWindow->Frame(); 697 698 if (!screenFrame.Contains(windowFrame)) { 699 // center the window 700 BPoint point((screenFrame.left + screenFrame.right) / 2, 701 (screenFrame.top + screenFrame.bottom) / 2); 702 703 point.x -= (windowFrame.Width() / 2); 704 point.y -= (windowFrame.Height() / 2); 705 fWindow->MoveTo(point); 706 } 707 708 fWindow->Show(); 709 fWindow->Unlock(); 710 } 711 } 712 } 713 714 715 void 716 TSwitchManager::QuickSwitch(BMessage *message) 717 { 718 uint32 modifiers = 0; 719 message->FindInt32("modifiers", (int32 *) &modifiers); 720 721 team_id team; 722 if (message->FindInt32("team", &team) == B_OK) { 723 bool forward = ((modifiers & B_SHIFT_KEY) == 0); 724 725 if ((modifiers & B_OPTION_KEY) != 0) 726 SwitchWindow(team, forward, true); 727 else 728 CycleApp(forward, true); 729 } 730 731 release_sem(fMainMonitor); 732 } 733 734 735 int32 736 TSwitchManager::CountVisibleGroups() 737 { 738 int32 result = 0; 739 740 int32 count = fGroupList.CountItems(); 741 for (int32 i = 0; i < count; i++) { 742 if (!OKToUse((TTeamGroup *) fGroupList.ItemAt(i))) 743 continue; 744 745 result++; 746 } 747 return result; 748 } 749 750 751 void 752 TSwitchManager::CycleWindow(bool forward, bool wrap) 753 { 754 int32 max = CountWindows(fCurrentIndex); 755 int32 prev = fCurrentWindow; 756 int32 next = fCurrentWindow; 757 758 if (forward) { 759 next++; 760 if (next >= max) { 761 if (!wrap) 762 return; 763 next = 0; 764 } 765 } else { 766 next--; 767 if (next < 0) { 768 if (!wrap) 769 return; 770 next = max - 1; 771 } 772 } 773 fCurrentWindow = next; 774 775 if (fCurrentWindow != prev) 776 fWindow->WindowView()->ShowIndex(fCurrentWindow); 777 } 778 779 780 void 781 TSwitchManager::CycleApp(bool forward, bool activateNow) 782 { 783 int32 startIndex = fCurrentIndex; 784 int32 max = fGroupList.CountItems(); 785 786 for (;;) { 787 if (forward) { 788 fCurrentIndex++; 789 if (fCurrentIndex >= max) 790 fCurrentIndex = 0; 791 } else { 792 fCurrentIndex--; 793 if (fCurrentIndex < 0) 794 fCurrentIndex = max - 1; 795 } 796 if ((fCurrentIndex == startIndex)) 797 // we've gone completely through the list without finding 798 // a good app. Oh well. 799 return; 800 801 if (!OKToUse((TTeamGroup *)fGroupList.ItemAt(fCurrentIndex))) 802 continue; 803 804 // if we're here then we found a good one 805 SwitchToApp(startIndex, fCurrentIndex, forward); 806 807 if (!activateNow) 808 break; 809 810 if (ActivateApp(false, false)) 811 break; 812 } 813 } 814 815 816 void 817 TSwitchManager::SwitchToApp(int32 previousIndex, int32 newIndex, bool forward) 818 { 819 int32 previousSlot = fCurrentSlot; 820 821 fCurrentIndex = newIndex; 822 fCurrentSlot = fWindow->SlotOf(fCurrentIndex); 823 fCurrentWindow = 0; 824 825 fWindow->Update(previousIndex, fCurrentIndex, previousSlot, fCurrentSlot, forward); 826 } 827 828 829 bool 830 TSwitchManager::ActivateApp(bool forceShow, bool allowWorkspaceSwitch) 831 { 832 // Let's get the info about the selected window. If it doesn't exist 833 // anymore then get info about first window. If that doesn't exist then 834 // do nothing. 835 window_info *windowInfo = WindowInfo(fCurrentIndex, fCurrentWindow); 836 if (windowInfo == NULL) { 837 windowInfo = WindowInfo(fCurrentIndex, 0); 838 if (windowInfo == NULL) 839 return false; 840 } 841 842 int32 currentWorkspace = current_workspace(); 843 TTeamGroup *teamGroup = (TTeamGroup *)fGroupList.ItemAt(fCurrentIndex); 844 // Let's handle the easy case first: There's only 1 team in the group 845 if (teamGroup->TeamList()->CountItems() == 1) { 846 bool result; 847 if (forceShow && (fCurrentWindow != 0 || windowInfo->is_mini)) 848 do_window_action(windowInfo->id, B_BRING_TO_FRONT, 849 BRect(0, 0, 0, 0), false); 850 851 if (!forceShow && windowInfo->is_mini) { 852 // we aren't unhiding minimized windows, so we can't do 853 // anything here 854 result = false; 855 } else if (!allowWorkspaceSwitch 856 && (windowInfo->workspaces & (1 << currentWorkspace)) == 0) { 857 // we're not supposed to switch workspaces so abort. 858 result = false; 859 } else { 860 result = true; 861 be_roster->ActivateApp((team_id)teamGroup->TeamList()->ItemAt(0)); 862 } 863 864 ASSERT(windowInfo); 865 free(windowInfo); 866 return result; 867 } 868 869 // Now the trickier case. We're trying to Bring to the Front a group 870 // of teams. The current window (defined by fCurrentWindow) will define 871 // which workspace we're going to. Then, once that is determined we 872 // want to bring to the front every window of the group of teams that 873 // lives in that workspace. 874 875 if ((windowInfo->workspaces & (1 << currentWorkspace)) == 0) { 876 if (!allowWorkspaceSwitch) { 877 // If the first window in the list isn't in current workspace, 878 // then none are. So we can't switch to this app. 879 ASSERT(windowInfo); 880 free(windowInfo); 881 return false; 882 } 883 int32 destWorkspace = LowBitIndex(windowInfo->workspaces); 884 // now switch to that workspace 885 activate_workspace(destWorkspace); 886 } 887 888 if (!forceShow && windowInfo->is_mini) { 889 // If the first window in the list is hidden then no windows in 890 // this group are visible. So we can't switch to this app. 891 ASSERT(windowInfo); 892 free(windowInfo); 893 return false; 894 } 895 896 int32 tokenCount; 897 int32 *tokens = get_token_list(-1, &tokenCount); 898 if (!tokens) { 899 ASSERT(windowInfo); 900 free(windowInfo); 901 return true; // weird error, so don't try to recover 902 } 903 904 BList windowsToActivate; 905 906 // Now we go through all the windows in the current workspace list in order. 907 // As we hit member teams we build the "activate" list. 908 for (int32 i = 0; i < tokenCount; i++) { 909 window_info *matchWindowInfo = get_window_info(tokens[i]); 910 if (!matchWindowInfo) { 911 // That window probably closed. Just go to the next one. 912 continue; 913 } 914 if (!IsVisibleInCurrentWorkspace(matchWindowInfo)) { 915 // first non-visible in workspace window means we're done. 916 free(matchWindowInfo); 917 break; 918 } 919 if (matchWindowInfo->id != windowInfo->id 920 && teamGroup->TeamList()->HasItem((void *)matchWindowInfo->team)) 921 windowsToActivate.AddItem((void *)matchWindowInfo->id); 922 923 free(matchWindowInfo); 924 } 925 926 free(tokens); 927 928 // Want to go through the list backwards to keep windows in same relative 929 // order. 930 int32 i = windowsToActivate.CountItems() - 1; 931 for (; i >= 0; i--) { 932 int32 wid = (int32) windowsToActivate.ItemAt(i); 933 do_window_action(wid, B_BRING_TO_FRONT, BRect(0, 0, 0, 0), false); 934 } 935 936 // now bring the select window on top of everything. 937 938 do_window_action(windowInfo->id, B_BRING_TO_FRONT, BRect(0, 0, 0, 0), false); 939 free(windowInfo); 940 return true; 941 } 942 943 944 void 945 TSwitchManager::QuitApp() 946 { 947 // check if we're in the last slot already (the last usable team group) 948 949 TTeamGroup *teamGroup; 950 int32 count = 0; 951 for (int32 i = fCurrentIndex + 1; i < fGroupList.CountItems(); i++) { 952 teamGroup = (TTeamGroup *)fGroupList.ItemAt(i); 953 954 if (!OKToUse(teamGroup)) 955 continue; 956 957 count++; 958 } 959 960 teamGroup = (TTeamGroup *)fGroupList.ItemAt(fCurrentIndex); 961 962 if (count == 0) { 963 // switch to previous app in the list so that we don't jump to 964 // the start of the list (try to keep the same position when 965 // the apps at the current index go away) 966 CycleApp(false, false); 967 } 968 969 // send the quit request to all teams in this group 970 971 for (int32 i = teamGroup->TeamList()->CountItems(); i-- > 0;) { 972 team_id team = (team_id)teamGroup->TeamList()->ItemAt(i); 973 app_info info; 974 if (be_roster->GetRunningAppInfo(team, &info) == B_OK) { 975 if (!strcasecmp(info.signature, kTrackerSignature)) { 976 // Tracker can't be quit this way 977 continue; 978 } 979 980 BMessenger messenger(NULL, team); 981 messenger.SendMessage(B_QUIT_REQUESTED); 982 } 983 } 984 } 985 986 987 void 988 TSwitchManager::HideApp() 989 { 990 // hide all teams in this group 991 992 TTeamGroup *teamGroup = (TTeamGroup *)fGroupList.ItemAt(fCurrentIndex); 993 994 for (int32 i = teamGroup->TeamList()->CountItems(); i-- > 0;) { 995 team_id team = (team_id)teamGroup->TeamList()->ItemAt(i); 996 app_info info; 997 if (be_roster->GetRunningAppInfo(team, &info) == B_OK) 998 do_minimize_team(BRect(), team, false); 999 } 1000 } 1001 1002 1003 window_info * 1004 TSwitchManager::WindowInfo(int32 groupIndex, int32 windowIndex) 1005 { 1006 TTeamGroup *teamGroup = (TTeamGroup *) fGroupList.ItemAt(groupIndex); 1007 if (!teamGroup) 1008 return NULL; 1009 1010 int32 tokenCount; 1011 int32 *tokens = get_token_list(-1, &tokenCount); 1012 if (!tokens) 1013 return NULL; 1014 1015 int32 matches = 0; 1016 1017 // Want to find the "windowIndex'th" window in window order that belongs 1018 // the the specified group (groupIndex). Since multiple teams can belong to 1019 // the same group (multiple-launch apps) we get the list of _every_ 1020 // window and go from there. 1021 1022 window_info *result = NULL; 1023 for (int32 i = 0; i < tokenCount; i++) { 1024 window_info *windowInfo = get_window_info(tokens[i]); 1025 if (windowInfo) { 1026 // skip hidden/special windows 1027 if (IsWindowOK(windowInfo) 1028 && (teamGroup->TeamList()->HasItem((void *)windowInfo->team))) { 1029 // this window belongs to the team! 1030 if (matches == windowIndex) { 1031 // we found it! 1032 result = windowInfo; 1033 break; 1034 } 1035 matches++; 1036 } 1037 free(windowInfo); 1038 } 1039 // else - that window probably closed. Just go to the next one. 1040 } 1041 1042 free(tokens); 1043 1044 return result; 1045 } 1046 1047 1048 int32 1049 TSwitchManager::CountWindows(int32 groupIndex, bool ) 1050 { 1051 TTeamGroup *teamGroup = (TTeamGroup *)fGroupList.ItemAt(groupIndex); 1052 if (!teamGroup) 1053 return 0; 1054 1055 int32 result = 0; 1056 1057 for (int32 i = 0; ; i++) { 1058 team_id teamID = (team_id)teamGroup->TeamList()->ItemAt(i); 1059 if (teamID == 0) 1060 break; 1061 1062 int32 count; 1063 int32 *tokens = get_token_list(teamID, &count); 1064 if (!tokens) 1065 continue; 1066 1067 for (int32 i = 0; i < count; i++) { 1068 window_info *windowInfo = get_window_info(tokens[i]); 1069 if (windowInfo) { 1070 if (IsWindowOK(windowInfo)) 1071 result++; 1072 free(windowInfo); 1073 } 1074 } 1075 free(tokens); 1076 } 1077 1078 return result; 1079 } 1080 1081 1082 void 1083 TSwitchManager::ActivateWindow(int32 windowID) 1084 { 1085 if (windowID == -1) 1086 windowID = fWindowID; 1087 1088 do_window_action(windowID, B_BRING_TO_FRONT, BRect(0, 0, 0, 0), false); 1089 } 1090 1091 1092 void 1093 TSwitchManager::SwitchWindow(team_id team, bool, bool activate) 1094 { 1095 // Find the _last_ window in the current workspace that belongs 1096 // to the group. This is the window to activate. 1097 1098 int32 index; 1099 TTeamGroup*teamGroup = FindTeam(team, &index); 1100 1101 // cycle through the windows in the active application 1102 int32 count; 1103 int32 *tokens = get_token_list(-1, &count); 1104 for (int32 i = count-1; i >= 0; i--) { 1105 window_info *windowInfo; 1106 windowInfo = get_window_info(tokens[i]); 1107 if (windowInfo && IsVisibleInCurrentWorkspace(windowInfo) 1108 && teamGroup->TeamList()->HasItem((void *)windowInfo->team)) { 1109 fWindowID = windowInfo->id; 1110 if (activate) 1111 ActivateWindow(windowInfo->id); 1112 1113 free(windowInfo); 1114 break; 1115 } 1116 free(windowInfo); 1117 } 1118 free(tokens); 1119 } 1120 1121 1122 void 1123 TSwitchManager::Unblock() 1124 { 1125 fBlock = false; 1126 fSkipUntil = system_time(); 1127 } 1128 1129 1130 int32 1131 TSwitchManager::CurrentIndex() 1132 { 1133 return fCurrentIndex; 1134 } 1135 1136 1137 int32 1138 TSwitchManager::CurrentWindow() 1139 { 1140 return fCurrentWindow; 1141 } 1142 1143 1144 int32 1145 TSwitchManager::CurrentSlot() 1146 { 1147 return fCurrentSlot; 1148 } 1149 1150 1151 BList * 1152 TSwitchManager::GroupList() 1153 { 1154 return &fGroupList; 1155 } 1156 1157 1158 // #pragma mark - 1159 1160 1161 TBox::TBox(BRect bounds, TSwitchManager *manager, TSwitcherWindow *window, 1162 TIconView *iview) 1163 : BBox(bounds, "top", B_FOLLOW_NONE, B_WILL_DRAW, B_NO_BORDER), 1164 fManager(manager), 1165 fWindow(window), 1166 fIconView(iview), 1167 fLeftScroller(false), 1168 fRightScroller(false) 1169 { 1170 } 1171 1172 1173 void 1174 TBox::AllAttached() 1175 { 1176 BRect centerRect(kCenterSlot * kSlotSize, 0, 1177 (kCenterSlot + 1) * kSlotSize - 1, kSlotSize - 1); 1178 BRect frame = fIconView->Frame(); 1179 1180 // scroll the centerRect to correct location 1181 centerRect.OffsetBy(frame.left, frame.top); 1182 1183 // switch to local coords 1184 fIconView->ConvertToParent(¢erRect); 1185 1186 fCenter = centerRect; 1187 } 1188 1189 1190 void 1191 TBox::MouseDown(BPoint where) 1192 { 1193 if (!fLeftScroller && !fRightScroller && !fUpScroller && !fDownScroller) 1194 return; 1195 1196 BRect frame = fIconView->Frame(); 1197 BRect bounds = Bounds(); 1198 1199 if (fLeftScroller) { 1200 BRect lhit(0, frame.top, frame.left, frame.bottom); 1201 if (lhit.Contains(where)) { 1202 // Want to scroll by NUMSLOTS-1 slots 1203 int32 previousIndex = fManager->CurrentIndex(); 1204 int32 previousSlot = fManager->CurrentSlot(); 1205 int32 newSlot = previousSlot - (kNumSlots - 1); 1206 if (newSlot < 0) 1207 newSlot = 0; 1208 int32 newIndex = fIconView->IndexAt(newSlot); 1209 1210 fManager->SwitchToApp(previousIndex, newIndex, false); 1211 } 1212 } 1213 1214 if (fRightScroller) { 1215 BRect rhit(frame.right, frame.top, bounds.right, frame.bottom); 1216 if (rhit.Contains(where)) { 1217 // Want to scroll by NUMSLOTS-1 slots 1218 int32 previousIndex = fManager->CurrentIndex(); 1219 int32 previousSlot = fManager->CurrentSlot(); 1220 int32 newSlot = previousSlot + (kNumSlots-1); 1221 int32 newIndex = fIconView->IndexAt(newSlot); 1222 1223 if (newIndex < 0) { 1224 // don't have a page full to scroll 1225 int32 valid = fManager->CountVisibleGroups(); 1226 newIndex = fIconView->IndexAt(valid-1); 1227 } 1228 fManager->SwitchToApp(previousIndex, newIndex, true); 1229 } 1230 } 1231 1232 frame = fWindow->WindowView()->Frame(); 1233 if (fUpScroller) { 1234 BRect hit1(frame.left - 10, frame.top, frame.left, (frame.top+frame.bottom)/2); 1235 BRect hit2(frame.right, frame.top, frame.right + 10, (frame.top+frame.bottom)/2); 1236 if (hit1.Contains(where) || hit2.Contains(where)) { 1237 // Want to scroll up 1 window 1238 fManager->CycleWindow(false, false); 1239 } 1240 } 1241 1242 if (fDownScroller) { 1243 BRect hit1(frame.left - 10, (frame.top+frame.bottom) / 2, frame.left, frame.bottom); 1244 BRect hit2(frame.right, (frame.top+frame.bottom) / 2, frame.right + 10, frame.bottom); 1245 if (hit1.Contains(where) || hit2.Contains(where)) { 1246 // Want to scroll down 1 window 1247 fManager->CycleWindow(true, false); 1248 } 1249 } 1250 } 1251 1252 1253 void 1254 TBox::Draw(BRect update) 1255 { 1256 static const int32 kChildInset = 7; 1257 static const int32 kWedge = 6; 1258 1259 BBox::Draw(update); 1260 1261 // The fancy border around the icon view 1262 1263 BRect bounds = Bounds(); 1264 float height = fIconView->Bounds().Height(); 1265 float center = (bounds.right + bounds.left) / 2; 1266 1267 BRect box(3, 3, bounds.right - 3, 3 + height + kChildInset * 2); 1268 rgb_color white = {255,255,255,255}; 1269 rgb_color standardGray = ui_color(B_PANEL_BACKGROUND_COLOR); 1270 rgb_color veryDarkGray = {128, 128, 128, 255}; 1271 rgb_color darkGray = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_DARKEN_1_TINT); 1272 1273 // Fill the area with dark gray 1274 SetHighColor(darkGray); 1275 box.InsetBy(1,1); 1276 FillRect(box); 1277 1278 box.InsetBy(-1,-1); 1279 1280 BeginLineArray(50); 1281 1282 // The main frame around the icon view 1283 AddLine(box.LeftTop(), BPoint(center-kWedge, box.top), veryDarkGray); 1284 AddLine(BPoint(center+kWedge, box.top), box.RightTop(), veryDarkGray); 1285 1286 AddLine(box.LeftBottom(), BPoint(center-kWedge, box.bottom), veryDarkGray); 1287 AddLine(BPoint(center+kWedge, box.bottom), box.RightBottom(), veryDarkGray); 1288 AddLine(box.LeftBottom() + BPoint(1, 1), 1289 BPoint(center-kWedge, box.bottom + 1), white); 1290 AddLine(BPoint(center+kWedge, box.bottom) + BPoint(0, 1), 1291 box.RightBottom() + BPoint(1, 1), white); 1292 1293 AddLine(box.LeftTop(), box.LeftBottom(), veryDarkGray); 1294 AddLine(box.RightTop(), box.RightBottom(), veryDarkGray); 1295 AddLine(box.RightTop() + BPoint(1, 1), 1296 box.RightBottom() + BPoint(1, 1), white); 1297 1298 // downward pointing area at top of frame 1299 BPoint point(center - kWedge, box.top); 1300 AddLine(point, point + BPoint(kWedge, kWedge), veryDarkGray); 1301 AddLine(point + BPoint(kWedge, kWedge), 1302 BPoint(center+kWedge, point.y), veryDarkGray); 1303 1304 AddLine(point + BPoint(1, 0), 1305 point + BPoint(1, 0) + BPoint(kWedge - 1, kWedge - 1), white); 1306 1307 AddLine(point + BPoint(2, -1) + BPoint(kWedge - 1, kWedge - 1), 1308 BPoint(center+kWedge-1, point.y), darkGray); 1309 1310 BPoint topPoint = point; 1311 1312 // upward pointing area at bottom of frame 1313 point.y = box.bottom; 1314 point.x = center - kWedge; 1315 AddLine(point, point + BPoint(kWedge, -kWedge), veryDarkGray); 1316 AddLine(point + BPoint(kWedge, -kWedge), 1317 BPoint(center+kWedge, point.y), veryDarkGray); 1318 1319 AddLine(point + BPoint(1, 0), 1320 point + BPoint(1, 0) + BPoint(kWedge - 1, -(kWedge - 1)), white); 1321 1322 AddLine(point + BPoint(2 , 1) + BPoint(kWedge - 1, -(kWedge - 1)), 1323 BPoint(center + kWedge - 1, point.y), darkGray); 1324 1325 BPoint bottomPoint = point; 1326 1327 EndLineArray(); 1328 1329 // fill the downward pointing arrow area 1330 SetHighColor(standardGray); 1331 FillTriangle(topPoint + BPoint(2, 0), 1332 topPoint + BPoint(2, 0) + BPoint(kWedge - 2, kWedge - 2), 1333 BPoint(center + kWedge - 2, topPoint.y)); 1334 1335 // fill the upward pointing arrow area 1336 SetHighColor(standardGray); 1337 FillTriangle(bottomPoint + BPoint(2,0), 1338 bottomPoint + BPoint(2, 0) + BPoint(kWedge - 2, -(kWedge - 2)), 1339 BPoint(center + kWedge - 2, bottomPoint.y)); 1340 1341 DrawIconScrollers(false); 1342 DrawWindowScrollers(false); 1343 1344 } 1345 1346 1347 void 1348 TBox::DrawIconScrollers(bool force) 1349 { 1350 bool updateLeft = false; 1351 bool updateRight = false; 1352 rgb_color leftc; 1353 rgb_color rightc; 1354 rgb_color bkg = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_DARKEN_1_TINT); 1355 rgb_color dark = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_DARKEN_4_TINT); 1356 1357 BRect rect = fIconView->Bounds(); 1358 if (rect.left > (kSlotSize * kCenterSlot)) { 1359 updateLeft = true; 1360 fLeftScroller = true; 1361 leftc = dark; 1362 } else { 1363 fLeftScroller = false; 1364 if (force) { 1365 updateLeft = true; 1366 leftc = bkg; 1367 } 1368 } 1369 1370 int32 maxIndex = fManager->GroupList()->CountItems() - 1; 1371 // last_frame is in fIconView coordinate space 1372 BRect lastFrame = fIconView->FrameOf(maxIndex); 1373 1374 if (lastFrame.right > rect.right) { 1375 updateRight = true; 1376 fRightScroller = true; 1377 rightc = dark; 1378 } else { 1379 fRightScroller = false; 1380 if (force) { 1381 updateRight = true; 1382 rightc = bkg; 1383 } 1384 } 1385 1386 rect = fIconView->Frame(); 1387 if (updateLeft) { 1388 SetHighColor(leftc); 1389 BPoint pt1, pt2, pt3; 1390 pt1.x = rect.left - 5; 1391 pt1.y = floorf((rect.bottom + rect.top) / 2); 1392 pt2.x = pt3.x = pt1.x + 3; 1393 pt2.y = pt1.y - 3; 1394 pt3.y = pt1.y + 3; 1395 FillTriangle(pt1, pt2, pt3); 1396 } 1397 if (updateRight) { 1398 SetHighColor(rightc); 1399 BPoint pt1, pt2, pt3; 1400 pt1.x = rect.right + 4; 1401 pt1.y = rintf((rect.bottom + rect.top) / 2); 1402 pt2.x = pt3.x = pt1.x - 4; 1403 pt2.y = pt1.y - 4; 1404 pt3.y = pt1.y + 4; 1405 FillTriangle(pt1, pt2, pt3); 1406 } 1407 } 1408 1409 1410 void 1411 TBox::DrawWindowScrollers(bool force) 1412 { 1413 bool updateUp = false; 1414 bool updateDown = false; 1415 rgb_color upColor; 1416 rgb_color downColor; 1417 rgb_color bkg = ui_color(B_PANEL_BACKGROUND_COLOR); 1418 rgb_color dark = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_DARKEN_4_TINT); 1419 1420 BRect rect = fWindow->WindowView()->Bounds(); 1421 if (rect.top != 0) { 1422 updateUp = true; 1423 fUpScroller = true; 1424 upColor = dark; 1425 } else { 1426 fUpScroller = false; 1427 if (force) { 1428 updateUp = true; 1429 upColor = bkg; 1430 } 1431 } 1432 1433 int32 groupIndex = fManager->CurrentIndex(); 1434 int32 maxIndex = fManager->CountWindows(groupIndex) - 1; 1435 1436 BRect lastFrame(0, 0, 0, 0); 1437 if (maxIndex >= 0) { 1438 lastFrame = fWindow->WindowView()->FrameOf(maxIndex); 1439 } 1440 if (maxIndex >= 0 && lastFrame.bottom > rect.bottom) { 1441 updateDown = true; 1442 fDownScroller = true; 1443 downColor = dark; 1444 } else { 1445 fDownScroller = false; 1446 if (force) { 1447 updateDown = true; 1448 downColor = bkg; 1449 } 1450 } 1451 1452 rect = fWindow->WindowView()->Frame(); 1453 rect.InsetBy(-3, 0); 1454 if (updateUp) { 1455 SetHighColor(upColor); 1456 BPoint pt1, pt2, pt3; 1457 pt1.x = rect.left - 6; 1458 pt1.y = rect.top + 3; 1459 pt2.y = pt3.y = pt1.y + 4; 1460 pt2.x = pt1.x - 4; 1461 pt3.x = pt1.x + 4; 1462 FillTriangle(pt1, pt2, pt3); 1463 1464 pt1.x += rect.Width() + 12; 1465 pt2.x += rect.Width() + 12; 1466 pt3.x += rect.Width() + 12; 1467 FillTriangle(pt1, pt2, pt3); 1468 } 1469 if (updateDown) { 1470 SetHighColor(downColor); 1471 BPoint pt1, pt2, pt3; 1472 pt1.x = rect.left - 6; 1473 pt1.y = rect.bottom - 3; 1474 pt2.y = pt3.y = pt1.y - 4; 1475 pt2.x = pt1.x - 4; 1476 pt3.x = pt1.x + 4; 1477 FillTriangle(pt1, pt2, pt3); 1478 1479 pt1.x += rect.Width() + 12; 1480 pt2.x += rect.Width() + 12; 1481 pt3.x += rect.Width() + 12; 1482 FillTriangle(pt1, pt2, pt3); 1483 1484 } 1485 Sync(); 1486 } 1487 1488 1489 // #pragma mark - 1490 1491 1492 TSwitcherWindow::TSwitcherWindow(BRect frame, TSwitchManager *manager) 1493 : BWindow(frame, "Twitcher", B_MODAL_WINDOW_LOOK, 1494 B_MODAL_ALL_WINDOW_FEEL, 1495 B_NOT_MINIMIZABLE | B_NOT_ZOOMABLE | B_NOT_RESIZABLE, B_ALL_WORKSPACES), 1496 fManager(manager), 1497 fHairTrigger(true) 1498 { 1499 BRect rect = frame; 1500 rect.OffsetTo(B_ORIGIN); 1501 rect.InsetBy(kHorizontalMargin, 0); 1502 rect.top = kVerticalMargin; 1503 rect.bottom = rect.top + kSlotSize - 1; 1504 1505 fIconView = new TIconView(rect, manager, this); 1506 1507 rect.top = rect.bottom + (kVerticalMargin * 1 + 4); 1508 rect.InsetBy(9, 0); 1509 1510 fWindowView = new TWindowView(rect, manager, this); 1511 fWindowView->ResizeToPreferred(); 1512 1513 fTopView = new TBox(Bounds(), fManager, this, fIconView); 1514 AddChild(fTopView); 1515 1516 SetPulseRate(0); 1517 fTopView->AddChild(fIconView); 1518 fTopView->AddChild(fWindowView); 1519 } 1520 1521 1522 TSwitcherWindow::~TSwitcherWindow() 1523 { 1524 } 1525 1526 1527 void 1528 TSwitcherWindow::MessageReceived(BMessage *message) 1529 { 1530 switch (message->what) { 1531 case B_KEY_DOWN: 1532 { 1533 int32 repeats = 0; 1534 if (message->FindInt32("be:key_repeat", &repeats) == B_OK 1535 && (fSkipKeyRepeats || (repeats % 6) != 0)) 1536 break; 1537 1538 // The first actual key press let's us listening to repeated keys 1539 fSkipKeyRepeats = false; 1540 1541 uint32 rawChar; 1542 uint32 modifiers; 1543 message->FindInt32("raw_char", 0, (int32 *)&rawChar); 1544 message->FindInt32("modifiers", 0, (int32 *)&modifiers); 1545 DoKey(rawChar, modifiers); 1546 break; 1547 } 1548 1549 default: 1550 BWindow::MessageReceived(message); 1551 } 1552 } 1553 1554 1555 void 1556 TSwitcherWindow::Redraw(int32 index) 1557 { 1558 BRect frame = fIconView->FrameOf(index); 1559 frame.right = fIconView->Bounds().right; 1560 fIconView->Invalidate(frame); 1561 } 1562 1563 1564 void 1565 TSwitcherWindow::DoKey(uint32 key, uint32 modifiers) 1566 { 1567 bool forward = ((modifiers & B_SHIFT_KEY) == 0); 1568 1569 switch (key) { 1570 case B_RIGHT_ARROW: 1571 case '`': 1572 fManager->CycleApp(true, false); 1573 break; 1574 1575 case B_LEFT_ARROW: 1576 case '~': 1577 fManager->CycleApp(false, false); 1578 break; 1579 1580 case B_UP_ARROW: 1581 fManager->CycleWindow(false, false); 1582 break; 1583 1584 case B_DOWN_ARROW: 1585 fManager->CycleWindow(true, false); 1586 break; 1587 1588 case B_TAB: 1589 fManager->CycleApp(forward, false); 1590 break; 1591 1592 case B_ESCAPE: 1593 fManager->Stop(false, 0); 1594 break; 1595 1596 case B_SPACE: 1597 case B_ENTER: 1598 fManager->Stop(true, modifiers); 1599 break; 1600 1601 case 'q': 1602 case 'Q': 1603 fManager->QuitApp(); 1604 break; 1605 1606 case 'h': 1607 case 'H': 1608 fManager->HideApp(); 1609 break; 1610 1611 #if _ALLOW_STICKY_ 1612 case 's': 1613 case 'S': 1614 if (fHairTrigger) { 1615 SetLook(B_TITLED_WINDOW_LOOK); 1616 fHairTrigger = false; 1617 } else { 1618 SetLook(B_MODAL_WINDOW_LOOK); 1619 fHairTrigger = true; 1620 } 1621 break; 1622 #endif 1623 } 1624 } 1625 1626 1627 bool 1628 TSwitcherWindow::QuitRequested() 1629 { 1630 ((TBarApp *) be_app)->Settings()->switcherLoc = Frame().LeftTop(); 1631 fManager->Stop(false, 0); 1632 return false; 1633 } 1634 1635 1636 void 1637 TSwitcherWindow::WindowActivated(bool state) 1638 { 1639 if (state) 1640 fManager->Unblock(); 1641 } 1642 1643 1644 void 1645 TSwitcherWindow::Update(int32 prev, int32 current, int32 previousSlot, 1646 int32 currentSlot, bool forward) 1647 { 1648 if (!IsHidden()) 1649 fIconView->Update(prev, current, previousSlot, currentSlot, forward); 1650 else 1651 fIconView->CenterOn(current); 1652 1653 fWindowView->UpdateGroup(current, 0); 1654 } 1655 1656 1657 void 1658 TSwitcherWindow::Hide() 1659 { 1660 fIconView->Hiding(); 1661 SetPulseRate(0); 1662 BWindow::Hide(); 1663 } 1664 1665 1666 void 1667 TSwitcherWindow::Show() 1668 { 1669 fHairTrigger = true; 1670 fSkipKeyRepeats = true; 1671 fIconView->Showing(); 1672 SetPulseRate(100000); 1673 SetLook(B_MODAL_WINDOW_LOOK); 1674 BWindow::Show(); 1675 } 1676 1677 1678 TBox * 1679 TSwitcherWindow::TopView() 1680 { 1681 return fTopView; 1682 } 1683 1684 1685 bool 1686 TSwitcherWindow::HairTrigger() 1687 { 1688 return fHairTrigger; 1689 } 1690 1691 1692 inline int32 1693 TSwitcherWindow::SlotOf(int32 i) 1694 { 1695 return fIconView->SlotOf(i); 1696 } 1697 1698 1699 inline TIconView * 1700 TSwitcherWindow::IconView() 1701 { 1702 return fIconView; 1703 } 1704 1705 1706 inline TWindowView * 1707 TSwitcherWindow::WindowView() 1708 { 1709 return fWindowView; 1710 } 1711 1712 1713 // #pragma mark - 1714 1715 1716 TIconView::TIconView(BRect frame, TSwitchManager *manager, TSwitcherWindow *switcherWindow) 1717 : BView(frame, "main_view", B_FOLLOW_NONE, 1718 B_WILL_DRAW | B_PULSE_NEEDED), 1719 fAutoScrolling(false), 1720 fSwitcher(switcherWindow), 1721 fManager(manager) 1722 { 1723 BRect rect(0, 0, kSlotSize - 1, kSlotSize - 1); 1724 rgb_color color = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_DARKEN_1_TINT); 1725 1726 fOffView = new BView(rect, "off_view", B_FOLLOW_NONE, B_WILL_DRAW); 1727 fOffView->SetHighColor(color); 1728 fOffBitmap = new BBitmap(rect, B_RGB32, true); 1729 fOffBitmap->AddChild(fOffView); 1730 1731 fCurrentSmall = new BBitmap(BRect(0, 0, 15, 15), kIconFormat); 1732 fCurrentLarge = new BBitmap(BRect(0, 0, 31, 31), kIconFormat); 1733 1734 SetViewColor(color); 1735 SetLowColor(color); 1736 } 1737 1738 1739 TIconView::~TIconView() 1740 { 1741 delete fCurrentSmall; 1742 delete fCurrentLarge; 1743 delete fOffBitmap; 1744 } 1745 1746 1747 void 1748 TIconView::KeyDown(const char *, int32) 1749 { 1750 } 1751 1752 1753 void 1754 TIconView::CacheIcons(TTeamGroup *teamGroup) 1755 { 1756 const BBitmap *bitmap = teamGroup->SmallIcon(); 1757 ASSERT(bitmap); 1758 fCurrentSmall->SetBits(bitmap->Bits(), bitmap->BitsLength(), 0, 1759 bitmap->ColorSpace()); 1760 1761 bitmap = teamGroup->LargeIcon(); 1762 ASSERT(bitmap); 1763 fCurrentLarge->SetBits(bitmap->Bits(), bitmap->BitsLength(), 0, 1764 bitmap->ColorSpace()); 1765 } 1766 1767 1768 void 1769 TIconView::AnimateIcon(BBitmap *startIcon, BBitmap *endIcon) 1770 { 1771 BRect centerRect(kCenterSlot*kSlotSize, 0, 1772 (kCenterSlot + 1) * kSlotSize - 1, kSlotSize - 1); 1773 BRect startIconBounds = startIcon->Bounds(); 1774 BRect bounds = Bounds(); 1775 float width = startIconBounds.Width(); 1776 int32 amount = (width < 20) ? -2 : 2; 1777 1778 // center the starting icon inside of centerRect 1779 float off = (centerRect.Width() - width) / 2; 1780 startIconBounds.OffsetTo(BPoint(off, off)); 1781 1782 // scroll the centerRect to correct location 1783 centerRect.OffsetBy(bounds.left, 0); 1784 1785 BRect destRect = fOffBitmap->Bounds(); 1786 // scroll to the centerRect location 1787 destRect.OffsetTo(centerRect.left, 0); 1788 // center the destRect inside of centerRect. 1789 off = (centerRect.Width() - destRect.Width()) / 2; 1790 destRect.OffsetBy(BPoint(off, off)); 1791 1792 fOffBitmap->Lock(); 1793 fOffView->SetDrawingMode(B_OP_ALPHA); 1794 for (int i = 0; i < 2; i++) { 1795 startIconBounds.InsetBy(amount,amount); 1796 snooze(20000); 1797 fOffView->FillRect(fOffView->Bounds()); 1798 fOffView->DrawBitmap(startIcon, startIconBounds); 1799 fOffView->Sync(); 1800 DrawBitmap(fOffBitmap, destRect); 1801 } 1802 for (int i = 0; i < 2; i++) { 1803 startIconBounds.InsetBy(amount,amount); 1804 snooze(20000); 1805 fOffView->FillRect(fOffView->Bounds()); 1806 fOffView->DrawBitmap(endIcon, startIconBounds); 1807 fOffView->Sync(); 1808 DrawBitmap(fOffBitmap, destRect); 1809 } 1810 1811 fOffView->SetDrawingMode(B_OP_COPY); 1812 fOffBitmap->Unlock(); 1813 } 1814 1815 1816 void 1817 TIconView::Update(int32, int32 current, int32 previousSlot, int32 currentSlot, 1818 bool forward) 1819 { 1820 // Animate the shrinking of the currently centered icon. 1821 AnimateIcon(fCurrentLarge, fCurrentSmall); 1822 1823 int32 nslots = abs(previousSlot - currentSlot); 1824 int32 stepSize = kScrollStep; 1825 1826 if (forward && (currentSlot < previousSlot)) { 1827 // we were at the end of the list and we just moved to the start 1828 forward = false; 1829 if (previousSlot - currentSlot > 4) 1830 stepSize *= 2; 1831 } else if (!forward && (currentSlot > previousSlot)) { 1832 // we're are moving backwards and we just hit start of list and 1833 // we wrapped to the end. 1834 forward = true; 1835 if (currentSlot - previousSlot > 4) 1836 stepSize *= 2; 1837 } 1838 1839 int32 scrollValue = forward ? stepSize : -stepSize; 1840 int32 total = 0; 1841 1842 fAutoScrolling = true; 1843 while (total < (nslots * kSlotSize)) { 1844 ScrollBy(scrollValue, 0); 1845 snooze(1000); 1846 total += stepSize; 1847 Window()->UpdateIfNeeded(); 1848 } 1849 fAutoScrolling = false; 1850 1851 TTeamGroup *teamGroup = (TTeamGroup *)fManager->GroupList()->ItemAt(current); 1852 ASSERT(teamGroup); 1853 CacheIcons(teamGroup); 1854 1855 // Animate the expansion of the currently centered icon 1856 AnimateIcon(fCurrentSmall, fCurrentLarge); 1857 } 1858 1859 1860 void 1861 TIconView::CenterOn(int32 index) 1862 { 1863 BRect rect = FrameOf(index); 1864 ScrollTo(rect.left - (kCenterSlot * kSlotSize), 0); 1865 } 1866 1867 1868 int32 1869 TIconView::ItemAtPoint(BPoint point) const 1870 { 1871 float tmpPointVerticalIndex = (point.x / kSlotSize) - kCenterSlot; 1872 if (tmpPointVerticalIndex < 0) 1873 return -1; 1874 1875 int32 pointVerticalIndex = (int32)tmpPointVerticalIndex; 1876 1877 for (int32 i = 0, verticalIndex = 0; ; i++) { 1878 1879 TTeamGroup *teamGroup = (TTeamGroup *)fManager->GroupList()->ItemAt(i); 1880 if (teamGroup == NULL) 1881 break; 1882 1883 if (!OKToUse(teamGroup)) 1884 continue; 1885 1886 if (verticalIndex == pointVerticalIndex) 1887 return i; 1888 1889 verticalIndex++; 1890 } 1891 return -1; 1892 } 1893 1894 1895 void 1896 TIconView::ScrollTo(BPoint where) 1897 { 1898 BView::ScrollTo(where); 1899 fSwitcher->TopView()->DrawIconScrollers(true); 1900 } 1901 1902 1903 int32 1904 TIconView::IndexAt(int32 slot) const 1905 { 1906 BList *list = fManager->GroupList(); 1907 int32 count = list->CountItems(); 1908 int32 slotIndex = 0; 1909 1910 for (int32 i = 0; i < count; i++) { 1911 TTeamGroup *teamGroup = (TTeamGroup *)list->ItemAt(i); 1912 1913 if (!OKToUse(teamGroup)) 1914 continue; 1915 1916 if (slotIndex == slot) { 1917 return i; 1918 } 1919 slotIndex++; 1920 } 1921 return -1; 1922 } 1923 1924 1925 int32 1926 TIconView::SlotOf(int32 index) const 1927 { 1928 BRect rect = FrameOf(index); 1929 return (int32)(rect.left / kSlotSize) - kCenterSlot; 1930 } 1931 1932 1933 BRect 1934 TIconView::FrameOf(int32 index) const 1935 { 1936 BList *list = fManager->GroupList(); 1937 int32 visible = kCenterSlot - 1; 1938 // first few slots in view are empty 1939 1940 TTeamGroup *teamGroup; 1941 for (int32 i = 0; i <= index; i++) { 1942 teamGroup = (TTeamGroup *)list->ItemAt(i); 1943 1944 if (!OKToUse(teamGroup)) 1945 continue; 1946 1947 visible++; 1948 } 1949 1950 return BRect(visible * kSlotSize, 0, (visible + 1) * kSlotSize - 1, kSlotSize - 1); 1951 } 1952 1953 1954 void 1955 TIconView::DrawTeams(BRect update) 1956 { 1957 int32 mainIndex = fManager->CurrentIndex(); 1958 BList *list = fManager->GroupList(); 1959 int32 count = list->CountItems(); 1960 1961 BRect rect(kCenterSlot * kSlotSize, 0, 1962 (kCenterSlot + 1) * kSlotSize - 1, kSlotSize - 1); 1963 1964 for (int32 i = 0; i < count; i++) { 1965 TTeamGroup *teamGroup = (TTeamGroup *) list->ItemAt(i); 1966 1967 if (!OKToUse(teamGroup)) 1968 continue; 1969 1970 if (rect.Intersects(update) && teamGroup) { 1971 #ifdef __HAIKU__ 1972 SetDrawingMode(B_OP_ALPHA); 1973 SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY); 1974 #else 1975 SetDrawingMode(B_OP_OVER); 1976 #endif 1977 teamGroup->Draw(this, rect, !fAutoScrolling && (i == mainIndex)); 1978 1979 if (i == mainIndex) 1980 CacheIcons(teamGroup); 1981 1982 SetDrawingMode(B_OP_COPY); 1983 } 1984 rect.OffsetBy(kSlotSize,0); 1985 } 1986 } 1987 1988 1989 void 1990 TIconView::Draw(BRect update) 1991 { 1992 DrawTeams(update); 1993 } 1994 1995 1996 void 1997 TIconView::MouseDown(BPoint where) 1998 { 1999 int32 index = ItemAtPoint(where); 2000 if (index >= 0) { 2001 int32 previousIndex = fManager->CurrentIndex(); 2002 int32 previousSlot = fManager->CurrentSlot(); 2003 int32 currentSlot = SlotOf(index); 2004 fManager->SwitchToApp(previousIndex, index, (currentSlot > previousSlot)); 2005 } 2006 } 2007 2008 2009 void 2010 TIconView::Pulse() 2011 { 2012 uint32 modifiersKeys = modifiers(); 2013 if (fSwitcher->HairTrigger() && (modifiersKeys & B_CONTROL_KEY) == 0) { 2014 fManager->Stop(true, modifiersKeys); 2015 return; 2016 } 2017 2018 if (!fSwitcher->HairTrigger()) { 2019 uint32 buttons; 2020 BPoint point; 2021 GetMouse(&point, &buttons); 2022 if (buttons != 0) { 2023 point = ConvertToScreen(point); 2024 if (!Window()->Frame().Contains(point)) 2025 fManager->Stop(false, 0); 2026 } 2027 } 2028 } 2029 2030 2031 void 2032 TIconView::Showing() 2033 { 2034 } 2035 2036 2037 void 2038 TIconView::Hiding() 2039 { 2040 ScrollTo(B_ORIGIN); 2041 } 2042 2043 2044 // #pragma mark - 2045 2046 2047 TWindowView::TWindowView(BRect rect, TSwitchManager *manager, TSwitcherWindow *window) 2048 : BView(rect, "wlist_view", B_FOLLOW_NONE, B_WILL_DRAW | B_PULSE_NEEDED), 2049 fCurrentToken(-1), 2050 fSwitcher(window), 2051 fManager(manager) 2052 { 2053 SetFont(be_plain_font); 2054 } 2055 2056 2057 void 2058 TWindowView::AttachedToWindow() 2059 { 2060 if (Parent()) 2061 SetViewColor(Parent()->ViewColor()); 2062 else 2063 SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 2064 } 2065 2066 2067 void 2068 TWindowView::ScrollTo(BPoint where) 2069 { 2070 BView::ScrollTo(where); 2071 fSwitcher->TopView()->DrawWindowScrollers(true); 2072 } 2073 2074 2075 2076 BRect 2077 TWindowView::FrameOf(int32 index) const 2078 { 2079 return BRect(0, index * fItemHeight, 100, ((index + 1) * fItemHeight) - 1); 2080 } 2081 2082 2083 const int32 kWindowScrollSteps = 3; 2084 2085 void 2086 TWindowView::GetPreferredSize(float *_width, float *_height) 2087 { 2088 font_height fh; 2089 be_plain_font->GetHeight(&fh); 2090 fItemHeight = (int32) fh.ascent + fh.descent; 2091 2092 // top & bottom margin 2093 fItemHeight = fItemHeight + 3 + 3; 2094 2095 // want fItemHeight to be divisible by kWindowScrollSteps. 2096 fItemHeight = ((((int) fItemHeight) + kWindowScrollSteps) / kWindowScrollSteps) 2097 * kWindowScrollSteps; 2098 2099 *_height = fItemHeight; 2100 2101 // leave width alone 2102 *_width = Bounds().Width(); 2103 } 2104 2105 2106 void 2107 TWindowView::ShowIndex(int32 newIndex) 2108 { 2109 // convert index to scroll location 2110 BPoint point(0, newIndex * fItemHeight); 2111 BRect bounds = Bounds(); 2112 2113 int32 groupIndex = fManager->CurrentIndex(); 2114 TTeamGroup *teamGroup = (TTeamGroup *)fManager->GroupList()->ItemAt(groupIndex); 2115 if (!teamGroup) 2116 return; 2117 2118 window_info *windowInfo = fManager->WindowInfo(groupIndex, newIndex); 2119 if (windowInfo == NULL) 2120 return; 2121 2122 fCurrentToken = windowInfo->id; 2123 free(windowInfo); 2124 2125 if (bounds.top == point.y) 2126 return; 2127 2128 int32 oldIndex = (int32) (bounds.top / fItemHeight); 2129 2130 int32 stepSize = (int32) (fItemHeight / kWindowScrollSteps); 2131 int32 scrollValue = (newIndex > oldIndex) ? stepSize : -stepSize; 2132 int32 total = 0; 2133 int32 nslots = abs(newIndex - oldIndex); 2134 2135 while (total < (nslots * (int32)fItemHeight)) { 2136 ScrollBy(0, scrollValue); 2137 snooze(10000); 2138 total += stepSize; 2139 Window()->UpdateIfNeeded(); 2140 } 2141 } 2142 2143 2144 void 2145 TWindowView::Draw(BRect update) 2146 { 2147 int32 groupIndex = fManager->CurrentIndex(); 2148 TTeamGroup *teamGroup = (TTeamGroup *) fManager->GroupList()->ItemAt(groupIndex); 2149 if (!teamGroup) 2150 return; 2151 2152 BRect bounds = Bounds(); 2153 int32 windowIndex = (int32) (bounds.top / fItemHeight); 2154 BRect windowRect = bounds; 2155 2156 windowRect.top = windowIndex * fItemHeight; 2157 windowRect.bottom = ((windowIndex+1) * fItemHeight) - 1; 2158 2159 for (int32 i = 0; i < 3; i++) { 2160 if (!update.Intersects(windowRect)) { 2161 windowIndex++; 2162 windowRect.OffsetBy(0, fItemHeight); 2163 continue; 2164 } 2165 2166 // is window in current workspace? 2167 2168 bool local = true; 2169 bool minimized = false; 2170 BString title; 2171 2172 window_info *windowInfo = fManager->WindowInfo(groupIndex, windowIndex); 2173 if (windowInfo != NULL) { 2174 if (SmartStrcmp(windowInfo->name, teamGroup->Name()) != 0) 2175 title << teamGroup->Name() << ": " << windowInfo->name; 2176 else 2177 title = teamGroup->Name(); 2178 2179 int32 currentWorkspace = current_workspace(); 2180 if ((windowInfo->workspaces & (1 << currentWorkspace)) == 0) 2181 local = false; 2182 2183 minimized = windowInfo->is_mini; 2184 free(windowInfo); 2185 } else 2186 title = teamGroup->Name(); 2187 2188 if (!title.Length()) 2189 return; 2190 2191 float stringWidth = StringWidth(title.String()); 2192 float maxWidth = bounds.Width() - (14 + 5); 2193 2194 if (stringWidth > maxWidth) { 2195 // window title is too long, need to truncate 2196 TruncateString(&title, B_TRUNCATE_END, maxWidth); 2197 stringWidth = maxWidth; 2198 } 2199 2200 BPoint point((bounds.Width() - (stringWidth + 14 + 5)) / 2, windowRect.bottom - 4); 2201 BPoint p(point.x, (windowRect.top + windowRect.bottom) / 2); 2202 SetDrawingMode(B_OP_OVER); 2203 const BBitmap *bitmap = AppResSet()->FindBitmap(B_MESSAGE_TYPE, 2204 minimized ? R_WindowHiddenIcon : R_WindowShownIcon); 2205 p.y -= (bitmap->Bounds().bottom - bitmap->Bounds().top) / 2; 2206 DrawBitmap(bitmap, p); 2207 2208 if (!local) { 2209 SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_DARKEN_4_TINT)); 2210 p.x -= 8; 2211 p.y += 4; 2212 StrokeLine(p + BPoint(2, 2), p + BPoint(2, 2)); 2213 StrokeLine(p + BPoint(4, 2), p + BPoint(6, 2)); 2214 2215 StrokeLine(p + BPoint(0, 5), p + BPoint(0, 5)); 2216 StrokeLine(p + BPoint(2, 5), p + BPoint(6, 5)); 2217 2218 StrokeLine(p + BPoint(1, 8), p + BPoint(1, 8)); 2219 StrokeLine(p + BPoint(3, 8), p + BPoint(6, 8)); 2220 2221 SetHighColor(0, 0, 0); 2222 } 2223 2224 point.x += 21; 2225 MovePenTo(point); 2226 2227 DrawString(title.String()); 2228 SetDrawingMode(B_OP_COPY); 2229 2230 windowIndex++; 2231 windowRect.OffsetBy(0, fItemHeight); 2232 } 2233 } 2234 2235 2236 void 2237 TWindowView::UpdateGroup(int32 , int32 windowIndex) 2238 { 2239 ScrollTo(0, windowIndex * fItemHeight); 2240 Invalidate(Bounds()); 2241 } 2242 2243 2244 void 2245 TWindowView::Pulse() 2246 { 2247 // If selected window went away then reset to first window 2248 window_info *windowInfo = get_window_info(fCurrentToken); 2249 if (windowInfo == NULL) { 2250 Invalidate(); 2251 ShowIndex(0); 2252 } else 2253 free(windowInfo); 2254 } 2255 2256