[AppWell] Allow popup menu to be persistent, and support direct window selection
When the user click+hold+release over the icon, the effect we want is for the menu to stick around. Also, allow the user to mouse over the actual windows and select them directly. If the user mouses over a window, reflect that in the menu.
This commit is contained in:
parent
458778bcfd
commit
6e31e59b57
@ -489,6 +489,12 @@ WellMenu.prototype = {
|
|||||||
_init: function(source) {
|
_init: function(source) {
|
||||||
this._source = 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 = new Shell.GenericContainer({ reactive: true });
|
||||||
this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
|
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._windowContainer.connect('activate', Lang.bind(this, this._onWindowActivate));
|
||||||
this.actor.add_actor(this._windowContainer);
|
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 = new Shell.DrawingArea();
|
||||||
this._arrow.connect('redraw', Lang.bind(this, function (area, texture) {
|
this._arrow.connect('redraw', Lang.bind(this, function (area, texture) {
|
||||||
Shell.draw_box_pointer(texture, WELL_MENU_BORDER_COLOR, WELL_MENU_BACKGROUND_COLOR);
|
Shell.draw_box_pointer(texture, WELL_MENU_BORDER_COLOR, WELL_MENU_BACKGROUND_COLOR);
|
||||||
@ -559,6 +573,8 @@ WellMenu.prototype = {
|
|||||||
_redisplay: function() {
|
_redisplay: function() {
|
||||||
this._windowContainer.remove_all();
|
this._windowContainer.remove_all();
|
||||||
|
|
||||||
|
this.didActivateWindow = false;
|
||||||
|
|
||||||
let windows = this._source.windows;
|
let windows = this._source.windows;
|
||||||
|
|
||||||
this._windowContainer.show();
|
this._windowContainer.show();
|
||||||
@ -595,7 +611,10 @@ WellMenu.prototype = {
|
|||||||
|
|
||||||
_appendWindows: function (windows, iconsDiffer) {
|
_appendWindows: function (windows, iconsDiffer) {
|
||||||
for (let i = 0; i < windows.length; i++) {
|
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
|
/* Use padding here rather than spacing in the box above so that
|
||||||
* we have a larger reactive area.
|
* we have a larger reactive area.
|
||||||
*/
|
*/
|
||||||
@ -604,16 +623,16 @@ WellMenu.prototype = {
|
|||||||
padding_bottom: 4,
|
padding_bottom: 4,
|
||||||
spacing: 4,
|
spacing: 4,
|
||||||
reactive: true });
|
reactive: true });
|
||||||
box._window = window;
|
box._window = metaWindow;
|
||||||
let vCenter;
|
let vCenter;
|
||||||
if (iconsDiffer) {
|
if (iconsDiffer) {
|
||||||
vCenter = new Big.Box({ y_align: Big.BoxAlignment.CENTER });
|
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);
|
vCenter.append(icon, Big.BoxPackFlags.NONE);
|
||||||
box.append(vCenter, Big.BoxPackFlags.NONE);
|
box.append(vCenter, Big.BoxPackFlags.NONE);
|
||||||
}
|
}
|
||||||
vCenter = new Big.Box({ y_align: Big.BoxAlignment.CENTER });
|
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,
|
font_name: WELL_MENU_FONT,
|
||||||
ellipsize: Pango.EllipsizeMode.END,
|
ellipsize: Pango.EllipsizeMode.END,
|
||||||
color: WELL_MENU_COLOR });
|
color: WELL_MENU_COLOR });
|
||||||
@ -639,22 +658,68 @@ WellMenu.prototype = {
|
|||||||
this.actor.show();
|
this.actor.show();
|
||||||
},
|
},
|
||||||
|
|
||||||
_onWindowUnselected: function (actor, child) {
|
_findWindowCloneForActor: function (actor) {
|
||||||
child.background_color = TRANSPARENT_COLOR;
|
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) {
|
_onWindowSelected: function (actor, child) {
|
||||||
child.background_color = WELL_MENU_SELECTED_COLOR;
|
this._setHighlightWindow(child._window);
|
||||||
|
|
||||||
let window = child._window;
|
|
||||||
this.emit('highlight-window', window);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_onWindowActivate: function (actor, child) {
|
_onWindowActivate: function (actor, child) {
|
||||||
let window = child._window;
|
let metaWindow = child._window;
|
||||||
Main.overview.activateWindow(window, Clutter.get_current_event_time());
|
this.didActivateWindow = true;
|
||||||
|
Main.overview.activateWindow(metaWindow, Clutter.get_current_event_time());
|
||||||
},
|
},
|
||||||
|
|
||||||
_onPopdown: function () {
|
_onPopdown: function () {
|
||||||
@ -793,15 +858,22 @@ RunningWellItem.prototype = {
|
|||||||
|
|
||||||
if (this._menu == null) {
|
if (this._menu == null) {
|
||||||
this._menu = new WellMenu(this);
|
this._menu = new WellMenu(this);
|
||||||
this._menu.connect('highlight-window', Lang.bind(this, function (menu, window) {
|
this._menu.connect('highlight-window', Lang.bind(this, function (menu, metaWindow) {
|
||||||
Main.overview.setHighlightWindow(window);
|
Main.overview.setHighlightWindow(metaWindow);
|
||||||
}));
|
}));
|
||||||
this._menu.connect('popup', Lang.bind(this, function (menu, isPoppedUp) {
|
this._menu.connect('popup', Lang.bind(this, function (menu, isPoppedUp) {
|
||||||
let id;
|
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)
|
if (isPoppedUp)
|
||||||
id = this.appInfo.get_id();
|
id = this.appInfo.get_id();
|
||||||
else
|
else
|
||||||
id = null;
|
id = null;
|
||||||
|
|
||||||
Main.overview.setApplicationWindowSelection(id);
|
Main.overview.setApplicationWindowSelection(id);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@ -371,6 +371,17 @@ Overview.prototype = {
|
|||||||
this.show();
|
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:
|
* activateWindow:
|
||||||
* @metaWindow: A #MetaWindow
|
* @metaWindow: A #MetaWindow
|
||||||
|
@ -1134,7 +1134,7 @@ Workspaces.prototype = {
|
|||||||
return null;
|
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);
|
||||||
if (clone)
|
if (clone)
|
||||||
@ -1181,7 +1181,7 @@ Workspaces.prototype = {
|
|||||||
let activeWorkspaceNum = global.screen.get_active_workspace_index();
|
let activeWorkspaceNum = global.screen.get_active_workspace_index();
|
||||||
let windowWorkspaceNum = metaWindow.get_workspace().index();
|
let windowWorkspaceNum = metaWindow.get_workspace().index();
|
||||||
|
|
||||||
let clone = this._lookupCloneForMetaWindow (metaWindow);
|
let clone = this.lookupCloneForMetaWindow (metaWindow);
|
||||||
clone.actor.raise_top();
|
clone.actor.raise_top();
|
||||||
|
|
||||||
if (windowWorkspaceNum != activeWorkspaceNum) {
|
if (windowWorkspaceNum != activeWorkspaceNum) {
|
||||||
|
@ -15,6 +15,9 @@ G_DEFINE_TYPE(ShellMenu, shell_menu, BIG_TYPE_BOX);
|
|||||||
struct _ShellMenuPrivate {
|
struct _ShellMenuPrivate {
|
||||||
gboolean have_grab;
|
gboolean have_grab;
|
||||||
|
|
||||||
|
gboolean released_on_source;
|
||||||
|
ClutterActor *source_actor;
|
||||||
|
|
||||||
ClutterActor *selected;
|
ClutterActor *selected;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -31,10 +34,10 @@ enum
|
|||||||
static guint shell_menu_signals [LAST_SIGNAL] = { 0 };
|
static guint shell_menu_signals [LAST_SIGNAL] = { 0 };
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
shell_menu_contains (ShellMenu *box,
|
shell_menu_contains (ClutterContainer *container,
|
||||||
ClutterActor *actor)
|
ClutterActor *actor)
|
||||||
{
|
{
|
||||||
while (actor != NULL && actor != (ClutterActor*)box)
|
while (actor != NULL && actor != (ClutterActor*)container)
|
||||||
{
|
{
|
||||||
actor = clutter_actor_get_parent (actor);
|
actor = clutter_actor_get_parent (actor);
|
||||||
}
|
}
|
||||||
@ -73,7 +76,7 @@ shell_menu_enter_event (ClutterActor *actor,
|
|||||||
{
|
{
|
||||||
ShellMenu *box = SHELL_MENU (actor);
|
ShellMenu *box = SHELL_MENU (actor);
|
||||||
|
|
||||||
if (!shell_menu_contains (box, event->source))
|
if (!shell_menu_contains (CLUTTER_CONTAINER (box), event->source))
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
|
||||||
if (event->source == (ClutterActor*)box)
|
if (event->source == (ClutterActor*)box)
|
||||||
@ -107,9 +110,21 @@ shell_menu_button_release_event (ClutterActor *actor,
|
|||||||
if (event->button != 1)
|
if (event->button != 1)
|
||||||
return FALSE;
|
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);
|
shell_menu_popdown (box);
|
||||||
|
|
||||||
if (!shell_menu_contains (box, event->source))
|
if (!shell_menu_contains (CLUTTER_CONTAINER (box), event->source))
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
|
||||||
if (box->priv->selected == NULL)
|
if (box->priv->selected == NULL)
|
||||||
@ -126,6 +141,7 @@ shell_menu_popup (ShellMenu *box,
|
|||||||
guint32 activate_time)
|
guint32 activate_time)
|
||||||
{
|
{
|
||||||
box->priv->have_grab = TRUE;
|
box->priv->have_grab = TRUE;
|
||||||
|
box->priv->released_on_source = FALSE;
|
||||||
clutter_grab_pointer (CLUTTER_ACTOR (box));
|
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);
|
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:
|
* shell_menu_append_separator:
|
||||||
* @box:
|
* @box:
|
||||||
|
@ -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_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_append_separator (ShellMenu *behavior, ClutterActor *separator, BigBoxPackFlags flags);
|
||||||
|
|
||||||
void shell_menu_popdown (ShellMenu *behavior);
|
void shell_menu_popdown (ShellMenu *behavior);
|
||||||
|
Loading…
Reference in New Issue
Block a user