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