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