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