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