diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index 3662d6faa..9caf83475 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -489,6 +489,8 @@ WellMenu.prototype = { _init: function(source) { this._source = source; + // 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 +510,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._onMenuButtonRelease)); + 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 +569,8 @@ WellMenu.prototype = { _redisplay: function() { this._windowContainer.remove_all(); + this.didActivateWindow = false; + let windows = this._source.windows; this._windowContainer.show(); @@ -595,7 +607,8 @@ WellMenu.prototype = { _appendWindows: function (windows, iconsDiffer) { for (let i = 0; i < windows.length; i++) { - let window = windows[i]; + let metaWindow = windows[i]; + /* Use padding here rather than spacing in the box above so that * we have a larger reactive area. */ @@ -604,16 +617,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 +652,62 @@ WellMenu.prototype = { this.actor.show(); }, - _onWindowUnselected: function (actor, child) { - child.background_color = TRANSPARENT_COLOR; + _findWindowCloneForActor: function (actor) { + if (actor._delegate instanceof Workspaces.WindowClone) + return actor._delegate; + 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 + _onMenuButtonRelease: function (actor, event) { + let clone = this._findWindowCloneForActor(event.get_source()); + if (clone) { + 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; + Main.overview.activateWindow(metaWindow, Clutter.get_current_event_time()); this.emit('popup', false); this.actor.hide(); }, @@ -800,6 +853,7 @@ RunningWellItem.prototype = { })); this._menu.connect('popup', Lang.bind(this, function (menu, isPoppedUp) { let id; + if (isPoppedUp) id = this.appInfo.get_id(); else diff --git a/js/ui/workspaces.js b/js/ui/workspaces.js index 39c48b5ae..3fcb6019e 100644 --- a/js/ui/workspaces.js +++ b/js/ui/workspaces.js @@ -107,6 +107,7 @@ WindowClone.prototype = { this.actor._delegate = this; this.realWindow = realWindow; this.metaWindow = realWindow.meta_window; + this.metaWindow._delegate = this; this.origX = realWindow.x; this.origY = realWindow.y; diff --git a/src/shell-menu.c b/src/shell-menu.c index cfb60a1dc..0eace370a 100644 --- a/src/shell-menu.c +++ b/src/shell-menu.c @@ -16,6 +16,9 @@ struct _ShellMenuPrivate { gboolean popped_up; gboolean have_grab; + gboolean released_on_source; + ClutterActor *source_actor; + ClutterActor *selected; }; @@ -32,10 +35,10 @@ enum static guint shell_menu_signals [LAST_SIGNAL] = { 0 }; static gboolean -shell_menu_contains (ShellMenu *box, - ClutterActor *actor) +container_contains (ClutterContainer *container, + ClutterActor *actor) { - while (actor != NULL && actor != (ClutterActor*)box) + while (actor != NULL && actor != (ClutterActor*)container) { actor = clutter_actor_get_parent (actor); } @@ -83,7 +86,7 @@ shell_menu_enter_event (ClutterActor *actor, { ShellMenu *box = SHELL_MENU (actor); - if (!shell_menu_contains (box, event->source)) + if (!container_contains (CLUTTER_CONTAINER (box), event->source)) return TRUE; if (event->source == (ClutterActor*)box) @@ -117,9 +120,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) && + container_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_nosignal (box); - if (!shell_menu_contains (CLUTTER_CONTAINER (box), event->source)) + if (!container_contains (CLUTTER_CONTAINER (box), event->source)) { g_signal_emit (G_OBJECT (box), shell_menu_signals[CANCELLED], 0); return FALSE; @@ -145,6 +160,7 @@ shell_menu_popup (ShellMenu *box, return; box->priv->popped_up = TRUE; box->priv->have_grab = TRUE; + box->priv->released_on_source = FALSE; clutter_grab_pointer (CLUTTER_ACTOR (box)); } @@ -163,6 +179,45 @@ shell_menu_popdown (ShellMenu *box) g_signal_emit (G_OBJECT (box), shell_menu_signals[CANCELLED], 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: @@ -183,12 +238,24 @@ shell_menu_append_separator (ShellMenu *box, big_box_append (BIG_BOX (box), separator, flags); } +static void +shell_menu_dispose (GObject *gobject) +{ + ShellMenu *self = SHELL_MENU (gobject); + + shell_menu_set_persistent_source (self, NULL); + + G_OBJECT_CLASS (shell_menu_parent_class)->dispose (gobject); +} + static void shell_menu_class_init (ShellMenuClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); + gobject_class->dispose = shell_menu_dispose; + actor_class->enter_event = shell_menu_enter_event; actor_class->leave_event = shell_menu_leave_event; actor_class->button_release_event = shell_menu_button_release_event; 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);