f9e4385e02
If track-hover is set, update the hover property automatically, and the "hover" pseudo class to match, as StClickable used to do. (Remove the corresponding code in StClickable). Tweak the tooltip handling to use track-hover, which also makes it slightly more reliable in the presence of reactive children, etc.
1594 lines
61 KiB
JavaScript
1594 lines
61 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';
|
|
|
|
const WORKSPACE_DRAGGING_SCALE = 0.85;
|
|
const WORKSPACE_SHADOW_SCALE = (1 - WORKSPACE_DRAGGING_SCALE) / 2;
|
|
|
|
function GenericWorkspacesView(width, height, x, y, workspaces) {
|
|
this._init(width, height, x, y, workspaces);
|
|
}
|
|
|
|
GenericWorkspacesView.prototype = {
|
|
_init: function(width, height, x, y, workspaces) {
|
|
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;
|
|
if (Main.overview.animationInProgress)
|
|
this._positionWorkspaces();
|
|
else
|
|
this._transitionWorkspaces();
|
|
}));
|
|
|
|
this._width = width;
|
|
this._height = height;
|
|
this._x = x;
|
|
this._y = y;
|
|
this._spacing = 0;
|
|
|
|
this._windowSelectionAppId = null;
|
|
this._highlightWindow = null;
|
|
|
|
let activeWorkspaceIndex = global.screen.get_active_workspace_index();
|
|
this._workspaces = workspaces;
|
|
|
|
// Add workspace actors
|
|
for (let w = 0; w < global.screen.n_workspaces; w++)
|
|
this._workspaces[w].actor.reparent(this._actor);
|
|
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();
|
|
}));
|
|
|
|
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() {
|
|
Main.overview.disconnect(this._overviewShowingId);
|
|
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];
|
|
},
|
|
|
|
createControllerBar: function() {
|
|
throw new Error("Not implemented");
|
|
},
|
|
|
|
canAddWorkspace: function() {
|
|
return global.screen.n_workspaces < MAX_WORKSPACES;
|
|
},
|
|
|
|
addWorkspace: function() {
|
|
throw new Error("Not implemented");
|
|
},
|
|
|
|
canRemoveWorkspace: function() {
|
|
throw new Error("Not implemented");
|
|
},
|
|
|
|
removeWorkspace: function() {
|
|
throw new Error("Not implemented");
|
|
},
|
|
|
|
updateWorkspaces: function() {
|
|
throw new Error("Not implemented");
|
|
},
|
|
|
|
_transitionWorkspaces: function() {
|
|
throw new Error("Not implemented");
|
|
},
|
|
|
|
_positionWorkspaces: function() {
|
|
throw new Error("Not implemented");
|
|
},
|
|
|
|
_activeWorkspaceChanged: function() {
|
|
throw new Error("Not implemented");
|
|
},
|
|
|
|
_acceptNewWorkspaceDrop: function(source, dropActor, x, y, time) {
|
|
this.addWorkspace();
|
|
return this.acceptNewWorkspaceDrop(source, dropActor, x, y, time);
|
|
}
|
|
};
|
|
|
|
function MosaicView(width, height, x, y, workspaces) {
|
|
this._init(width, height, x, y, workspaces);
|
|
}
|
|
|
|
MosaicView.prototype = {
|
|
__proto__: GenericWorkspacesView.prototype,
|
|
|
|
_init: function(width, height, x, y, workspaces) {
|
|
GenericWorkspacesView.prototype._init.call(this, width, height, x, y, workspaces);
|
|
|
|
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);
|
|
},
|
|
|
|
// 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++;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
_transitionWorkspaces: function() {
|
|
// update workspace parameters
|
|
this._positionWorkspaces();
|
|
|
|
let active = global.screen.get_active_workspace_index();
|
|
let activeWorkspace = this._workspaces[active];
|
|
// scale is the factor needed to translate from the new scale
|
|
// (this view) to the currently active scale (previous view)
|
|
let scale = this._workspaces[0].actor.scale_x / activeWorkspace.scale;
|
|
|
|
for (let w = 0; w < this._workspaces.length; w++) {
|
|
let workspace = this._workspaces[w];
|
|
let originX, originY;
|
|
let dx, dy;
|
|
|
|
// The correct transition would be a straightforward animation
|
|
// of each workspace's old position/scale to the new one;
|
|
// however, this looks overly busy, so we only use a zoom effect.
|
|
// Unfortunately this implies that we cannot pretend to not knowing
|
|
// the other view's layout at this point:
|
|
// We position the workspaces in the grid, which we scale up so
|
|
// that the active workspace fills the viewport.
|
|
dx = workspace.gridX - activeWorkspace.gridX;
|
|
dy = workspace.gridY - activeWorkspace.gridY;
|
|
originX = this._x + scale * dx;
|
|
originY = this._y + scale * dy;
|
|
|
|
workspace.actor.set_position(originX, originY);
|
|
|
|
workspace.positionWindows(Workspace.WindowPositionFlags.ANIMATE);
|
|
workspace.setSelected(false);
|
|
workspace.hideWindowsOverlays();
|
|
|
|
Tweener.addTween(workspace.actor,
|
|
{ x: workspace.gridX,
|
|
y: workspace.gridY,
|
|
scale_x: workspace.scale,
|
|
scale_y: workspace.scale,
|
|
time: Overview.ANIMATION_TIME,
|
|
transition: 'easeOutQuad',
|
|
onComplete: function() {
|
|
workspace.zoomToOverview(false);
|
|
if (workspace.metaWorkspace.index() == active)
|
|
workspace.setSelected(true);
|
|
}});
|
|
}
|
|
},
|
|
|
|
updateWorkspaces: function(oldNumWorkspaces, newNumWorkspaces, lostWorkspaces) {
|
|
let oldScale = this._workspaces[0].scale;
|
|
let oldGridWidth = Math.ceil(Math.sqrt(oldNumWorkspaces));
|
|
let oldGridHeight = Math.ceil(oldNumWorkspaces / oldGridWidth);
|
|
|
|
// Add actors
|
|
if (newNumWorkspaces > oldNumWorkspaces)
|
|
for (let w = oldNumWorkspaces; w < newNumWorkspaces; w++)
|
|
this._actor.add_actor(this._workspaces[w].actor);
|
|
|
|
// 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(); });
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
},
|
|
|
|
_activeWorkspaceChanged: function(wm, from, to, direction) {
|
|
this._workspaces[from].setSelected(false);
|
|
this._workspaces[to].setSelected(true);
|
|
},
|
|
|
|
createControllerBar: function() {
|
|
return null;
|
|
},
|
|
|
|
addWorkspace: function() {
|
|
global.screen.append_new_workspace(false, global.get_current_time());
|
|
},
|
|
|
|
canRemoveWorkspace: function() {
|
|
return global.screen.n_workspaces > 1;
|
|
},
|
|
|
|
removeWorkspace: function() {
|
|
let removedIndex = this._workspaces.length - 1;
|
|
let metaWorkspace = this._workspaces[removedIndex].metaWorkspace;
|
|
global.screen.remove_workspace(metaWorkspace,
|
|
global.get_current_time());
|
|
}
|
|
};
|
|
|
|
function NewWorkspaceArea() {
|
|
this._init();
|
|
}
|
|
|
|
NewWorkspaceArea.prototype = {
|
|
_init: function() {
|
|
let width = Math.ceil(global.screen_width * WORKSPACE_SHADOW_SCALE);
|
|
this.actor = new Clutter.Group({ width: width,
|
|
height: global.screen_height,
|
|
x: global.screen_width });
|
|
|
|
this._child1 = new St.Bin({ style_class: 'new-workspace-area',
|
|
width: width,
|
|
height: global.screen_height });
|
|
this._child2 = new St.Bin({ style_class: 'new-workspace-area-internal',
|
|
width: width,
|
|
height: global.screen_height,
|
|
reactive: true });
|
|
this.actor.add_actor(this._child1);
|
|
this.actor.add_actor(this._child2);
|
|
},
|
|
|
|
setStyle: function(isHover) {
|
|
this._child1.set_hover(isHover);
|
|
}
|
|
};
|
|
|
|
function SingleView(width, height, x, y, workspaces) {
|
|
this._init(width, height, x, y, workspaces);
|
|
}
|
|
|
|
SingleView.prototype = {
|
|
__proto__: GenericWorkspacesView.prototype,
|
|
|
|
_init: function(width, height, x, y, workspaces) {
|
|
this._newWorkspaceArea = new NewWorkspaceArea();
|
|
this._leftShadow = new St.Bin({ style_class: 'left-workspaces-shadow',
|
|
width: Math.ceil(global.screen_width * WORKSPACE_SHADOW_SCALE),
|
|
height: global.screen_height,
|
|
x: global.screen_width })
|
|
this._rightShadow = new St.Bin({ style_class: 'right-workspaces-shadow',
|
|
width: Math.ceil(global.screen_width * WORKSPACE_SHADOW_SCALE),
|
|
height: global.screen_height,
|
|
x: global.screen_width })
|
|
|
|
GenericWorkspacesView.prototype._init.call(this, width, height, x, y, workspaces);
|
|
|
|
for (let i = 0; i < this._workspaces.length; i++) {
|
|
this._workspaces[i]._windowDragBeginId = this._workspaces[i].connect('window-drag-begin',
|
|
Lang.bind(this, this._onWindowDragBegin));
|
|
this._workspaces[i]._windowDragEndId = this._workspaces[i].connect('window-drag-end',
|
|
Lang.bind(this, this._onWindowDragEnd));
|
|
}
|
|
|
|
this._actor.add_actor(this._newWorkspaceArea.actor);
|
|
this._actor.add_actor(this._leftShadow);
|
|
this._actor.add_actor(this._rightShadow);
|
|
|
|
this.actor.style_class = "workspaces single";
|
|
this._actor.set_clip(x, y, width, height);
|
|
this._indicatorsPanel = null;
|
|
this._indicatorsPanelWidth = 0;
|
|
this._scroll = null;
|
|
this._lostWorkspaces = [];
|
|
this._scrolling = false;
|
|
this._animatingScroll = false;
|
|
|
|
let primary = global.get_primary_monitor();
|
|
this._dropGroup = new Clutter.Group({ x: 0, y: 0,
|
|
width: primary.width,
|
|
height: primary.height });
|
|
this._dropGroup._delegate = this;
|
|
global.stage.add_actor(this._dropGroup);
|
|
this._dropGroup.lower_bottom();
|
|
this._dragIndex = -1;
|
|
|
|
this._buttonPressId = 0;
|
|
this._capturedEventId = 0;
|
|
this._timeoutId = 0;
|
|
this._windowDragBeginId = 0;
|
|
this._windowDragEndId = 0;
|
|
},
|
|
|
|
_positionWorkspaces: function() {
|
|
let scale;
|
|
|
|
if (this._inDrag)
|
|
scale = this._width * WORKSPACE_DRAGGING_SCALE / global.screen_width;
|
|
else
|
|
scale = this._width / global.screen_width;
|
|
let active = global.screen.get_active_workspace_index();
|
|
let _width = this._workspaces[0].actor.width * scale;
|
|
|
|
this._setWorkspaceDraggable(active, true);
|
|
|
|
for (let w = 0; w < this._workspaces.length; w++) {
|
|
let workspace = this._workspaces[w];
|
|
|
|
if (this._inDrag)
|
|
workspace.opacity = 200;
|
|
else
|
|
workspace.opacity = 255;
|
|
if (active == w)
|
|
workspace.opacity = 255;
|
|
|
|
workspace.gridRow = 0;
|
|
workspace.gridCol = 0;
|
|
|
|
workspace.scale = scale;
|
|
workspace.gridX = this._x + (this._width - _width) / 2 + (w - active) * (_width + this._spacing);
|
|
workspace.gridY = this._y + (this._height - workspace.actor.height * scale) / 2;
|
|
|
|
workspace.setSelected(false);
|
|
}
|
|
|
|
this._newWorkspaceArea.scale = scale;
|
|
this._newWorkspaceArea.gridX = this._x + (this._width - _width) / 2 + (this._workspaces.length - active) * (_width + this._spacing);
|
|
this._newWorkspaceArea.gridY = this._y + (this._height - this._newWorkspaceArea.actor.height * scale) / 2;
|
|
|
|
this._leftShadow.scale = scale;
|
|
this._leftShadow.gridX = this._x + (this._width - _width) / 2 - (this._leftShadow.width * scale + this._spacing);
|
|
this._leftShadow.gridY = this._y + (this._height - this._leftShadow.height * scale) / 2;
|
|
|
|
this._rightShadow.scale = scale;
|
|
this._rightShadow.gridX = this._x + (this._width - _width) / 2 + (_width + this._spacing);
|
|
this._rightShadow.gridY = this._y + (this._height - this._rightShadow.height * scale) / 2;
|
|
},
|
|
|
|
_transitionWorkspaces: function() {
|
|
// update workspace parameters
|
|
this._positionWorkspaces();
|
|
|
|
let active = global.screen.get_active_workspace_index();
|
|
let activeActor = this._workspaces[active].actor;
|
|
// scale is the factor needed to translate from the currently
|
|
// active scale (previous view) to the new scale (this view)
|
|
let scale = this._workspaces[active].scale / activeActor.scale_x;
|
|
|
|
for (let w = 0; w < this._workspaces.length; w++) {
|
|
let workspace = this._workspaces[w];
|
|
let targetX, targetY;
|
|
|
|
// The correct transition would be a straightforward animation
|
|
// of each workspace's old position/scale to the new one;
|
|
// however, this looks overly busy, so we only use a zoom effect.
|
|
// Therefore we scale up each workspace's distance to the active
|
|
// workspace, so the latter fills the viewport while the other
|
|
// workspaces maintain their relative position
|
|
targetX = this._x + scale * (workspace.actor.x - activeActor.x);
|
|
targetY = this._y + scale * (workspace.actor.y - activeActor.y);
|
|
|
|
workspace.positionWindows(Workspace.WindowPositionFlags.ANIMATE);
|
|
workspace.setSelected(false);
|
|
workspace._hideAllOverlays();
|
|
|
|
Tweener.addTween(workspace.actor,
|
|
{ x: targetX,
|
|
y: targetY,
|
|
scale_x: workspace.scale,
|
|
scale_y: workspace.scale,
|
|
time: Overview.ANIMATION_TIME,
|
|
transition: 'easeOutQuad',
|
|
onComplete: function() {
|
|
workspace.zoomToOverview(false);
|
|
}});
|
|
}
|
|
},
|
|
|
|
_scrollToActive: function(showAnimation) {
|
|
let active = global.screen.get_active_workspace_index();
|
|
|
|
this._updateWorkspaceActors(showAnimation);
|
|
this._scrollScrollBarToIndex(active, showAnimation);
|
|
},
|
|
|
|
// _setWorkspaceDraggable:
|
|
// @index: workspace index
|
|
// @draggable: whether workspace @index should be draggable
|
|
//
|
|
// If @draggable is %true, set up workspace @index to allow switching
|
|
// workspaces by dragging the desktop - if a draggable workspace has
|
|
// been set up before, it will be reset before the new one is made
|
|
// draggable.
|
|
// If @draggable is %false, workspace @index is reset to no longer allow
|
|
// dragging.
|
|
_setWorkspaceDraggable: function(index, draggable) {
|
|
if (index < 0 || index >= global.n_workspaces)
|
|
return;
|
|
|
|
let dragActor = this._workspaces[index]._desktop.actor;
|
|
|
|
if (draggable) {
|
|
this._workspaces[index].actor.reactive = true;
|
|
|
|
// reset old draggable workspace
|
|
if (this._dragIndex > -1)
|
|
this._setWorkspaceDraggable(this._dragIndex, false);
|
|
|
|
this._dragIndex = index;
|
|
this._buttonPressId = dragActor.connect('button-press-event',
|
|
Lang.bind(this, this._onButtonPress));
|
|
} else {
|
|
this._dragIndex = -1;
|
|
|
|
if (this._buttonPressId > 0) {
|
|
if (dragActor.get_stage())
|
|
dragActor.disconnect(this._buttonPressId);
|
|
this._buttonPressId = 0;
|
|
}
|
|
|
|
if (this._capturedEventId > 0) {
|
|
global.stage.disconnect(this._capturedEventId);
|
|
this._capturedEventId = 0;
|
|
}
|
|
}
|
|
},
|
|
|
|
// start dragging the active workspace
|
|
_onButtonPress: function(actor, event) {
|
|
if (this._dragIndex == -1)
|
|
return;
|
|
|
|
let [stageX, stageY] = event.get_coords();
|
|
this._dragStartX = this._dragX = stageX;
|
|
this._scrolling = true;
|
|
this._capturedEventId = global.stage.connect('captured-event',
|
|
Lang.bind(this, this._onCapturedEvent));
|
|
},
|
|
|
|
// handle captured events while dragging a workspace
|
|
_onCapturedEvent: function(actor, event) {
|
|
let active = global.screen.get_active_workspace_index();
|
|
let stageX, stageY;
|
|
|
|
switch (event.type()) {
|
|
case Clutter.EventType.BUTTON_RELEASE:
|
|
this._scrolling = false;
|
|
|
|
[stageX, stageY] = event.get_coords();
|
|
|
|
// default to snapping back to the original workspace
|
|
let activate = this._dragIndex;
|
|
let last = global.screen.n_workspaces - 1;
|
|
|
|
// switch workspaces according to the drag direction
|
|
if (stageX > this._dragStartX && activate > 0)
|
|
activate--;
|
|
else if (stageX < this._dragStartX && activate < last)
|
|
activate++;
|
|
|
|
if (activate != active) {
|
|
let workspace = this._workspaces[activate].metaWorkspace;
|
|
workspace.activate(global.get_current_time());
|
|
} else {
|
|
this._scrollToActive(true);
|
|
}
|
|
|
|
if (stageX == this._dragStartX)
|
|
// no motion? It's a click!
|
|
return false;
|
|
|
|
return true;
|
|
|
|
case Clutter.EventType.MOTION:
|
|
[stageX, stageY] = event.get_coords();
|
|
let dx = this._dragX - stageX;
|
|
let primary = global.get_primary_monitor();
|
|
|
|
this._scroll.adjustment.value += (dx / primary.width);
|
|
this._dragX = stageX;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
_updateWorkspaceActors: function(showAnimation) {
|
|
let active = global.screen.get_active_workspace_index();
|
|
|
|
this._positionWorkspaces();
|
|
|
|
let dx = this._workspaces[0].gridX - this._workspaces[0].actor.x;
|
|
|
|
this._setWorkspaceDraggable(active, true);
|
|
for (let w = 0; w < this._workspaces.length; w++) {
|
|
let workspace = this._workspaces[w];
|
|
|
|
workspace.actor.show();
|
|
workspace.hideWindowsOverlays();
|
|
|
|
let i = w;
|
|
if (showAnimation) {
|
|
Tweener.addTween(workspace.actor,
|
|
{ x: workspace.gridX,
|
|
y: workspace.gridY,
|
|
scale_x: workspace.scale,
|
|
scale_y: workspace.scale,
|
|
time: WORKSPACE_SWITCH_TIME,
|
|
opacity: workspace.opacity,
|
|
transition: 'easeOutQuad',
|
|
onCompleteScope: this,
|
|
onComplete: function() {
|
|
if (i == active) {
|
|
if (!this._inDrag)
|
|
workspace.showWindowsOverlays();
|
|
} else
|
|
workspace.actor.visible = Math.abs(i - active) <= 1;
|
|
}});
|
|
} else {
|
|
workspace.actor.set_scale(workspace.scale, workspace.scale);
|
|
workspace.actor.set_position(workspace.gridX, workspace.gridY);
|
|
workspace.actor.opacity = workspace.opacity;
|
|
if (i == active) {
|
|
if (!this._inDrag)
|
|
workspace.showWindowsOverlays();
|
|
} else
|
|
workspace.actor.visible = Math.abs(i - active) <= 1;
|
|
}
|
|
workspace.positionWindows(0);
|
|
}
|
|
if (active)
|
|
this._leftShadow.show();
|
|
else
|
|
this._leftShadow.hide();
|
|
|
|
if (active == this._workspaces.length - 1)
|
|
this._rightShadow.hide();
|
|
else
|
|
this._rightShadow.show();
|
|
|
|
this._leftShadow.raise_top();
|
|
this._rightShadow.raise_top();
|
|
|
|
if (showAnimation) {
|
|
Tweener.addTween(this._newWorkspaceArea.actor,
|
|
{ x: this._newWorkspaceArea.gridX,
|
|
y: this._newWorkspaceArea.gridY,
|
|
scale_x: this._newWorkspaceArea.scale,
|
|
scale_y: this._newWorkspaceArea.scale,
|
|
time: WORKSPACE_SWITCH_TIME,
|
|
transition: 'easeOutQuad'
|
|
});
|
|
this._leftShadow.x = this._leftShadow.gridX;
|
|
Tweener.addTween(this._leftShadow,
|
|
{ y: this._leftShadow.gridY,
|
|
scale_x: this._leftShadow.scale,
|
|
scale_y: this._leftShadow.scale,
|
|
time: WORKSPACE_SWITCH_TIME,
|
|
transition: 'easeOutQuad'
|
|
});
|
|
this._rightShadow.x = this._rightShadow.gridX;
|
|
Tweener.addTween(this._rightShadow,
|
|
{ y: this._rightShadow.gridY,
|
|
scale_x: this._rightShadow.scale,
|
|
scale_y: this._rightShadow.scale,
|
|
time: WORKSPACE_SWITCH_TIME,
|
|
transition: 'easeOutQuad'
|
|
});
|
|
} else {
|
|
this._newWorkspaceArea.actor.set_scale(this._newWorkspaceArea.scale, this._newWorkspaceArea.scale);
|
|
this._newWorkspaceArea.actor.set_position(this._newWorkspaceArea.gridX, this._newWorkspaceArea.gridY);
|
|
|
|
this._leftShadow.set_scale(this._leftShadow.scale, this._leftShadow.scale);
|
|
this._leftShadow.set_position(this._leftShadow.gridX, this._leftShadow.gridY);
|
|
this._rightShadow.set_scale(this._rightShadow.scale, this._rightShadow.scale);
|
|
this._rightShadow.set_position(this._rightShadow.gridX, this._rightShadow.gridY);
|
|
}
|
|
|
|
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;
|
|
}
|
|
},
|
|
|
|
updateWorkspaces: function(oldNumWorkspaces, newNumWorkspaces, lostWorkspaces) {
|
|
let active = global.screen.get_active_workspace_index();
|
|
|
|
for (let l = 0; l < lostWorkspaces.length; l++)
|
|
lostWorkspaces[l].disconnectAll();
|
|
|
|
if (this._scroll != null)
|
|
this._scroll.adjustment.upper = newNumWorkspaces;
|
|
|
|
if (newNumWorkspaces > oldNumWorkspaces) {
|
|
for (let w = oldNumWorkspaces; w < newNumWorkspaces; w++) {
|
|
this._actor.add_actor(this._workspaces[w].actor);
|
|
this._workspaces[w]._windowDragBeginId = this._workspaces[w].connect('window-drag-begin',
|
|
Lang.bind(this, this._onWindowDragBegin));
|
|
this._workspaces[w]._windowDragEndId = this._workspaces[w].connect('window-drag-end',
|
|
Lang.bind(this, this._onWindowDragEnd));
|
|
}
|
|
|
|
this._positionWorkspaces();
|
|
this._updateWorkspaceActors();
|
|
this._scrollScrollBarToIndex(active + 1, false);
|
|
} else {
|
|
this._lostWorkspaces = lostWorkspaces;
|
|
this._scrollScrollBarToIndex(active, false);
|
|
this._scrollToActive(true);
|
|
}
|
|
|
|
this._updatePanelVisibility();
|
|
},
|
|
|
|
_activeWorkspaceChanged: function(wm, from, to, direction) {
|
|
this._updatePanelVisibility();
|
|
|
|
if (this._scrolling)
|
|
return;
|
|
|
|
this._scrollToActive(true);
|
|
},
|
|
|
|
_onDestroy: function() {
|
|
GenericWorkspacesView.prototype._onDestroy.call(this);
|
|
this._setWorkspaceDraggable(this._dragIndex, false);
|
|
this._dropGroup.destroy();
|
|
if (this._timeoutId) {
|
|
Mainloop.source_remove(this._timeoutId);
|
|
this._timeoutId = 0;
|
|
}
|
|
for (let w = 0; w < this._workspaces.length; w++) {
|
|
if (this._workspaces[w]._windowDragBeginId) {
|
|
this._workspaces[w].disconnect(this._workspaces[w]._windowDragBeginId);
|
|
this._workspaces[w]._windowDragBeginId = 0;
|
|
}
|
|
if (this._workspaces[w]._windowDragEndId) {
|
|
this._workspaces[w].disconnect(this._workspaces[w]._windowDragEndId);
|
|
this._workspaces[w]._windowDragEndId = 0;
|
|
}
|
|
}
|
|
},
|
|
|
|
acceptDrop: function(source, dropActor, x, y, time) {
|
|
if (x < this._x || y < this._y || y > this._y + this._height) {
|
|
this._dropGroup.lower_bottom();
|
|
dropActor.hide();
|
|
let target = global.stage.get_actor_at_pos(Clutter.PickMode.ALL, x, y);
|
|
dropActor.show();
|
|
|
|
if (target._delegate && target._delegate != this && target._delegate.acceptDrop) {
|
|
let [targX, targY] = target.get_transformed_position();
|
|
return target._delegate.acceptDrop(source, dropActor,
|
|
(x - targX) / target.scale_x,
|
|
(y - targY) / target.scale_y,
|
|
time);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
for (let i = 0; i < this._workspaces.length; i++) {
|
|
let [dx, dy] = this._workspaces[i].actor.get_transformed_position();
|
|
let [dw, dh] = this._workspaces[i].actor.get_transformed_size();
|
|
|
|
if (x > dx && x < dx + dw && y > dy && y < dy + dh)
|
|
return this._workspaces[i].acceptDrop(source, dropActor, x, y, time);
|
|
}
|
|
|
|
let [dx, dy] = this._newWorkspaceArea.actor.get_transformed_position();
|
|
let [dw, dh] = this._newWorkspaceArea.actor.get_transformed_size();
|
|
if (x > dx && y > dy && y < dy + dh)
|
|
return this._acceptNewWorkspaceDrop(source, dropActor, x, y, time);
|
|
|
|
return false;
|
|
},
|
|
|
|
_onWindowDragBegin: function(w, actor) {
|
|
if (!this._scroll || this._scrolling)
|
|
return;
|
|
|
|
this._inDrag = true;
|
|
this._updateWorkspaceActors(true);
|
|
|
|
this._dropGroup.raise_top();
|
|
},
|
|
|
|
handleDragOver: function(self, actor, x, y) {
|
|
let onPanel = false;
|
|
|
|
let activeWorkspaceIndex = global.screen.get_active_workspace_index();
|
|
if (x == 0 && activeWorkspaceIndex > 0 && this._dragOverLastX !== 0) {
|
|
this._workspaces[activeWorkspaceIndex - 1].metaWorkspace.activate(global.get_current_time());
|
|
this._workspaces[activeWorkspaceIndex - 1].setReservedSlot(actor._delegate);
|
|
this._dragOverLastX = 0;
|
|
return;
|
|
}
|
|
if (x == global.screen_width - 1 && this._workspaces[activeWorkspaceIndex + 1] &&
|
|
this._dragOverLastX != global.screen_width - 1) {
|
|
this._workspaces[activeWorkspaceIndex + 1].metaWorkspace.activate(global.get_current_time());
|
|
this._workspaces[activeWorkspaceIndex + 1].setReservedSlot(actor._delegate);
|
|
this._dragOverLastX = global.screen_width - 1;
|
|
return;
|
|
}
|
|
|
|
this._dragOverLastX = x;
|
|
|
|
let [dx, dy] = this._newWorkspaceArea.actor.get_transformed_position();
|
|
let [dw, dh] = this._newWorkspaceArea.actor.get_transformed_size();
|
|
this._newWorkspaceArea.setStyle(x > dx && y > dy && y < dy + dh);
|
|
|
|
[dx, dy] = this._leftShadow.get_transformed_position();
|
|
[dw, dh] = this._leftShadow.get_transformed_size();
|
|
if (this._workspaces[activeWorkspaceIndex - 1]) {
|
|
if (x > dx && x < dx + dw && y > dy && y < dy + dh) {
|
|
onPanel = -1;
|
|
this._workspaces[activeWorkspaceIndex - 1].actor.opacity = 255;
|
|
} else
|
|
this._workspaces[activeWorkspaceIndex - 1].actor.opacity = 200;
|
|
}
|
|
|
|
[dx, dy] = this._rightShadow.get_transformed_position();
|
|
[dw, dh] = this._rightShadow.get_transformed_size();
|
|
if (this._workspaces[activeWorkspaceIndex + 1]) {
|
|
if (x > dx && x < dx + dw && y > dy && y < dy + dh) {
|
|
onPanel = 1;
|
|
this._workspaces[activeWorkspaceIndex + 1].actor.opacity = 255;
|
|
} else
|
|
this._workspaces[activeWorkspaceIndex + 1].actor.opacity = 200;
|
|
}
|
|
if (onPanel) {
|
|
if (!this._timeoutId)
|
|
this._timeoutId = Mainloop.timeout_add_seconds (1, Lang.bind(this, function() {
|
|
let i = global.screen.get_active_workspace_index();
|
|
if (this._workspaces[i + onPanel]) {
|
|
this._workspaces[i + onPanel].metaWorkspace.activate(global.get_current_time());
|
|
this._workspaces[i + onPanel].setReservedSlot(actor._delegate);
|
|
}
|
|
return true;
|
|
}));
|
|
} else {
|
|
if (this._timeoutId) {
|
|
Mainloop.source_remove(this._timeoutId);
|
|
this._timeoutId = 0;
|
|
}
|
|
}
|
|
},
|
|
|
|
_onWindowDragEnd: function(w, actor) {
|
|
if (this._timeoutId) {
|
|
Mainloop.source_remove(this._timeoutId);
|
|
this._timeoutId = 0;
|
|
}
|
|
this._dropGroup.lower_bottom();
|
|
actor.opacity = 255;
|
|
this._inDrag = false;
|
|
this._updateWorkspaceActors(true);
|
|
|
|
for (let i = 0; i < this._workspaces.length; i++)
|
|
this._workspaces[i].setReservedSlot(null);
|
|
},
|
|
|
|
// 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();
|
|
this._workspaces[i].actor.visible = Math.abs(i - adj.value) <= 1;
|
|
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 actor = new St.BoxLayout({ style_class: 'single-view-controls',
|
|
pack_start: true,
|
|
vertical: 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);
|
|
}));
|
|
|
|
actor.add(this._createPositionalIndicator(), { expand: true,
|
|
x_fill: true,
|
|
y_fill: true });
|
|
actor.add(this._scroll, { expand: true,
|
|
x_fill: true,
|
|
y_fill: false,
|
|
y_align: St.Align.START });
|
|
|
|
this._updatePanelVisibility();
|
|
|
|
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 == 0)
|
|
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;
|
|
},
|
|
|
|
canRemoveWorkspace: function() {
|
|
return global.screen.get_active_workspace_index() != 0;
|
|
},
|
|
|
|
_updatePanelVisibility: function() {
|
|
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();
|
|
},
|
|
|
|
addWorkspace: function() {
|
|
let ws = global.screen.append_new_workspace(false,
|
|
global.get_current_time());
|
|
ws.activate(global.get_current_time());
|
|
},
|
|
|
|
removeWorkspace: function() {
|
|
let removedIndex = global.screen.get_active_workspace_index();
|
|
let metaWorkspace = this._workspaces[removedIndex].metaWorkspace;
|
|
global.screen.remove_workspace(metaWorkspace,
|
|
global.get_current_time());
|
|
}
|
|
};
|
|
|
|
function WorkspacesControls() {
|
|
this._init();
|
|
}
|
|
|
|
WorkspacesControls.prototype = {
|
|
_init: function() {
|
|
this.actor = new St.BoxLayout({ style_class: 'workspaces-bar' });
|
|
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
|
|
|
|
this._gconf = Shell.GConf.get_default();
|
|
|
|
let view = this._gconf.get_string(WORKSPACES_VIEW_KEY).toUpperCase();
|
|
if (view in WorkspacesViewType)
|
|
this._currentViewType = WorkspacesViewType[view];
|
|
else
|
|
this._currentViewType = WorkspacesViewType.SINGLE;
|
|
|
|
this._currentView = null;
|
|
|
|
// View switcher button
|
|
this._toggleViewButton = new St.Button();
|
|
this._updateToggleButtonStyle();
|
|
|
|
this._toggleViewButton.connect('clicked', Lang.bind(this, function() {
|
|
if (this._currentViewType == WorkspacesViewType.SINGLE)
|
|
this._setView(WorkspacesViewType.GRID);
|
|
else
|
|
this._setView(WorkspacesViewType.SINGLE);
|
|
}));
|
|
|
|
this.actor.add(this._toggleViewButton, { y_fill: false, y_align: St.Align.START });
|
|
|
|
// View specific controls
|
|
this._viewControls = new St.Bin({ x_fill: true, y_fill: true });
|
|
this.actor.add(this._viewControls, { expand: true, x_fill: true });
|
|
|
|
// Add/remove workspace buttons
|
|
this._removeButton = new St.Button({ style_class: 'workspace-controls remove' });
|
|
this._removeButton.connect('clicked', Lang.bind(this, function() {
|
|
this._currentView.removeWorkspace();
|
|
}));
|
|
this.actor.add(this._removeButton, { y_fill: false,
|
|
y_align: St.Align.START });
|
|
|
|
this._addButton = new St.Button({ style_class: 'workspace-controls add' });
|
|
this._addButton.connect('clicked', Lang.bind(this, function() {
|
|
this._currentView.addWorkspace();
|
|
}));
|
|
this._addButton._delegate = this._addButton;
|
|
this._addButton._delegate.acceptDrop = Lang.bind(this,
|
|
function(source, actor, x, y, time) {
|
|
return this._currentView._acceptNewWorkspaceDrop(source, actor, x, y, time);
|
|
});
|
|
this.actor.add(this._addButton, { y_fill: false,
|
|
y_align: St.Align.START });
|
|
|
|
this._nWorkspacesNotifyId =
|
|
global.screen.connect('notify::n-workspaces',
|
|
Lang.bind(this, this._workspacesChanged));
|
|
|
|
this._workspacesChanged();
|
|
},
|
|
|
|
updateControls: function(view) {
|
|
this._currentView = view;
|
|
|
|
let newControls = this._currentView.createControllerBar();
|
|
if (newControls) {
|
|
this._viewControls.child = newControls;
|
|
this._viewControls.child.opacity = 0;
|
|
Tweener.addTween(this._viewControls.child,
|
|
{ opacity: 255,
|
|
time: Overview.ANIMATION_TIME,
|
|
transition: 'easeOutQuad' });
|
|
} else {
|
|
if (this._viewControls.child)
|
|
Tweener.addTween(this._viewControls.child,
|
|
{ opacity: 0,
|
|
time: Overview.ANIMATION_TIME,
|
|
transition: 'easeOutQuad',
|
|
onComplete: Lang.bind(this, function() {
|
|
this._viewControls.child.destroy();
|
|
})});
|
|
}
|
|
},
|
|
|
|
_updateToggleButtonStyle: function() {
|
|
if (this._currentViewType == WorkspacesViewType.SINGLE)
|
|
this._toggleViewButton.set_style_class_name('workspace-controls switch-mosaic');
|
|
else
|
|
this._toggleViewButton.set_style_class_name('workspace-controls switch-single');
|
|
},
|
|
|
|
_setView: function(view) {
|
|
if (this._currentViewType == view)
|
|
return;
|
|
|
|
if (WorkspacesViewType.SINGLE == view)
|
|
this._toggleViewButton.set_style_class_name('workspace-controls switch-mosaic');
|
|
else
|
|
this._toggleViewButton.set_style_class_name('workspace-controls switch-single');
|
|
|
|
this._currentViewType = view;
|
|
this._gconf.set_string(WORKSPACES_VIEW_KEY, view);
|
|
},
|
|
|
|
setCanRemove: function(canRemove) {
|
|
this._setButtonSensitivity(this._removeButton, canRemove);
|
|
},
|
|
|
|
setCanAdd: function(canAdd) {
|
|
this._setButtonSensitivity(this._addButton, canAdd);
|
|
},
|
|
|
|
_onDestroy: function() {
|
|
if (this._nWorkspacesNotifyId > 0) {
|
|
global.screen.disconnect(this._nWorkspacesNotifyId);
|
|
this._nWorkspacesNotifyId = 0;
|
|
}
|
|
},
|
|
|
|
_setButtonSensitivity: function(button, sensitive) {
|
|
if (button == null)
|
|
return;
|
|
button.reactive = sensitive;
|
|
button.opacity = sensitive ? 255 : 85;
|
|
},
|
|
|
|
_workspacesChanged: function() {
|
|
if (global.screen.n_workspaces == 1)
|
|
this._toggleViewButton.hide();
|
|
else
|
|
this._toggleViewButton.show();
|
|
}
|
|
};
|
|
Signals.addSignalMethods(WorkspacesControls.prototype);
|
|
|
|
function WorkspacesManager(width, height, x, y) {
|
|
this._init(width, height, x, y);
|
|
}
|
|
|
|
WorkspacesManager.prototype = {
|
|
_init: function(width, height, x, y) {
|
|
this._workspacesWidth = width;
|
|
this._workspacesHeight = height;
|
|
this._workspacesX = x;
|
|
this._workspacesY = y;
|
|
|
|
this._workspaces = [];
|
|
for (let w = 0; w < global.screen.n_workspaces; w++) {
|
|
let metaWorkspace = global.screen.get_workspace_by_index(w);
|
|
this._workspaces[w] = new Workspace.Workspace(metaWorkspace);
|
|
}
|
|
|
|
this.workspacesView = null;
|
|
this.controlsBar = new WorkspacesControls();
|
|
this._updateView();
|
|
|
|
this.controlsBar.actor.connect('destroy',
|
|
Lang.bind(this, this._onDestroy));
|
|
this._viewChangedId =
|
|
Shell.GConf.get_default().connect('changed::' + WORKSPACES_VIEW_KEY,
|
|
Lang.bind(this, this._updateView));
|
|
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._updateControlsSensitivity));
|
|
|
|
},
|
|
|
|
_updateView: function() {
|
|
let viewType, newView;
|
|
|
|
let view = Shell.GConf.get_default().get_string(WORKSPACES_VIEW_KEY).toUpperCase();
|
|
if (view in WorkspacesViewType)
|
|
viewType = WorkspacesViewType[view];
|
|
else
|
|
viewType = WorkspacesViewType.SINGLE;
|
|
|
|
switch (viewType) {
|
|
case WorkspacesViewType.SINGLE:
|
|
newView = new SingleView(this._workspacesWidth,
|
|
this._workspacesHeight,
|
|
this._workspacesX,
|
|
this._workspacesY,
|
|
this._workspaces);
|
|
break;
|
|
case WorkspacesViewType.GRID:
|
|
default:
|
|
newView = new MosaicView(this._workspacesWidth,
|
|
this._workspacesHeight,
|
|
this._workspacesX,
|
|
this._workspacesY,
|
|
this._workspaces);
|
|
break;
|
|
}
|
|
if (this.workspacesView)
|
|
this.workspacesView.destroy();
|
|
this.workspacesView = newView;
|
|
|
|
this.controlsBar.updateControls(this.workspacesView);
|
|
|
|
this.emit('view-changed');
|
|
},
|
|
|
|
_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;
|
|
|
|
let lostWorkspaces = [];
|
|
if (newNumWorkspaces > oldNumWorkspaces) {
|
|
// Assume workspaces are only added at the end
|
|
for (let w = oldNumWorkspaces; w < newNumWorkspaces; w++) {
|
|
let metaWorkspace = global.screen.get_workspace_by_index(w);
|
|
this._workspaces[w] = new Workspace.Workspace(metaWorkspace);
|
|
}
|
|
} else {
|
|
// Assume workspaces are only removed sequentially
|
|
// (e.g. 2,3,4 - not 2,4,7)
|
|
let removedIndex;
|
|
let removedNum = oldNumWorkspaces - newNumWorkspaces;
|
|
for (let w = 0; w < oldNumWorkspaces; w++) {
|
|
let metaWorkspace = global.screen.get_workspace_by_index(w);
|
|
if (this._workspaces[w].metaWorkspace != metaWorkspace) {
|
|
removedIndex = w;
|
|
break;
|
|
}
|
|
}
|
|
|
|
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 < lostWorkspaces.length; l++)
|
|
lostWorkspaces[l].setReactive(false);
|
|
}
|
|
|
|
this.workspacesView.updateWorkspaces(oldNumWorkspaces,
|
|
newNumWorkspaces,
|
|
lostWorkspaces);
|
|
|
|
this.controlsBar.setCanAdd(this.workspacesView.canAddWorkspace());
|
|
this.controlsBar.setCanRemove(this.workspacesView.canRemoveWorkspace());
|
|
},
|
|
|
|
_updateControlsSensitivity: function() {
|
|
this.controlsBar.setCanAdd(this.workspacesView.canAddWorkspace());
|
|
this.controlsBar.setCanRemove(this.workspacesView.canRemoveWorkspace());
|
|
},
|
|
|
|
_onDestroy: function() {
|
|
if (this._nWorkspacesNotifyId > 0)
|
|
global.screen.disconnect(this._nWorkspacesNotifyId);
|
|
if (this._switchWorkspaceNotifyId > 0)
|
|
global.window_manager.disconnect(this._switchWorkspaceNotifyId);
|
|
if (this._viewChangedId > 0)
|
|
Shell.GConf.get_default().disconnect(this._viewChangedId);
|
|
for (let w = 0; w < this._workspaces.length; w++) {
|
|
this._workspaces[w].disconnectAll();
|
|
this._workspaces[w].destroy();
|
|
}
|
|
}
|
|
};
|
|
Signals.addSignalMethods(WorkspacesManager.prototype);
|