1 /* 2 * Copyright 2002-2008, Haiku, Inc. 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 <Alert.h> 15 #include <Application.h> 16 #include <Dragger.h> 17 #include <Entry.h> 18 #include <File.h> 19 #include <FindDirectory.h> 20 #include <MenuItem.h> 21 #include <Path.h> 22 #include <PopUpMenu.h> 23 #include <Roster.h> 24 #include <Screen.h> 25 #include <TextView.h> 26 #include <Window.h> 27 28 #include <ctype.h> 29 #include <stdio.h> 30 #include <stdlib.h> 31 #include <string.h> 32 33 #include <ViewPrivate.h> 34 #include <WindowPrivate.h> 35 36 37 static const char* kSignature = "application/x-vnd.Be-WORK"; 38 static const char* kOldSettingFile = "Workspace_data"; 39 static const char* kSettingsFile = "Workspaces_settings"; 40 41 static const uint32 kMsgChangeCount = 'chWC'; 42 static const uint32 kMsgToggleTitle = 'tgTt'; 43 static const uint32 kMsgToggleBorder = 'tgBd'; 44 static const uint32 kMsgToggleAutoRaise = 'tgAR'; 45 static const uint32 kMsgToggleAlwaysOnTop = 'tgAT'; 46 47 static const float kScreenBorderOffset = 10.0; 48 49 50 class WorkspacesSettings { 51 public: 52 WorkspacesSettings(); 53 virtual ~WorkspacesSettings(); 54 55 BRect WindowFrame() const { return fWindowFrame; } 56 BRect ScreenFrame() const { return fScreenFrame; } 57 58 bool AutoRaising() const { return fAutoRaising; } 59 bool AlwaysOnTop() const { return fAlwaysOnTop; } 60 bool HasTitle() const { return fHasTitle; } 61 bool HasBorder() const { return fHasBorder; } 62 63 void UpdateFramesForScreen(BRect screenFrame); 64 void UpdateScreenFrame(); 65 66 void SetWindowFrame(BRect); 67 void SetAutoRaising(bool enable) { fAutoRaising = enable; } 68 void SetAlwaysOnTop(bool enable) { fAlwaysOnTop = enable; } 69 void SetHasTitle(bool enable) { fHasTitle = enable; } 70 void SetHasBorder(bool enable) { fHasBorder = enable; } 71 72 private: 73 status_t _Open(BFile& file, int mode); 74 75 BRect fWindowFrame; 76 BRect fScreenFrame; 77 bool fAutoRaising; 78 bool fAlwaysOnTop; 79 bool fHasTitle; 80 bool fHasBorder; 81 }; 82 83 class WorkspacesView : public BView { 84 public: 85 WorkspacesView(BRect frame); 86 WorkspacesView(BMessage* archive); 87 ~WorkspacesView(); 88 89 static WorkspacesView* Instantiate(BMessage* archive); 90 virtual status_t Archive(BMessage* archive, bool deep = true) const; 91 92 virtual void MessageReceived(BMessage* message); 93 virtual void MouseMoved(BPoint where, uint32 transit, 94 const BMessage* dragMessage); 95 virtual void MouseDown(BPoint where); 96 97 private: 98 void _AboutRequested(); 99 }; 100 101 class WorkspacesWindow : public BWindow { 102 public: 103 WorkspacesWindow(WorkspacesSettings *settings); 104 virtual ~WorkspacesWindow(); 105 106 virtual void ScreenChanged(BRect frame, color_space mode); 107 virtual void FrameMoved(BPoint origin); 108 virtual void FrameResized(float width, float height); 109 virtual void Zoom(BPoint origin, float width, float height); 110 111 virtual void MessageReceived(BMessage *msg); 112 virtual bool QuitRequested(); 113 114 void SetAutoRaise(bool enable); 115 bool IsAutoRaising() const { return fAutoRaising; } 116 117 private: 118 WorkspacesSettings *fSettings; 119 BRect fPreviousFrame; 120 bool fAutoRaising; 121 }; 122 123 class WorkspacesApp : public BApplication { 124 public: 125 WorkspacesApp(); 126 virtual ~WorkspacesApp(); 127 128 virtual void AboutRequested(); 129 virtual void ArgvReceived(int32 argc, char **argv); 130 virtual void ReadyToRun(); 131 132 void Usage(const char *programName); 133 134 private: 135 WorkspacesWindow* fWindow; 136 }; 137 138 139 WorkspacesSettings::WorkspacesSettings() 140 : 141 fAutoRaising(false), 142 fAlwaysOnTop(false), 143 fHasTitle(true), 144 fHasBorder(true) 145 { 146 UpdateScreenFrame(); 147 148 bool loaded = false; 149 BScreen screen; 150 151 BFile file; 152 if (_Open(file, B_READ_ONLY) == B_OK) { 153 BMessage settings; 154 if (settings.Unflatten(&file) == B_OK) { 155 if (settings.FindRect("window", &fWindowFrame) == B_OK 156 && settings.FindRect("screen", &fScreenFrame) == B_OK) 157 loaded = true; 158 159 settings.FindBool("auto-raise", &fAutoRaising); 160 settings.FindBool("always on top", &fAlwaysOnTop); 161 162 if (settings.FindBool("has title", &fHasTitle) != B_OK) 163 fHasTitle = true; 164 if (settings.FindBool("has border", &fHasBorder) != B_OK) 165 fHasBorder = true; 166 } 167 } else { 168 // try reading BeOS compatible settings 169 BPath path; 170 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK) { 171 path.Append(kOldSettingFile); 172 BFile file(path.Path(), B_READ_ONLY); 173 if (file.InitCheck() == B_OK 174 && file.Read(&fWindowFrame, sizeof(BRect)) == sizeof(BRect)) { 175 // we now also store the frame of the screen to know 176 // in which context the window frame has been chosen 177 BRect frame; 178 if (file.Read(&frame, sizeof(BRect)) == sizeof(BRect)) 179 fScreenFrame = frame; 180 else 181 fScreenFrame = screen.Frame(); 182 183 loaded = true; 184 } 185 } 186 } 187 188 if (loaded) { 189 // if the current screen frame is different from the one 190 // just loaded, we need to alter the window frame accordingly 191 if (fScreenFrame != screen.Frame()) 192 UpdateFramesForScreen(screen.Frame()); 193 } 194 195 if (!loaded 196 || !(screen.Frame().right + 5 >= fWindowFrame.right 197 && screen.Frame().bottom + 5 >= fWindowFrame.bottom 198 && screen.Frame().left - 5 <= fWindowFrame.left 199 && screen.Frame().top - 5 <= fWindowFrame.top)) { 200 // set to some usable defaults 201 fWindowFrame = fScreenFrame; 202 fWindowFrame.OffsetBy(-kScreenBorderOffset, -kScreenBorderOffset); 203 fWindowFrame.left = fWindowFrame.right - 160; 204 fWindowFrame.top = fWindowFrame.bottom - 140; 205 } 206 } 207 208 209 WorkspacesSettings::~WorkspacesSettings() 210 { 211 // write settings file 212 BFile file; 213 if (_Open(file, B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE) != B_OK) 214 return; 215 216 BMessage settings('wksp'); 217 218 if (settings.AddRect("window", fWindowFrame) == B_OK 219 && settings.AddRect("screen", fScreenFrame) == B_OK 220 && settings.AddBool("auto-raise", fAutoRaising) == B_OK 221 && settings.AddBool("always on top", fAlwaysOnTop) == B_OK 222 && settings.AddBool("has title", fHasTitle) == B_OK 223 && settings.AddBool("has border", fHasBorder) == B_OK) 224 settings.Flatten(&file); 225 } 226 227 228 status_t 229 WorkspacesSettings::_Open(BFile& file, int mode) 230 { 231 BPath path; 232 status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path); 233 if (status != B_OK) 234 status = find_directory(B_COMMON_SETTINGS_DIRECTORY, &path); 235 if (status != B_OK) 236 return status; 237 238 path.Append(kSettingsFile); 239 240 status = file.SetTo(path.Path(), mode); 241 if (mode == B_READ_ONLY && status == B_ENTRY_NOT_FOUND) { 242 if (find_directory(B_COMMON_SETTINGS_DIRECTORY, &path) == B_OK) { 243 path.Append(kSettingsFile); 244 status = file.SetTo(path.Path(), mode); 245 } 246 } 247 248 return status; 249 } 250 251 252 void 253 WorkspacesSettings::UpdateFramesForScreen(BRect newScreenFrame) 254 { 255 // don't change the position if the screen frame hasn't changed 256 if (newScreenFrame == fScreenFrame) 257 return; 258 259 // adjust horizontal position 260 if (fWindowFrame.right > fScreenFrame.right / 2) 261 fWindowFrame.OffsetTo(newScreenFrame.right 262 - (fScreenFrame.right - fWindowFrame.left), fWindowFrame.top); 263 264 // adjust vertical position 265 if (fWindowFrame.bottom > fScreenFrame.bottom / 2) 266 fWindowFrame.OffsetTo(fWindowFrame.left, 267 newScreenFrame.bottom - (fScreenFrame.bottom - fWindowFrame.top)); 268 269 fScreenFrame = newScreenFrame; 270 } 271 272 273 void 274 WorkspacesSettings::UpdateScreenFrame() 275 { 276 BScreen screen; 277 fScreenFrame = screen.Frame(); 278 } 279 280 281 void 282 WorkspacesSettings::SetWindowFrame(BRect frame) 283 { 284 fWindowFrame = frame; 285 } 286 287 288 // #pragma mark - 289 290 291 WorkspacesView::WorkspacesView(BRect frame) 292 : BView(frame, "workspaces", B_FOLLOW_ALL, kWorkspacesViewFlag) 293 { 294 frame.OffsetTo(B_ORIGIN); 295 frame.top = frame.bottom - 7; 296 frame.left = frame.right - 7; 297 BDragger* dragger = new BDragger(frame, this, 298 B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM); 299 AddChild(dragger); 300 } 301 302 303 WorkspacesView::WorkspacesView(BMessage* archive) 304 : BView(archive) 305 { 306 } 307 308 309 WorkspacesView::~WorkspacesView() 310 { 311 } 312 313 314 /*static*/ WorkspacesView* 315 WorkspacesView::Instantiate(BMessage* archive) 316 { 317 if (!validate_instantiation(archive, "WorkspacesView")) 318 return NULL; 319 320 return new WorkspacesView(archive); 321 } 322 323 324 status_t 325 WorkspacesView::Archive(BMessage* archive, bool deep) const 326 { 327 status_t status = BView::Archive(archive, deep); 328 if (status == B_OK) 329 status = archive->AddString("add_on", kSignature); 330 if (status == B_OK) 331 status = archive->AddString("class", "WorkspacesView"); 332 333 return status; 334 } 335 336 337 void 338 WorkspacesView::_AboutRequested() 339 { 340 BAlert *alert = new BAlert("about", "Workspaces\n" 341 "written by François Revol, Axel Dörfler, and Matt Madia.\n\n" 342 "Copyright 2002-2008, Haiku.\n\n" 343 "Send windows behind using the Option key. " 344 "Move windows to front using the Control key.\n", "Ok"); 345 BTextView *view = alert->TextView(); 346 BFont font; 347 348 view->SetStylable(true); 349 350 view->GetFont(&font); 351 font.SetSize(18); 352 font.SetFace(B_BOLD_FACE); 353 view->SetFontAndColor(0, 10, &font); 354 355 alert->Go(); 356 } 357 358 359 void 360 WorkspacesView::MessageReceived(BMessage* message) 361 { 362 switch (message->what) { 363 case B_ABOUT_REQUESTED: 364 _AboutRequested(); 365 break; 366 367 case kMsgChangeCount: 368 be_roster->Launch("application/x-vnd.Be-SCRN"); 369 break; 370 371 default: 372 BView::MessageReceived(message); 373 break; 374 } 375 } 376 377 378 void 379 WorkspacesView::MouseMoved(BPoint where, uint32 transit, 380 const BMessage* dragMessage) 381 { 382 if (Window() == NULL || EventMask() == 0) 383 return; 384 385 // Auto-Raise 386 387 where = ConvertToScreen(where); 388 BScreen screen(Window()); 389 BRect frame = screen.Frame(); 390 if (where.x == frame.left || where.x == frame.right 391 || where.y == frame.top || where.y == frame.bottom) { 392 // cursor is on screen edge 393 if (Window()->Frame().Contains(where)) 394 Window()->Activate(); 395 } 396 } 397 398 399 void 400 WorkspacesView::MouseDown(BPoint where) 401 { 402 int32 buttons = 0; 403 if (Window() != NULL && Window()->CurrentMessage() != NULL) 404 Window()->CurrentMessage()->FindInt32("buttons", &buttons); 405 406 if ((buttons & B_SECONDARY_MOUSE_BUTTON) == 0) 407 return; 408 409 // open context menu 410 411 BPopUpMenu *menu = new BPopUpMenu(B_EMPTY_STRING, false, false); 412 menu->SetFont(be_plain_font); 413 414 // TODO: alternatively change the count here directly? 415 BMenuItem* changeItem = new BMenuItem("Change Workspace Count" 416 B_UTF8_ELLIPSIS, new BMessage(kMsgChangeCount)); 417 menu->AddItem(changeItem); 418 419 WorkspacesWindow* window = dynamic_cast<WorkspacesWindow*>(Window()); 420 if (window != NULL) { 421 BMenuItem* item; 422 423 menu->AddSeparatorItem(); 424 menu->AddItem(item = new BMenuItem("No title", 425 new BMessage(kMsgToggleTitle))); 426 if (window->Look() == B_MODAL_WINDOW_LOOK) 427 item->SetMarked(true); 428 menu->AddItem(item = new BMenuItem("No Border", 429 new BMessage(kMsgToggleBorder))); 430 if (window->Look() == B_NO_BORDER_WINDOW_LOOK) 431 item->SetMarked(true); 432 433 menu->AddSeparatorItem(); 434 menu->AddItem(item = new BMenuItem("Always On Top", 435 new BMessage(kMsgToggleAlwaysOnTop))); 436 if (window->Feel() == B_FLOATING_ALL_WINDOW_FEEL) 437 item->SetMarked(true); 438 menu->AddItem(item = new BMenuItem("Auto-Raise", 439 new BMessage(kMsgToggleAutoRaise))); 440 if (window->IsAutoRaising()) 441 item->SetMarked(true); 442 443 menu->AddSeparatorItem(); 444 menu->AddItem(new BMenuItem("About" B_UTF8_ELLIPSIS, 445 new BMessage(B_ABOUT_REQUESTED))); 446 menu->AddItem(new BMenuItem("Quit", new BMessage(B_QUIT_REQUESTED))); 447 menu->SetTargetForItems(window); 448 } 449 450 changeItem->SetTarget(this); 451 ConvertToScreen(&where); 452 menu->Go(where, true, true, true); 453 } 454 455 456 // #pragma mark - 457 458 459 WorkspacesWindow::WorkspacesWindow(WorkspacesSettings *settings) 460 : BWindow(settings->WindowFrame(), "Workspaces", B_TITLED_WINDOW_LOOK, 461 B_NORMAL_WINDOW_FEEL, B_AVOID_FRONT | B_WILL_ACCEPT_FIRST_CLICK, 462 B_ALL_WORKSPACES), 463 fSettings(settings), 464 fAutoRaising(false) 465 { 466 AddChild(new WorkspacesView(Bounds())); 467 fPreviousFrame = Frame(); 468 469 if (!fSettings->HasTitle()) 470 SetLook(B_MODAL_WINDOW_LOOK); 471 else if (!fSettings->HasBorder()) 472 SetLook(B_NO_BORDER_WINDOW_LOOK); 473 474 if (fSettings->AlwaysOnTop()) 475 SetFeel(B_FLOATING_ALL_WINDOW_FEEL); 476 else 477 SetAutoRaise(fSettings->AutoRaising()); 478 } 479 480 481 WorkspacesWindow::~WorkspacesWindow() 482 { 483 delete fSettings; 484 } 485 486 487 void 488 WorkspacesWindow::ScreenChanged(BRect rect, color_space mode) 489 { 490 fPreviousFrame = fSettings->WindowFrame(); 491 // work-around for a bug in BeOS, see explanation in FrameMoved() 492 493 fSettings->UpdateFramesForScreen(rect); 494 MoveTo(fSettings->WindowFrame().LeftTop()); 495 } 496 497 498 void 499 WorkspacesWindow::FrameMoved(BPoint origin) 500 { 501 if (origin == fPreviousFrame.LeftTop()) { 502 // This works around a bug in BeOS; when you change the window 503 // position in WorkspaceActivated() or ScreenChanged(), it will 504 // send an old repositioning message *after* the FrameMoved() 505 // that originated your change has arrived 506 return; 507 } 508 509 fSettings->SetWindowFrame(Frame()); 510 } 511 512 513 void 514 WorkspacesWindow::FrameResized(float width, float height) 515 { 516 fSettings->SetWindowFrame(Frame()); 517 } 518 519 520 void 521 WorkspacesWindow::Zoom(BPoint origin, float width, float height) 522 { 523 BScreen screen; 524 origin = screen.Frame().RightBottom(); 525 origin.x -= kScreenBorderOffset + fSettings->WindowFrame().Width(); 526 origin.y -= kScreenBorderOffset + fSettings->WindowFrame().Height(); 527 528 MoveTo(origin); 529 } 530 531 532 void 533 WorkspacesWindow::MessageReceived(BMessage *message) 534 { 535 switch (message->what) { 536 case B_SIMPLE_DATA: 537 { 538 // Drop from Tracker 539 entry_ref ref; 540 for (int i = 0; (message->FindRef("refs", i, &ref) == B_OK); i++) 541 be_roster->Launch(&ref); 542 break; 543 } 544 545 case B_ABOUT_REQUESTED: 546 PostMessage(message, ChildAt(0)); 547 break; 548 549 case kMsgToggleBorder: 550 { 551 bool enable = false; 552 if (Look() == B_NO_BORDER_WINDOW_LOOK) 553 enable = true; 554 555 if (enable) 556 SetLook(B_TITLED_WINDOW_LOOK); 557 else { 558 SetLook(B_NO_BORDER_WINDOW_LOOK); 559 fSettings->SetHasTitle(true); 560 } 561 562 fSettings->SetHasBorder(enable); 563 break; 564 } 565 566 case kMsgToggleTitle: 567 { 568 bool enable = false; 569 if (Look() == B_MODAL_WINDOW_LOOK) 570 enable = true; 571 572 if (enable) 573 SetLook(B_TITLED_WINDOW_LOOK); 574 else { 575 SetLook(B_MODAL_WINDOW_LOOK); 576 fSettings->SetHasBorder(true); 577 } 578 579 fSettings->SetHasTitle(enable); 580 break; 581 } 582 583 case kMsgToggleAutoRaise: 584 SetAutoRaise(!IsAutoRaising()); 585 SetFeel(B_NORMAL_WINDOW_FEEL); 586 break; 587 588 case kMsgToggleAlwaysOnTop: 589 { 590 bool enable = false; 591 if (Feel() != B_FLOATING_ALL_WINDOW_FEEL) 592 enable = true; 593 594 if (enable) 595 SetFeel(B_FLOATING_ALL_WINDOW_FEEL); 596 else 597 SetFeel(B_NORMAL_WINDOW_FEEL); 598 599 fSettings->SetAlwaysOnTop(enable); 600 break; 601 } 602 603 default: 604 BWindow::MessageReceived(message); 605 break; 606 } 607 } 608 609 610 bool 611 WorkspacesWindow::QuitRequested() 612 { 613 be_app->PostMessage(B_QUIT_REQUESTED); 614 return true; 615 } 616 617 618 void 619 WorkspacesWindow::SetAutoRaise(bool enable) 620 { 621 if (enable == fAutoRaising) 622 return; 623 624 fAutoRaising = enable; 625 fSettings->SetAutoRaising(enable); 626 627 if (enable) 628 ChildAt(0)->SetEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY); 629 else 630 ChildAt(0)->SetEventMask(0); 631 } 632 633 634 // #pragma mark - 635 636 637 WorkspacesApp::WorkspacesApp() 638 : BApplication(kSignature) 639 { 640 fWindow = new WorkspacesWindow(new WorkspacesSettings()); 641 } 642 643 644 WorkspacesApp::~WorkspacesApp() 645 { 646 } 647 648 649 void 650 WorkspacesApp::AboutRequested() 651 { 652 fWindow->PostMessage(B_ABOUT_REQUESTED); 653 } 654 655 656 void 657 WorkspacesApp::Usage(const char *programName) 658 { 659 printf("Usage: %s [options] [workspace]\n" 660 "where \"options\" is one of:\n" 661 " --notitle\t\ttitle bar removed. border and resize kept.\n" 662 " --noborder\t\ttitle, border, and resize removed.\n" 663 " --avoidfocus\t\tprevents the window from being the target of keyboard events.\n" 664 " --alwaysontop\t\tkeeps window on top\n" 665 " --notmovable\t\twindow can't be moved around\n" 666 " --autoraise\t\tauto-raise the workspace window when it's at the screen corner\n" 667 " --help\t\tdisplay this help and exit\n" 668 "and \"workspace\" is the number of the Workspace to which to switch (0-31)\n", 669 programName); 670 671 // quit only if we aren't running already 672 if (IsLaunching()) 673 Quit(); 674 } 675 676 677 void 678 WorkspacesApp::ArgvReceived(int32 argc, char **argv) 679 { 680 for (int i = 1; i < argc; i++) { 681 if (argv[i][0] == '-' && argv[i][1] == '-') { 682 // evaluate --arguments 683 if (!strcmp(argv[i], "--notitle")) 684 fWindow->SetLook(B_MODAL_WINDOW_LOOK); 685 else if (!strcmp(argv[i], "--noborder")) 686 fWindow->SetLook(B_NO_BORDER_WINDOW_LOOK); 687 else if (!strcmp(argv[i], "--avoidfocus")) 688 fWindow->SetFlags(fWindow->Flags() | B_AVOID_FOCUS); 689 else if (!strcmp(argv[i], "--notmovable")) 690 fWindow->SetFlags(fWindow->Flags() | B_NOT_MOVABLE); 691 else if (!strcmp(argv[i], "--alwaysontop")) 692 fWindow->SetFeel(B_FLOATING_ALL_WINDOW_FEEL); 693 else if (!strcmp(argv[i], "--autoraise")) 694 fWindow->SetAutoRaise(true); 695 else { 696 const char *programName = strrchr(argv[0], '/'); 697 programName = programName ? programName + 1 : argv[0]; 698 699 Usage(programName); 700 } 701 } else if (isdigit(*argv[i])) { 702 // check for a numeric arg, if not already given 703 activate_workspace(atoi(argv[i])); 704 705 // if the app is running, don't quit 706 // but if it isn't, cancel the complete run, so it doesn't 707 // open any window 708 if (IsLaunching()) 709 Quit(); 710 } else if (!strcmp(argv[i], "-")) { 711 activate_workspace(current_workspace() - 1); 712 713 if (IsLaunching()) 714 Quit(); 715 } else if (!strcmp(argv[i], "+")) { 716 activate_workspace(current_workspace() + 1); 717 718 if (IsLaunching()) 719 Quit(); 720 } else { 721 // some unknown arguments were specified 722 fprintf(stderr, "Invalid argument: %s\n", argv[i]); 723 724 if (IsLaunching()) 725 Quit(); 726 } 727 } 728 } 729 730 731 void 732 WorkspacesApp::ReadyToRun() 733 { 734 fWindow->Show(); 735 } 736 737 738 // #pragma mark - 739 740 741 int 742 main(int32 argc, char **argv) 743 { 744 WorkspacesApp app; 745 app.Run(); 746 747 return 0; 748 } 749