21641d8925
When workspaces slide in/out in mosaic view, they may cross with the workspace controls, which looks pretty weird. Also adjust the vertical spacing in the grid view so that the grid height matches the workspace height in the linear view. https://bugzilla.gnome.org/show_bug.cgi?id=610350
1046 lines
38 KiB
JavaScript
1046 lines
38 KiB
JavaScript
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
|
|
|
const Big = imports.gi.Big;
|
|
const Clutter = imports.gi.Clutter;
|
|
const GdkPixbuf = imports.gi.GdkPixbuf;
|
|
const Gdk = imports.gi.Gdk;
|
|
const Gtk = imports.gi.Gtk;
|
|
const Lang = imports.lang;
|
|
const Mainloop = imports.mainloop;
|
|
const Meta = imports.gi.Meta;
|
|
const Pango = imports.gi.Pango;
|
|
const Shell = imports.gi.Shell;
|
|
const St = imports.gi.St;
|
|
const Signals = imports.signals;
|
|
|
|
const DND = imports.ui.dnd;
|
|
const Lightbox = imports.ui.lightbox;
|
|
const Main = imports.ui.main;
|
|
const Overview = imports.ui.overview;
|
|
const Panel = imports.ui.panel;
|
|
const Tweener = imports.ui.tweener;
|
|
const Workspace = imports.ui.workspace;
|
|
|
|
const WORKSPACE_SWITCH_TIME = 0.25;
|
|
// Note that mutter has a compile-time limit of 36
|
|
const MAX_WORKSPACES = 16;
|
|
|
|
// The values here are also used for gconf, and the key and value
|
|
// names must match
|
|
const WorkspacesViewType = {
|
|
SINGLE: 'single',
|
|
GRID: 'grid'
|
|
};
|
|
const WORKSPACES_VIEW_KEY = 'overview/workspaces_view';
|
|
|
|
function GenericWorkspacesView(width, height, x, y, animate) {
|
|
this._init(width, height, x, y, animate);
|
|
}
|
|
|
|
GenericWorkspacesView.prototype = {
|
|
_init: function(width, height, x, y, animate) {
|
|
this.actor = new St.Bin({ style_class: "workspaces" });
|
|
this._actor = new Clutter.Group();
|
|
|
|
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
|
|
|
|
this.actor.add_actor(this._actor);
|
|
this.actor.connect('style-changed', Lang.bind(this,
|
|
function() {
|
|
let node = this.actor.get_theme_node();
|
|
let [a, spacing] = node.get_length('spacing', false);
|
|
this._spacing = spacing;
|
|
this._positionWorkspaces();
|
|
}));
|
|
|
|
this._width = width;
|
|
this._height = height;
|
|
this._x = x;
|
|
this._y = y;
|
|
this._spacing = 0;
|
|
|
|
this._windowSelectionAppId = null;
|
|
|
|
this._workspaces = [];
|
|
|
|
this._highlightWindow = null;
|
|
|
|
let activeWorkspaceIndex = global.screen.get_active_workspace_index();
|
|
|
|
// Create and position workspace objects
|
|
for (let w = 0; w < global.screen.n_workspaces; w++) {
|
|
this._addWorkspaceActor(w);
|
|
}
|
|
this._workspaces[activeWorkspaceIndex].actor.raise_top();
|
|
|
|
// Position/scale the desktop windows and their children after the
|
|
// workspaces have been created. This cannot be done first because
|
|
// window movement depends on the Workspaces object being accessible
|
|
// as an Overview member.
|
|
this._overviewShowingId =
|
|
Main.overview.connect('showing',
|
|
Lang.bind(this, function() {
|
|
this._onRestacked();
|
|
for (let w = 0; w < this._workspaces.length; w++)
|
|
this._workspaces[w].zoomToOverview(animate);
|
|
}));
|
|
|
|
// Track changes to the number of workspaces
|
|
this._nWorkspacesNotifyId =
|
|
global.screen.connect('notify::n-workspaces',
|
|
Lang.bind(this, this._workspacesChanged));
|
|
this._switchWorkspaceNotifyId =
|
|
global.window_manager.connect('switch-workspace',
|
|
Lang.bind(this, this._activeWorkspaceChanged));
|
|
this._restackedNotifyId =
|
|
global.screen.connect('restacked',
|
|
Lang.bind(this, this._onRestacked));
|
|
},
|
|
|
|
_lookupWorkspaceForMetaWindow: function (metaWindow) {
|
|
for (let i = 0; i < this._workspaces.length; i++) {
|
|
if (this._workspaces[i].containsMetaWindow(metaWindow))
|
|
return this._workspaces[i];
|
|
}
|
|
return null;
|
|
},
|
|
|
|
_lookupCloneForMetaWindow: function (metaWindow) {
|
|
for (let i = 0; i < this._workspaces.length; i++) {
|
|
let clone = this._workspaces[i].lookupCloneForMetaWindow(metaWindow);
|
|
if (clone)
|
|
return clone;
|
|
}
|
|
return null;
|
|
},
|
|
|
|
setHighlightWindow: function (metaWindow) {
|
|
// Looping over all workspaces is easier than keeping track of the last
|
|
// highlighted window while trying to handle the window or workspace possibly
|
|
// going away.
|
|
for (let i = 0; i < this._workspaces.length; i++) {
|
|
this._workspaces[i].setHighlightWindow(null);
|
|
}
|
|
if (metaWindow != null) {
|
|
let workspace = this._lookupWorkspaceForMetaWindow(metaWindow);
|
|
workspace.setHighlightWindow(metaWindow);
|
|
}
|
|
},
|
|
|
|
_clearApplicationWindowSelection: function(reposition) {
|
|
if (this._windowSelectionAppId == null)
|
|
return;
|
|
this._windowSelectionAppId = null;
|
|
|
|
for (let i = 0; i < this._workspaces.length; i++) {
|
|
this._workspaces[i].setLightboxMode(false);
|
|
this._workspaces[i].setShowOnlyWindows(null, reposition);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* setApplicationWindowSelection:
|
|
* @appid: Application identifier string
|
|
*
|
|
* Enter a mode which shows only the windows owned by the
|
|
* given application, and allow highlighting of a specific
|
|
* window with setHighlightWindow().
|
|
*/
|
|
setApplicationWindowSelection: function (appId) {
|
|
if (appId == null) {
|
|
this._clearApplicationWindowSelection(true);
|
|
return;
|
|
}
|
|
|
|
if (appId == this._windowSelectionAppId)
|
|
return;
|
|
|
|
this._windowSelectionAppId = appId;
|
|
|
|
let appSys = Shell.AppSystem.get_default();
|
|
|
|
let showOnlyWindows = {};
|
|
let app = appSys.get_app(appId);
|
|
let windows = app.get_windows();
|
|
for (let i = 0; i < windows.length; i++) {
|
|
showOnlyWindows[windows[i]] = 1;
|
|
}
|
|
|
|
for (let i = 0; i < this._workspaces.length; i++) {
|
|
this._workspaces[i].setLightboxMode(true);
|
|
this._workspaces[i].setShowOnlyWindows(showOnlyWindows, true);
|
|
}
|
|
},
|
|
|
|
hide: function() {
|
|
let activeWorkspaceIndex = global.screen.get_active_workspace_index();
|
|
let activeWorkspace = this._workspaces[activeWorkspaceIndex];
|
|
|
|
if (this._windowSelectionAppId != null)
|
|
this._clearApplicationWindowSelection(false);
|
|
|
|
activeWorkspace.actor.raise_top();
|
|
|
|
for (let w = 0; w < this._workspaces.length; w++)
|
|
this._workspaces[w].zoomFromOverview();
|
|
},
|
|
|
|
destroy: function() {
|
|
this.actor.destroy();
|
|
},
|
|
|
|
_onDestroy: function() {
|
|
for (let w = 0; w < this._workspaces.length; w++)
|
|
this._workspaces[w].destroy();
|
|
this._workspaces = [];
|
|
|
|
Main.overview.disconnect(this._overviewShowingId);
|
|
global.screen.disconnect(this._nWorkspacesNotifyId);
|
|
global.window_manager.disconnect(this._switchWorkspaceNotifyId);
|
|
global.screen.disconnect(this._restackedNotifyId);
|
|
},
|
|
|
|
getScale: function() {
|
|
return this._workspaces[0].scale;
|
|
},
|
|
|
|
_onRestacked: function() {
|
|
let stack = global.get_windows();
|
|
let stackIndices = {};
|
|
|
|
for (let i = 0; i < stack.length; i++) {
|
|
// Use the stable sequence for an integer to use as a hash key
|
|
stackIndices[stack[i].get_meta_window().get_stable_sequence()] = i;
|
|
}
|
|
|
|
for (let i = 0; i < this._workspaces.length; i++)
|
|
this._workspaces[i].syncStacking(stackIndices);
|
|
},
|
|
|
|
// Handles a drop onto the (+) button; assumes the new workspace
|
|
// has already been added
|
|
acceptNewWorkspaceDrop: function(source, dropActor, x, y, time) {
|
|
return this._workspaces[this._workspaces.length - 1].acceptDrop(source, dropActor, x, y, time);
|
|
},
|
|
|
|
// Get the grid position of the active workspace.
|
|
getActiveWorkspacePosition: function() {
|
|
let activeWorkspaceIndex = global.screen.get_active_workspace_index();
|
|
let activeWorkspace = this._workspaces[activeWorkspaceIndex];
|
|
|
|
return [activeWorkspace.gridX, activeWorkspace.gridY];
|
|
},
|
|
|
|
_setButtonSensitivity: function(button, sensitive) {
|
|
if (button == null)
|
|
return;
|
|
if (sensitive && !button.reactive) {
|
|
button.reactive = true;
|
|
button.opacity = 255;
|
|
} else if (!sensitive && button.reactive) {
|
|
button.reactive = false;
|
|
button.opacity = 85;
|
|
}
|
|
},
|
|
|
|
createControllerBar: function() {
|
|
throw new Error("Not implemented");
|
|
},
|
|
|
|
_positionWorkspaces: function() {
|
|
throw new Error("Not implemented");
|
|
},
|
|
|
|
_workspacesChanged: function() {
|
|
throw new Error("Not implemented");
|
|
},
|
|
|
|
_activeWorkspaceChanged: function() {
|
|
throw new Error("Not implemented");
|
|
},
|
|
|
|
_addWorkspaceActor: function() {
|
|
throw new Error("Not implemented");
|
|
}
|
|
}
|
|
|
|
function MosaicView(width, height, x, y, animate) {
|
|
this._init(width, height, x, y, animate);
|
|
}
|
|
|
|
MosaicView.prototype = {
|
|
__proto__: GenericWorkspacesView.prototype,
|
|
|
|
_init: function(width, height, x, y, animate) {
|
|
GenericWorkspacesView.prototype._init.call(this, width, height, x, y, animate);
|
|
|
|
this.actor.style_class = "workspaces mosaic";
|
|
this._actor.set_clip(x - Workspace.FRAME_SIZE,
|
|
y - Workspace.FRAME_SIZE,
|
|
width + 2 * Workspace.FRAME_SIZE,
|
|
height + 2 * Workspace.FRAME_SIZE);
|
|
this._workspaces[global.screen.get_active_workspace_index()].setSelected(true);
|
|
|
|
this._removeButton = null;
|
|
this._addButton = null;
|
|
},
|
|
|
|
// Assign grid positions to workspaces. We can't just do a simple
|
|
// row-major or column-major numbering, because we don't want the
|
|
// existing workspaces to get rearranged when we add a row or
|
|
// column. So we alternate between adding to rows and adding to
|
|
// columns. (So, eg, when going from a 2x2 grid of 4 workspaces to
|
|
// a 3x2 grid of 5 workspaces, the 4 existing workspaces stay
|
|
// where they are, and the 5th one is added to the end of the
|
|
// first row.)
|
|
//
|
|
// FIXME: need to make the metacity internal layout agree with this!
|
|
_positionWorkspaces: function() {
|
|
let gridWidth = Math.ceil(Math.sqrt(this._workspaces.length));
|
|
let gridHeight = Math.ceil(this._workspaces.length / gridWidth);
|
|
|
|
// adjust vertical spacing so workspaces can preserve their aspect
|
|
// ratio without exceeding this._height
|
|
let verticalSpacing = this._spacing * this._height / this._width;
|
|
|
|
let wsWidth = (this._width - (gridWidth - 1) * this._spacing) / gridWidth;
|
|
let wsHeight = (this._height - (gridHeight - 1) * verticalSpacing) / gridHeight;
|
|
let scale = wsWidth / global.screen_width;
|
|
|
|
let span = 1, n = 0, row = 0, col = 0, horiz = true;
|
|
|
|
for (let w = 0; w < this._workspaces.length; w++) {
|
|
let workspace = this._workspaces[w];
|
|
|
|
workspace.gridRow = row;
|
|
workspace.gridCol = col;
|
|
|
|
workspace.gridX = this._x + workspace.gridCol * (wsWidth + this._spacing);
|
|
workspace.gridY = this._y + workspace.gridRow * (wsHeight + verticalSpacing);
|
|
workspace.scale = scale;
|
|
|
|
if (horiz) {
|
|
col++;
|
|
if (col == span) {
|
|
row = 0;
|
|
horiz = false;
|
|
}
|
|
} else {
|
|
row++;
|
|
if (row == span) {
|
|
col = 0;
|
|
horiz = true;
|
|
span++;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
_workspacesChanged: function() {
|
|
let oldNumWorkspaces = this._workspaces.length;
|
|
let newNumWorkspaces = global.screen.n_workspaces;
|
|
|
|
if (oldNumWorkspaces == newNumWorkspaces)
|
|
return;
|
|
|
|
let oldScale = this._workspaces[0].scale;
|
|
let oldGridWidth = Math.ceil(Math.sqrt(oldNumWorkspaces));
|
|
let oldGridHeight = Math.ceil(oldNumWorkspaces / oldGridWidth);
|
|
let lostWorkspaces = [];
|
|
|
|
if (newNumWorkspaces > oldNumWorkspaces) {
|
|
// Create new workspace groups
|
|
for (let w = oldNumWorkspaces; w < newNumWorkspaces; w++) {
|
|
this._addWorkspaceActor(w);
|
|
}
|
|
|
|
} else {
|
|
// Truncate the list of workspaces
|
|
// FIXME: assumes that the workspaces are being removed from
|
|
// the end of the list, not the start/middle
|
|
lostWorkspaces = this._workspaces.splice(newNumWorkspaces);
|
|
}
|
|
|
|
// Figure out the new layout
|
|
this._positionWorkspaces();
|
|
let newScale = this._workspaces[0].scale;
|
|
let newGridWidth = Math.ceil(Math.sqrt(newNumWorkspaces));
|
|
let newGridHeight = Math.ceil(newNumWorkspaces / newGridWidth);
|
|
|
|
if (newGridWidth != oldGridWidth || newGridHeight != oldGridHeight) {
|
|
// We need to resize/move the existing workspaces/windows
|
|
let existingWorkspaces = Math.min(oldNumWorkspaces, newNumWorkspaces);
|
|
for (let w = 0; w < existingWorkspaces; w++)
|
|
this._workspaces[w].resizeToGrid(oldScale);
|
|
}
|
|
|
|
if (newScale != oldScale) {
|
|
// The workspace scale affects window size/positioning because we clamp
|
|
// window size to a 1:1 ratio and never scale them up
|
|
let existingWorkspaces = Math.min(oldNumWorkspaces, newNumWorkspaces);
|
|
for (let w = 0; w < existingWorkspaces; w++)
|
|
this._workspaces[w].positionWindows(Workspace.WindowPositionFlags.ANIMATE);
|
|
}
|
|
|
|
if (newNumWorkspaces > oldNumWorkspaces) {
|
|
// Slide new workspaces in from offscreen
|
|
// New workspaces can contain windows.
|
|
for (let w = oldNumWorkspaces; w < newNumWorkspaces; w++) {
|
|
this._workspaces[w].positionWindows(0);
|
|
this._workspaces[w].slideIn(oldScale);
|
|
}
|
|
} else {
|
|
// Slide old workspaces out
|
|
for (let w = 0; w < lostWorkspaces.length; w++) {
|
|
let workspace = lostWorkspaces[w];
|
|
workspace.slideOut(function () { workspace.destroy(); });
|
|
}
|
|
|
|
// FIXME: deal with windows on the lost workspaces
|
|
}
|
|
|
|
// Reset the selection state; if we went from > 1 workspace to 1,
|
|
// this has the side effect of removing the frame border
|
|
let activeIndex = global.screen.get_active_workspace_index();
|
|
this._workspaces[activeIndex].setSelected(true);
|
|
|
|
this._updateButtonsVisibility();
|
|
},
|
|
|
|
_activeWorkspaceChanged: function(wm, from, to, direction) {
|
|
this._workspaces[from].setSelected(false);
|
|
this._workspaces[to].setSelected(true);
|
|
},
|
|
|
|
_addWorkspaceActor: function(workspaceNum) {
|
|
let workspace = new Workspace.Workspace(workspaceNum, this._actor);
|
|
this._workspaces[workspaceNum] = workspace;
|
|
this._actor.add_actor(workspace.actor);
|
|
},
|
|
|
|
createControllerBar: function() {
|
|
let actor = new St.BoxLayout({ 'pack-start': true });
|
|
let bin = new St.Bin();
|
|
let addButton = new St.Button({ style_class: "workspace-controls add" });
|
|
this._addButton = addButton;
|
|
addButton.connect('clicked', Lang.bind(this, this._addNewWorkspace));
|
|
addButton._delegate = addButton;
|
|
addButton._delegate.acceptDrop = Lang.bind(this, function(source, actor, x, y, time) {
|
|
return this._acceptNewWorkspaceDrop(source, actor, x, y, time);
|
|
});
|
|
actor.add(bin, { x_align: St.Align.END });
|
|
bin.set_child(addButton);
|
|
bin.set_alignment(St.Align.END, St.Align.START);
|
|
|
|
bin = new St.Bin();
|
|
let removeButton = new St.Button({ style_class: "workspace-controls remove" });
|
|
this._removeButton = removeButton;
|
|
removeButton.connect('clicked', Lang.bind(this, function() {
|
|
if (this._workspaces.length <= 1)
|
|
return;
|
|
global.screen.remove_workspace(this._workspaces[this._workspaces.length - 1]._metaWorkspace, global.get_current_time());
|
|
}));
|
|
actor.add(bin, { expand: true, x_fill: true, x_align: St.Align.END });
|
|
this._updateButtonsVisibility();
|
|
bin.set_child(removeButton);
|
|
bin.set_alignment(St.Align.END, St.Align.START);
|
|
|
|
return actor;
|
|
},
|
|
|
|
_updateButtonsVisibility: function() {
|
|
let canRemove = (global.screen.n_workspaces > 1);
|
|
let canAdd = (global.screen.n_workspaces < MAX_WORKSPACES);
|
|
|
|
this._setButtonSensitivity(this._removeButton, canRemove);
|
|
this._setButtonSensitivity(this._addButton, canAdd);
|
|
},
|
|
|
|
_addNewWorkspace: function() {
|
|
global.screen.append_new_workspace(false, global.get_current_time());
|
|
},
|
|
|
|
_acceptNewWorkspaceDrop: function(source, dropActor, x, y, time) {
|
|
this._addNewWorkspace();
|
|
return this.acceptNewWorkspaceDrop(source, dropActor, x, y, time);
|
|
}
|
|
};
|
|
|
|
function SingleView(width, height, x, y, animate) {
|
|
this._init(width, height, x, y, animate);
|
|
}
|
|
|
|
SingleView.prototype = {
|
|
__proto__: GenericWorkspacesView.prototype,
|
|
|
|
_init: function(width, height, x, y, animate) {
|
|
GenericWorkspacesView.prototype._init.call(this, width, height, x, y, animate);
|
|
|
|
this.actor.style_class = "workspaces single";
|
|
this._actor.set_clip(x, y, width, height);
|
|
this._addButton = null;
|
|
this._removeButton = null;
|
|
this._indicatorsPanel = null;
|
|
this._indicatorsPanelWidth = null;
|
|
this._scroll = null;
|
|
this._lostWorkspaces = [];
|
|
this._scrolling = false;
|
|
this._animatingScroll = false;
|
|
},
|
|
|
|
_positionWorkspaces: function() {
|
|
let scale = this._width / global.screen_width;
|
|
let active = global.screen.get_active_workspace_index();
|
|
|
|
for (let w = 0; w < this._workspaces.length; w++) {
|
|
let workspace = this._workspaces[w];
|
|
|
|
workspace.gridRow = 0;
|
|
workspace.gridCol = 0;
|
|
|
|
workspace.scale = scale;
|
|
let _width = workspace.actor.width * scale;
|
|
workspace.gridX = this._x + (w - active) * (_width + this._spacing);
|
|
workspace.gridY = this._y;
|
|
|
|
workspace.setSelected(false);
|
|
}
|
|
},
|
|
|
|
_updateWorkspaceActors: function() {
|
|
for (let w = 0; w < this._workspaces.length; w++) {
|
|
let workspace = this._workspaces[w];
|
|
|
|
workspace.actor.set_scale(workspace.scale, workspace.scale);
|
|
workspace.actor.set_position(workspace.gridX, workspace.gridY);
|
|
workspace.positionWindows(0);
|
|
}
|
|
},
|
|
|
|
_scrollToActive: function(showAnimation) {
|
|
let active = global.screen.get_active_workspace_index();
|
|
|
|
this._scrollWorkspacesToIndex(active, showAnimation);
|
|
this._scrollScrollBarToIndex(active, showAnimation);
|
|
},
|
|
|
|
_scrollWorkspacesToIndex: function(index, showAnimation) {
|
|
let active = global.screen.get_active_workspace_index();
|
|
let targetWorkspaceNewX = this._x;
|
|
let targetWorkspaceCurrentX = this._workspaces[index].gridX;
|
|
let dx = targetWorkspaceNewX - targetWorkspaceCurrentX;
|
|
|
|
for (let w = 0; w < this._workspaces.length; w++) {
|
|
let workspace = this._workspaces[w];
|
|
|
|
workspace.gridX += dx;
|
|
workspace.actor.show();
|
|
workspace._hideAllOverlays();
|
|
|
|
let visible = (w == active);
|
|
if (showAnimation) {
|
|
Tweener.addTween(workspace.actor,
|
|
{ x: workspace.gridX,
|
|
time: WORKSPACE_SWITCH_TIME,
|
|
transition: 'easeOutQuad',
|
|
onComplete: function() {
|
|
if (visible)
|
|
workspace._fadeInAllOverlays();
|
|
else
|
|
workspace.actor.hide();
|
|
}});
|
|
} else {
|
|
workspace.actor.x = workspace.gridX;
|
|
if (visible)
|
|
workspace._fadeInAllOverlays();
|
|
else
|
|
workspace.actor.hide();
|
|
}
|
|
}
|
|
|
|
for (let l = 0; l < this._lostWorkspaces.length; l++) {
|
|
let workspace = this._lostWorkspaces[l];
|
|
|
|
workspace.gridX += dx;
|
|
workspace.actor.show();
|
|
workspace._hideAllOverlays();
|
|
|
|
if (showAnimation) {
|
|
Tweener.addTween(workspace.actor,
|
|
{ x: workspace.gridX,
|
|
time: WORKSPACE_SWITCH_TIME,
|
|
transition: 'easeOutQuad',
|
|
onComplete: Lang.bind(this, this._cleanWorkspaces)
|
|
});
|
|
} else {
|
|
this._cleanWorkspaces();
|
|
}
|
|
}
|
|
},
|
|
|
|
_cleanWorkspaces: function() {
|
|
if (this._lostWorkspaces.length == 0)
|
|
return;
|
|
|
|
for (let l = 0; l < this._lostWorkspaces.length; l++)
|
|
this._lostWorkspaces[l].destroy();
|
|
this._lostWorkspaces = [];
|
|
|
|
this._positionWorkspaces();
|
|
this._updateWorkspaceActors();
|
|
},
|
|
|
|
_scrollScrollBarToIndex: function(index, showAnimation) {
|
|
if (!this._scroll || this._scrolling)
|
|
return;
|
|
|
|
this._animatingScroll = true;
|
|
|
|
if (showAnimation) {
|
|
Tweener.addTween(this._scroll.adjustment, {
|
|
value: index,
|
|
time: WORKSPACE_SWITCH_TIME,
|
|
transition: 'easeOutQuad',
|
|
onComplete: Lang.bind(this,
|
|
function() {
|
|
this._animatingScroll = false;
|
|
})
|
|
});
|
|
} else {
|
|
this._scroll.adjustment.value = index;
|
|
this._animatingScroll = false;
|
|
}
|
|
},
|
|
|
|
_workspacesChanged: function() {
|
|
let oldNumWorkspaces = this._workspaces.length;
|
|
let newNumWorkspaces = global.screen.n_workspaces;
|
|
let active = global.screen.get_active_workspace_index();
|
|
|
|
if (oldNumWorkspaces == newNumWorkspaces)
|
|
return;
|
|
|
|
if (this._scroll != null)
|
|
this._scroll.adjustment.upper = newNumWorkspaces;
|
|
|
|
if (newNumWorkspaces > oldNumWorkspaces) {
|
|
// Create new workspace groups
|
|
for (let w = oldNumWorkspaces; w < newNumWorkspaces; w++)
|
|
this._addWorkspaceActor(w);
|
|
this._positionWorkspaces();
|
|
this._updateWorkspaceActors();
|
|
this._scrollScrollBarToIndex(active + 1, false);
|
|
|
|
} else {
|
|
let active = global.screen.get_active_workspace_index();
|
|
let removedNum = oldNumWorkspaces - newNumWorkspaces;
|
|
let removedIndex = active + 1;
|
|
this._lostWorkspaces = this._workspaces.splice(removedIndex,
|
|
removedNum);
|
|
|
|
// Don't let the user try to select this workspace as it's
|
|
// making its exit.
|
|
for (let l = 0; l < this._lostWorkspaces.length; l++)
|
|
this._lostWorkspaces[l]._desktop.actor.reactive = false;
|
|
|
|
// reassign workspaceNum and metaWorkspace, as lost workspaces
|
|
// have not necessarily been removed from the end
|
|
for (let i = removedIndex; i < this._workspaces.length; i++) {
|
|
let metaWorkspace = global.screen.get_workspace_by_index(i);
|
|
this._workspaces[i].workspaceNum = i;
|
|
this._workspaces[i]._metaWorkspace = metaWorkspace;
|
|
}
|
|
|
|
this._scrollScrollBarToIndex(active, false);
|
|
this._scrollToActive(true);
|
|
}
|
|
|
|
this._updatePanelVisibility();
|
|
},
|
|
|
|
_activeWorkspaceChanged: function(wm, from, to, direction) {
|
|
this._updatePanelVisibility();
|
|
|
|
if (this._scrolling)
|
|
return;
|
|
|
|
this._scrollToActive(true);
|
|
},
|
|
|
|
_addWorkspaceActor: function(workspaceNum) {
|
|
let workspace = new Workspace.Workspace(workspaceNum, this._actor);
|
|
|
|
this._actor.add_actor(workspace.actor);
|
|
this._workspaces[workspaceNum] = workspace;
|
|
},
|
|
|
|
// handle changes to the scroll bar's adjustment:
|
|
// sync the workspaces' positions to the position of the scroll bar handle
|
|
// and change the active workspace if appropriate
|
|
_onScroll: function(adj) {
|
|
if (this._animatingScroll)
|
|
return;
|
|
|
|
let active = global.screen.get_active_workspace_index();
|
|
let current = Math.round(adj.value);
|
|
|
|
if (active != current) {
|
|
let metaWorkspace = this._workspaces[current]._metaWorkspace;
|
|
|
|
if (!this._scrolling) {
|
|
// This here is a little tricky - we get here when StScrollBar
|
|
// animates paging; we switch the active workspace, but
|
|
// leave out any extra animation (just like we would do when
|
|
// the handle was dragged)
|
|
// If StScrollBar emitted scroll-start before and scroll-stop
|
|
// after the animation, this would not be necessary
|
|
this._scrolling = true;
|
|
metaWorkspace.activate(global.get_current_time());
|
|
this._scrolling = false;
|
|
} else {
|
|
metaWorkspace.activate(global.get_current_time());
|
|
}
|
|
}
|
|
|
|
let last = this._workspaces.length - 1;
|
|
let firstWorkspaceX = this._workspaces[0].actor.x;
|
|
let lastWorkspaceX = this._workspaces[last].actor.x;
|
|
let workspacesWidth = lastWorkspaceX - firstWorkspaceX;
|
|
|
|
// The scrollbar is hidden when there is only one workspace, so
|
|
// adj.upper should at least be 2 - but better be safe than sorry
|
|
if (adj.upper == 1)
|
|
return;
|
|
|
|
let currentX = firstWorkspaceX;
|
|
let newX = this._x - adj.value / (adj.upper - 1) * workspacesWidth;
|
|
|
|
let dx = newX - currentX;
|
|
|
|
for (let i = 0; i < this._workspaces.length; i++) {
|
|
this._workspaces[i]._hideAllOverlays();
|
|
if (Math.abs(i - adj.value) <= 1)
|
|
this._workspaces[i].actor.show();
|
|
else
|
|
this._workspaces[i].actor.hide();
|
|
this._workspaces[i].actor.x += dx;
|
|
}
|
|
|
|
if (!this._scrolling && active == adj.value) {
|
|
// Again, work around the paging in StScrollBar: simulate
|
|
// the effect of scroll-stop
|
|
this._scrolling = true;
|
|
this._scrollToActive(false);
|
|
this._scrolling = false;
|
|
}
|
|
},
|
|
|
|
// handle scroll wheel events:
|
|
// activate the next or previous workspace and let the signal handler
|
|
// manage the animation
|
|
_onScrollEvent: function(actor, event) {
|
|
let direction = event.get_scroll_direction();
|
|
let current = global.screen.get_active_workspace_index();
|
|
let last = global.screen.n_workspaces - 1;
|
|
let activate = current;
|
|
if (direction == Clutter.ScrollDirection.DOWN && current < last)
|
|
activate++;
|
|
else if (direction == Clutter.ScrollDirection.UP && current > 0)
|
|
activate--;
|
|
|
|
if (activate != current) {
|
|
let metaWorkspace = this._workspaces[activate]._metaWorkspace;
|
|
metaWorkspace.activate(global.get_current_time());
|
|
}
|
|
},
|
|
|
|
createControllerBar: function() {
|
|
let panel = new St.BoxLayout({ style_class: 'single-view-controls',
|
|
pack_start: true,
|
|
vertical: true });
|
|
|
|
let actor = new St.BoxLayout({ 'pack-start': true });
|
|
let active = global.screen.get_active_workspace_index();
|
|
let adj = new St.Adjustment({ value: active,
|
|
lower: 0,
|
|
page_increment: 1,
|
|
page_size: 1,
|
|
step_increment: 0,
|
|
upper: this._workspaces.length });
|
|
this._scroll = new St.ScrollBar({ adjustment: adj,
|
|
vertical: false,
|
|
name: 'SwitchScroll' });
|
|
|
|
// we have set adj.step_increment to 0, so all scroll wheel events
|
|
// are processed with this handler - this allows us to animate the
|
|
// workspace switch
|
|
this._scroll.connect('scroll-event',
|
|
Lang.bind(this, this._onScrollEvent));
|
|
|
|
this._scroll.adjustment.connect('notify::value',
|
|
Lang.bind(this, this._onScroll));
|
|
|
|
|
|
this._scroll.connect('scroll-start', Lang.bind(this,
|
|
function() {
|
|
this._scrolling = true;
|
|
}));
|
|
this._scroll.connect('scroll-stop', Lang.bind(this,
|
|
function() {
|
|
this._scrolling = false;
|
|
this._scrollToActive(true);
|
|
}));
|
|
|
|
let addButton = new St.Button({ style_class: "workspace-controls add" });
|
|
this._addButton = addButton;
|
|
addButton.connect('clicked', Lang.bind(this, this._addNewWorkspace));
|
|
addButton._delegate = addButton;
|
|
addButton._delegate.acceptDrop = Lang.bind(this, function(source, actor, x, y, time) {
|
|
return this._acceptNewWorkspaceDrop(source, actor, x, y, time);
|
|
});
|
|
actor.add(addButton, {x_align: St.Align.END, y_align: St.Align.START, 'y-fill': false});
|
|
|
|
let removeButton = new St.Button({ style_class: "workspace-controls remove" });
|
|
this._removeButton = removeButton;
|
|
removeButton.connect('clicked', Lang.bind(this, function() {
|
|
if (this._workspaces.length <= 1)
|
|
return;
|
|
let index = global.screen.get_active_workspace_index();
|
|
if (index == 0)
|
|
return;
|
|
global.screen.remove_workspace(this._workspaces[index]._metaWorkspace, global.get_current_time());
|
|
}));
|
|
actor.add(removeButton, { x_align: St.Align.END, y_align: St.Align.START, 'y-fill': false });
|
|
this._updatePanelVisibility();
|
|
|
|
panel.add(this._createPositionalIndicator(), {expand: true, 'x-fill': true, 'y-fill': true});
|
|
panel.add(this._scroll, { expand: true,
|
|
'x-fill': true,
|
|
'y-fill': false,
|
|
y_align: St.Align.START });
|
|
actor.add(panel, {expand: true, 'x-fill': true, 'y-fill': true});
|
|
|
|
return actor;
|
|
},
|
|
|
|
_addIndicatorClone: function(i, active) {
|
|
let actor = new St.Button({ style_class: 'workspace-indicator' });
|
|
if (active) {
|
|
actor.style_class = 'workspace-indicator active';
|
|
}
|
|
actor.connect('clicked', Lang.bind(this, function() {
|
|
if (this._workspaces[i] != undefined)
|
|
this._workspaces[i]._metaWorkspace.activate(global.get_current_time());
|
|
}));
|
|
|
|
actor._delegate = {};
|
|
actor._delegate.acceptDrop = Lang.bind(this, function(source, actor, x, y, time) {
|
|
if (this._workspaces[i].acceptDrop(source, actor, x, y, time)) {
|
|
this._workspaces[i]._metaWorkspace.activate(global.get_current_time());
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
});
|
|
|
|
actor.connect('scroll-event', Lang.bind(this, this._onScrollEvent));
|
|
|
|
this._indicatorsPanel.add_actor(actor);
|
|
|
|
let [a, spacing] = actor.get_theme_node().get_length('border-spacing', false);
|
|
if (this._indicatorsPanelWidth < spacing * (i + 1) + actor.width * (i + 1))
|
|
actor.hide();
|
|
actor.x = spacing * i + actor.width * i;
|
|
},
|
|
|
|
_fillPositionalIndicator: function() {
|
|
if (this._indicatorsPanel == null || this._indicatorsPanelWidth == null)
|
|
return;
|
|
let width = this._indicatorsPanelWidth;
|
|
this._indicatorsPanel.remove_all();
|
|
|
|
let activeWorkspaceIndex = global.screen.get_active_workspace_index();
|
|
for (let i = 0; i < this._workspaces.length; i++) {
|
|
this._addIndicatorClone(i, i == activeWorkspaceIndex);
|
|
}
|
|
this._indicatorsPanel.x = (this._indicatorsPanelWidth - this._indicatorsPanel.width) / 2;
|
|
},
|
|
|
|
_createPositionalIndicator: function() {
|
|
let actor = new St.Bin({ style_class: 'panel-button' });
|
|
let group = new Clutter.Group();
|
|
|
|
this._indicatorsPanel = new Shell.GenericContainer();
|
|
this._indicatorsPanel.connect('get-preferred-width', Lang.bind(this, function (actor, fh, alloc) {
|
|
let children = actor.get_children();
|
|
let width = 0;
|
|
for (let i = 0; i < children.length; i++) {
|
|
if (!children[i].visible)
|
|
continue;
|
|
if (children[i].x + children[i].width <= width)
|
|
continue;
|
|
width = children[i].x + children[i].width;
|
|
}
|
|
alloc.min_size = width;
|
|
alloc.natural_size = width;
|
|
}));
|
|
this._indicatorsPanel.connect('get-preferred-height', Lang.bind(this, function (actor, fw, alloc) {
|
|
let children = actor.get_children();
|
|
let height = 0;
|
|
if (children.length)
|
|
height = children[0].height;
|
|
alloc.min_size = height;
|
|
alloc.natural_size = height;
|
|
}));
|
|
this._indicatorsPanel.connect('allocate', Lang.bind(this, function (actor, box, flags) {
|
|
let children = actor.get_children();
|
|
for (let i = 0; i < children.length; i++) {
|
|
if (!children[i].visible)
|
|
continue;
|
|
let childBox = new Clutter.ActorBox();
|
|
childBox.x1 = children[i].x;
|
|
childBox.y1 = 0;
|
|
childBox.x2 = children[i].x + children[i].width;
|
|
childBox.y2 = children[i].height;
|
|
children[i].allocate(childBox, flags);
|
|
}
|
|
}));
|
|
|
|
group.add_actor(this._indicatorsPanel);
|
|
actor.set_child(group);
|
|
actor.set_alignment(St.Align.START, St.Align.START);
|
|
actor.set_fill(true, true);
|
|
this._indicatorsPanel.hide();
|
|
actor.connect('notify::width', Lang.bind(this, function(actor) {
|
|
this._indicatorsPanelWidth = actor.width;
|
|
this._updatePanelVisibility();
|
|
}));
|
|
actor.connect('destroy', Lang.bind(this, function() {
|
|
this._indicatorsPanel = null;
|
|
}));
|
|
return actor;
|
|
},
|
|
|
|
_updatePanelVisibility: function() {
|
|
let canRemove = (global.screen.get_active_workspace_index() != 0);
|
|
let canAdd = (global.screen.n_workspaces < MAX_WORKSPACES);
|
|
|
|
this._setButtonSensitivity(this._removeButton, canRemove);
|
|
this._setButtonSensitivity(this._addButton, canAdd);
|
|
|
|
let showSwitches = (global.screen.n_workspaces > 1);
|
|
if (this._scroll != null) {
|
|
if (showSwitches)
|
|
this._scroll.show();
|
|
else
|
|
this._scroll.hide();
|
|
}
|
|
if (this._indicatorsPanel != null) {
|
|
if (showSwitches)
|
|
this._indicatorsPanel.show();
|
|
else
|
|
this._indicatorsPanel.hide();
|
|
}
|
|
this._fillPositionalIndicator();
|
|
},
|
|
|
|
_addNewWorkspace: function() {
|
|
let ws = global.screen.append_new_workspace(false,
|
|
global.get_current_time());
|
|
ws.activate(global.get_current_time());
|
|
},
|
|
|
|
_acceptNewWorkspaceDrop: function(source, dropActor, x, y, time) {
|
|
this._addNewWorkspace();
|
|
return this.acceptNewWorkspaceDrop(source, dropActor, x, y, time);
|
|
}
|
|
};
|
|
|
|
function WorkspacesViewSwitch() {
|
|
this._init();
|
|
}
|
|
|
|
WorkspacesViewSwitch.prototype = {
|
|
_init: function() {
|
|
this._gconf = Shell.GConf.get_default();
|
|
this._mosaicViewButton = null;
|
|
this._singleViewButton = null;
|
|
this._controlsBar = null;
|
|
|
|
let view = this._gconf.get_string(WORKSPACES_VIEW_KEY).toUpperCase();
|
|
if (view in WorkspacesViewType)
|
|
this._currentViewType = WorkspacesViewType[view];
|
|
else
|
|
this._currentViewType = WorkspacesViewType.SINGLE;
|
|
},
|
|
|
|
_setView: function(view) {
|
|
this._mosaicViewButton.set_checked(WorkspacesViewType.GRID == view);
|
|
this._singleViewButton.set_checked(WorkspacesViewType.SINGLE == view);
|
|
|
|
if (this._currentViewType == view)
|
|
return;
|
|
this._currentViewType = view;
|
|
this._gconf.set_string(WORKSPACES_VIEW_KEY, view);
|
|
this.emit('view-changed');
|
|
},
|
|
|
|
createCurrentWorkspaceView: function(width, height, x, y, animate) {
|
|
switch (this._currentViewType) {
|
|
case WorkspacesViewType.SINGLE:
|
|
return new SingleView(width, height, x, y, animate);
|
|
case WorkspacesViewType.GRID:
|
|
return new MosaicView(width, height, x, y, animate);
|
|
default:
|
|
return new MosaicView(width, height, x, y, animate);
|
|
}
|
|
},
|
|
|
|
createControlsBar: function() {
|
|
let actor = new St.BoxLayout();
|
|
|
|
this._mosaicViewButton = new St.Button({ style_class: "workspace-controls switch-mosaic" });
|
|
this._mosaicViewButton.set_toggle_mode(true);
|
|
this._mosaicViewButton.connect('clicked', Lang.bind(this, function() {
|
|
this._setView(WorkspacesViewType.GRID);
|
|
}));
|
|
actor.add(this._mosaicViewButton, {'y-fill' : false, 'y-align' : St.Align.START});
|
|
|
|
this._singleViewButton = new St.Button({ style_class: "workspace-controls switch-single" });
|
|
this._singleViewButton.set_toggle_mode(true);
|
|
this._singleViewButton.connect('clicked', Lang.bind(this, function() {
|
|
this._setView(WorkspacesViewType.SINGLE);
|
|
}));
|
|
actor.add(this._singleViewButton, {'y-fill' : false, 'y-align' : St.Align.START});
|
|
|
|
if (this._currentViewType == WorkspacesViewType.GRID)
|
|
this._mosaicViewButton.set_checked(true);
|
|
else
|
|
this._singleViewButton.set_checked(true);
|
|
|
|
this._nWorkspacesNotifyId =
|
|
global.screen.connect('notify::n-workspaces',
|
|
Lang.bind(this, this._workspacesChanged));
|
|
|
|
actor.connect('destroy', Lang.bind(this, function() {
|
|
this._controlsBar = null;
|
|
global.screen.disconnect(this._nWorkspacesNotifyId);
|
|
}));
|
|
|
|
this._controlsBar = actor;
|
|
this._workspacesChanged();
|
|
return actor;
|
|
},
|
|
|
|
_workspacesChanged: function() {
|
|
if (this._controlsBar == null)
|
|
return;
|
|
if (global.screen.n_workspaces == 1)
|
|
this._controlsBar.set_opacity(0);
|
|
else
|
|
this._controlsBar.set_opacity(255);
|
|
}
|
|
};
|
|
|
|
Signals.addSignalMethods(WorkspacesViewSwitch.prototype);
|