diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index fe5b43299..92bfb70ab 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -489,6 +489,12 @@ WellMenu.prototype = { _init: function(source) { this._source = source; + // Holds the WindowClone objects for our windows, used in the button release + // callback to find the window actor we released over + this._cachedWindowClones = []; + + // Whether or not we successfully picked a window + this.didActivateWindow = false; this.actor = new Shell.GenericContainer({ reactive: true }); this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth)); @@ -508,6 +514,14 @@ WellMenu.prototype = { this._windowContainer.connect('activate', Lang.bind(this, this._onWindowActivate)); this.actor.add_actor(this._windowContainer); + // Stay popped up on release over application icon + this._windowContainer.set_persistent_source(this._source.actor); + + // Intercept events while the menu has the pointer grab to do window-related effects + this._windowContainer.connect('enter-event', Lang.bind(this, this._onMenuEnter)); + this._windowContainer.connect('leave-event', Lang.bind(this, this._onMenuLeave)); + this._windowContainer.connect('button-release-event', Lang.bind(this, this._onMenuRelease)); + 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); @@ -559,6 +573,8 @@ WellMenu.prototype = { _redisplay: function() { this._windowContainer.remove_all(); + this.didActivateWindow = false; + let windows = this._source.windows; this._windowContainer.show(); @@ -595,7 +611,10 @@ WellMenu.prototype = { _appendWindows: function (windows, iconsDiffer) { for (let i = 0; i < windows.length; i++) { - let window = windows[i]; + let metaWindow = windows[i]; + + this._cachedWindowClones.push(Main.overview.lookupCloneForWindow(metaWindow)); + /* Use padding here rather than spacing in the box above so that * we have a larger reactive area. */ @@ -604,16 +623,16 @@ WellMenu.prototype = { padding_bottom: 4, spacing: 4, reactive: true }); - box._window = window; + box._window = metaWindow; 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"); + let icon = Shell.TextureCache.get_default().bind_pixbuf_property(metaWindow, "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, + let label = new Clutter.Text({ text: metaWindow.title, font_name: WELL_MENU_FONT, ellipsize: Pango.EllipsizeMode.END, color: WELL_MENU_COLOR }); @@ -639,22 +658,68 @@ WellMenu.prototype = { this.actor.show(); }, - _onWindowUnselected: function (actor, child) { - child.background_color = TRANSPARENT_COLOR; + _findWindowCloneForActor: function (actor) { + for (let i = 0; i < this._cachedWindowClones.length; i++) { + let clone = this._cachedWindowClones[i]; + if (clone.actor == actor) { + return clone; + } + } + return null; + }, - this.emit('highlight-window', null); + // This function is called while the menu has a pointer grab; what we want + // to do is see if the mouse was released over a window clone actor + _onMenuRelease: function (actor, event) { + let clone = this._findWindowCloneForActor(event.get_source()); + if (clone) { + this.didActivateWindow = true; + Main.overview.activateWindow(clone.metaWindow, event.get_time()); + } + }, + + _setHighlightWindow: function (metaWindow) { + let children = this._windowContainer.get_children(); + for (let i = 0; i < children.length; i++) { + let child = children[i]; + let menuMetaWindow = child._window; + if (metaWindow != null && menuMetaWindow == metaWindow) { + child.background_color = WELL_MENU_SELECTED_COLOR; + } else { + child.background_color = TRANSPARENT_COLOR; + } + } + this.emit('highlight-window', metaWindow); + }, + + // Called while menu has a pointer grab + _onMenuEnter: function (actor, event) { + let clone = this._findWindowCloneForActor(event.get_source()); + if (clone) { + this._setHighlightWindow(clone.metaWindow); + } + }, + + // Called while menu has a pointer grab + _onMenuLeave: function (actor, event) { + let clone = this._findWindowCloneForActor(event.get_source()); + if (clone) { + this._setHighlightWindow(null); + } + }, + + _onWindowUnselected: function (actor, child) { + this._setHighlightWindow(null); }, _onWindowSelected: function (actor, child) { - child.background_color = WELL_MENU_SELECTED_COLOR; - - let window = child._window; - this.emit('highlight-window', window); + this._setHighlightWindow(child._window); }, _onWindowActivate: function (actor, child) { - let window = child._window; - Main.overview.activateWindow(window, Clutter.get_current_event_time()); + let metaWindow = child._window; + this.didActivateWindow = true; + Main.overview.activateWindow(metaWindow, Clutter.get_current_event_time()); }, _onPopdown: function () { @@ -793,15 +858,22 @@ RunningWellItem.prototype = { 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('highlight-window', Lang.bind(this, function (menu, metaWindow) { + Main.overview.setHighlightWindow(metaWindow); })); this._menu.connect('popup', Lang.bind(this, function (menu, isPoppedUp) { let id; + + // If we successfully picked a window, don't reset the workspace + // state, since that causes visual noise. The workspace gets + // recreated each time we enter the overview + if (!isPoppedUp && menu.didActivateWindow) + return; if (isPoppedUp) id = this.appInfo.get_id(); else id = null; + Main.overview.setApplicationWindowSelection(id); })); } diff --git a/js/ui/overview.js b/js/ui/overview.js index 4f50e6fe8..4a78db9ca 100644 --- a/js/ui/overview.js +++ b/js/ui/overview.js @@ -371,6 +371,17 @@ Overview.prototype = { this.show(); }, + /** + * lookupCloneForWindow: + * @metaWindow: A #MetaWindow + * + * Given a #MetaWindow instance, find the WindowClone object + * which represents it in the workspaces display. + */ + lookupCloneForWindow: function (metaWindow) { + return this._workspaces.lookupCloneForMetaWindow(metaWindow); + }, + /** * activateWindow: * @metaWindow: A #MetaWindow diff --git a/js/ui/workspaces.js b/js/ui/workspaces.js index 39c48b5ae..be208f93b 100644 --- a/js/ui/workspaces.js +++ b/js/ui/workspaces.js @@ -1134,7 +1134,7 @@ Workspaces.prototype = { return null; }, - _lookupCloneForMetaWindow: function (metaWindow) { + lookupCloneForMetaWindow: function (metaWindow) { for (let i = 0; i < this._workspaces.length; i++) { let clone = this._workspaces[i].lookupCloneForMetaWindow(metaWindow); if (clone) @@ -1181,7 +1181,7 @@ Workspaces.prototype = { let activeWorkspaceNum = global.screen.get_active_workspace_index(); let windowWorkspaceNum = metaWindow.get_workspace().index(); - let clone = this._lookupCloneForMetaWindow (metaWindow); + let clone = this.lookupCloneForMetaWindow (metaWindow); clone.actor.raise_top(); if (windowWorkspaceNum != activeWorkspaceNum) { diff --git a/src/shell-menu.c b/src/shell-menu.c index 6debaf8f7..d09f6a025 100644 --- a/src/shell-menu.c +++ b/src/shell-menu.c @@ -15,6 +15,9 @@ G_DEFINE_TYPE(ShellMenu, shell_menu, BIG_TYPE_BOX); struct _ShellMenuPrivate { gboolean have_grab; + gboolean released_on_source; + ClutterActor *source_actor; + ClutterActor *selected; }; @@ -31,10 +34,10 @@ enum static guint shell_menu_signals [LAST_SIGNAL] = { 0 }; static gboolean -shell_menu_contains (ShellMenu *box, - ClutterActor *actor) +shell_menu_contains (ClutterContainer *container, + ClutterActor *actor) { - while (actor != NULL && actor != (ClutterActor*)box) + while (actor != NULL && actor != (ClutterActor*)container) { actor = clutter_actor_get_parent (actor); } @@ -73,7 +76,7 @@ shell_menu_enter_event (ClutterActor *actor, { ShellMenu *box = SHELL_MENU (actor); - if (!shell_menu_contains (box, event->source)) + if (!shell_menu_contains (CLUTTER_CONTAINER (box), event->source)) return TRUE; if (event->source == (ClutterActor*)box) @@ -107,9 +110,21 @@ shell_menu_button_release_event (ClutterActor *actor, if (event->button != 1) return FALSE; + if (box->priv->source_actor && !box->priv->released_on_source) + { + if (box->priv->source_actor == event->source || + (CLUTTER_IS_CONTAINER (box->priv->source_actor) && + shell_menu_contains (CLUTTER_CONTAINER (box->priv->source_actor), event->source))) + { + /* On the next release, we want to pop down the menu regardless */ + box->priv->released_on_source = TRUE; + return TRUE; + } + } + shell_menu_popdown (box); - if (!shell_menu_contains (box, event->source)) + if (!shell_menu_contains (CLUTTER_CONTAINER (box), event->source)) return FALSE; if (box->priv->selected == NULL) @@ -126,6 +141,7 @@ shell_menu_popup (ShellMenu *box, guint32 activate_time) { box->priv->have_grab = TRUE; + box->priv->released_on_source = FALSE; clutter_grab_pointer (CLUTTER_ACTOR (box)); } @@ -138,6 +154,45 @@ shell_menu_popdown (ShellMenu *box) g_signal_emit (G_OBJECT (box), shell_menu_signals[POPDOWN], 0); } +static void +on_source_destroyed (ClutterActor *actor, + ShellMenu *box) +{ + box->priv->source_actor = NULL; +} + +/** + * shell_menu_set_persistent_source: + * @box: + * @source: Actor to use as menu origin + * + * This function changes the menu behavior on button release. Normally + * when the mouse is released anywhere, the menu "pops down"; when this + * function is called, if the mouse is released over the source actor, + * the menu stays. + * + * The given @source actor must be reactive for this function to work. + */ +void +shell_menu_set_persistent_source (ShellMenu *box, + ClutterActor *source) +{ + if (box->priv->source_actor) + { + g_signal_handlers_disconnect_by_func (G_OBJECT (box->priv->source_actor), + G_CALLBACK (on_source_destroyed), + box); + } + box->priv->source_actor = source; + if (box->priv->source_actor) + { + g_signal_connect (G_OBJECT (box->priv->source_actor), + "destroy", + G_CALLBACK (on_source_destroyed), + box); + } +} + /** * shell_menu_append_separator: * @box: diff --git a/src/shell-menu.h b/src/shell-menu.h index 1d0c57111..bfe909adc 100644 --- a/src/shell-menu.h +++ b/src/shell-menu.h @@ -32,6 +32,8 @@ GType shell_menu_get_type (void) G_GNUC_CONST; void shell_menu_popup (ShellMenu *behavior, guint button, guint32 activate_time); +void shell_menu_set_persistent_source (ShellMenu *behavior, ClutterActor *source); + void shell_menu_append_separator (ShellMenu *behavior, ClutterActor *separator, BigBoxPackFlags flags); void shell_menu_popdown (ShellMenu *behavior);