haiku/src/apps/workspaces/Workspaces.cpp

1151 lines
28 KiB
C++

/*
* 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;
}