popupMenu: Split the remote menu code into its own module
This is getting sufficiently complicated to deserve its own place. https://bugzilla.gnome.org/show_bug.cgi?id=700257
This commit is contained in:
parent
bfd1cc99a0
commit
5803f69605
@ -77,6 +77,7 @@ nobase_dist_js_DATA = \
|
||||
ui/pointerWatcher.js \
|
||||
ui/popupMenu.js \
|
||||
ui/remoteSearch.js \
|
||||
ui/remoteMenu.js \
|
||||
ui/runDialog.js \
|
||||
ui/screencast.js \
|
||||
ui/screenshot.js \
|
||||
|
@ -21,6 +21,7 @@ const DND = imports.ui.dnd;
|
||||
const Overview = imports.ui.overview;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
const PanelMenu = imports.ui.panelMenu;
|
||||
const RemoteMenu = imports.ui.remoteMenu;
|
||||
const Main = imports.ui.main;
|
||||
const Tweener = imports.ui.tweener;
|
||||
|
||||
@ -621,11 +622,11 @@ const AppMenuButton = new Lang.Class({
|
||||
let menu;
|
||||
|
||||
if (this._targetApp.action_group && this._targetApp.menu) {
|
||||
if (this.menu instanceof PopupMenu.RemoteMenu &&
|
||||
if (this.menu instanceof RemoteMenu.RemoteMenu &&
|
||||
this.menu.actionGroup == this._targetApp.action_group)
|
||||
return;
|
||||
|
||||
menu = new PopupMenu.RemoteMenu(this.actor, this._targetApp.menu, this._targetApp.action_group);
|
||||
menu = new RemoteMenu.RemoteMenu(this.actor, this._targetApp.menu, this._targetApp.action_group);
|
||||
menu.connect('activate', Lang.bind(this, function() {
|
||||
let win = this._targetApp.get_windows()[0];
|
||||
win.check_alive(global.get_current_time());
|
||||
|
@ -1772,205 +1772,6 @@ const PopupComboBoxMenuItem = new Lang.Class({
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* RemoteMenu:
|
||||
*
|
||||
* A PopupMenu that tracks a GMenuModel and shows its actions
|
||||
* (exposed by GApplication/GActionGroup)
|
||||
*/
|
||||
const RemoteMenu = new Lang.Class({
|
||||
Name: 'RemoteMenu',
|
||||
Extends: PopupMenu,
|
||||
|
||||
_init: function(sourceActor, model, actionGroup) {
|
||||
this.parent(sourceActor, 0.0, St.Side.TOP);
|
||||
|
||||
this.model = model;
|
||||
this.actionGroup = actionGroup;
|
||||
|
||||
this._actions = {};
|
||||
this._trackMenu(model, this);
|
||||
|
||||
this._actionStateChangeId = this.actionGroup.connect('action-state-changed', Lang.bind(this, this._actionStateChanged));
|
||||
this._actionEnableChangeId = this.actionGroup.connect('action-enabled-changed', Lang.bind(this, this._actionEnabledChanged));
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
if (this._actionStateChangeId) {
|
||||
this.actionGroup.disconnect(this._actionStateChangeId);
|
||||
this._actionStateChangeId = 0;
|
||||
}
|
||||
|
||||
if (this._actionEnableChangeId) {
|
||||
this.actionGroup.disconnect(this._actionEnableChangeId);
|
||||
this._actionEnableChangeId = 0;
|
||||
}
|
||||
|
||||
this.parent();
|
||||
},
|
||||
|
||||
_actionAdded: function(model, item, index) {
|
||||
let action_id = item.action_id;
|
||||
|
||||
if (!this._actions[action_id])
|
||||
this._actions[action_id] = { enabled: this.actionGroup.get_action_enabled(action_id),
|
||||
state: this.actionGroup.get_action_state(action_id),
|
||||
items: [ ],
|
||||
};
|
||||
let action = this._actions[action_id];
|
||||
let target, destroyId, specificSignalId;
|
||||
|
||||
if (action.state) {
|
||||
// Docs have get_state_hint(), except that the DBus protocol
|
||||
// has no provision for it (so ShellApp does not implement it,
|
||||
// and neither GApplication), and g_action_get_state_hint()
|
||||
// always returns null
|
||||
// Funny :)
|
||||
|
||||
switch (String.fromCharCode(action.state.classify())) {
|
||||
case 'b':
|
||||
action.items.push(item);
|
||||
item.setOrnament(action.state.get_boolean() ?
|
||||
Ornament.CHECK : Ornament.NONE);
|
||||
specificSignalId = item.connect('activate', Lang.bind(this, function(item) {
|
||||
this.actionGroup.activate_action(action_id, null);
|
||||
}));
|
||||
break;
|
||||
case 's':
|
||||
action.items.push(item);
|
||||
item._remoteTarget = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_TARGET, null).deep_unpack();
|
||||
item.setOrnament(action.state.deep_unpack() == item._remoteTarget ?
|
||||
Ornament.DOT : Ornament.NONE);
|
||||
specificSignalId = item.connect('activate', Lang.bind(this, function(item) {
|
||||
this.actionGroup.activate_action(action_id, GLib.Variant.new_string(item._remoteTarget));
|
||||
}));
|
||||
break;
|
||||
default:
|
||||
log('Action "%s" has state of type %s, which is not supported'.format(action_id, action.state.get_type_string()));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
target = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_TARGET, null);
|
||||
action.items.push(item);
|
||||
specificSignalId = item.connect('activate', Lang.bind(this, function() {
|
||||
this.actionGroup.activate_action(action_id, target);
|
||||
}));
|
||||
}
|
||||
|
||||
item.actor.reactive = item.actor.can_focus = action.enabled;
|
||||
|
||||
destroyId = item.connect('destroy', Lang.bind(this, function() {
|
||||
item.disconnect(destroyId);
|
||||
item.disconnect(specificSignalId);
|
||||
|
||||
let pos = action.items.indexOf(item);
|
||||
if (pos != -1)
|
||||
action.items.splice(pos, 1);
|
||||
}));
|
||||
},
|
||||
|
||||
_trackMenu: function(model, item) {
|
||||
item._tracker = Shell.MenuTracker.new(model,
|
||||
null, /* action namespace */
|
||||
Lang.bind(this, this._insertItem, item),
|
||||
Lang.bind(this, this._removeItem, item));
|
||||
|
||||
item.connect('destroy', function() {
|
||||
item._tracker.destroy();
|
||||
item._tracker = null;
|
||||
});
|
||||
},
|
||||
|
||||
_createMenuItem: function(model, index) {
|
||||
let labelValue = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_LABEL, null);
|
||||
let label = labelValue ? labelValue.deep_unpack() : '';
|
||||
// remove all underscores that are not followed by another underscore
|
||||
label = label.replace(/_([^_])/, '$1');
|
||||
|
||||
let submenuModel = model.get_item_link(index, Gio.MENU_LINK_SUBMENU);
|
||||
if (submenuModel) {
|
||||
let item = new PopupSubMenuMenuItem(label);
|
||||
this._trackMenu(submenuModel, item.menu);
|
||||
return item;
|
||||
}
|
||||
|
||||
let item = new PopupMenuItem(label);
|
||||
let action_id = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_ACTION, null).deep_unpack();
|
||||
item.actor.can_focus = item.actor.reactive = false;
|
||||
|
||||
item.action_id = action_id;
|
||||
|
||||
if (this.actionGroup.has_action(action_id)) {
|
||||
this._actionAdded(model, item, index);
|
||||
return item;
|
||||
}
|
||||
|
||||
let signalId = this.actionGroup.connect('action-added', Lang.bind(this, function(actionGroup, actionName) {
|
||||
actionGroup.disconnect(signalId);
|
||||
if (this._actions[actionName]) return;
|
||||
|
||||
this._actionAdded(model, item, index);
|
||||
}));
|
||||
|
||||
return item;
|
||||
},
|
||||
|
||||
_actionStateChanged: function(actionGroup, action_id) {
|
||||
let action = this._actions[action_id];
|
||||
if (!action)
|
||||
return;
|
||||
|
||||
action.state = actionGroup.get_action_state(action_id);
|
||||
if (action.items.length) {
|
||||
switch (String.fromCharCode(action.state.classify())) {
|
||||
case 'b':
|
||||
for (let i = 0; i < action.items.length; i++)
|
||||
action.items[i].setOrnament(action.state.get_boolean() ?
|
||||
Ornament.CHECK : Ornament.NONE);
|
||||
break;
|
||||
case 'd':
|
||||
for (let i = 0; i < action.items.length; i++)
|
||||
action.items[i].setValue(action.state.get_double());
|
||||
break;
|
||||
case 's':
|
||||
for (let i = 0; i < action.items.length; i++)
|
||||
action.items[i].setOrnament(action.items[i]._remoteTarget == action.state.deep_unpack() ?
|
||||
Ornament.DOT : Ornament.NONE);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_actionEnabledChanged: function(actionGroup, action_id) {
|
||||
let action = this._actions[action_id];
|
||||
if (!action)
|
||||
return;
|
||||
|
||||
action.enabled = actionGroup.get_action_enabled(action_id);
|
||||
if (action.items.length) {
|
||||
for (let i = 0; i < action.items.length; i++) {
|
||||
let item = action.items[i];
|
||||
item.actor.reactive = item.actor.can_focus = action.enabled;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_insertItem: function(position, model, item_index, action_namespace, is_separator, target) {
|
||||
let item;
|
||||
|
||||
if (is_separator)
|
||||
item = new PopupSeparatorMenuItem();
|
||||
else
|
||||
item = this._createMenuItem(model, item_index);
|
||||
|
||||
target.addMenuItem(item, position);
|
||||
},
|
||||
|
||||
_removeItem: function(position, target) {
|
||||
let items = target._getMenuItems();
|
||||
items[position].destroy();
|
||||
},
|
||||
});
|
||||
|
||||
/* Basic implementation of a menu manager.
|
||||
* Call addMenu to add menus
|
||||
*/
|
||||
|
210
js/ui/remoteMenu.js
Normal file
210
js/ui/remoteMenu.js
Normal file
@ -0,0 +1,210 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
const Shell = imports.gi.Shell;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
|
||||
/**
|
||||
* RemoteMenu:
|
||||
*
|
||||
* A PopupMenu that tracks a GMenuModel and shows its actions
|
||||
* (exposed by GApplication/GActionGroup)
|
||||
*/
|
||||
const RemoteMenu = new Lang.Class({
|
||||
Name: 'RemoteMenu',
|
||||
Extends: PopupMenu.PopupMenu,
|
||||
|
||||
_init: function(sourceActor, model, actionGroup) {
|
||||
this.parent(sourceActor, 0.0, St.Side.TOP);
|
||||
|
||||
this.model = model;
|
||||
this.actionGroup = actionGroup;
|
||||
|
||||
this._actions = {};
|
||||
this._trackMenu(model, this);
|
||||
|
||||
this._actionStateChangeId = this.actionGroup.connect('action-state-changed', Lang.bind(this, this._actionStateChanged));
|
||||
this._actionEnableChangeId = this.actionGroup.connect('action-enabled-changed', Lang.bind(this, this._actionEnabledChanged));
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
if (this._actionStateChangeId) {
|
||||
this.actionGroup.disconnect(this._actionStateChangeId);
|
||||
this._actionStateChangeId = 0;
|
||||
}
|
||||
|
||||
if (this._actionEnableChangeId) {
|
||||
this.actionGroup.disconnect(this._actionEnableChangeId);
|
||||
this._actionEnableChangeId = 0;
|
||||
}
|
||||
|
||||
this.parent();
|
||||
},
|
||||
|
||||
_actionAdded: function(model, item, index) {
|
||||
let action_id = item.action_id;
|
||||
|
||||
if (!this._actions[action_id])
|
||||
this._actions[action_id] = { enabled: this.actionGroup.get_action_enabled(action_id),
|
||||
state: this.actionGroup.get_action_state(action_id),
|
||||
items: [ ],
|
||||
};
|
||||
let action = this._actions[action_id];
|
||||
let target, destroyId, specificSignalId;
|
||||
|
||||
if (action.state) {
|
||||
// Docs have get_state_hint(), except that the DBus protocol
|
||||
// has no provision for it (so ShellApp does not implement it,
|
||||
// and neither GApplication), and g_action_get_state_hint()
|
||||
// always returns null
|
||||
// Funny :)
|
||||
|
||||
switch (String.fromCharCode(action.state.classify())) {
|
||||
case 'b':
|
||||
action.items.push(item);
|
||||
item.setOrnament(action.state.get_boolean() ?
|
||||
PopupMenu.Ornament.CHECK :
|
||||
PopupMenu.Ornament.NONE);
|
||||
specificSignalId = item.connect('activate', Lang.bind(this, function(item) {
|
||||
this.actionGroup.activate_action(action_id, null);
|
||||
}));
|
||||
break;
|
||||
case 's':
|
||||
action.items.push(item);
|
||||
item._remoteTarget = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_TARGET, null).deep_unpack();
|
||||
item.setOrnament(action.state.deep_unpack() == item._remoteTarget ?
|
||||
PopupMenu.Ornament.DOT :
|
||||
PopupMenu.Ornament.NONE);
|
||||
specificSignalId = item.connect('activate', Lang.bind(this, function(item) {
|
||||
this.actionGroup.activate_action(action_id, GLib.Variant.new_string(item._remoteTarget));
|
||||
}));
|
||||
break;
|
||||
default:
|
||||
log('Action "%s" has state of type %s, which is not supported'.format(action_id, action.state.get_type_string()));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
target = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_TARGET, null);
|
||||
action.items.push(item);
|
||||
specificSignalId = item.connect('activate', Lang.bind(this, function() {
|
||||
this.actionGroup.activate_action(action_id, target);
|
||||
}));
|
||||
}
|
||||
|
||||
item.actor.reactive = item.actor.can_focus = action.enabled;
|
||||
|
||||
destroyId = item.connect('destroy', Lang.bind(this, function() {
|
||||
item.disconnect(destroyId);
|
||||
item.disconnect(specificSignalId);
|
||||
|
||||
let pos = action.items.indexOf(item);
|
||||
if (pos != -1)
|
||||
action.items.splice(pos, 1);
|
||||
}));
|
||||
},
|
||||
|
||||
_trackMenu: function(model, item) {
|
||||
item._tracker = Shell.MenuTracker.new(model,
|
||||
null, /* action namespace */
|
||||
Lang.bind(this, this._insertItem, item),
|
||||
Lang.bind(this, this._removeItem, item));
|
||||
|
||||
item.connect('destroy', function() {
|
||||
item._tracker.destroy();
|
||||
item._tracker = null;
|
||||
});
|
||||
},
|
||||
|
||||
_createMenuItem: function(model, index) {
|
||||
let labelValue = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_LABEL, null);
|
||||
let label = labelValue ? labelValue.deep_unpack() : '';
|
||||
// remove all underscores that are not followed by another underscore
|
||||
label = label.replace(/_([^_])/, '$1');
|
||||
|
||||
let submenuModel = model.get_item_link(index, Gio.MENU_LINK_SUBMENU);
|
||||
if (submenuModel) {
|
||||
let item = new PopupMenu.PopupSubMenuMenuItem(label);
|
||||
this._trackMenu(submenuModel, item.menu);
|
||||
return item;
|
||||
}
|
||||
|
||||
let item = new PopupMenu.PopupMenuItem(label);
|
||||
let action_id = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_ACTION, null).deep_unpack();
|
||||
item.actor.can_focus = item.actor.reactive = false;
|
||||
|
||||
item.action_id = action_id;
|
||||
|
||||
if (this.actionGroup.has_action(action_id)) {
|
||||
this._actionAdded(model, item, index);
|
||||
return item;
|
||||
}
|
||||
|
||||
let signalId = this.actionGroup.connect('action-added', Lang.bind(this, function(actionGroup, actionName) {
|
||||
actionGroup.disconnect(signalId);
|
||||
if (this._actions[actionName]) return;
|
||||
|
||||
this._actionAdded(model, item, index);
|
||||
}));
|
||||
|
||||
return item;
|
||||
},
|
||||
|
||||
_actionStateChanged: function(actionGroup, action_id) {
|
||||
let action = this._actions[action_id];
|
||||
if (!action)
|
||||
return;
|
||||
|
||||
action.state = actionGroup.get_action_state(action_id);
|
||||
if (action.items.length) {
|
||||
switch (String.fromCharCode(action.state.classify())) {
|
||||
case 'b':
|
||||
for (let i = 0; i < action.items.length; i++)
|
||||
action.items[i].setOrnament(action.state.get_boolean() ?
|
||||
Ornament.CHECK : Ornament.NONE);
|
||||
break;
|
||||
case 'd':
|
||||
for (let i = 0; i < action.items.length; i++)
|
||||
action.items[i].setValue(action.state.get_double());
|
||||
break;
|
||||
case 's':
|
||||
for (let i = 0; i < action.items.length; i++)
|
||||
action.items[i].setOrnament(action.items[i]._remoteTarget == action.state.deep_unpack() ?
|
||||
Ornament.DOT : Ornament.NONE);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_actionEnabledChanged: function(actionGroup, action_id) {
|
||||
let action = this._actions[action_id];
|
||||
if (!action)
|
||||
return;
|
||||
|
||||
action.enabled = actionGroup.get_action_enabled(action_id);
|
||||
if (action.items.length) {
|
||||
for (let i = 0; i < action.items.length; i++) {
|
||||
let item = action.items[i];
|
||||
item.actor.reactive = item.actor.can_focus = action.enabled;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_insertItem: function(position, model, item_index, action_namespace, is_separator, target) {
|
||||
let item;
|
||||
|
||||
if (is_separator)
|
||||
item = new PopupMenu.PopupSeparatorMenuItem();
|
||||
else
|
||||
item = this._createMenuItem(model, item_index);
|
||||
|
||||
target.addMenuItem(item, position);
|
||||
},
|
||||
|
||||
_removeItem: function(position, target) {
|
||||
let items = target._getMenuItems();
|
||||
items[position].destroy();
|
||||
},
|
||||
});
|
Loading…
Reference in New Issue
Block a user