Bug 591763 - Add application window menu
When we have multiple windows for an application, implement the following behavior: * On click + immediate release, go to the most recently used * On click, hold for 0.6s, pop up a menu with windows, filtering the window list to just those windows. Mouse over on the window list highlights the moused-over window. Implement this by splitting well item into InactiveWellItem and RunningWellItem, sharing a base class BaseWellItem.
This commit is contained in:
parent
22c445cffc
commit
25410a730e
@ -26,6 +26,27 @@ const WELL_DEFAULT_COLUMNS = 4;
|
|||||||
const WELL_ITEM_HSPACING = 0;
|
const WELL_ITEM_HSPACING = 0;
|
||||||
const WELL_ITEM_VSPACING = 4;
|
const WELL_ITEM_VSPACING = 4;
|
||||||
|
|
||||||
|
const WELL_MENU_POPUP_TIMEOUT_MS = 600;
|
||||||
|
|
||||||
|
const TRANSPARENT_COLOR = new Clutter.Color();
|
||||||
|
TRANSPARENT_COLOR.from_pixel(0x00000000);
|
||||||
|
|
||||||
|
const WELL_MENU_BACKGROUND_COLOR = new Clutter.Color();
|
||||||
|
WELL_MENU_BACKGROUND_COLOR.from_pixel(0x292929ff);
|
||||||
|
const WELL_MENU_FONT = 'Sans 14px';
|
||||||
|
const WELL_MENU_COLOR = new Clutter.Color();
|
||||||
|
WELL_MENU_COLOR.from_pixel(0xffffffff);
|
||||||
|
const WELL_MENU_SELECTED_COLOR = new Clutter.Color();
|
||||||
|
WELL_MENU_SELECTED_COLOR.from_pixel(0x005b97ff);
|
||||||
|
const WELL_MENU_BORDER_COLOR = new Clutter.Color();
|
||||||
|
WELL_MENU_BORDER_COLOR.from_pixel(0x787878ff);
|
||||||
|
const WELL_MENU_SEPARATOR_COLOR = new Clutter.Color();
|
||||||
|
WELL_MENU_SEPARATOR_COLOR.from_pixel(0x787878ff);
|
||||||
|
const WELL_MENU_BORDER_WIDTH = 1;
|
||||||
|
const WELL_MENU_ARROW_SIZE = 12;
|
||||||
|
const WELL_MENU_CORNER_RADIUS = 4;
|
||||||
|
const WELL_MENU_PADDING = 4;
|
||||||
|
|
||||||
const MENU_ICON_SIZE = 24;
|
const MENU_ICON_SIZE = 24;
|
||||||
const MENU_SPACING = 15;
|
const MENU_SPACING = 15;
|
||||||
|
|
||||||
@ -161,7 +182,6 @@ MenuItem.prototype = {
|
|||||||
}
|
}
|
||||||
Signals.addSignalMethods(MenuItem.prototype);
|
Signals.addSignalMethods(MenuItem.prototype);
|
||||||
|
|
||||||
|
|
||||||
/* This class represents a display containing a collection of application items.
|
/* This class represents a display containing a collection of application items.
|
||||||
* The applications are sorted based on their popularity by default, and based on
|
* The applications are sorted based on their popularity by default, and based on
|
||||||
* their name if some search filter is applied.
|
* their name if some search filter is applied.
|
||||||
@ -448,35 +468,202 @@ AppDisplay.prototype = {
|
|||||||
|
|
||||||
Signals.addSignalMethods(AppDisplay.prototype);
|
Signals.addSignalMethods(AppDisplay.prototype);
|
||||||
|
|
||||||
function WellDisplayItem(appInfo, isFavorite) {
|
function WellMenu(source) {
|
||||||
|
this._init(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
WellMenu.prototype = {
|
||||||
|
_init: function(source) {
|
||||||
|
this._source = source;
|
||||||
|
|
||||||
|
|
||||||
|
this.actor = new Shell.GenericContainer({ reactive: true });
|
||||||
|
this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
|
||||||
|
this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
|
||||||
|
this.actor.connect('allocate', Lang.bind(this, this._allocate));
|
||||||
|
|
||||||
|
this._windowContainer = new Shell.Menu({ orientation: Big.BoxOrientation.VERTICAL,
|
||||||
|
border_color: WELL_MENU_BORDER_COLOR,
|
||||||
|
border: WELL_MENU_BORDER_WIDTH,
|
||||||
|
background_color: WELL_MENU_BACKGROUND_COLOR,
|
||||||
|
padding: 4,
|
||||||
|
corner_radius: WELL_MENU_CORNER_RADIUS,
|
||||||
|
width: Main.overview._dash.actor.width * 0.75 });
|
||||||
|
this._windowContainer.connect('popdown', Lang.bind(this, this._onPopdown));
|
||||||
|
this._windowContainer.connect('unselected', Lang.bind(this, this._onWindowUnselected));
|
||||||
|
this._windowContainer.connect('selected', Lang.bind(this, this._onWindowSelected));
|
||||||
|
this._windowContainer.connect('activate', Lang.bind(this, this._onWindowActivate));
|
||||||
|
this.actor.add_actor(this._windowContainer);
|
||||||
|
|
||||||
|
this._arrow = new Shell.DrawingArea();
|
||||||
|
this._arrow.connect('redraw', Lang.bind(this, function (area, texture) {
|
||||||
|
Shell.draw_box_pointer(texture, WELL_MENU_BORDER_COLOR, WELL_MENU_BACKGROUND_COLOR);
|
||||||
|
}));
|
||||||
|
this.actor.add_actor(this._arrow);
|
||||||
|
|
||||||
|
let stage = Shell.Global.get().stage;
|
||||||
|
|
||||||
|
// Chain our visibility and lifecycle to that of the source
|
||||||
|
source.actor.connect('notify::mapped', Lang.bind(this, function () {
|
||||||
|
if (!source.actor.mapped)
|
||||||
|
this._windowContainer.popdown();
|
||||||
|
}));
|
||||||
|
source.actor.connect('destroy', Lang.bind(this, function () { this.actor.destroy(); }));
|
||||||
|
|
||||||
|
stage.add_actor(this.actor);
|
||||||
|
},
|
||||||
|
|
||||||
|
_getPreferredWidth: function(actor, forHeight, alloc) {
|
||||||
|
let [min, natural] = this._windowContainer.get_preferred_width(forHeight);
|
||||||
|
alloc.min_size = min + WELL_MENU_ARROW_SIZE;
|
||||||
|
alloc.natural_size = natural + WELL_MENU_ARROW_SIZE;
|
||||||
|
},
|
||||||
|
|
||||||
|
_getPreferredHeight: function(actor, forWidth, alloc) {
|
||||||
|
let [min, natural] = this._windowContainer.get_preferred_height(forWidth);
|
||||||
|
alloc.min_size = min;
|
||||||
|
alloc.natural_size = natural;
|
||||||
|
},
|
||||||
|
|
||||||
|
_allocate: function(actor, box, flags) {
|
||||||
|
let childBox = new Clutter.ActorBox();
|
||||||
|
|
||||||
|
let width = box.x2 - box.x1;
|
||||||
|
let height = box.y2 - box.y1;
|
||||||
|
|
||||||
|
childBox.x1 = 0;
|
||||||
|
childBox.x2 = WELL_MENU_ARROW_SIZE;
|
||||||
|
childBox.y1 = (height / 2) - (WELL_MENU_ARROW_SIZE / 2);
|
||||||
|
childBox.y2 = childBox.y1 + WELL_MENU_ARROW_SIZE;
|
||||||
|
this._arrow.allocate(childBox, flags);
|
||||||
|
|
||||||
|
/* overlap by one pixel to hide the border */
|
||||||
|
childBox.x1 = WELL_MENU_ARROW_SIZE - 1;
|
||||||
|
childBox.x2 = width;
|
||||||
|
childBox.y1 = 0;
|
||||||
|
childBox.y2 = height;
|
||||||
|
this._windowContainer.allocate(childBox, flags);
|
||||||
|
},
|
||||||
|
|
||||||
|
_redisplay: function() {
|
||||||
|
this._windowContainer.remove_all();
|
||||||
|
|
||||||
|
let windows = this._source.windows;
|
||||||
|
|
||||||
|
this._windowContainer.show();
|
||||||
|
|
||||||
|
let iconsDiffer = false;
|
||||||
|
let texCache = Shell.TextureCache.get_default();
|
||||||
|
let firstIcon = windows[0].mini_icon;
|
||||||
|
for (let i = 1; i < windows.length; i++) {
|
||||||
|
if (!texCache.pixbuf_equal(windows[i].mini_icon, firstIcon)) {
|
||||||
|
iconsDiffer = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let activeWorkspace = Shell.Global.get().screen.get_active_workspace();
|
||||||
|
|
||||||
|
let currentWorkspaceWindows = windows.filter(function (w) {
|
||||||
|
return w.get_workspace() == activeWorkspace;
|
||||||
|
});
|
||||||
|
let otherWorkspaceWindows = windows.filter(function (w) {
|
||||||
|
return w.get_workspace() != activeWorkspace;
|
||||||
|
});
|
||||||
|
|
||||||
|
this._appendWindows(currentWorkspaceWindows, iconsDiffer);
|
||||||
|
if (currentWorkspaceWindows.length > 0 && otherWorkspaceWindows.length > 0) {
|
||||||
|
let box = new Big.Box({ padding_top: 2, padding_bottom: 2 });
|
||||||
|
box.append(new Clutter.Rectangle({ height: 1,
|
||||||
|
color: WELL_MENU_SEPARATOR_COLOR }),
|
||||||
|
Big.BoxPackFlags.EXPAND);
|
||||||
|
this._windowContainer.append_separator(box, Big.BoxPackFlags.NONE);
|
||||||
|
}
|
||||||
|
this._appendWindows(otherWorkspaceWindows, iconsDiffer);
|
||||||
|
},
|
||||||
|
|
||||||
|
_appendWindows: function (windows, iconsDiffer) {
|
||||||
|
for (let i = 0; i < windows.length; i++) {
|
||||||
|
let window = windows[i];
|
||||||
|
/* Use padding here rather than spacing in the box above so that
|
||||||
|
* we have a larger reactive area.
|
||||||
|
*/
|
||||||
|
let box = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
|
||||||
|
padding_top: 4,
|
||||||
|
padding_bottom: 4,
|
||||||
|
spacing: 4,
|
||||||
|
reactive: true });
|
||||||
|
box._window = window;
|
||||||
|
let vCenter;
|
||||||
|
if (iconsDiffer) {
|
||||||
|
vCenter = new Big.Box({ y_align: Big.BoxAlignment.CENTER });
|
||||||
|
let icon = Shell.TextureCache.get_default().bind_pixbuf_property(window, "mini-icon");
|
||||||
|
vCenter.append(icon, Big.BoxPackFlags.NONE);
|
||||||
|
box.append(vCenter, Big.BoxPackFlags.NONE);
|
||||||
|
}
|
||||||
|
vCenter = new Big.Box({ y_align: Big.BoxAlignment.CENTER });
|
||||||
|
let label = new Clutter.Text({ text: window.title,
|
||||||
|
font_name: WELL_MENU_FONT,
|
||||||
|
ellipsize: Pango.EllipsizeMode.END,
|
||||||
|
color: WELL_MENU_COLOR });
|
||||||
|
vCenter.append(label, Big.BoxPackFlags.NONE);
|
||||||
|
box.append(vCenter, Big.BoxPackFlags.NONE);
|
||||||
|
this._windowContainer.append(box, Big.BoxPackFlags.NONE);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
popup: function() {
|
||||||
|
let [stageX, stageY] = this._source.actor.get_transformed_position();
|
||||||
|
let [stageWidth, stageHeight] = this._source.actor.get_transformed_size();
|
||||||
|
|
||||||
|
this._redisplay();
|
||||||
|
|
||||||
|
this._windowContainer.popup(0, Clutter.get_current_event_time());
|
||||||
|
|
||||||
|
this.emit('popup', true);
|
||||||
|
|
||||||
|
let y = Math.floor(stageY + (stageHeight / 2) - (this.actor.height / 2));
|
||||||
|
this.actor.set_position(stageX + stageWidth, y);
|
||||||
|
this.actor.show();
|
||||||
|
},
|
||||||
|
|
||||||
|
_onWindowUnselected: function (actor, child) {
|
||||||
|
child.background_color = TRANSPARENT_COLOR;
|
||||||
|
|
||||||
|
this.emit('highlight-window', null);
|
||||||
|
},
|
||||||
|
|
||||||
|
_onWindowSelected: function (actor, child) {
|
||||||
|
child.background_color = WELL_MENU_SELECTED_COLOR;
|
||||||
|
|
||||||
|
let window = child._window;
|
||||||
|
this.emit('highlight-window', window);
|
||||||
|
},
|
||||||
|
|
||||||
|
_onWindowActivate: function (actor, child) {
|
||||||
|
let window = child._window;
|
||||||
|
Main.overview.activateWindow(window, Clutter.get_current_event_time());
|
||||||
|
},
|
||||||
|
|
||||||
|
_onPopdown: function () {
|
||||||
|
this.emit('highlight-window', null);
|
||||||
|
this.emit('popup', false);
|
||||||
|
this.actor.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Signals.addSignalMethods(WellMenu.prototype);
|
||||||
|
|
||||||
|
function BaseWellItem(appInfo, isFavorite) {
|
||||||
this._init(appInfo, isFavorite);
|
this._init(appInfo, isFavorite);
|
||||||
}
|
}
|
||||||
|
|
||||||
WellDisplayItem.prototype = {
|
BaseWellItem.prototype = {
|
||||||
__proto__ : AppIcon.AppIcon.prototype,
|
|
||||||
|
|
||||||
_init: function(appInfo, isFavorite) {
|
_init: function(appInfo, isFavorite) {
|
||||||
AppIcon.AppIcon.prototype._init.call(this, appInfo);
|
this.appInfo = appInfo;
|
||||||
|
|
||||||
this.isFavorite = isFavorite;
|
this.isFavorite = isFavorite;
|
||||||
|
this.icon = new AppIcon.AppIcon(appInfo);
|
||||||
this.actor.connect('button-release-event', Lang.bind(this, function (b, e) {
|
this.windows = this.icon.windows;
|
||||||
this._handleActivate();
|
|
||||||
}));
|
|
||||||
|
|
||||||
let draggable = DND.makeDraggable(this.actor);
|
|
||||||
},
|
|
||||||
|
|
||||||
_handleActivate: function () {
|
|
||||||
if (this._windows.length == 0) {
|
|
||||||
this.appInfo.launch();
|
|
||||||
Main.overview.hide();
|
|
||||||
} else {
|
|
||||||
/* Pick the first window and activate it;
|
|
||||||
* In the future, we want to have a menu dropdown here. */
|
|
||||||
let first = this._windows[0];
|
|
||||||
Main.overview.activateWindow(first, Clutter.get_current_event_time());
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
shellWorkspaceLaunch : function() {
|
shellWorkspaceLaunch : function() {
|
||||||
@ -492,17 +679,149 @@ WellDisplayItem.prototype = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
getDragActor: function(stageX, stageY) {
|
getDragActor: function(stageX, stageY) {
|
||||||
return this.appInfo.create_icon_texture(this._icon.height);
|
return this.icon.getDragActor(stageX, stageY);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Returns the original icon that is being used as a source for the cloned texture
|
// Returns the original icon that is being used as a source for the cloned texture
|
||||||
// that represents the item as it is being dragged.
|
// that represents the item as it is being dragged.
|
||||||
getDragActorSource: function() {
|
getDragActorSource: function() {
|
||||||
return this._icon;
|
return this.icon.getDragActorSource();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function RunningWellItem(appInfo, isFavorite) {
|
||||||
|
this._init(appInfo, isFavorite);
|
||||||
|
}
|
||||||
|
|
||||||
|
RunningWellItem.prototype = {
|
||||||
|
__proto__: BaseWellItem.prototype,
|
||||||
|
|
||||||
|
_init: function(appInfo, isFavorite) {
|
||||||
|
BaseWellItem.prototype._init.call(this, appInfo, isFavorite);
|
||||||
|
|
||||||
|
this._menuTimeoutId = 0;
|
||||||
|
this._menu = null;
|
||||||
|
this._dragStartX = 0;
|
||||||
|
this._dragStartY = 0;
|
||||||
|
|
||||||
|
this.actor = new Shell.ButtonBox({ orientation: Big.BoxOrientation.VERTICAL,
|
||||||
|
border: WELL_MENU_BORDER_WIDTH,
|
||||||
|
corner_radius: WELL_MENU_CORNER_RADIUS,
|
||||||
|
reactive: true });
|
||||||
|
this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress));
|
||||||
|
this.actor.connect('notify::hover', Lang.bind(this, this._onHoverChanged));
|
||||||
|
this.actor.connect('activate', Lang.bind(this, this.activateMostRecentWindow));
|
||||||
|
|
||||||
|
this.icon.actor._delegate = this;
|
||||||
|
this._draggable = DND.makeDraggable(this.icon.actor, true);
|
||||||
|
|
||||||
|
this.actor.append(this.icon.actor, Big.BoxPackFlags.NONE);
|
||||||
},
|
},
|
||||||
|
|
||||||
setWidth: function(width) {
|
activateMostRecentWindow: function () {
|
||||||
this._nameBox.width = width + GLOW_PADDING * 2;
|
// The _get_windows_for_app sorts them for us
|
||||||
|
let mostRecentWindow = this.windows[0];
|
||||||
|
Main.overview.activateWindow(mostRecentWindow, Clutter.get_current_event_time());
|
||||||
|
},
|
||||||
|
|
||||||
|
_onHoverChanged: function() {
|
||||||
|
let hover = this.actor.hover;
|
||||||
|
if (!hover && this._menuTimeoutId > 0) {
|
||||||
|
Mainloop.source_remove(this._menuTimeoutId);
|
||||||
|
this._menuTimeoutId = 0;
|
||||||
|
if (this.actor.pressed && this._dragStartX != null) {
|
||||||
|
this.actor.fake_release();
|
||||||
|
this._draggable.startDrag(this.icon.actor, this._dragStartX, this._dragStartY,
|
||||||
|
Clutter.get_current_event_time());
|
||||||
|
} else {
|
||||||
|
this._dragStartX = null;
|
||||||
|
this._dragStartY = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_onButtonPress: function(actor, event) {
|
||||||
|
let [stageX, stageY] = event.get_coords();
|
||||||
|
this._dragStartX = stageX;
|
||||||
|
this._dragStartY = stageY;
|
||||||
|
if (this._menuTimeoutId > 0)
|
||||||
|
Mainloop.source_remove(this._menuTimeoutId);
|
||||||
|
this._menuTimeoutId = Mainloop.timeout_add(WELL_MENU_POPUP_TIMEOUT_MS,
|
||||||
|
Lang.bind(this, this._popupMenu));
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
_popupMenu: function() {
|
||||||
|
this._menuTimeoutId = 0;
|
||||||
|
|
||||||
|
this.actor.fake_release();
|
||||||
|
|
||||||
|
if (this._menu == null) {
|
||||||
|
this._menu = new WellMenu(this);
|
||||||
|
this._menu.connect('highlight-window', Lang.bind(this, function (menu, window) {
|
||||||
|
Main.overview.setHighlightWindow(window);
|
||||||
|
}));
|
||||||
|
this._menu.connect('popup', Lang.bind(this, function (menu, isPoppedUp) {
|
||||||
|
let id;
|
||||||
|
if (isPoppedUp)
|
||||||
|
id = this.appInfo.get_id();
|
||||||
|
else
|
||||||
|
id = null;
|
||||||
|
Main.overview.setWindowApplicationFilter(id);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
this._menu.popup();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function InactiveWellItem(appInfo, isFavorite) {
|
||||||
|
this._init(appInfo, isFavorite);
|
||||||
|
}
|
||||||
|
|
||||||
|
InactiveWellItem.prototype = {
|
||||||
|
__proto__: BaseWellItem.prototype,
|
||||||
|
|
||||||
|
_init : function(appInfo, isFavorite) {
|
||||||
|
BaseWellItem.prototype._init.call(this, appInfo, isFavorite);
|
||||||
|
|
||||||
|
this.actor = new Shell.ButtonBox({ orientation: Big.BoxOrientation.VERTICAL,
|
||||||
|
border: WELL_MENU_BORDER_WIDTH,
|
||||||
|
corner_radius: WELL_MENU_CORNER_RADIUS,
|
||||||
|
reactive: true });
|
||||||
|
this.actor._delegate = this;
|
||||||
|
this.actor.connect('notify::pressed', Lang.bind(this, this._onPressedChanged));
|
||||||
|
this.actor.connect('notify::hover', Lang.bind(this, this._onHoverChanged));
|
||||||
|
this.actor.connect('activate', Lang.bind(this, this._onActivate));
|
||||||
|
|
||||||
|
this.icon.actor._delegate = this;
|
||||||
|
let draggable = DND.makeDraggable(this.icon.actor);
|
||||||
|
|
||||||
|
this.actor.append(this.icon.actor, Big.BoxPackFlags.NONE);
|
||||||
|
},
|
||||||
|
|
||||||
|
_onPressedChanged: function() {
|
||||||
|
let pressed = this.actor.pressed;
|
||||||
|
if (pressed) {
|
||||||
|
this.actor.border_color = WELL_MENU_BORDER_COLOR;
|
||||||
|
} else {
|
||||||
|
this.actor.border_color = TRANSPARENT_COLOR;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_onHoverChanged: function() {
|
||||||
|
let hover = this.actor.hover;
|
||||||
|
},
|
||||||
|
|
||||||
|
_onActivate: function() {
|
||||||
|
if (this.windows.length == 0) {
|
||||||
|
this.appInfo.launch();
|
||||||
|
Main.overview.hide();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -755,10 +1074,15 @@ AppWell.prototype = {
|
|||||||
this._displays = displays;
|
this._displays = displays;
|
||||||
},
|
},
|
||||||
|
|
||||||
_addApps: function(apps) {
|
_addApps: function(apps, isFavorite) {
|
||||||
for (let i = 0; i < apps.length; i++) {
|
for (let i = 0; i < apps.length; i++) {
|
||||||
let app = apps[i];
|
let app = apps[i];
|
||||||
let display = new WellDisplayItem(app, this.isFavorite);
|
let windows = this._appMonitor.get_windows_for_app(app.get_id());
|
||||||
|
let display;
|
||||||
|
if (windows.length > 0)
|
||||||
|
display = new RunningWellItem(app, isFavorite);
|
||||||
|
else
|
||||||
|
display = new InactiveWellItem(app, isFavorite);
|
||||||
this._grid.actor.add_actor(display.actor);
|
this._grid.actor.add_actor(display.actor);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -770,7 +1094,7 @@ AppWell.prototype = {
|
|||||||
let appSystem = Shell.AppSystem.get_default();
|
let appSystem = Shell.AppSystem.get_default();
|
||||||
|
|
||||||
let app = null;
|
let app = null;
|
||||||
if (source instanceof WellDisplayItem) {
|
if (source instanceof BaseWellItem) {
|
||||||
app = source.appInfo;
|
app = source.appInfo;
|
||||||
} else if (source instanceof AppDisplayItem) {
|
} else if (source instanceof AppDisplayItem) {
|
||||||
app = appSystem.lookup_cached_app(source.getId());
|
app = appSystem.lookup_cached_app(source.getId());
|
||||||
|
@ -24,20 +24,22 @@ function AppIcon(appInfo) {
|
|||||||
AppIcon.prototype = {
|
AppIcon.prototype = {
|
||||||
_init : function(appInfo) {
|
_init : function(appInfo) {
|
||||||
this.appInfo = appInfo;
|
this.appInfo = appInfo;
|
||||||
|
this.windows = Shell.AppMonitor.get_default().get_windows_for_app(appInfo.get_id());
|
||||||
|
for (let i = 0; i < this.windows.length; i++) {
|
||||||
|
this.windows[i].connect('notify::user-time', Lang.bind(this, this._resortWindows));
|
||||||
|
}
|
||||||
|
this._resortWindows();
|
||||||
|
|
||||||
this.actor = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
|
this.actor = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
|
||||||
corner_radius: 2,
|
corner_radius: 2,
|
||||||
border: 0,
|
|
||||||
padding: 1,
|
padding: 1,
|
||||||
border_color: GenericDisplay.ITEM_DISPLAY_SELECTED_BACKGROUND_COLOR,
|
|
||||||
reactive: true });
|
reactive: true });
|
||||||
this.actor._delegate = this;
|
|
||||||
|
|
||||||
let iconBox = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
|
let iconBox = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
|
||||||
x_align: Big.BoxAlignment.CENTER,
|
x_align: Big.BoxAlignment.CENTER,
|
||||||
y_align: Big.BoxAlignment.CENTER });
|
y_align: Big.BoxAlignment.CENTER });
|
||||||
this._icon = appInfo.create_icon_texture(APP_ICON_SIZE);
|
this.icon = appInfo.create_icon_texture(APP_ICON_SIZE);
|
||||||
iconBox.append(this._icon, Big.BoxPackFlags.NONE);
|
iconBox.append(this.icon, Big.BoxPackFlags.NONE);
|
||||||
|
|
||||||
this.actor.append(iconBox, Big.BoxPackFlags.EXPAND);
|
this.actor.append(iconBox, Big.BoxPackFlags.EXPAND);
|
||||||
|
|
||||||
@ -98,7 +100,6 @@ AppIcon.prototype = {
|
|||||||
this._name.allocate(childBox, flags);
|
this._name.allocate(childBox, flags);
|
||||||
|
|
||||||
// Now the glow
|
// Now the glow
|
||||||
|
|
||||||
if (this._glowBox != null) {
|
if (this._glowBox != null) {
|
||||||
let glowPaddingHoriz = Math.max(0, xPadding - GLOW_PADDING_HORIZONTAL);
|
let glowPaddingHoriz = Math.max(0, xPadding - GLOW_PADDING_HORIZONTAL);
|
||||||
glowPaddingHoriz = Math.max(GLOW_PADDING_HORIZONTAL, glowPaddingHoriz);
|
glowPaddingHoriz = Math.max(GLOW_PADDING_HORIZONTAL, glowPaddingHoriz);
|
||||||
@ -108,5 +109,25 @@ AppIcon.prototype = {
|
|||||||
childBox.y2 = availHeight;
|
childBox.y2 = availHeight;
|
||||||
this._glowBox.allocate(childBox, flags);
|
this._glowBox.allocate(childBox, flags);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_resortWindows: function() {
|
||||||
|
this.windows.sort(function (a, b) {
|
||||||
|
let timeA = a.get_user_time();
|
||||||
|
let timeB = b.get_user_time();
|
||||||
|
if (timeA == timeB)
|
||||||
|
return 0;
|
||||||
|
else if (timeA > timeB)
|
||||||
|
return -1;
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getDragActor: function() {
|
||||||
|
return this.appInfo.create_icon_texture(APP_ICON_SIZE);
|
||||||
|
},
|
||||||
|
|
||||||
|
getDragActorSource: function() {
|
||||||
|
return this.icon;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -232,7 +232,7 @@ Overview.prototype = {
|
|||||||
// This allows the user to place the item on any workspace.
|
// This allows the user to place the item on any workspace.
|
||||||
handleDragOver : function(source, actor, x, y, time) {
|
handleDragOver : function(source, actor, x, y, time) {
|
||||||
if (source instanceof GenericDisplay.GenericDisplayItem
|
if (source instanceof GenericDisplay.GenericDisplayItem
|
||||||
|| source instanceof AppDisplay.WellDisplayItem) {
|
|| source instanceof AppDisplay.BaseWellItem) {
|
||||||
if (this._activeDisplayPane != null)
|
if (this._activeDisplayPane != null)
|
||||||
this._activeDisplayPane.close();
|
this._activeDisplayPane.close();
|
||||||
return true;
|
return true;
|
||||||
@ -390,6 +390,29 @@ Overview.prototype = {
|
|||||||
this.hide();
|
this.hide();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* setHighlightWindow:
|
||||||
|
* @metaWindow: A #MetaWindow
|
||||||
|
*
|
||||||
|
* Draw the user's attention to the given window @metaWindow.
|
||||||
|
*/
|
||||||
|
setHighlightWindow: function (metaWindow) {
|
||||||
|
if (this._workspaces)
|
||||||
|
this._workspaces.setHighlightWindow(metaWindow);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* setWindowApplicationFilter:
|
||||||
|
* @id: A string application identifier
|
||||||
|
*
|
||||||
|
* Hide all windows which are not owned by the application
|
||||||
|
* identified by @id.
|
||||||
|
*/
|
||||||
|
setWindowApplicationFilter: function (id) {
|
||||||
|
if (this._workspaces)
|
||||||
|
this._workspaces.setWindowApplicationFilter(id);
|
||||||
|
},
|
||||||
|
|
||||||
//// Private methods ////
|
//// Private methods ////
|
||||||
|
|
||||||
_showDone: function() {
|
_showDone: function() {
|
||||||
|
@ -25,6 +25,8 @@ const WINDOWCLONE_TITLE_COLOR = new Clutter.Color();
|
|||||||
WINDOWCLONE_TITLE_COLOR.from_pixel(0xffffffff);
|
WINDOWCLONE_TITLE_COLOR.from_pixel(0xffffffff);
|
||||||
const FRAME_COLOR = new Clutter.Color();
|
const FRAME_COLOR = new Clutter.Color();
|
||||||
FRAME_COLOR.from_pixel(0xffffffff);
|
FRAME_COLOR.from_pixel(0xffffffff);
|
||||||
|
const LIGHTBOX_COLOR = new Clutter.Color();
|
||||||
|
LIGHTBOX_COLOR.from_pixel(0x00000044);
|
||||||
|
|
||||||
// Define a layout scheme for small window counts. For larger
|
// Define a layout scheme for small window counts. For larger
|
||||||
// counts we fall back to an algorithm. We need more schemes here
|
// counts we fall back to an algorithm. We need more schemes here
|
||||||
@ -64,6 +66,8 @@ WindowClone.prototype = {
|
|||||||
this.origX = realWindow.x;
|
this.origX = realWindow.x;
|
||||||
this.origY = realWindow.y;
|
this.origY = realWindow.y;
|
||||||
|
|
||||||
|
this._title = null;
|
||||||
|
|
||||||
this.actor.connect('button-release-event',
|
this.actor.connect('button-release-event',
|
||||||
Lang.bind(this, this._onButtonRelease));
|
Lang.bind(this, this._onButtonRelease));
|
||||||
|
|
||||||
@ -79,6 +83,29 @@ WindowClone.prototype = {
|
|||||||
this._inDrag = false;
|
this._inDrag = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setHighlighted: function (highlighted) {
|
||||||
|
let factor = 0.1;
|
||||||
|
if (highlighted) {
|
||||||
|
this.actor.scale_x += factor;
|
||||||
|
this.actor.scale_y += factor;
|
||||||
|
} else {
|
||||||
|
this.actor.scale_x -= factor;
|
||||||
|
this.actor.scale_y -= factor;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setVisibleWithChrome: function(visible) {
|
||||||
|
if (visible) {
|
||||||
|
this.actor.show();
|
||||||
|
if (this._title)
|
||||||
|
this._title.show();
|
||||||
|
} else {
|
||||||
|
this.actor.hide();
|
||||||
|
if (this._title)
|
||||||
|
this._title.hide();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
destroy: function () {
|
destroy: function () {
|
||||||
this.actor.destroy();
|
this.actor.destroy();
|
||||||
if (this._title)
|
if (this._title)
|
||||||
@ -280,6 +307,14 @@ Workspace.prototype = {
|
|||||||
this.actor.height = global.screen_height;
|
this.actor.height = global.screen_height;
|
||||||
this.scale = 1.0;
|
this.scale = 1.0;
|
||||||
|
|
||||||
|
this._lightbox = new Clutter.Rectangle({ color: LIGHTBOX_COLOR });
|
||||||
|
this.actor.connect('notify::allocation', Lang.bind(this, function () {
|
||||||
|
let [width, height] = this.actor.get_size();
|
||||||
|
this._lightbox.set_size(width, height);
|
||||||
|
}));
|
||||||
|
this.actor.add_actor(this._lightbox);
|
||||||
|
this._lightbox.hide();
|
||||||
|
|
||||||
let windows = global.get_windows().filter(this._isMyWindow, this);
|
let windows = global.get_windows().filter(this._isMyWindow, this);
|
||||||
|
|
||||||
// Find the desktop window
|
// Find the desktop window
|
||||||
@ -311,6 +346,9 @@ Workspace.prototype = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A filter for what windows we display
|
||||||
|
this._showOnlyWindows = null;
|
||||||
|
|
||||||
// Track window changes
|
// Track window changes
|
||||||
this._windowAddedId = this._metaWorkspace.connect('window-added',
|
this._windowAddedId = this._metaWorkspace.connect('window-added',
|
||||||
Lang.bind(this, this._windowAdded));
|
Lang.bind(this, this._windowAdded));
|
||||||
@ -386,6 +424,32 @@ Workspace.prototype = {
|
|||||||
return index < 0 ? null : this._windows[index];
|
return index < 0 ? null : this._windows[index];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
containsMetaWindow: function (metaWindow) {
|
||||||
|
return this._lookupIndex(metaWindow) >= 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
setShowOnlyWindows: function(showOnlyWindows) {
|
||||||
|
this._showOnlyWindows = showOnlyWindows;
|
||||||
|
this.positionWindows(false);
|
||||||
|
},
|
||||||
|
|
||||||
|
setLightboxMode: function (showLightbox) {
|
||||||
|
if (showLightbox)
|
||||||
|
this._lightbox.show();
|
||||||
|
else
|
||||||
|
this._lightbox.hide();
|
||||||
|
},
|
||||||
|
|
||||||
|
setHighlightWindow: function (metaWindow) {
|
||||||
|
for (let i = 0; i < this._windows.length; i++) {
|
||||||
|
this._windows[i].actor.lower(this._lightbox);
|
||||||
|
}
|
||||||
|
if (metaWindow != null) {
|
||||||
|
let clone = this.lookupCloneForMetaWindow(metaWindow);
|
||||||
|
clone.actor.raise(this._lightbox);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
_adjustRemoveButton : function() {
|
_adjustRemoveButton : function() {
|
||||||
this._removeButton.set_scale(1.0 / this.actor.scale_x,
|
this._removeButton.set_scale(1.0 / this.actor.scale_x,
|
||||||
1.0 / this.actor.scale_y);
|
1.0 / this.actor.scale_y);
|
||||||
@ -440,12 +504,37 @@ Workspace.prototype = {
|
|||||||
positionWindows : function(workspaceZooming) {
|
positionWindows : function(workspaceZooming) {
|
||||||
let global = Shell.Global.get();
|
let global = Shell.Global.get();
|
||||||
|
|
||||||
|
let totalVisible = 0;
|
||||||
|
|
||||||
|
for (let i = 1; i < this._windows.length; i++) {
|
||||||
|
let clone = this._windows[i];
|
||||||
|
|
||||||
|
if (this._showOnlyWindows != null && !(clone.metaWindow in this._showOnlyWindows))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
totalVisible += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let previousWindow = this._windows[0];
|
||||||
|
let visibleIndex = 0;
|
||||||
for (let i = 1; i < this._windows.length; i++) {
|
for (let i = 1; i < this._windows.length; i++) {
|
||||||
let clone = this._windows[i];
|
let clone = this._windows[i];
|
||||||
let icon = this._windowIcons[i];
|
let icon = this._windowIcons[i];
|
||||||
clone.stackAbove = this._windows[i - 1].actor;
|
|
||||||
|
|
||||||
let [xCenter, yCenter, fraction] = this._computeWindowPosition(i);
|
if (this._showOnlyWindows != null && !(clone.metaWindow in this._showOnlyWindows)) {
|
||||||
|
clone.setVisibleWithChrome(false);
|
||||||
|
icon.hide();
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
clone.setVisibleWithChrome(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
clone.stackAbove = previousWindow.actor;
|
||||||
|
previousWindow = clone;
|
||||||
|
|
||||||
|
visibleIndex += 1;
|
||||||
|
|
||||||
|
let [xCenter, yCenter, fraction] = this._computeWindowPosition(visibleIndex, totalVisible);
|
||||||
xCenter = xCenter * global.screen_width;
|
xCenter = xCenter * global.screen_width;
|
||||||
yCenter = yCenter * global.screen_height;
|
yCenter = yCenter * global.screen_height;
|
||||||
|
|
||||||
@ -508,6 +597,8 @@ Workspace.prototype = {
|
|||||||
for (let i = 1; i < this._windows.length; i++) {
|
for (let i = 1; i < this._windows.length; i++) {
|
||||||
let clone = this._windows[i];
|
let clone = this._windows[i];
|
||||||
let icon = this._windowIcons[i];
|
let icon = this._windowIcons[i];
|
||||||
|
if (this._showOnlyWindows != null && !(clone.metaWindow in this._showOnlyWindows))
|
||||||
|
continue;
|
||||||
this._fadeInWindowIcon(clone, icon);
|
this._fadeInWindowIcon(clone, icon);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -794,10 +885,10 @@ Workspace.prototype = {
|
|||||||
return clone;
|
return clone;
|
||||||
},
|
},
|
||||||
|
|
||||||
_computeWindowPosition : function(index) {
|
_computeWindowPosition : function(index, totalWindows) {
|
||||||
// ignore this._windows[0], which is the desktop
|
// ignore this._windows[0], which is the desktop
|
||||||
let windowIndex = index - 1;
|
let windowIndex = index - 1;
|
||||||
let numberOfWindows = this._windows.length - 1;
|
let numberOfWindows = totalWindows;
|
||||||
|
|
||||||
if (numberOfWindows in POSITIONS)
|
if (numberOfWindows in POSITIONS)
|
||||||
return POSITIONS[numberOfWindows][windowIndex];
|
return POSITIONS[numberOfWindows][windowIndex];
|
||||||
@ -872,6 +963,8 @@ Workspaces.prototype = {
|
|||||||
|
|
||||||
this.actor = new Clutter.Group();
|
this.actor = new Clutter.Group();
|
||||||
|
|
||||||
|
this._appIdFilter = null;
|
||||||
|
|
||||||
let screenHeight = global.screen_height;
|
let screenHeight = global.screen_height;
|
||||||
|
|
||||||
this._width = width;
|
this._width = width;
|
||||||
@ -881,6 +974,8 @@ Workspaces.prototype = {
|
|||||||
|
|
||||||
this._workspaces = [];
|
this._workspaces = [];
|
||||||
|
|
||||||
|
this._highlightWindow = null;
|
||||||
|
|
||||||
let activeWorkspaceIndex = global.screen.get_active_workspace_index();
|
let activeWorkspaceIndex = global.screen.get_active_workspace_index();
|
||||||
let activeWorkspace;
|
let activeWorkspace;
|
||||||
|
|
||||||
@ -927,6 +1022,14 @@ Workspaces.prototype = {
|
|||||||
Lang.bind(this, this._activeWorkspaceChanged));
|
Lang.bind(this, this._activeWorkspaceChanged));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_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) {
|
_lookupCloneForMetaWindow: function (metaWindow) {
|
||||||
for (let i = 0; i < this._workspaces.length; i++) {
|
for (let i = 0; i < this._workspaces.length; i++) {
|
||||||
let clone = this._workspaces[i].lookupCloneForMetaWindow(metaWindow);
|
let clone = this._workspaces[i].lookupCloneForMetaWindow(metaWindow);
|
||||||
@ -936,6 +1039,39 @@ Workspaces.prototype = {
|
|||||||
return null;
|
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].setLightboxMode(metaWindow != null);
|
||||||
|
this._workspaces[i].setHighlightWindow(null);
|
||||||
|
}
|
||||||
|
if (metaWindow != null) {
|
||||||
|
let workspace = this._lookupWorkspaceForMetaWindow(metaWindow);
|
||||||
|
workspace.setHighlightWindow(metaWindow);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setWindowApplicationFilter: function (appId) {
|
||||||
|
let appSys = Shell.AppMonitor.get_default();
|
||||||
|
|
||||||
|
let showOnlyWindows;
|
||||||
|
if (appId) {
|
||||||
|
let windows = appSys.get_windows_for_app(appId);
|
||||||
|
showOnlyWindows = {};
|
||||||
|
for (let i = 0; i < windows.length; i++) {
|
||||||
|
showOnlyWindows[windows[i]] = 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showOnlyWindows = null;
|
||||||
|
}
|
||||||
|
this._appIdFilter = appId;
|
||||||
|
for (let i = 0; i < this._workspaces.length; i++) {
|
||||||
|
this._workspaces[i].setShowOnlyWindows(showOnlyWindows);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// Should only be called from active Overview context
|
// Should only be called from active Overview context
|
||||||
activateWindowFromOverview: function (metaWindow, time) {
|
activateWindowFromOverview: function (metaWindow, time) {
|
||||||
let global = Shell.Global.get();
|
let global = Shell.Global.get();
|
||||||
|
@ -146,6 +146,36 @@ shell_draw_clock (ClutterCairoTexture *texture,
|
|||||||
cairo_destroy (cr);
|
cairo_destroy (cr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
shell_draw_box_pointer (ClutterCairoTexture *texture,
|
||||||
|
ClutterColor *border_color,
|
||||||
|
ClutterColor *background_color)
|
||||||
|
{
|
||||||
|
guint width, height;
|
||||||
|
cairo_t *cr;
|
||||||
|
|
||||||
|
clutter_cairo_texture_get_surface_size (texture, &width, &height);
|
||||||
|
|
||||||
|
clutter_cairo_texture_clear (texture);
|
||||||
|
cr = clutter_cairo_texture_create (texture);
|
||||||
|
|
||||||
|
cairo_set_line_width (cr, 1.0);
|
||||||
|
|
||||||
|
clutter_cairo_set_source_color (cr, border_color);
|
||||||
|
|
||||||
|
cairo_move_to (cr, width, 0);
|
||||||
|
cairo_line_to (cr, 0, floor (height * 0.5));
|
||||||
|
cairo_line_to (cr, width, height);
|
||||||
|
|
||||||
|
cairo_stroke_preserve (cr);
|
||||||
|
|
||||||
|
clutter_cairo_set_source_color (cr, background_color);
|
||||||
|
|
||||||
|
cairo_fill (cr);
|
||||||
|
|
||||||
|
cairo_destroy (cr);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
hook_paint_red_border (ClutterActor *actor,
|
hook_paint_red_border (ClutterActor *actor,
|
||||||
gpointer user_data)
|
gpointer user_data)
|
||||||
|
@ -13,6 +13,10 @@ ClutterCairoTexture *shell_create_vertical_gradient (ClutterColor *top,
|
|||||||
ClutterCairoTexture *shell_create_horizontal_gradient (ClutterColor *left,
|
ClutterCairoTexture *shell_create_horizontal_gradient (ClutterColor *left,
|
||||||
ClutterColor *right);
|
ClutterColor *right);
|
||||||
|
|
||||||
|
void shell_draw_box_pointer (ClutterCairoTexture *texture,
|
||||||
|
ClutterColor *border_color,
|
||||||
|
ClutterColor *background_color);
|
||||||
|
|
||||||
void shell_draw_clock (ClutterCairoTexture *texture,
|
void shell_draw_clock (ClutterCairoTexture *texture,
|
||||||
int hour,
|
int hour,
|
||||||
int minute);
|
int minute);
|
||||||
|
Loading…
Reference in New Issue
Block a user