1 /* 2 * Copyright 2002-2016, Haiku, Inc. All rights reserved. 3 * Copyright 2002, François Revol, revol@free.fr. 4 * This file is distributed under the terms of the MIT License. 5 * 6 * Authors: 7 * François Revol, revol@free.fr 8 * Axel Dörfler, axeld@pinc-software.de 9 * Oliver "Madison" Kohl, 10 * Matt Madia 11 * Daniel Devine, devine@ddevnet.net 12 */ 13 14 15 #include <AboutWindow.h> 16 #include <Application.h> 17 #include <Catalog.h> 18 #include <Deskbar.h> 19 #include <Dragger.h> 20 #include <Entry.h> 21 #include <File.h> 22 #include <FindDirectory.h> 23 #include <Locale.h> 24 #include <MenuItem.h> 25 #include <Path.h> 26 #include <PopUpMenu.h> 27 #include <Roster.h> 28 #include <Screen.h> 29 #include <TextView.h> 30 #include <Window.h> 31 32 #include <ctype.h> 33 #include <stdio.h> 34 #include <stdlib.h> 35 #include <string.h> 36 37 #include <InterfacePrivate.h> 38 #include <ViewPrivate.h> 39 #include <WindowPrivate.h> 40 41 #undef B_TRANSLATION_CONTEXT 42 #define B_TRANSLATION_CONTEXT "Workspaces" 43 44 45 static const char* kDeskbarItemName = "workspaces"; 46 static const char* kSignature = "application/x-vnd.Be-WORK"; 47 static const char* kDeskbarSignature = "application/x-vnd.Be-TSKB"; 48 static const char* kScreenPrefletSignature = "application/x-vnd.Haiku-Screen"; 49 static const char* kOldSettingFile = "Workspace_data"; 50 static const char* kSettingsFile = "Workspaces_settings"; 51 52 static const uint32 kMsgChangeCount = 'chWC'; 53 static const uint32 kMsgToggleTitle = 'tgTt'; 54 static const uint32 kMsgToggleBorder = 'tgBd'; 55 static const uint32 kMsgToggleAutoRaise = 'tgAR'; 56 static const uint32 kMsgToggleAlwaysOnTop = 'tgAT'; 57 static const uint32 kMsgToggleLiveInDeskbar = 'tgDb'; 58 static const uint32 kMsgToggleSwitchOnWheel = 'tgWh'; 59 60 61 extern "C" _EXPORT BView* instantiate_deskbar_item(); 62 63 64 static status_t 65 OpenSettingsFile(BFile& file, int mode) 66 { 67 BPath path; 68 status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path); 69 if (status != B_OK) 70 status = find_directory(B_SYSTEM_SETTINGS_DIRECTORY, &path); 71 if (status != B_OK) 72 return status; 73 74 status = path.Append(kSettingsFile); 75 if (status != B_OK) 76 return status; 77 78 status = file.SetTo(path.Path(), mode); 79 if (mode == B_READ_ONLY && status == B_ENTRY_NOT_FOUND) { 80 if (find_directory(B_SYSTEM_SETTINGS_DIRECTORY, &path) == B_OK 81 && path.Append(kSettingsFile) == B_OK) { 82 status = file.SetTo(path.Path(), mode); 83 } 84 } 85 86 return status; 87 } 88 89 90 class WorkspacesSettings { 91 public: 92 WorkspacesSettings(); 93 virtual ~WorkspacesSettings(); 94 95 BRect WindowFrame() const { return fWindowFrame; } 96 BRect ScreenFrame() const { return fScreenFrame; } 97 98 bool AutoRaising() const { return fAutoRaising; } 99 bool AlwaysOnTop() const { return fAlwaysOnTop; } 100 bool HasTitle() const { return fHasTitle; } 101 bool HasBorder() const { return fHasBorder; } 102 bool SettingsLoaded() const { return fLoaded; } 103 104 void UpdateFramesForScreen(BRect screenFrame); 105 void UpdateScreenFrame(); 106 107 void SetWindowFrame(BRect); 108 void SetAutoRaising(bool enable) { fAutoRaising = enable; } 109 void SetAlwaysOnTop(bool enable) { fAlwaysOnTop = enable; } 110 void SetHasTitle(bool enable) { fHasTitle = enable; } 111 void SetHasBorder(bool enable) { fHasBorder = enable; } 112 113 private: 114 BRect fWindowFrame; 115 BRect fScreenFrame; 116 bool fAutoRaising; 117 bool fAlwaysOnTop; 118 bool fHasTitle; 119 bool fHasBorder; 120 bool fLoaded; 121 }; 122 123 class WorkspacesView : public BView { 124 public: 125 WorkspacesView(BRect frame, bool showDragger); 126 WorkspacesView(BMessage* archive); 127 ~WorkspacesView(); 128 129 static WorkspacesView* Instantiate(BMessage* archive); 130 virtual status_t Archive(BMessage* archive, bool deep = true) const; 131 132 virtual void AttachedToWindow(); 133 virtual void DetachedFromWindow(); 134 virtual void FrameMoved(BPoint newPosition); 135 virtual void FrameResized(float newWidth, float newHeight); 136 virtual void MessageReceived(BMessage* message); 137 virtual void MouseMoved(BPoint where, uint32 transit, 138 const BMessage* dragMessage); 139 virtual void MouseDown(BPoint where); 140 141 bool SwitchOnWheel() const { return fSwitchOnWheel; } 142 void SetSwitchOnWheel(bool enable); 143 144 private: 145 void _AboutRequested(); 146 147 void _UpdateParentClipping(); 148 void _ExcludeFromParentClipping(); 149 void _CleanupParentClipping(); 150 151 friend class WorkspacesWindow; 152 153 void _LoadSettings(); 154 void _SaveSettings(); 155 156 BView* fParentWhichDrawsOnChildren; 157 BRect fCurrentFrame; 158 bool fSwitchOnWheel; 159 }; 160 161 class WorkspacesWindow : public BWindow { 162 public: 163 WorkspacesWindow(WorkspacesSettings *settings); 164 virtual ~WorkspacesWindow(); 165 166 virtual void ScreenChanged(BRect frame, color_space mode); 167 virtual void FrameMoved(BPoint origin); 168 virtual void FrameResized(float width, float height); 169 virtual void Zoom(BPoint origin, float width, float height); 170 171 virtual void MessageReceived(BMessage *msg); 172 virtual bool QuitRequested(); 173 174 void SetAutoRaise(bool enable); 175 bool IsAutoRaising() const { return fSettings->AutoRaising(); } 176 177 float GetTabHeight() { return fSettings->HasTitle() ? fTabHeight : 0; } 178 float GetBorderWidth() { return fBorderWidth; } 179 float GetScreenBorderOffset() { return 2.0 * fBorderWidth; } 180 181 private: 182 WorkspacesSettings *fSettings; 183 WorkspacesView *fWorkspacesView; 184 float fTabHeight; 185 float fBorderWidth; 186 }; 187 188 class WorkspacesApp : public BApplication { 189 public: 190 WorkspacesApp(); 191 virtual ~WorkspacesApp(); 192 193 virtual void AboutRequested(); 194 virtual void ArgvReceived(int32 argc, char **argv); 195 virtual void ReadyToRun(); 196 197 void Usage(const char *programName); 198 199 private: 200 WorkspacesWindow* fWindow; 201 }; 202 203 204 // #pragma mark - WorkspacesSettings 205 206 207 WorkspacesSettings::WorkspacesSettings() 208 : 209 fAutoRaising(false), 210 fAlwaysOnTop(false), 211 fHasTitle(true), 212 fHasBorder(true), 213 fLoaded(false) 214 { 215 UpdateScreenFrame(); 216 217 BScreen screen; 218 219 BFile file; 220 if (OpenSettingsFile(file, B_READ_ONLY) == B_OK) { 221 BMessage settings; 222 if (settings.Unflatten(&file) == B_OK) { 223 fLoaded = settings.FindRect("window", &fWindowFrame) == B_OK 224 && settings.FindRect("screen", &fScreenFrame) == B_OK; 225 settings.FindBool("auto-raise", &fAutoRaising); 226 settings.FindBool("always on top", &fAlwaysOnTop); 227 if (settings.FindBool("has title", &fHasTitle) != B_OK) 228 fHasTitle = true; 229 if (settings.FindBool("has border", &fHasBorder) != B_OK) 230 fHasBorder = true; 231 } 232 } else { 233 // try reading BeOS compatible settings 234 BPath path; 235 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK) { 236 path.Append(kOldSettingFile); 237 BFile file(path.Path(), B_READ_ONLY); 238 if (file.InitCheck() == B_OK 239 && file.Read(&fWindowFrame, sizeof(BRect)) == sizeof(BRect)) { 240 // we now also store the frame of the screen to know 241 // in which context the window frame has been chosen 242 BRect frame; 243 if (file.Read(&frame, sizeof(BRect)) == sizeof(BRect)) 244 fScreenFrame = frame; 245 else 246 fScreenFrame = screen.Frame(); 247 248 fLoaded = true; 249 } 250 } 251 } 252 253 if (fLoaded) { 254 // if the current screen frame is different from the one 255 // just loaded, we need to alter the window frame accordingly 256 if (fScreenFrame != screen.Frame()) 257 UpdateFramesForScreen(screen.Frame()); 258 } 259 } 260 261 262 WorkspacesSettings::~WorkspacesSettings() 263 { 264 BFile file; 265 if (OpenSettingsFile(file, B_WRITE_ONLY | B_ERASE_FILE | B_CREATE_FILE) 266 != B_OK) { 267 return; 268 } 269 270 // switch on wheel saved by view later on 271 272 BMessage settings('wksp'); 273 if (settings.AddRect("window", fWindowFrame) == B_OK 274 && settings.AddRect("screen", fScreenFrame) == B_OK 275 && settings.AddBool("auto-raise", fAutoRaising) == B_OK 276 && settings.AddBool("always on top", fAlwaysOnTop) == B_OK 277 && settings.AddBool("has title", fHasTitle) == B_OK 278 && settings.AddBool("has border", fHasBorder) == B_OK) { 279 settings.Flatten(&file); 280 } 281 } 282 283 284 void 285 WorkspacesSettings::UpdateFramesForScreen(BRect newScreenFrame) 286 { 287 // don't change the position if the screen frame hasn't changed 288 if (newScreenFrame == fScreenFrame) 289 return; 290 291 // adjust horizontal position 292 if (fWindowFrame.right > fScreenFrame.right / 2) { 293 fWindowFrame.OffsetTo(newScreenFrame.right 294 - (fScreenFrame.right - fWindowFrame.left), fWindowFrame.top); 295 } 296 297 // adjust vertical position 298 if (fWindowFrame.bottom > fScreenFrame.bottom / 2) { 299 fWindowFrame.OffsetTo(fWindowFrame.left, 300 newScreenFrame.bottom - (fScreenFrame.bottom - fWindowFrame.top)); 301 } 302 303 fScreenFrame = newScreenFrame; 304 } 305 306 307 void 308 WorkspacesSettings::UpdateScreenFrame() 309 { 310 BScreen screen; 311 fScreenFrame = screen.Frame(); 312 } 313 314 315 void 316 WorkspacesSettings::SetWindowFrame(BRect frame) 317 { 318 fWindowFrame = frame; 319 } 320 321 322 // #pragma mark - WorkspacesView 323 324 325 WorkspacesView::WorkspacesView(BRect frame, bool showDragger = true) 326 : 327 BView(frame, kDeskbarItemName, B_FOLLOW_ALL, 328 kWorkspacesViewFlag | B_FRAME_EVENTS), 329 fParentWhichDrawsOnChildren(NULL), 330 fCurrentFrame(frame), 331 fSwitchOnWheel(false) 332 { 333 _LoadSettings(); 334 335 if (showDragger) { 336 frame.OffsetTo(B_ORIGIN); 337 frame.top = frame.bottom - 7; 338 frame.left = frame.right - 7; 339 BDragger* dragger = new BDragger(frame, this, 340 B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM); 341 AddChild(dragger); 342 } 343 } 344 345 346 WorkspacesView::WorkspacesView(BMessage* archive) 347 : 348 BView(archive), 349 fParentWhichDrawsOnChildren(NULL), 350 fCurrentFrame(Frame()), 351 fSwitchOnWheel(false) 352 { 353 _LoadSettings(); 354 355 // Just in case we are instantiated from an older archive... 356 SetFlags(Flags() | B_FRAME_EVENTS); 357 // Make sure the auto-raise feature didn't leave any artifacts - this is 358 // not a good idea to keep enabled for a replicant. 359 if (EventMask() != 0) 360 SetEventMask(0); 361 } 362 363 364 WorkspacesView::~WorkspacesView() 365 { 366 _SaveSettings(); 367 } 368 369 370 /*static*/ WorkspacesView* 371 WorkspacesView::Instantiate(BMessage* archive) 372 { 373 if (!validate_instantiation(archive, "WorkspacesView")) 374 return NULL; 375 376 return new WorkspacesView(archive); 377 } 378 379 380 status_t 381 WorkspacesView::Archive(BMessage* archive, bool deep) const 382 { 383 status_t status = BView::Archive(archive, deep); 384 if (status == B_OK) 385 status = archive->AddString("add_on", kSignature); 386 if (status == B_OK) 387 status = archive->AddString("class", "WorkspacesView"); 388 389 return status; 390 } 391 392 393 void 394 WorkspacesView::_AboutRequested() 395 { 396 BAboutWindow* window = new BAboutWindow( 397 B_TRANSLATE_SYSTEM_NAME("Workspaces"), kSignature); 398 399 const char* authors[] = { 400 "Axel Dörfler", 401 "Oliver \"Madison\" Kohl", 402 "Matt Madia", 403 "François Revol", 404 NULL 405 }; 406 407 const char* extraCopyrights[] = { 408 "2002 François Revol", 409 NULL 410 }; 411 412 const char* extraInfo = "Send windows behind using the Option key. " 413 "Move windows to front using the Control key.\n"; 414 415 window->AddCopyright(2002, "Haiku, Inc.", 416 extraCopyrights); 417 window->AddAuthors(authors); 418 window->AddExtraInfo(extraInfo); 419 420 window->Show(); 421 } 422 423 424 void 425 WorkspacesView::AttachedToWindow() 426 { 427 BView* parent = Parent(); 428 if (parent != NULL && (parent->Flags() & B_DRAW_ON_CHILDREN) != 0) { 429 fParentWhichDrawsOnChildren = parent; 430 _ExcludeFromParentClipping(); 431 } 432 } 433 434 435 void 436 WorkspacesView::DetachedFromWindow() 437 { 438 if (fParentWhichDrawsOnChildren != NULL) 439 _CleanupParentClipping(); 440 } 441 442 443 void 444 WorkspacesView::FrameMoved(BPoint newPosition) 445 { 446 _UpdateParentClipping(); 447 } 448 449 450 void 451 WorkspacesView::FrameResized(float newWidth, float newHeight) 452 { 453 _UpdateParentClipping(); 454 } 455 456 457 void 458 WorkspacesView::_UpdateParentClipping() 459 { 460 if (fParentWhichDrawsOnChildren != NULL) { 461 _CleanupParentClipping(); 462 _ExcludeFromParentClipping(); 463 fParentWhichDrawsOnChildren->Invalidate(fCurrentFrame); 464 fCurrentFrame = Frame(); 465 } 466 } 467 468 469 void 470 WorkspacesView::_ExcludeFromParentClipping() 471 { 472 // Prevent the parent view to draw over us. Do so in a way that allows 473 // restoring the parent to the previous state. 474 fParentWhichDrawsOnChildren->PushState(); 475 476 BRegion clipping(fParentWhichDrawsOnChildren->Bounds()); 477 clipping.Exclude(Frame()); 478 fParentWhichDrawsOnChildren->ConstrainClippingRegion(&clipping); 479 } 480 481 482 void 483 WorkspacesView::_CleanupParentClipping() 484 { 485 // Restore the previous parent state. NOTE: This relies on views 486 // being detached in exactly the opposite order as them being 487 // attached. Otherwise we would mess up states if a sibbling view did 488 // the same thing we did in AttachedToWindow()... 489 fParentWhichDrawsOnChildren->PopState(); 490 } 491 492 493 void 494 WorkspacesView::_LoadSettings() 495 { 496 BFile file; 497 if (OpenSettingsFile(file, B_READ_ONLY) == B_OK) { 498 BMessage settings; 499 if (settings.Unflatten(&file) == B_OK) 500 settings.FindBool("switch on wheel", &fSwitchOnWheel); 501 } 502 } 503 504 505 void 506 WorkspacesView::_SaveSettings() 507 { 508 BFile file; 509 if (OpenSettingsFile(file, B_READ_ONLY | B_CREATE_FILE) != B_OK) 510 return; 511 512 BMessage settings('wksp'); 513 settings.Unflatten(&file); 514 515 if (OpenSettingsFile(file, B_WRITE_ONLY | B_ERASE_FILE) != B_OK) 516 return; 517 518 if (settings.ReplaceBool("switch on wheel", fSwitchOnWheel) != B_OK) 519 settings.AddBool("switch on wheel", fSwitchOnWheel); 520 521 settings.Flatten(&file); 522 } 523 524 525 void 526 WorkspacesView::MessageReceived(BMessage* message) 527 { 528 switch (message->what) { 529 case B_ABOUT_REQUESTED: 530 _AboutRequested(); 531 break; 532 533 case B_MOUSE_WHEEL_CHANGED: 534 { 535 if (!fSwitchOnWheel) 536 break; 537 538 float deltaY = message->FindFloat("be:wheel_delta_y"); 539 if (deltaY > 0.1) 540 activate_workspace(current_workspace() + 1); 541 else if (deltaY < -0.1) 542 activate_workspace(current_workspace() - 1); 543 break; 544 } 545 546 case kMsgChangeCount: 547 be_roster->Launch(kScreenPrefletSignature); 548 break; 549 550 case kMsgToggleLiveInDeskbar: 551 { 552 // only actually used from the replicant itself 553 // since HasItem() locks up we just remove directly. 554 BDeskbar deskbar; 555 // we shouldn't do this here actually, but it works for now... 556 deskbar.RemoveItem(kDeskbarItemName); 557 break; 558 } 559 560 case kMsgToggleSwitchOnWheel: 561 { 562 fSwitchOnWheel = !fSwitchOnWheel; 563 break; 564 } 565 566 default: 567 BView::MessageReceived(message); 568 break; 569 } 570 } 571 572 573 void 574 WorkspacesView::MouseMoved(BPoint where, uint32 transit, 575 const BMessage* dragMessage) 576 { 577 WorkspacesWindow* window = dynamic_cast<WorkspacesWindow*>(Window()); 578 if (window == NULL || !window->IsAutoRaising()) 579 return; 580 581 // Auto-Raise 582 583 where = ConvertToScreen(where); 584 BScreen screen(window); 585 BRect screenFrame = screen.Frame(); 586 BRect windowFrame = window->Frame(); 587 float tabHeight = window->GetTabHeight(); 588 float borderWidth = window->GetBorderWidth(); 589 590 if (where.x == screenFrame.left || where.x == screenFrame.right 591 || where.y == screenFrame.top || where.y == screenFrame.bottom) { 592 // cursor is on screen edge 593 594 // Stretch frame to also accept mouse moves over the window borders 595 windowFrame.InsetBy(-borderWidth, -(tabHeight + borderWidth)); 596 597 if (windowFrame.Contains(where)) 598 window->Activate(); 599 } 600 } 601 602 603 void 604 WorkspacesView::MouseDown(BPoint where) 605 { 606 // With enabled auto-raise feature, we'll get mouse messages we don't 607 // want to handle here. 608 if (!Bounds().Contains(where)) 609 return; 610 611 int32 buttons = 0; 612 if (Window() != NULL && Window()->CurrentMessage() != NULL) 613 Window()->CurrentMessage()->FindInt32("buttons", &buttons); 614 615 if ((buttons & B_SECONDARY_MOUSE_BUTTON) == 0) 616 return; 617 618 // open context menu 619 620 BPopUpMenu *menu = new BPopUpMenu(B_EMPTY_STRING, false, false); 621 menu->SetFont(be_plain_font); 622 623 // TODO: alternatively change the count here directly? 624 BMenuItem* changeItem = new BMenuItem(B_TRANSLATE("Change workspace count" 625 B_UTF8_ELLIPSIS), new BMessage(kMsgChangeCount)); 626 menu->AddItem(changeItem); 627 628 BMenuItem* switchItem = new BMenuItem(B_TRANSLATE("Switch on mouse wheel"), 629 new BMessage(kMsgToggleSwitchOnWheel)); 630 menu->AddItem(switchItem); 631 switchItem->SetMarked(fSwitchOnWheel); 632 633 WorkspacesWindow *window = dynamic_cast<WorkspacesWindow*>(Window()); 634 if (window != NULL) { 635 // inside Workspaces app 636 BMenuItem* item; 637 638 menu->AddSeparatorItem(); 639 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Show window tab"), 640 new BMessage(kMsgToggleTitle))); 641 if (window->Look() == B_TITLED_WINDOW_LOOK) 642 item->SetMarked(true); 643 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Show window border"), 644 new BMessage(kMsgToggleBorder))); 645 if (window->Look() == B_TITLED_WINDOW_LOOK 646 || window->Look() == B_MODAL_WINDOW_LOOK) { 647 item->SetMarked(true); 648 } 649 650 menu->AddSeparatorItem(); 651 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Always on top"), 652 new BMessage(kMsgToggleAlwaysOnTop))); 653 if (window->Feel() == B_FLOATING_ALL_WINDOW_FEEL) 654 item->SetMarked(true); 655 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Auto-raise"), 656 new BMessage(kMsgToggleAutoRaise))); 657 if (window->IsAutoRaising()) 658 item->SetMarked(true); 659 if (be_roster->IsRunning(kDeskbarSignature)) { 660 menu->AddItem(item = new BMenuItem( 661 B_TRANSLATE("Live in the Deskbar"), 662 new BMessage(kMsgToggleLiveInDeskbar))); 663 BDeskbar deskbar; 664 item->SetMarked(deskbar.HasItem(kDeskbarItemName)); 665 } 666 667 menu->AddSeparatorItem(); 668 menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"), 669 new BMessage(B_QUIT_REQUESTED))); 670 menu->SetTargetForItems(window); 671 } else { 672 // we're replicated in some way... 673 BMenuItem* item; 674 675 menu->AddSeparatorItem(); 676 677 // check which way 678 BDragger *dragger = dynamic_cast<BDragger*>(ChildAt(0)); 679 if (dragger) { 680 // replicant 681 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Remove replicant"), 682 new BMessage(B_TRASH_TARGET))); 683 item->SetTarget(dragger); 684 } else { 685 // Deskbar item 686 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Remove replicant"), 687 new BMessage(kMsgToggleLiveInDeskbar))); 688 item->SetTarget(this); 689 } 690 } 691 692 changeItem->SetTarget(this); 693 switchItem->SetTarget(this); 694 695 ConvertToScreen(&where); 696 menu->Go(where, true, true, true); 697 } 698 699 700 void 701 WorkspacesView::SetSwitchOnWheel(bool enable) 702 { 703 if (enable == fSwitchOnWheel) 704 return; 705 706 fSwitchOnWheel = enable; 707 } 708 709 710 // #pragma mark - WorkspacesWindow 711 712 713 WorkspacesWindow::WorkspacesWindow(WorkspacesSettings *settings) 714 : 715 BWindow(settings->WindowFrame(), B_TRANSLATE_SYSTEM_NAME("Workspaces"), 716 B_TITLED_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL, 717 B_AVOID_FRONT | B_WILL_ACCEPT_FIRST_CLICK | B_CLOSE_ON_ESCAPE, 718 B_ALL_WORKSPACES), 719 fSettings(settings), 720 fWorkspacesView(NULL) 721 { 722 // Turn window decor on to grab decor widths. 723 BMessage windowSettings; 724 float borderWidth = 0; 725 726 SetLook(B_TITLED_WINDOW_LOOK); 727 if (GetDecoratorSettings(&windowSettings) == B_OK) { 728 BRect tabFrame = windowSettings.FindRect("tab frame"); 729 borderWidth = windowSettings.FindFloat("border width"); 730 fTabHeight = tabFrame.Height(); 731 fBorderWidth = borderWidth; 732 } 733 734 if (!fSettings->SettingsLoaded()) { 735 // No settings, compute a reasonable default frame. 736 // We aim for previews at 10% of actual screen size, and matching the 737 // aspect ratio. We then scale that down, until it fits the screen. 738 // Finally, we put the window on the bottom right of the screen so the 739 // auto-raise mode can be used. 740 741 BScreen screen; 742 743 float screenWidth = screen.Frame().Width(); 744 float screenHeight = screen.Frame().Height(); 745 float aspectRatio = screenWidth / screenHeight; 746 747 uint32 columns, rows; 748 BPrivate::get_workspaces_layout(&columns, &rows); 749 750 // default size of ~1/10 of screen width 751 float workspaceWidth = screenWidth / 10; 752 float workspaceHeight = workspaceWidth / aspectRatio; 753 754 float width = floor(workspaceWidth * columns); 755 float height = floor(workspaceHeight * rows); 756 757 // If you have too many workspaces to fit on the screen, shrink until 758 // they fit. 759 while (width + 2 * borderWidth > screenWidth 760 || height + 2 * borderWidth + GetTabHeight() > screenHeight) { 761 width = floor(0.95 * width); 762 height = floor(0.95 * height); 763 } 764 765 BRect frame = fSettings->ScreenFrame(); 766 frame.OffsetBy(-2.0 * borderWidth, -2.0 * borderWidth); 767 frame.left = frame.right - width; 768 frame.top = frame.bottom - height; 769 ResizeTo(frame.Width(), frame.Height()); 770 771 // Put it in bottom corner by default. 772 MoveTo(screenWidth - frame.Width() - borderWidth, 773 screenHeight - frame.Height() - borderWidth); 774 775 fSettings->SetWindowFrame(frame); 776 } 777 778 if (!fSettings->HasBorder()) 779 SetLook(B_NO_BORDER_WINDOW_LOOK); 780 else if (!fSettings->HasTitle()) 781 SetLook(B_MODAL_WINDOW_LOOK); 782 783 fWorkspacesView = new WorkspacesView(Bounds()); 784 AddChild(fWorkspacesView); 785 786 if (fSettings->AlwaysOnTop()) 787 SetFeel(B_FLOATING_ALL_WINDOW_FEEL); 788 else 789 SetAutoRaise(fSettings->AutoRaising()); 790 } 791 792 793 WorkspacesWindow::~WorkspacesWindow() 794 { 795 delete fSettings; 796 } 797 798 799 void 800 WorkspacesWindow::ScreenChanged(BRect rect, color_space mode) 801 { 802 fSettings->UpdateFramesForScreen(rect); 803 MoveTo(fSettings->WindowFrame().LeftTop()); 804 } 805 806 807 void 808 WorkspacesWindow::FrameMoved(BPoint origin) 809 { 810 fSettings->SetWindowFrame(Frame()); 811 } 812 813 814 void 815 WorkspacesWindow::FrameResized(float width, float height) 816 { 817 if (!(modifiers() & B_SHIFT_KEY)) { 818 BWindow::FrameResized(width, height); 819 return; 820 } 821 822 uint32 columns, rows; 823 BPrivate::get_workspaces_layout(&columns, &rows); 824 825 BScreen screen; 826 float screenWidth = screen.Frame().Width(); 827 float screenHeight = screen.Frame().Height(); 828 829 float windowAspectRatio 830 = (columns * screenWidth) / (rows * screenHeight); 831 832 float newHeight = width / windowAspectRatio; 833 834 if (height != newHeight) 835 ResizeTo(width, newHeight); 836 837 fSettings->SetWindowFrame(Frame()); 838 } 839 840 841 void 842 WorkspacesWindow::Zoom(BPoint origin, float width, float height) 843 { 844 BScreen screen; 845 float screenWidth = screen.Frame().Width(); 846 float screenHeight = screen.Frame().Height(); 847 float aspectRatio = screenWidth / screenHeight; 848 849 uint32 columns, rows; 850 BPrivate::get_workspaces_layout(&columns, &rows); 851 852 float workspaceWidth = Frame().Width() / columns; 853 float workspaceHeight = workspaceWidth / aspectRatio; 854 855 width = floor(workspaceWidth * columns); 856 height = floor(workspaceHeight * rows); 857 858 while (width + 2 * GetScreenBorderOffset() > screenWidth 859 || height + 2 * GetScreenBorderOffset() + GetTabHeight() 860 > screenHeight) { 861 width = floor(0.95 * width); 862 height = floor(0.95 * height); 863 } 864 865 ResizeTo(width, height); 866 867 if (fSettings->AutoRaising()) { 868 // The auto-raising mode makes sense only if the window is positionned 869 // exactly in the bottom-right corner. If the setting is enabled, move 870 // the window there. 871 origin = screen.Frame().RightBottom(); 872 origin.x -= GetScreenBorderOffset() + width; 873 origin.y -= GetScreenBorderOffset() + height; 874 875 MoveTo(origin); 876 } 877 } 878 879 880 void 881 WorkspacesWindow::MessageReceived(BMessage *message) 882 { 883 switch (message->what) { 884 case B_SIMPLE_DATA: 885 { 886 // Drop from Tracker 887 entry_ref ref; 888 for (int i = 0; (message->FindRef("refs", i, &ref) == B_OK); i++) 889 be_roster->Launch(&ref); 890 break; 891 } 892 893 case B_ABOUT_REQUESTED: 894 PostMessage(message, ChildAt(0)); 895 break; 896 897 case kMsgToggleBorder: 898 { 899 bool enable = false; 900 if (Look() == B_NO_BORDER_WINDOW_LOOK) 901 enable = true; 902 903 if (enable) 904 if (fSettings->HasTitle()) 905 SetLook(B_TITLED_WINDOW_LOOK); 906 else 907 SetLook(B_MODAL_WINDOW_LOOK); 908 else 909 SetLook(B_NO_BORDER_WINDOW_LOOK); 910 911 fSettings->SetHasBorder(enable); 912 break; 913 } 914 915 case kMsgToggleTitle: 916 { 917 bool enable = false; 918 if (Look() == B_MODAL_WINDOW_LOOK 919 || Look() == B_NO_BORDER_WINDOW_LOOK) 920 enable = true; 921 922 if (enable) 923 SetLook(B_TITLED_WINDOW_LOOK); 924 else 925 SetLook(B_MODAL_WINDOW_LOOK); 926 927 // No matter what the setting for title, we must force the border on 928 fSettings->SetHasBorder(true); 929 fSettings->SetHasTitle(enable); 930 break; 931 } 932 933 case kMsgToggleAutoRaise: 934 SetAutoRaise(!IsAutoRaising()); 935 SetFeel(B_NORMAL_WINDOW_FEEL); 936 break; 937 938 case kMsgToggleAlwaysOnTop: 939 { 940 bool enable = false; 941 if (Feel() != B_FLOATING_ALL_WINDOW_FEEL) 942 enable = true; 943 944 if (enable) 945 SetFeel(B_FLOATING_ALL_WINDOW_FEEL); 946 else 947 SetFeel(B_NORMAL_WINDOW_FEEL); 948 949 fSettings->SetAlwaysOnTop(enable); 950 break; 951 } 952 953 case kMsgToggleLiveInDeskbar: 954 { 955 BDeskbar deskbar; 956 if (deskbar.HasItem(kDeskbarItemName)) 957 deskbar.RemoveItem(kDeskbarItemName); 958 else { 959 fWorkspacesView->_SaveSettings(); 960 // save "switch on wheel" setting for replicant to load 961 entry_ref ref; 962 be_roster->FindApp(kSignature, &ref); 963 deskbar.AddItem(&ref); 964 } 965 break; 966 } 967 968 default: 969 BWindow::MessageReceived(message); 970 break; 971 } 972 } 973 974 975 bool 976 WorkspacesWindow::QuitRequested() 977 { 978 be_app->PostMessage(B_QUIT_REQUESTED); 979 return true; 980 } 981 982 983 void 984 WorkspacesWindow::SetAutoRaise(bool enable) 985 { 986 fSettings->SetAutoRaising(enable); 987 988 if (enable) 989 ChildAt(0)->SetEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY); 990 else 991 ChildAt(0)->SetEventMask(0); 992 } 993 994 995 // #pragma mark - WorkspacesApp 996 997 998 WorkspacesApp::WorkspacesApp() 999 : BApplication(kSignature) 1000 { 1001 fWindow = new WorkspacesWindow(new WorkspacesSettings()); 1002 } 1003 1004 1005 WorkspacesApp::~WorkspacesApp() 1006 { 1007 } 1008 1009 1010 void 1011 WorkspacesApp::AboutRequested() 1012 { 1013 fWindow->PostMessage(B_ABOUT_REQUESTED); 1014 } 1015 1016 1017 void 1018 WorkspacesApp::Usage(const char *programName) 1019 { 1020 printf(B_TRANSLATE("Usage: %s [options] [workspace]\n" 1021 "where \"options\" are:\n" 1022 " --notitle\t\ttitle bar removed, border and resize kept\n" 1023 " --noborder\t\ttitle, border, and resize removed\n" 1024 " --avoidfocus\t\tprevents the window from being the target of " 1025 "keyboard events\n" 1026 " --alwaysontop\t\tkeeps window on top\n" 1027 " --notmovable\t\twindow can't be moved around\n" 1028 " --autoraise\t\tauto-raise the workspace window when it's at the " 1029 "screen edge\n" 1030 " --help\t\tdisplay this help and exit\n" 1031 "and \"workspace\" is the number of the Workspace to which to switch " 1032 "(0-31)\n"), 1033 programName); 1034 1035 // quit only if we aren't running already 1036 if (IsLaunching()) 1037 Quit(); 1038 } 1039 1040 1041 void 1042 WorkspacesApp::ArgvReceived(int32 argc, char **argv) 1043 { 1044 for (int i = 1; i < argc; i++) { 1045 if (argv[i][0] == '-' && argv[i][1] == '-') { 1046 // evaluate --arguments 1047 if (!strcmp(argv[i], "--notitle")) 1048 fWindow->SetLook(B_MODAL_WINDOW_LOOK); 1049 else if (!strcmp(argv[i], "--noborder")) 1050 fWindow->SetLook(B_NO_BORDER_WINDOW_LOOK); 1051 else if (!strcmp(argv[i], "--avoidfocus")) 1052 fWindow->SetFlags(fWindow->Flags() | B_AVOID_FOCUS); 1053 else if (!strcmp(argv[i], "--notmovable")) 1054 fWindow->SetFlags(fWindow->Flags() | B_NOT_MOVABLE); 1055 else if (!strcmp(argv[i], "--alwaysontop")) 1056 fWindow->SetFeel(B_FLOATING_ALL_WINDOW_FEEL); 1057 else if (!strcmp(argv[i], "--autoraise")) 1058 fWindow->SetAutoRaise(true); 1059 else { 1060 const char *programName = strrchr(argv[0], '/'); 1061 programName = programName ? programName + 1 : argv[0]; 1062 1063 Usage(programName); 1064 } 1065 } else if (isdigit(*argv[i])) { 1066 // check for a numeric arg, if not already given 1067 activate_workspace(atoi(argv[i])); 1068 1069 // if the app is running, don't quit 1070 // but if it isn't, cancel the complete run, so it doesn't 1071 // open any window 1072 if (IsLaunching()) 1073 Quit(); 1074 } else if (!strcmp(argv[i], "-")) { 1075 activate_workspace(current_workspace() - 1); 1076 1077 if (IsLaunching()) 1078 Quit(); 1079 } else if (!strcmp(argv[i], "+")) { 1080 activate_workspace(current_workspace() + 1); 1081 1082 if (IsLaunching()) 1083 Quit(); 1084 } else { 1085 // some unknown arguments were specified 1086 fprintf(stderr, B_TRANSLATE("Invalid argument: %s\n"), argv[i]); 1087 1088 if (IsLaunching()) 1089 Quit(); 1090 } 1091 } 1092 } 1093 1094 1095 BView* instantiate_deskbar_item() 1096 { 1097 // Calculate the correct size of the Deskbar replicant first 1098 1099 BScreen screen; 1100 float screenWidth = screen.Frame().Width(); 1101 float screenHeight = screen.Frame().Height(); 1102 float aspectRatio = screenWidth / screenHeight; 1103 uint32 columns, rows; 1104 BPrivate::get_workspaces_layout(&columns, &rows); 1105 1106 // ╔═╤═╕ A Deskbar replicant can be 16px tall and 129px wide at most. 1107 // ║ │ │ We use 1px for the top and left borders (shown as double) 1108 // ╟─┼─┤ and divide the remainder equally. However, we keep in mind 1109 // ║ │ │ that the actual width and height of each workspace is smaller 1110 // ╙─┴─┘ by 1px, because of bottom/right borders (shown as single). 1111 // When calculating workspace width, we must ensure that the assumed 1112 // actual workspace height is not negative. Zero is OK. 1113 1114 float height = 16; 1115 float rowHeight = floor((height - 1) / rows); 1116 if (rowHeight < 1) 1117 rowHeight = 1; 1118 1119 float columnWidth = floor((rowHeight - 1) * aspectRatio) + 1; 1120 1121 float width = columnWidth * columns + 1; 1122 if (width > 129) 1123 width = 129; 1124 1125 return new WorkspacesView(BRect (0, 0, width - 1, height - 1), false); 1126 } 1127 1128 1129 void 1130 WorkspacesApp::ReadyToRun() 1131 { 1132 fWindow->Show(); 1133 } 1134 1135 1136 // #pragma mark - 1137 1138 1139 int 1140 main(int argc, char **argv) 1141 { 1142 WorkspacesApp app; 1143 app.Run(); 1144 1145 return 0; 1146 } 1147