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