/*
 * Copyright 2002-2016, Haiku, Inc. All rights reserved.
 * Copyright 2002, François Revol, revol@free.fr.
 * This file is distributed under the terms of the MIT License.
 *
 * Authors:
 *		François Revol, revol@free.fr
 *		Axel Dörfler, axeld@pinc-software.de
 *		Oliver "Madison" Kohl,
 *		Matt Madia
 *		Daniel Devine, devine@ddevnet.net
 */
 
 
#include <AboutWindow.h>
#include <Application.h>
#include <Catalog.h>
#include <Deskbar.h>
#include <Dragger.h>
#include <Entry.h>
#include <File.h>
#include <FindDirectory.h>
#include <Locale.h>
#include <MenuItem.h>
#include <Path.h>
#include <PopUpMenu.h>
#include <Roster.h>
#include <Screen.h>
#include <TextView.h>
#include <Window.h>
 
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
#include <InterfacePrivate.h>
#include <ViewPrivate.h>
#include <WindowPrivate.h>
 
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Workspaces"
 
 
static const char* kDeskbarItemName = "workspaces";
static const char* kSignature = "application/x-vnd.Be-WORK";
static const char* kDeskbarSignature = "application/x-vnd.Be-TSKB";
static const char* kScreenPrefletSignature = "application/x-vnd.Haiku-Screen";
static const char* kOldSettingFile = "Workspace_data";
static const char* kSettingsFile = "Workspaces_settings";
 
static const uint32 kMsgChangeCount = 'chWC';
static const uint32 kMsgToggleTitle = 'tgTt';
static const uint32 kMsgToggleBorder = 'tgBd';
static const uint32 kMsgToggleAutoRaise = 'tgAR';
static const uint32 kMsgToggleAlwaysOnTop = 'tgAT';
static const uint32 kMsgToggleLiveInDeskbar = 'tgDb';
static const uint32 kMsgToggleSwitchOnWheel = 'tgWh';
 
 
extern "C" _EXPORT BView* instantiate_deskbar_item(float maxWidth,
	float maxHeight);
 
 
static status_t
OpenSettingsFile(BFile& file, int mode)
{
	BPath path;
	status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path);
	if (status != B_OK)
		status = find_directory(B_SYSTEM_SETTINGS_DIRECTORY, &path);
	if (status != B_OK)
		return status;
 
	status = path.Append(kSettingsFile);
	if (status != B_OK)
		return status;
 
	status = file.SetTo(path.Path(), mode);
	if (mode == B_READ_ONLY && status == B_ENTRY_NOT_FOUND) {
		if (find_directory(B_SYSTEM_SETTINGS_DIRECTORY, &path) == B_OK
			&& path.Append(kSettingsFile) == B_OK) {
			status = file.SetTo(path.Path(), mode);
		}
	}
 
	return status;
}
 
 
class WorkspacesSettings {
	public:
		WorkspacesSettings();
		virtual ~WorkspacesSettings();
 
		BRect WindowFrame() const { return fWindowFrame; }
		BRect ScreenFrame() const { return fScreenFrame; }
 
		bool AutoRaising() const { return fAutoRaising; }
		bool AlwaysOnTop() const { return fAlwaysOnTop; }
		bool HasTitle() const { return fHasTitle; }
		bool HasBorder() const { return fHasBorder; }
		bool SettingsLoaded() const { return fLoaded; }
 
		void UpdateFramesForScreen(BRect screenFrame);
		void UpdateScreenFrame();
 
		void SetWindowFrame(BRect);
		void SetAutoRaising(bool enable) { fAutoRaising = enable; }
		void SetAlwaysOnTop(bool enable) { fAlwaysOnTop = enable; }
		void SetHasTitle(bool enable) { fHasTitle = enable; }
		void SetHasBorder(bool enable) { fHasBorder = enable; }
 
	private:
		BRect	fWindowFrame;
		BRect	fScreenFrame;
		bool	fAutoRaising;
		bool	fAlwaysOnTop;
		bool	fHasTitle;
		bool	fHasBorder;
		bool	fLoaded;
};
 
class WorkspacesView : public BView {
	public:
		WorkspacesView(BRect frame, bool showDragger);
		WorkspacesView(BMessage* archive);
		~WorkspacesView();
 
		static	WorkspacesView* Instantiate(BMessage* archive);
		virtual	status_t Archive(BMessage* archive, bool deep = true) const;
 
		virtual void AttachedToWindow();
		virtual void DetachedFromWindow();
		virtual void FrameMoved(BPoint newPosition);
		virtual void FrameResized(float newWidth, float newHeight);
		virtual void MessageReceived(BMessage* message);
		virtual void MouseMoved(BPoint where, uint32 transit,
			const BMessage* dragMessage);
		virtual void MouseDown(BPoint where);
 
		bool SwitchOnWheel() const { return fSwitchOnWheel; }
		void SetSwitchOnWheel(bool enable);
 
	private:
		void _AboutRequested();
 
		void _UpdateParentClipping();
		void _ExcludeFromParentClipping();
		void _CleanupParentClipping();
 
		friend class WorkspacesWindow;
 
		void _LoadSettings();
		void _SaveSettings();
 
		BView*	fParentWhichDrawsOnChildren;
		BRect	fCurrentFrame;
		bool	fSwitchOnWheel;
};
 
class WorkspacesWindow : public BWindow {
	public:
		WorkspacesWindow(WorkspacesSettings *settings);
		virtual ~WorkspacesWindow();
 
		virtual void ScreenChanged(BRect frame, color_space mode);
		virtual void FrameMoved(BPoint origin);
		virtual void FrameResized(float width, float height);
		virtual void Zoom(BPoint origin, float width, float height);
 
		virtual void MessageReceived(BMessage *msg);
		virtual bool QuitRequested();
 
		void SetAutoRaise(bool enable);
		bool IsAutoRaising() const { return fSettings->AutoRaising(); }
 
		float GetTabHeight() { return fSettings->HasTitle() ? fTabHeight : 0; }
		float GetBorderWidth() { return fBorderWidth; }
		float GetScreenBorderOffset() { return 2.0 * fBorderWidth; }
 
	private:
		WorkspacesSettings *fSettings;
		WorkspacesView *fWorkspacesView;
		float	fTabHeight;
		float	fBorderWidth;
};
 
class WorkspacesApp : public BApplication {
	public:
		WorkspacesApp();
		virtual ~WorkspacesApp();
 
		virtual void AboutRequested();
		virtual void ArgvReceived(int32 argc, char **argv);
		virtual void ReadyToRun();
 
		void Usage(const char *programName);
 
	private:
		WorkspacesWindow*	fWindow;
};
 
 
//	#pragma mark - WorkspacesSettings
 
 
WorkspacesSettings::WorkspacesSettings()
	:
	fAutoRaising(false),
	fAlwaysOnTop(false),
	fHasTitle(true),
	fHasBorder(true),
	fLoaded(false)
{
	UpdateScreenFrame();
 
	BScreen screen;
 
	BFile file;
	if (OpenSettingsFile(file, B_READ_ONLY) == B_OK) {
		BMessage settings;
		if (settings.Unflatten(&file) == B_OK) {
			fLoaded = settings.FindRect("window", &fWindowFrame) == B_OK
				&& settings.FindRect("screen", &fScreenFrame) == B_OK;
			settings.FindBool("auto-raise", &fAutoRaising);
			settings.FindBool("always on top", &fAlwaysOnTop);
			if (settings.FindBool("has title", &fHasTitle) != B_OK)
				fHasTitle = true;
			if (settings.FindBool("has border", &fHasBorder) != B_OK)
				fHasBorder = true;
		}
	} else {
		// try reading BeOS compatible settings
		BPath path;
		if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK) {
			path.Append(kOldSettingFile);
			BFile file(path.Path(), B_READ_ONLY);
			if (file.InitCheck() == B_OK
				&& file.Read(&fWindowFrame, sizeof(BRect)) == sizeof(BRect)) {
				// we now also store the frame of the screen to know
				// in which context the window frame has been chosen
				BRect frame;
				if (file.Read(&frame, sizeof(BRect)) == sizeof(BRect))
					fScreenFrame = frame;
				else
					fScreenFrame = screen.Frame();
 
				fLoaded = true;
			}
		}
	}
 
	if (fLoaded) {
		// if the current screen frame is different from the one
		// just loaded, we need to alter the window frame accordingly
		if (fScreenFrame != screen.Frame())
			UpdateFramesForScreen(screen.Frame());
	}
}
 
 
WorkspacesSettings::~WorkspacesSettings()
{
	BFile file;
	if (OpenSettingsFile(file, B_WRITE_ONLY | B_ERASE_FILE | B_CREATE_FILE)
			!= B_OK) {
		return;
	}
 
	// switch on wheel saved by view later on
 
	BMessage settings('wksp');
	if (settings.AddRect("window", fWindowFrame) == B_OK
		&& settings.AddRect("screen", fScreenFrame) == B_OK
		&& settings.AddBool("auto-raise", fAutoRaising) == B_OK
		&& settings.AddBool("always on top", fAlwaysOnTop) == B_OK
		&& settings.AddBool("has title", fHasTitle) == B_OK
		&& settings.AddBool("has border", fHasBorder) == B_OK) {
		settings.Flatten(&file);
	}
}
 
 
void
WorkspacesSettings::UpdateFramesForScreen(BRect newScreenFrame)
{
	// don't change the position if the screen frame hasn't changed
	if (newScreenFrame == fScreenFrame)
		return;
 
	// adjust horizontal position
	if (fWindowFrame.right > fScreenFrame.right / 2) {
		fWindowFrame.OffsetTo(newScreenFrame.right
			- (fScreenFrame.right - fWindowFrame.left), fWindowFrame.top);
	}
 
	// adjust vertical position
	if (fWindowFrame.bottom > fScreenFrame.bottom / 2) {
		fWindowFrame.OffsetTo(fWindowFrame.left,
			newScreenFrame.bottom - (fScreenFrame.bottom - fWindowFrame.top));
	}
 
	fScreenFrame = newScreenFrame;
}
 
 
void
WorkspacesSettings::UpdateScreenFrame()
{
	BScreen screen;
	fScreenFrame = screen.Frame();
}
 
 
void
WorkspacesSettings::SetWindowFrame(BRect frame)
{
	fWindowFrame = frame;
}
 
 
//	#pragma mark - WorkspacesView
 
 
WorkspacesView::WorkspacesView(BRect frame, bool showDragger = true)
	:
	BView(frame, kDeskbarItemName, B_FOLLOW_ALL,
		kWorkspacesViewFlag | B_FRAME_EVENTS),
	fParentWhichDrawsOnChildren(NULL),
	fCurrentFrame(frame),
	fSwitchOnWheel(false)
{
	_LoadSettings();
 
	if (showDragger) {
		frame.OffsetTo(B_ORIGIN);
		frame.top = frame.bottom - 7;
		frame.left = frame.right - 7;
		BDragger* dragger = new BDragger(frame, this,
			B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM);
		AddChild(dragger);
	}
}
 
 
WorkspacesView::WorkspacesView(BMessage* archive)
	:
	BView(archive),
	fParentWhichDrawsOnChildren(NULL),
	fCurrentFrame(Frame()),
	fSwitchOnWheel(false)
{
	_LoadSettings();
 
	// Just in case we are instantiated from an older archive...
	SetFlags(Flags() | B_FRAME_EVENTS);
	// Make sure the auto-raise feature didn't leave any artifacts - this is
	// not a good idea to keep enabled for a replicant.
	if (EventMask() != 0)
		SetEventMask(0);
}
 
 
WorkspacesView::~WorkspacesView()
{
	_SaveSettings();
}
 
 
/*static*/ WorkspacesView*
WorkspacesView::Instantiate(BMessage* archive)
{
	if (!validate_instantiation(archive, "WorkspacesView"))
		return NULL;
 
	return new WorkspacesView(archive);
}
 
 
status_t
WorkspacesView::Archive(BMessage* archive, bool deep) const
{
	status_t status = BView::Archive(archive, deep);
	if (status == B_OK)
		status = archive->AddString("add_on", kSignature);
	if (status == B_OK)
		status = archive->AddString("class", "WorkspacesView");
 
	return status;
}
 
 
void
WorkspacesView::_AboutRequested()
{
	BAboutWindow* window = new BAboutWindow(
		B_TRANSLATE_SYSTEM_NAME("Workspaces"), kSignature);
 
	const char* authors[] = {
		"Axel Dörfler",
		"Oliver \"Madison\" Kohl",
		"Matt Madia",
		"François Revol",
		NULL
	};
 
	const char* extraCopyrights[] = {
		"2002 François Revol",
		NULL
	};
 
	const char* extraInfo = "Send windows behind using the Option key. "
		"Move windows to front using the Control key.\n";
 
	window->AddCopyright(2002, "Haiku, Inc.",
			extraCopyrights);
	window->AddAuthors(authors);
	window->AddExtraInfo(extraInfo);
 
	window->Show();
}
 
 
void
WorkspacesView::AttachedToWindow()
{
	BView* parent = Parent();
	if (parent != NULL && (parent->Flags() & B_DRAW_ON_CHILDREN) != 0) {
		fParentWhichDrawsOnChildren = parent;
		_ExcludeFromParentClipping();
	}
}
 
 
void
WorkspacesView::DetachedFromWindow()
{
	if (fParentWhichDrawsOnChildren != NULL)
		_CleanupParentClipping();
}
 
 
void
WorkspacesView::FrameMoved(BPoint newPosition)
{
	_UpdateParentClipping();
}
 
 
void
WorkspacesView::FrameResized(float newWidth, float newHeight)
{
	_UpdateParentClipping();
}
 
 
void
WorkspacesView::_UpdateParentClipping()
{
	if (fParentWhichDrawsOnChildren != NULL) {
		_CleanupParentClipping();
		_ExcludeFromParentClipping();
		fParentWhichDrawsOnChildren->Invalidate(fCurrentFrame);
		fCurrentFrame = Frame();
	}
}
 
 
void
WorkspacesView::_ExcludeFromParentClipping()
{
	// Prevent the parent view to draw over us. Do so in a way that allows
	// restoring the parent to the previous state.
	fParentWhichDrawsOnChildren->PushState();
 
	BRegion clipping(fParentWhichDrawsOnChildren->Bounds());
	clipping.Exclude(Frame());
	fParentWhichDrawsOnChildren->ConstrainClippingRegion(&clipping);
}
 
 
void
WorkspacesView::_CleanupParentClipping()
{
	// Restore the previous parent state. NOTE: This relies on views
	// being detached in exactly the opposite order as them being
	// attached. Otherwise we would mess up states if a sibbling view did
	// the same thing we did in AttachedToWindow()...
	fParentWhichDrawsOnChildren->PopState();
}
 
 
void
WorkspacesView::_LoadSettings()
{
	BFile file;
	if (OpenSettingsFile(file, B_READ_ONLY) == B_OK) {
		BMessage settings;
		if (settings.Unflatten(&file) == B_OK)
			settings.FindBool("switch on wheel", &fSwitchOnWheel);
	}
}
 
 
void
WorkspacesView::_SaveSettings()
{
	BFile file;
	if (OpenSettingsFile(file, B_READ_ONLY | B_CREATE_FILE) != B_OK)
		return;
 
	BMessage settings('wksp');
	settings.Unflatten(&file);
 
	if (OpenSettingsFile(file, B_WRITE_ONLY | B_ERASE_FILE) != B_OK)
		return;
 
	if (settings.ReplaceBool("switch on wheel", fSwitchOnWheel) != B_OK)
		settings.AddBool("switch on wheel", fSwitchOnWheel);
 
	settings.Flatten(&file);
}
 
 
void
WorkspacesView::MessageReceived(BMessage* message)
{
	switch (message->what) {
		case B_ABOUT_REQUESTED:
			_AboutRequested();
			break;
 
		case B_MOUSE_WHEEL_CHANGED:
		{
			if (!fSwitchOnWheel)
				break;
 
			float deltaY = message->FindFloat("be:wheel_delta_y");
			if (deltaY > 0.1)
				activate_workspace(current_workspace() + 1);
			else if (deltaY < -0.1)
				activate_workspace(current_workspace() - 1);
			break;
		}
 
		case kMsgChangeCount:
			be_roster->Launch(kScreenPrefletSignature);
			break;
 
		case kMsgToggleLiveInDeskbar:
		{
			// only actually used from the replicant itself
			// since HasItem() locks up we just remove directly.
			BDeskbar deskbar;
			// we shouldn't do this here actually, but it works for now...
			deskbar.RemoveItem(kDeskbarItemName);
			break;
		}
 
		case kMsgToggleSwitchOnWheel:
		{
			fSwitchOnWheel = !fSwitchOnWheel;
			break;
		}
 
		default:
			BView::MessageReceived(message);
			break;
	}
}
 
 
void
WorkspacesView::MouseMoved(BPoint where, uint32 transit,
	const BMessage* dragMessage)
{
	WorkspacesWindow* window = dynamic_cast<WorkspacesWindow*>(Window());
	if (window == NULL || !window->IsAutoRaising())
		return;
 
	// Auto-Raise
 
	where = ConvertToScreen(where);
	BScreen screen(window);
	BRect screenFrame = screen.Frame();
	BRect windowFrame = window->Frame();
	float tabHeight = window->GetTabHeight();
	float borderWidth = window->GetBorderWidth();
 
	if (where.x == screenFrame.left || where.x == screenFrame.right
			|| where.y == screenFrame.top || where.y == screenFrame.bottom) {
		// cursor is on screen edge
 
		// Stretch frame to also accept mouse moves over the window borders
		windowFrame.InsetBy(-borderWidth, -(tabHeight + borderWidth));
 
		if (windowFrame.Contains(where))
			window->Activate();
	}
}
 
 
void
WorkspacesView::MouseDown(BPoint where)
{
	// With enabled auto-raise feature, we'll get mouse messages we don't
	// want to handle here.
	if (!Bounds().Contains(where))
		return;
 
	int32 buttons = 0;
	if (Window() != NULL && Window()->CurrentMessage() != NULL)
		Window()->CurrentMessage()->FindInt32("buttons", &buttons);
 
	if ((buttons & B_SECONDARY_MOUSE_BUTTON) == 0)
		return;
 
	// open context menu
 
	BPopUpMenu *menu = new BPopUpMenu(B_EMPTY_STRING, false, false);
	menu->SetFont(be_plain_font);
 
	// TODO: alternatively change the count here directly?
	BMenuItem* changeItem = new BMenuItem(B_TRANSLATE("Change workspace count"
		B_UTF8_ELLIPSIS), new BMessage(kMsgChangeCount));
	menu->AddItem(changeItem);
 
	BMenuItem* switchItem = new BMenuItem(B_TRANSLATE("Switch on mouse wheel"),
		new BMessage(kMsgToggleSwitchOnWheel));
	menu->AddItem(switchItem);
	switchItem->SetMarked(fSwitchOnWheel);
 
	WorkspacesWindow *window = dynamic_cast<WorkspacesWindow*>(Window());
	if (window != NULL) {
		// inside Workspaces app
		BMenuItem* item;
 
		menu->AddSeparatorItem();
		menu->AddItem(item = new BMenuItem(B_TRANSLATE("Show window tab"),
			new BMessage(kMsgToggleTitle)));
		if (window->Look() == B_TITLED_WINDOW_LOOK)
			item->SetMarked(true);
		menu->AddItem(item = new BMenuItem(B_TRANSLATE("Show window border"),
			new BMessage(kMsgToggleBorder)));
		if (window->Look() == B_TITLED_WINDOW_LOOK
			|| window->Look() == B_MODAL_WINDOW_LOOK) {
			item->SetMarked(true);
		}
 
		menu->AddSeparatorItem();
		menu->AddItem(item = new BMenuItem(B_TRANSLATE("Always on top"),
			new BMessage(kMsgToggleAlwaysOnTop)));
		if (window->Feel() == B_FLOATING_ALL_WINDOW_FEEL)
			item->SetMarked(true);
		menu->AddItem(item = new BMenuItem(B_TRANSLATE("Auto-raise"),
			new BMessage(kMsgToggleAutoRaise)));
		if (window->IsAutoRaising())
			item->SetMarked(true);
		if (be_roster->IsRunning(kDeskbarSignature)) {
			menu->AddItem(item = new BMenuItem(
				B_TRANSLATE("Live in the Deskbar"),
				new BMessage(kMsgToggleLiveInDeskbar)));
			BDeskbar deskbar;
			item->SetMarked(deskbar.HasItem(kDeskbarItemName));
		}
 
		menu->AddSeparatorItem();
		menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"),
			new BMessage(B_QUIT_REQUESTED)));
		menu->SetTargetForItems(window);
	} else {
		// we're replicated in some way...
		BMenuItem* item;
 
		menu->AddSeparatorItem();
 
		// check which way
		BDragger *dragger = dynamic_cast<BDragger*>(ChildAt(0));
		if (dragger) {
			// replicant
			menu->AddItem(item = new BMenuItem(B_TRANSLATE("Remove replicant"),
				new BMessage(B_TRASH_TARGET)));
			item->SetTarget(dragger);
		} else {
			// Deskbar item
			menu->AddItem(item = new BMenuItem(B_TRANSLATE("Remove replicant"),
				new BMessage(kMsgToggleLiveInDeskbar)));
			item->SetTarget(this);
		}
	}
 
	changeItem->SetTarget(this);
	switchItem->SetTarget(this);
 
	ConvertToScreen(&where);
	menu->Go(where, true, true, true);
}
 
 
void
WorkspacesView::SetSwitchOnWheel(bool enable)
{
	if (enable == fSwitchOnWheel)
		return;
 
	fSwitchOnWheel = enable;
}
 
 
//	#pragma mark - WorkspacesWindow
 
 
WorkspacesWindow::WorkspacesWindow(WorkspacesSettings *settings)
	:
	BWindow(settings->WindowFrame(), B_TRANSLATE_SYSTEM_NAME("Workspaces"),
		B_TITLED_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL,
		B_AVOID_FRONT | B_WILL_ACCEPT_FIRST_CLICK | B_CLOSE_ON_ESCAPE,
		B_ALL_WORKSPACES),
	fSettings(settings),
	fWorkspacesView(NULL)
{
	// Turn window decor on to grab decor widths.
	BMessage windowSettings;
	float borderWidth = 0;
 
	SetLook(B_TITLED_WINDOW_LOOK);
	if (GetDecoratorSettings(&windowSettings) == B_OK) {
		BRect tabFrame = windowSettings.FindRect("tab frame");
		borderWidth = windowSettings.FindFloat("border width");
		fTabHeight = tabFrame.Height();
		fBorderWidth = borderWidth;
	}
 
	if (!fSettings->SettingsLoaded()) {
		// No settings, compute a reasonable default frame.
		// We aim for previews at 10% of actual screen size, and matching the
		// aspect ratio. We then scale that down, until it fits the screen.
		// Finally, we put the window on the bottom right of the screen so the
		// auto-raise mode can be used.
 
		BScreen screen;
 
		float screenWidth = screen.Frame().Width();
		float screenHeight = screen.Frame().Height();
		float aspectRatio = screenWidth / screenHeight;
 
		uint32 columns, rows;
		BPrivate::get_workspaces_layout(&columns, &rows);
 
		// default size of ~1/10 of screen width
		float workspaceWidth = screenWidth / 10;
		float workspaceHeight = workspaceWidth / aspectRatio;
 
		float width = floor(workspaceWidth * columns);
		float height = floor(workspaceHeight * rows);
 
		// If you have too many workspaces to fit on the screen, shrink until
		// they fit.
		while (width + 2 * borderWidth > screenWidth
				|| height + 2 * borderWidth + GetTabHeight() > screenHeight) {
			width = floor(0.95 * width);
			height = floor(0.95 * height);
		}
 
		BRect frame = fSettings->ScreenFrame();
		frame.OffsetBy(-2.0 * borderWidth, -2.0 * borderWidth);
		frame.left = frame.right - width;
		frame.top = frame.bottom - height;
		ResizeTo(frame.Width(), frame.Height());
 
		// Put it in bottom corner by default.
		MoveTo(screenWidth - frame.Width() - borderWidth,
			screenHeight - frame.Height() - borderWidth);
 
		fSettings->SetWindowFrame(frame);
	}
 
	if (!fSettings->HasBorder())
		SetLook(B_NO_BORDER_WINDOW_LOOK);
	else if (!fSettings->HasTitle())
		SetLook(B_MODAL_WINDOW_LOOK);
 
	fWorkspacesView = new WorkspacesView(Bounds());
	AddChild(fWorkspacesView);
 
	if (fSettings->AlwaysOnTop())
		SetFeel(B_FLOATING_ALL_WINDOW_FEEL);
	else
		SetAutoRaise(fSettings->AutoRaising());
}
 
 
WorkspacesWindow::~WorkspacesWindow()
{
	delete fSettings;
}
 
 
void
WorkspacesWindow::ScreenChanged(BRect rect, color_space mode)
{
	fSettings->UpdateFramesForScreen(rect);
	MoveTo(fSettings->WindowFrame().LeftTop());
}
 
 
void
WorkspacesWindow::FrameMoved(BPoint origin)
{
	fSettings->SetWindowFrame(Frame());
}
 
 
void
WorkspacesWindow::FrameResized(float width, float height)
{
	if (!(modifiers() & B_SHIFT_KEY)) {
		BWindow::FrameResized(width, height);
		return;
	}
 
	uint32 columns, rows;
	BPrivate::get_workspaces_layout(&columns, &rows);
 
	BScreen screen;
	float screenWidth = screen.Frame().Width();
	float screenHeight = screen.Frame().Height();
 
	float windowAspectRatio
		= (columns * screenWidth) / (rows * screenHeight);
 
	float newHeight = width / windowAspectRatio;
 
	if (height != newHeight)
		ResizeTo(width, newHeight);
 
	fSettings->SetWindowFrame(Frame());
}
 
 
void
WorkspacesWindow::Zoom(BPoint origin, float width, float height)
{
	BScreen screen;
	float screenWidth = screen.Frame().Width();
	float screenHeight = screen.Frame().Height();
	float aspectRatio = screenWidth / screenHeight;
 
	uint32 columns, rows;
	BPrivate::get_workspaces_layout(&columns, &rows);
 
	float workspaceWidth = Frame().Width() / columns;
	float workspaceHeight = workspaceWidth / aspectRatio;
 
	width = floor(workspaceWidth * columns);
	height = floor(workspaceHeight * rows);
 
	while (width + 2 * GetScreenBorderOffset() > screenWidth
		|| height + 2 * GetScreenBorderOffset() + GetTabHeight()
			> screenHeight) {
		width = floor(0.95 * width);
		height = floor(0.95 * height);
	}
 
	ResizeTo(width, height);
 
	if (fSettings->AutoRaising()) {
		// The auto-raising mode makes sense only if the window is positionned
		// exactly in the bottom-right corner. If the setting is enabled, move
		// the window there.
		origin = screen.Frame().RightBottom();
		origin.x -= GetScreenBorderOffset() + width;
		origin.y -= GetScreenBorderOffset() + height;
 
		MoveTo(origin);
	}
}
 
 
void
WorkspacesWindow::MessageReceived(BMessage *message)
{
	switch (message->what) {
		case B_SIMPLE_DATA:
		{
			// Drop from Tracker
			entry_ref ref;
			for (int i = 0; (message->FindRef("refs", i, &ref) == B_OK); i++)
				be_roster->Launch(&ref);
			break;
		}
 
		case B_ABOUT_REQUESTED:
			PostMessage(message, ChildAt(0));
			break;
 
		case kMsgToggleBorder:
		{
			bool enable = false;
			if (Look() == B_NO_BORDER_WINDOW_LOOK)
				enable = true;
 
			if (enable)
				if (fSettings->HasTitle())
					SetLook(B_TITLED_WINDOW_LOOK);
				else
					SetLook(B_MODAL_WINDOW_LOOK);
			else
				SetLook(B_NO_BORDER_WINDOW_LOOK);
 
			fSettings->SetHasBorder(enable);
			break;
		}
 
		case kMsgToggleTitle:
		{
			bool enable = false;
			if (Look() == B_MODAL_WINDOW_LOOK
				|| Look() == B_NO_BORDER_WINDOW_LOOK)
				enable = true;
 
			if (enable)
				SetLook(B_TITLED_WINDOW_LOOK);
			else
				SetLook(B_MODAL_WINDOW_LOOK);
 
			// No matter what the setting for title, we must force the border on
			fSettings->SetHasBorder(true);
			fSettings->SetHasTitle(enable);
			break;
		}
 
		case kMsgToggleAutoRaise:
			SetAutoRaise(!IsAutoRaising());
			SetFeel(B_NORMAL_WINDOW_FEEL);
			break;
 
		case kMsgToggleAlwaysOnTop:
		{
			bool enable = false;
			if (Feel() != B_FLOATING_ALL_WINDOW_FEEL)
				enable = true;
 
			if (enable)
				SetFeel(B_FLOATING_ALL_WINDOW_FEEL);
			else
				SetFeel(B_NORMAL_WINDOW_FEEL);
 
			fSettings->SetAlwaysOnTop(enable);
			break;
		}
 
		case kMsgToggleLiveInDeskbar:
		{
			BDeskbar deskbar;
			if (deskbar.HasItem(kDeskbarItemName))
				deskbar.RemoveItem(kDeskbarItemName);
			else {
				fWorkspacesView->_SaveSettings();
					// save "switch on wheel" setting for replicant to load
				entry_ref ref;
				be_roster->FindApp(kSignature, &ref);
				deskbar.AddItem(&ref);
			}
			break;
		}
 
		default:
			BWindow::MessageReceived(message);
			break;
	}
}
 
 
bool
WorkspacesWindow::QuitRequested()
{
	be_app->PostMessage(B_QUIT_REQUESTED);
	return true;
}
 
 
void
WorkspacesWindow::SetAutoRaise(bool enable)
{
	fSettings->SetAutoRaising(enable);
 
	if (enable)
		ChildAt(0)->SetEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
	else
		ChildAt(0)->SetEventMask(0);
}
 
 
//	#pragma mark - WorkspacesApp
 
 
WorkspacesApp::WorkspacesApp()
	: BApplication(kSignature)
{
	fWindow = new WorkspacesWindow(new WorkspacesSettings());
}
 
 
WorkspacesApp::~WorkspacesApp()
{
}
 
 
void
WorkspacesApp::AboutRequested()
{
	fWindow->PostMessage(B_ABOUT_REQUESTED);
}
 
 
void
WorkspacesApp::Usage(const char *programName)
{
	printf(B_TRANSLATE("Usage: %s [options] [workspace]\n"
		"where \"options\" are:\n"
		"  --notitle\t\ttitle bar removed, border and resize kept\n"
		"  --noborder\t\ttitle, border, and resize removed\n"
		"  --avoidfocus\t\tprevents the window from being the target of "
		"keyboard events\n"
		"  --alwaysontop\t\tkeeps window on top\n"
		"  --notmovable\t\twindow can't be moved around\n"
		"  --autoraise\t\tauto-raise the workspace window when it's at the "
		"screen edge\n"
		"  --help\t\tdisplay this help and exit\n"
		"and \"workspace\" is the number of the Workspace to which to switch "
		"(0-31)\n"),
		programName);
 
	// quit only if we aren't running already
	if (IsLaunching())
		Quit();
}
 
 
void
WorkspacesApp::ArgvReceived(int32 argc, char **argv)
{
	for (int i = 1;  i < argc;  i++) {
		if (argv[i][0] == '-' && argv[i][1] == '-') {
			// evaluate --arguments
			if (!strcmp(argv[i], "--notitle"))
				fWindow->SetLook(B_MODAL_WINDOW_LOOK);
			else if (!strcmp(argv[i], "--noborder"))
				fWindow->SetLook(B_NO_BORDER_WINDOW_LOOK);
			else if (!strcmp(argv[i], "--avoidfocus"))
				fWindow->SetFlags(fWindow->Flags() | B_AVOID_FOCUS);
			else if (!strcmp(argv[i], "--notmovable"))
				fWindow->SetFlags(fWindow->Flags() | B_NOT_MOVABLE);
			else if (!strcmp(argv[i], "--alwaysontop"))
				fWindow->SetFeel(B_FLOATING_ALL_WINDOW_FEEL);
			else if (!strcmp(argv[i], "--autoraise"))
				fWindow->SetAutoRaise(true);
			else {
				const char *programName = strrchr(argv[0], '/');
				programName = programName ? programName + 1 : argv[0];
 
				Usage(programName);
			}
		} else if (isdigit(*argv[i])) {
			// check for a numeric arg, if not already given
			activate_workspace(atoi(argv[i]));
 
			// if the app is running, don't quit
			// but if it isn't, cancel the complete run, so it doesn't
			// open any window
			if (IsLaunching())
				Quit();
		} else if (!strcmp(argv[i], "-")) {
			activate_workspace(current_workspace() - 1);
 
			if (IsLaunching())
				Quit();
		} else if (!strcmp(argv[i], "+")) {
			activate_workspace(current_workspace() + 1);
 
			if (IsLaunching())
				Quit();
		} else {
			// some unknown arguments were specified
			fprintf(stderr, B_TRANSLATE("Invalid argument: %s\n"), argv[i]);
 
			if (IsLaunching())
				Quit();
		}
	}
}
 
 
void
WorkspacesApp::ReadyToRun()
{
	fWindow->Show();
}
 
 
//	#pragma mark -
 
 
BView*
instantiate_deskbar_item(float maxWidth, float maxHeight)
{
	// Calculate the correct size of the Deskbar replicant first
 
	BScreen screen;
	float screenWidth = screen.Frame().Width();
	float screenHeight = screen.Frame().Height();
	float aspectRatio = screenWidth / screenHeight;
	uint32 columns, rows;
	BPrivate::get_workspaces_layout(&columns, &rows);
 
	// We use 1px for the top and left borders (shown as double)
	// and divide the remainder equally. However, we keep in mind
	// that the actual width and height of each workspace is smaller
	// by 1px, because of bottom/right borders (shown as single).
	// When calculating workspace width, we must ensure that the assumed
	// actual workspace height is not negative. Zero is OK.
 
	float height = maxHeight;
	float rowHeight = floor((height - 1) / rows);
	if (rowHeight < 1)
		rowHeight = 1;
 
	float columnWidth = floor((rowHeight - 1) * aspectRatio) + 1;
 
	float width = columnWidth * columns + 1;
	if (width > maxWidth)
		width = maxWidth;
 
	return new WorkspacesView(BRect (0, 0, width - 1, height - 1), false);
}
 
 
//	#pragma mark -
 
 
int
main(int argc, char **argv)
{
	WorkspacesApp app;
	app.Run();
 
	return 0;
}

V763 Parameter 'height' is always rewritten in function body before being used.

V773 Visibility scope of the 'menu' pointer was exited without releasing the memory. A memory leak is possible.

V763 Parameter 'width' is always rewritten in function body before being used.