xref: /haiku/src/apps/workspaces/Workspaces.cpp (revision 746cac055adc6ac3308c7bc2d29040fb95689cc9)
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