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