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