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