Compare commits
7 Commits
citadel
...
wip/fmuell
Author | SHA1 | Date | |
---|---|---|---|
|
ee3cd450a5 | ||
|
996369a22d | ||
|
ce6ab7e121 | ||
|
3da747ec2d | ||
|
593f15ad63 | ||
|
acdbd28262 | ||
|
ca70a4fc17 |
@ -104,13 +104,6 @@
|
||||
|
||||
<schema id="org.gnome.shell.keybindings" path="/org/gnome/shell/keybindings/"
|
||||
gettext-domain="@GETTEXT_PACKAGE@">
|
||||
<key name="open-application-menu" type="as">
|
||||
<default>["<Super>F10"]</default>
|
||||
<summary>Keybinding to open the application menu</summary>
|
||||
<description>
|
||||
Keybinding to open the application menu.
|
||||
</description>
|
||||
</key>
|
||||
<key name="toggle-application-view" type="as">
|
||||
<default>["<Super>a"]</default>
|
||||
<summary>Keybinding to open the “Show Applications” view</summary>
|
||||
|
@ -82,7 +82,6 @@
|
||||
<file>ui/panelMenu.js</file>
|
||||
<file>ui/pointerWatcher.js</file>
|
||||
<file>ui/popupMenu.js</file>
|
||||
<file>ui/remoteMenu.js</file>
|
||||
<file>ui/remoteSearch.js</file>
|
||||
<file>ui/runDialog.js</file>
|
||||
<file>ui/screenShield.js</file>
|
||||
|
340
js/ui/panel.js
340
js/ui/panel.js
@ -2,36 +2,25 @@
|
||||
|
||||
const Cairo = imports.cairo;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const GObject = imports.gi.GObject;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Pango = imports.gi.Pango;
|
||||
const Shell = imports.gi.Shell;
|
||||
const St = imports.gi.St;
|
||||
const Signals = imports.signals;
|
||||
const Atk = imports.gi.Atk;
|
||||
|
||||
const Animation = imports.ui.animation;
|
||||
const Config = imports.misc.config;
|
||||
const CtrlAltTab = imports.ui.ctrlAltTab;
|
||||
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;
|
||||
|
||||
var PANEL_ICON_SIZE = 16;
|
||||
var APP_MENU_ICON_MARGIN = 0;
|
||||
|
||||
var BUTTON_DND_ACTIVATION_TIMEOUT = 250;
|
||||
|
||||
var SPINNER_ANIMATION_TIME = 1.0;
|
||||
|
||||
// To make sure the panel corners blend nicely with the panel,
|
||||
// we draw background and borders the same way, e.g. drawing
|
||||
// them as filled shapes from the outside inwards instead of
|
||||
@ -75,328 +64,6 @@ function _unpremultiply(color) {
|
||||
blue: blue, alpha: color.alpha });
|
||||
};
|
||||
|
||||
/**
|
||||
* AppMenuButton:
|
||||
*
|
||||
* This class manages the "application menu" component. It tracks the
|
||||
* currently focused application. However, when an app is launched,
|
||||
* this menu also handles startup notification for it. So when we
|
||||
* have an active startup notification, we switch modes to display that.
|
||||
*/
|
||||
var AppMenuButton = GObject.registerClass({
|
||||
Signals: {'changed': {}},
|
||||
}, class AppMenuButton extends PanelMenu.Button {
|
||||
_init(panel) {
|
||||
super._init(0.0, null, true);
|
||||
|
||||
this.actor.accessible_role = Atk.Role.MENU;
|
||||
|
||||
this._startingApps = [];
|
||||
|
||||
this._menuManager = panel.menuManager;
|
||||
this._gtkSettings = Gtk.Settings.get_default();
|
||||
this._targetApp = null;
|
||||
this._appMenuNotifyId = 0;
|
||||
this._actionGroupNotifyId = 0;
|
||||
this._busyNotifyId = 0;
|
||||
|
||||
let bin = new St.Bin({ name: 'appMenu' });
|
||||
bin.connect('style-changed', this._onStyleChanged.bind(this));
|
||||
this.actor.add_actor(bin);
|
||||
|
||||
this.actor.bind_property("reactive", this.actor, "can-focus", 0);
|
||||
this.actor.reactive = false;
|
||||
|
||||
this._container = new St.BoxLayout({ style_class: 'panel-status-menu-box' });
|
||||
bin.set_child(this._container);
|
||||
|
||||
let textureCache = St.TextureCache.get_default();
|
||||
textureCache.connect('icon-theme-changed',
|
||||
this._onIconThemeChanged.bind(this));
|
||||
|
||||
this._iconBox = new St.Bin({ style_class: 'app-menu-icon' });
|
||||
this._container.add_actor(this._iconBox);
|
||||
|
||||
this._label = new St.Label({ y_expand: true,
|
||||
y_align: Clutter.ActorAlign.CENTER });
|
||||
this._container.add_actor(this._label);
|
||||
this._arrow = PopupMenu.arrowIcon(St.Side.BOTTOM);
|
||||
this._container.add_actor(this._arrow);
|
||||
|
||||
this._visible = this._gtkSettings.gtk_shell_shows_app_menu &&
|
||||
!Main.overview.visible;
|
||||
if (!this._visible)
|
||||
this.hide();
|
||||
this._overviewHidingId = Main.overview.connect('hiding', this._sync.bind(this));
|
||||
this._overviewShowingId = Main.overview.connect('showing', this._sync.bind(this));
|
||||
this._showsAppMenuId = this._gtkSettings.connect('notify::gtk-shell-shows-app-menu',
|
||||
this._sync.bind(this));
|
||||
|
||||
this._stop = true;
|
||||
|
||||
this._spinner = null;
|
||||
|
||||
let tracker = Shell.WindowTracker.get_default();
|
||||
let appSys = Shell.AppSystem.get_default();
|
||||
this._focusAppNotifyId =
|
||||
tracker.connect('notify::focus-app', this._focusAppChanged.bind(this));
|
||||
this._appStateChangedSignalId =
|
||||
appSys.connect('app-state-changed', this._onAppStateChanged.bind(this));
|
||||
this._switchWorkspaceNotifyId =
|
||||
global.window_manager.connect('switch-workspace', this._sync.bind(this));
|
||||
|
||||
this._sync();
|
||||
}
|
||||
|
||||
fadeIn() {
|
||||
if (this._visible)
|
||||
return;
|
||||
|
||||
this._visible = true;
|
||||
this.actor.reactive = true;
|
||||
this.show();
|
||||
Tweener.removeTweens(this.actor);
|
||||
Tweener.addTween(this.actor,
|
||||
{ opacity: 255,
|
||||
time: Overview.ANIMATION_TIME,
|
||||
transition: 'easeOutQuad' });
|
||||
}
|
||||
|
||||
fadeOut() {
|
||||
if (!this._visible)
|
||||
return;
|
||||
|
||||
this._visible = false;
|
||||
this.actor.reactive = false;
|
||||
Tweener.removeTweens(this.actor);
|
||||
Tweener.addTween(this.actor,
|
||||
{ opacity: 0,
|
||||
time: Overview.ANIMATION_TIME,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete() {
|
||||
this.hide();
|
||||
},
|
||||
onCompleteScope: this });
|
||||
}
|
||||
|
||||
_onStyleChanged(actor) {
|
||||
let node = actor.get_theme_node();
|
||||
let [success, icon] = node.lookup_url('spinner-image', false);
|
||||
if (!success || (this._spinnerIcon && this._spinnerIcon.equal(icon)))
|
||||
return;
|
||||
this._spinnerIcon = icon;
|
||||
this._spinner = new Animation.AnimatedIcon(this._spinnerIcon, PANEL_ICON_SIZE);
|
||||
this._container.add_actor(this._spinner.actor);
|
||||
this._spinner.actor.hide();
|
||||
}
|
||||
|
||||
_syncIcon() {
|
||||
if (!this._targetApp)
|
||||
return;
|
||||
|
||||
let icon = this._targetApp.create_icon_texture(PANEL_ICON_SIZE - APP_MENU_ICON_MARGIN);
|
||||
this._iconBox.set_child(icon);
|
||||
}
|
||||
|
||||
_onIconThemeChanged() {
|
||||
if (this._iconBox.child == null)
|
||||
return;
|
||||
|
||||
this._syncIcon();
|
||||
}
|
||||
|
||||
stopAnimation() {
|
||||
if (this._stop)
|
||||
return;
|
||||
|
||||
this._stop = true;
|
||||
|
||||
if (this._spinner == null)
|
||||
return;
|
||||
|
||||
Tweener.addTween(this._spinner.actor,
|
||||
{ opacity: 0,
|
||||
time: SPINNER_ANIMATION_TIME,
|
||||
transition: "easeOutQuad",
|
||||
onCompleteScope: this,
|
||||
onComplete() {
|
||||
this._spinner.stop();
|
||||
this._spinner.actor.opacity = 255;
|
||||
this._spinner.actor.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
startAnimation() {
|
||||
this._stop = false;
|
||||
|
||||
if (this._spinner == null)
|
||||
return;
|
||||
|
||||
this._spinner.play();
|
||||
this._spinner.actor.show();
|
||||
}
|
||||
|
||||
_onAppStateChanged(appSys, app) {
|
||||
let state = app.state;
|
||||
if (state != Shell.AppState.STARTING)
|
||||
this._startingApps = this._startingApps.filter(a => a != app);
|
||||
else if (state == Shell.AppState.STARTING)
|
||||
this._startingApps.push(app);
|
||||
// For now just resync on all running state changes; this is mainly to handle
|
||||
// cases where the focused window's application changes without the focus
|
||||
// changing. An example case is how we map OpenOffice.org based on the window
|
||||
// title which is a dynamic property.
|
||||
this._sync();
|
||||
}
|
||||
|
||||
_focusAppChanged() {
|
||||
let tracker = Shell.WindowTracker.get_default();
|
||||
let focusedApp = tracker.focus_app;
|
||||
if (!focusedApp) {
|
||||
// If the app has just lost focus to the panel, pretend
|
||||
// nothing happened; otherwise you can't keynav to the
|
||||
// app menu.
|
||||
if (global.stage.key_focus != null)
|
||||
return;
|
||||
}
|
||||
this._sync();
|
||||
}
|
||||
|
||||
_findTargetApp() {
|
||||
let workspaceManager = global.workspace_manager;
|
||||
let workspace = workspaceManager.get_active_workspace();
|
||||
let tracker = Shell.WindowTracker.get_default();
|
||||
let focusedApp = tracker.focus_app;
|
||||
if (focusedApp && focusedApp.is_on_workspace(workspace))
|
||||
return focusedApp;
|
||||
|
||||
for (let i = 0; i < this._startingApps.length; i++)
|
||||
if (this._startingApps[i].is_on_workspace(workspace))
|
||||
return this._startingApps[i];
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
_sync() {
|
||||
let targetApp = this._findTargetApp();
|
||||
|
||||
if (this._targetApp != targetApp) {
|
||||
if (this._appMenuNotifyId) {
|
||||
this._targetApp.disconnect(this._appMenuNotifyId);
|
||||
this._appMenuNotifyId = 0;
|
||||
}
|
||||
if (this._actionGroupNotifyId) {
|
||||
this._targetApp.disconnect(this._actionGroupNotifyId);
|
||||
this._actionGroupNotifyId = 0;
|
||||
}
|
||||
if (this._busyNotifyId) {
|
||||
this._targetApp.disconnect(this._busyNotifyId);
|
||||
this._busyNotifyId = 0;
|
||||
}
|
||||
|
||||
this._targetApp = targetApp;
|
||||
|
||||
if (this._targetApp) {
|
||||
this._appMenuNotifyId = this._targetApp.connect('notify::menu', this._sync.bind(this));
|
||||
this._actionGroupNotifyId = this._targetApp.connect('notify::action-group', this._sync.bind(this));
|
||||
this._busyNotifyId = this._targetApp.connect('notify::busy', this._sync.bind(this));
|
||||
this._label.set_text(this._targetApp.get_name());
|
||||
this.actor.set_accessible_name(this._targetApp.get_name());
|
||||
}
|
||||
}
|
||||
|
||||
let shellShowsAppMenu = this._gtkSettings.gtk_shell_shows_app_menu;
|
||||
Meta.prefs_set_show_fallback_app_menu(!shellShowsAppMenu);
|
||||
|
||||
let visible = (this._targetApp != null &&
|
||||
shellShowsAppMenu &&
|
||||
!Main.overview.visibleTarget);
|
||||
if (visible)
|
||||
this.fadeIn();
|
||||
else
|
||||
this.fadeOut();
|
||||
|
||||
let isBusy = (this._targetApp != null &&
|
||||
(this._targetApp.get_state() == Shell.AppState.STARTING ||
|
||||
this._targetApp.get_busy()));
|
||||
if (isBusy)
|
||||
this.startAnimation();
|
||||
else
|
||||
this.stopAnimation();
|
||||
|
||||
this.actor.reactive = (visible && !isBusy);
|
||||
|
||||
this._syncIcon();
|
||||
this._maybeSetMenu();
|
||||
this.emit('changed');
|
||||
}
|
||||
|
||||
_maybeSetMenu() {
|
||||
let menu;
|
||||
|
||||
if (this._targetApp == null) {
|
||||
menu = null;
|
||||
} else if (this._targetApp.action_group && this._targetApp.menu) {
|
||||
if (this.menu instanceof RemoteMenu.RemoteMenu &&
|
||||
this.menu.actionGroup == this._targetApp.action_group)
|
||||
return;
|
||||
|
||||
menu = new RemoteMenu.RemoteMenu(this.actor, this._targetApp.menu, this._targetApp.action_group);
|
||||
menu.connect('activate', () => {
|
||||
let win = this._targetApp.get_windows()[0];
|
||||
win.check_alive(global.get_current_time());
|
||||
});
|
||||
|
||||
} else {
|
||||
if (this.menu && this.menu.isDummyQuitMenu)
|
||||
return;
|
||||
|
||||
// fallback to older menu
|
||||
menu = new PopupMenu.PopupMenu(this.actor, 0.0, St.Side.TOP, 0);
|
||||
menu.isDummyQuitMenu = true;
|
||||
menu.addAction(_("Quit"), () => {
|
||||
this._targetApp.request_quit();
|
||||
});
|
||||
}
|
||||
|
||||
this.setMenu(menu);
|
||||
if (menu)
|
||||
this._menuManager.addMenu(menu);
|
||||
}
|
||||
|
||||
_onDestroy() {
|
||||
if (this._appStateChangedSignalId > 0) {
|
||||
let appSys = Shell.AppSystem.get_default();
|
||||
appSys.disconnect(this._appStateChangedSignalId);
|
||||
this._appStateChangedSignalId = 0;
|
||||
}
|
||||
if (this._focusAppNotifyId > 0) {
|
||||
let tracker = Shell.WindowTracker.get_default();
|
||||
tracker.disconnect(this._focusAppNotifyId);
|
||||
this._focusAppNotifyId = 0;
|
||||
}
|
||||
if (this._overviewHidingId > 0) {
|
||||
Main.overview.disconnect(this._overviewHidingId);
|
||||
this._overviewHidingId = 0;
|
||||
}
|
||||
if (this._overviewShowingId > 0) {
|
||||
Main.overview.disconnect(this._overviewShowingId);
|
||||
this._overviewShowingId = 0;
|
||||
}
|
||||
if (this._showsAppMenuId > 0) {
|
||||
this._gtkSettings.disconnect(this._showsAppMenuId);
|
||||
this._showsAppMenuId = 0;
|
||||
}
|
||||
if (this._switchWorkspaceNotifyId > 0) {
|
||||
global.window_manager.disconnect(this._switchWorkspaceNotifyId);
|
||||
this._switchWorkspaceNotifyId = 0;
|
||||
}
|
||||
|
||||
super._onDestroy();
|
||||
}
|
||||
});
|
||||
|
||||
var ActivitiesButton = GObject.registerClass(
|
||||
class ActivitiesButton extends PanelMenu.Button {
|
||||
_init() {
|
||||
@ -755,7 +422,6 @@ class AggregateMenu extends PanelMenu.Button {
|
||||
const PANEL_ITEM_IMPLEMENTATIONS = {
|
||||
'activities': ActivitiesButton,
|
||||
'aggregateMenu': AggregateMenu,
|
||||
'appMenu': AppMenuButton,
|
||||
'dateMenu': imports.ui.dateMenu.DateMenuButton,
|
||||
'a11y': imports.ui.status.accessibility.ATIndicator,
|
||||
'keyboard': imports.ui.status.keyboard.InputSourceIndicator,
|
||||
@ -774,6 +440,8 @@ class Panel extends St.Widget {
|
||||
|
||||
this._sessionStyle = null;
|
||||
|
||||
Meta.prefs_set_show_fallback_app_menu(true);
|
||||
|
||||
this.statusArea = {};
|
||||
|
||||
this.menuManager = new PopupMenu.PopupMenuManager(this);
|
||||
@ -992,10 +660,6 @@ class Panel extends St.Widget {
|
||||
menu.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
|
||||
}
|
||||
|
||||
toggleAppMenu() {
|
||||
this._toggleMenu(this.statusArea.appMenu);
|
||||
}
|
||||
|
||||
toggleCalendar() {
|
||||
this._toggleMenu(this.statusArea.dateMenu);
|
||||
}
|
||||
|
@ -1,199 +0,0 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Atk = imports.gi.Atk;
|
||||
const GLib = imports.gi.GLib;
|
||||
const GObject = imports.gi.GObject;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Shell = imports.gi.Shell;
|
||||
const ShellMenu = imports.gi.ShellMenu;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
|
||||
function stripMnemonics(label) {
|
||||
if (!label)
|
||||
return '';
|
||||
|
||||
// remove all underscores that are not followed by another underscore
|
||||
return label.replace(/_([^_])/, '$1');
|
||||
}
|
||||
|
||||
function _insertItem(menu, trackerItem, position) {
|
||||
let mapper;
|
||||
|
||||
if (trackerItem.get_is_separator())
|
||||
mapper = new RemoteMenuSeparatorItemMapper(trackerItem);
|
||||
else if (trackerItem.get_has_submenu())
|
||||
mapper = new RemoteMenuSubmenuItemMapper(trackerItem);
|
||||
else
|
||||
mapper = new RemoteMenuItemMapper(trackerItem);
|
||||
|
||||
let item = mapper.menuItem;
|
||||
menu.addMenuItem(item, position);
|
||||
}
|
||||
|
||||
function _removeItem(menu, position) {
|
||||
let items = menu._getMenuItems();
|
||||
items[position].destroy();
|
||||
}
|
||||
|
||||
var RemoteMenuSeparatorItemMapper = class {
|
||||
constructor(trackerItem) {
|
||||
this._trackerItem = trackerItem;
|
||||
this.menuItem = new PopupMenu.PopupSeparatorMenuItem();
|
||||
this._trackerItem.connect('notify::label', this._updateLabel.bind(this));
|
||||
this._updateLabel();
|
||||
|
||||
this.menuItem.connect('destroy', () => {
|
||||
trackerItem.run_dispose();
|
||||
});
|
||||
}
|
||||
|
||||
_updateLabel() {
|
||||
this.menuItem.label.text = stripMnemonics(this._trackerItem.label);
|
||||
}
|
||||
};
|
||||
|
||||
var RequestSubMenu = class extends PopupMenu.PopupSubMenuMenuItem {
|
||||
constructor() {
|
||||
super('');
|
||||
this._requestOpen = false;
|
||||
}
|
||||
|
||||
_setOpenState(open) {
|
||||
this.emit('request-open', open);
|
||||
this._requestOpen = open;
|
||||
}
|
||||
|
||||
_getOpenState() {
|
||||
return this._requestOpen;
|
||||
}
|
||||
};
|
||||
|
||||
var RemoteMenuSubmenuItemMapper = class {
|
||||
constructor(trackerItem) {
|
||||
this._trackerItem = trackerItem;
|
||||
this.menuItem = new RequestSubMenu();
|
||||
this._trackerItem.connect('notify::label', this._updateLabel.bind(this));
|
||||
this._updateLabel();
|
||||
|
||||
this._tracker = Shell.MenuTracker.new_for_item_submenu(this._trackerItem,
|
||||
_insertItem.bind(null, this.menuItem.menu),
|
||||
_removeItem.bind(null, this.menuItem.menu));
|
||||
|
||||
this.menuItem.connect('request-open', (menu, open) => {
|
||||
this._trackerItem.request_submenu_shown(open);
|
||||
});
|
||||
|
||||
this._trackerItem.connect('notify::submenu-shown', () => {
|
||||
this.menuItem.setSubmenuShown(this._trackerItem.get_submenu_shown());
|
||||
});
|
||||
|
||||
this.menuItem.connect('destroy', () => {
|
||||
trackerItem.run_dispose();
|
||||
});
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this._tracker.destroy();
|
||||
}
|
||||
|
||||
_updateLabel() {
|
||||
this.menuItem.label.text = stripMnemonics(this._trackerItem.label);
|
||||
}
|
||||
};
|
||||
|
||||
var RemoteMenuItemMapper = class {
|
||||
constructor(trackerItem) {
|
||||
this._trackerItem = trackerItem;
|
||||
|
||||
this.menuItem = new PopupMenu.PopupBaseMenuItem();
|
||||
this._icon = new St.Icon({ style_class: 'popup-menu-icon' });
|
||||
this.menuItem.actor.add_child(this._icon);
|
||||
|
||||
this._label = new St.Label();
|
||||
this.menuItem.actor.add_child(this._label);
|
||||
this.menuItem.actor.label_actor = this._label;
|
||||
|
||||
this.menuItem.connect('activate', () => {
|
||||
this._trackerItem.activated();
|
||||
});
|
||||
|
||||
this._trackerItem.bind_property('visible', this.menuItem.actor, 'visible', GObject.BindingFlags.SYNC_CREATE);
|
||||
|
||||
this._trackerItem.connect('notify::icon', this._updateIcon.bind(this));
|
||||
this._trackerItem.connect('notify::label', this._updateLabel.bind(this));
|
||||
this._trackerItem.connect('notify::sensitive', this._updateSensitivity.bind(this));
|
||||
this._trackerItem.connect('notify::role', this._updateRole.bind(this));
|
||||
this._trackerItem.connect('notify::toggled', this._updateDecoration.bind(this));
|
||||
|
||||
this._updateIcon();
|
||||
this._updateLabel();
|
||||
this._updateSensitivity();
|
||||
this._updateRole();
|
||||
|
||||
this.menuItem.connect('destroy', () => {
|
||||
trackerItem.run_dispose();
|
||||
});
|
||||
}
|
||||
|
||||
_updateIcon() {
|
||||
this._icon.gicon = this._trackerItem.icon;
|
||||
this._icon.visible = (this._icon.gicon != null);
|
||||
}
|
||||
|
||||
_updateLabel() {
|
||||
this._label.text = stripMnemonics(this._trackerItem.label);
|
||||
}
|
||||
|
||||
_updateSensitivity() {
|
||||
this.menuItem.setSensitive(this._trackerItem.sensitive);
|
||||
}
|
||||
|
||||
_updateDecoration() {
|
||||
let ornamentForRole = {};
|
||||
ornamentForRole[ShellMenu.MenuTrackerItemRole.RADIO] = PopupMenu.Ornament.DOT;
|
||||
ornamentForRole[ShellMenu.MenuTrackerItemRole.CHECK] = PopupMenu.Ornament.CHECK;
|
||||
|
||||
let ornament = PopupMenu.Ornament.NONE;
|
||||
if (this._trackerItem.toggled)
|
||||
ornament = ornamentForRole[this._trackerItem.role];
|
||||
|
||||
this.menuItem.setOrnament(ornament);
|
||||
}
|
||||
|
||||
_updateRole() {
|
||||
let a11yRoles = {};
|
||||
a11yRoles[ShellMenu.MenuTrackerItemRole.NORMAL] = Atk.Role.MENU_ITEM;
|
||||
a11yRoles[ShellMenu.MenuTrackerItemRole.RADIO] = Atk.Role.RADIO_MENU_ITEM;
|
||||
a11yRoles[ShellMenu.MenuTrackerItemRole.CHECK] = Atk.Role.CHECK_MENU_ITEM;
|
||||
|
||||
let a11yRole = a11yRoles[this._trackerItem.role];
|
||||
this.menuItem.actor.accessible_role = a11yRole;
|
||||
|
||||
this._updateDecoration();
|
||||
}
|
||||
};
|
||||
|
||||
var RemoteMenu = class extends PopupMenu.PopupMenu {
|
||||
constructor(sourceActor, model, actionGroup) {
|
||||
super(sourceActor, 0.0, St.Side.TOP);
|
||||
|
||||
this._model = model;
|
||||
this._actionGroup = actionGroup;
|
||||
this._tracker = Shell.MenuTracker.new(this._actionGroup,
|
||||
this._model,
|
||||
null, /* action namespace */
|
||||
_insertItem.bind(null, this),
|
||||
_removeItem.bind(null, this));
|
||||
}
|
||||
|
||||
get actionGroup() {
|
||||
return this._actionGroup;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this._tracker.destroy();
|
||||
super.destroy();
|
||||
}
|
||||
};
|
@ -98,7 +98,7 @@ const _modes = {
|
||||
'keyring', 'autorunManager', 'automountManager'],
|
||||
|
||||
panel: {
|
||||
left: ['activities', 'appMenu'],
|
||||
left: ['activities'],
|
||||
center: ['dateMenu'],
|
||||
right: ['a11y', 'keyboard', 'aggregateMenu']
|
||||
}
|
||||
|
@ -923,13 +923,6 @@ var WindowManager = class {
|
||||
Shell.ActionMode.ALL,
|
||||
this._toggleTweens.bind(this));
|
||||
|
||||
this.addKeybinding('open-application-menu',
|
||||
new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
|
||||
Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
|
||||
Shell.ActionMode.NORMAL |
|
||||
Shell.ActionMode.POPUP,
|
||||
this._toggleAppMenu.bind(this));
|
||||
|
||||
this.addKeybinding('toggle-message-tray',
|
||||
new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
|
||||
Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
|
||||
@ -2012,10 +2005,6 @@ var WindowManager = class {
|
||||
Main.ctrlAltTabManager.popup(binding.is_reversed(), binding.get_name(), binding.get_mask());
|
||||
}
|
||||
|
||||
_toggleAppMenu(display, window, event, binding) {
|
||||
Main.panel.toggleAppMenu();
|
||||
}
|
||||
|
||||
_toggleCalendar(display, window, event, binding) {
|
||||
Main.panel.toggleCalendar();
|
||||
}
|
||||
|
@ -3,12 +3,10 @@
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Meta = imports.gi.Meta;
|
||||
const St = imports.gi.St;
|
||||
const Shell = imports.gi.Shell;
|
||||
|
||||
const BoxPointer = imports.ui.boxpointer;
|
||||
const Main = imports.ui.main;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
const RemoteMenu = imports.ui.remoteMenu;
|
||||
|
||||
var WindowMenu = class extends PopupMenu.PopupMenu {
|
||||
constructor(window, sourceActor) {
|
||||
@ -175,22 +173,6 @@ var WindowMenu = class extends PopupMenu.PopupMenu {
|
||||
}
|
||||
};
|
||||
|
||||
var AppMenu = class extends RemoteMenu.RemoteMenu {
|
||||
constructor(window, sourceActor) {
|
||||
let app = Shell.WindowTracker.get_default().get_window_app(window);
|
||||
|
||||
super(sourceActor, app.menu, app.action_group);
|
||||
|
||||
this.actor.add_style_class_name('fallback-app-menu');
|
||||
let variant = window.get_gtk_theme_variant();
|
||||
if (variant)
|
||||
this.actor.add_style_class_name(variant);
|
||||
|
||||
Main.layoutManager.uiGroup.add_actor(this.actor);
|
||||
this.actor.hide();
|
||||
}
|
||||
};
|
||||
|
||||
var WindowMenuManager = class {
|
||||
constructor() {
|
||||
this._manager = new PopupMenu.PopupMenuManager({ actor: Main.layoutManager.dummyCursor });
|
||||
@ -203,8 +185,9 @@ var WindowMenuManager = class {
|
||||
}
|
||||
|
||||
showWindowMenuForWindow(window, type, rect) {
|
||||
let menuType = (type == Meta.WindowMenuType.WM) ? WindowMenu : AppMenu;
|
||||
let menu = new menuType(window, this._sourceActor);
|
||||
if (type != Meta.WindowMenuType.WM)
|
||||
throw new Error('Unsupported window menu type');
|
||||
let menu = new WindowMenu(window, this._sourceActor);
|
||||
|
||||
this._manager.addMenu(menu);
|
||||
|
||||
|
@ -1,596 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2013 Canonical Limited
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the licence, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Ryan Lortie <desrt@desrt.ca>
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "gtkmenutracker.h"
|
||||
|
||||
/**
|
||||
* SECTION:gtkmenutracker
|
||||
* @Title: GtkMenuTracker
|
||||
* @Short_description: A helper class for interpreting #GMenuModel
|
||||
*
|
||||
* #GtkMenuTracker is a simple object to ease implementations of #GMenuModel.
|
||||
* Given a #GtkActionObservable (usually a #GActionMuxer) along with a
|
||||
* #GMenuModel, it will tell you which menu items to create and where to place
|
||||
* them. If a menu item is removed, it will tell you the position of the menu
|
||||
* item to remove.
|
||||
*
|
||||
* Using #GtkMenuTracker is fairly simple. The only guarantee you must make
|
||||
* to #GtkMenuTracker is that you must obey all insert signals and track the
|
||||
* position of items that #GtkMenuTracker gives you. That is, #GtkMenuTracker
|
||||
* expects positions of all the latter items to change when it calls your
|
||||
* insertion callback with an early position, as it may ask you to remove
|
||||
* an item with a readjusted position later.
|
||||
*
|
||||
* #GtkMenuTracker will give you a #GtkMenuTrackerItem in your callback. You
|
||||
* must hold onto this object until a remove signal is emitted. This item
|
||||
* represents a single menu item, which can be one of three classes: normal item,
|
||||
* separator, or submenu.
|
||||
*
|
||||
* Certain properties on the #GtkMenuTrackerItem are mutable, and you must
|
||||
* listen for changes in the item. For more details, see the documentation
|
||||
* for #GtkMenuTrackerItem along with https://live.gnome.org/GApplication/GMenuModel.
|
||||
*
|
||||
* The idea of @with_separators is for special cases where menu models may
|
||||
* be tracked in places where separators are not available, like in toplevel
|
||||
* "File", "Edit" menu bars. Ignoring separator items is wrong, as #GtkMenuTracker
|
||||
* expects the position to change, so we must tell #GtkMenuTracker to ignore
|
||||
* separators itself.
|
||||
*/
|
||||
|
||||
typedef struct _GtkMenuTrackerSection GtkMenuTrackerSection;
|
||||
|
||||
struct _GtkMenuTracker
|
||||
{
|
||||
GtkActionObservable *observable;
|
||||
GtkMenuTrackerInsertFunc insert_func;
|
||||
GtkMenuTrackerRemoveFunc remove_func;
|
||||
gpointer user_data;
|
||||
|
||||
GtkMenuTrackerSection *toplevel;
|
||||
};
|
||||
|
||||
struct _GtkMenuTrackerSection
|
||||
{
|
||||
gpointer model; /* may be a GtkMenuTrackerItem or a GMenuModel */
|
||||
GSList *items;
|
||||
gchar *action_namespace;
|
||||
|
||||
guint separator_label : 1;
|
||||
guint with_separators : 1;
|
||||
guint has_separator : 1;
|
||||
guint is_fake : 1;
|
||||
|
||||
gulong handler;
|
||||
};
|
||||
|
||||
static GtkMenuTrackerSection * gtk_menu_tracker_section_new (GtkMenuTracker *tracker,
|
||||
GMenuModel *model,
|
||||
gboolean with_separators,
|
||||
gboolean separator_label,
|
||||
gint offset,
|
||||
const gchar *action_namespace);
|
||||
static void gtk_menu_tracker_section_free (GtkMenuTrackerSection *section);
|
||||
|
||||
static GtkMenuTrackerSection *
|
||||
gtk_menu_tracker_section_find_model (GtkMenuTrackerSection *section,
|
||||
gpointer model,
|
||||
gint *offset)
|
||||
{
|
||||
GSList *item;
|
||||
|
||||
if (section->has_separator)
|
||||
(*offset)++;
|
||||
|
||||
if (section->model == model)
|
||||
return section;
|
||||
|
||||
for (item = section->items; item; item = item->next)
|
||||
{
|
||||
GtkMenuTrackerSection *subsection = item->data;
|
||||
|
||||
if (subsection)
|
||||
{
|
||||
GtkMenuTrackerSection *found_section;
|
||||
|
||||
found_section = gtk_menu_tracker_section_find_model (subsection, model, offset);
|
||||
|
||||
if (found_section)
|
||||
return found_section;
|
||||
}
|
||||
else
|
||||
(*offset)++;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* this is responsible for syncing the showing of a separator for a
|
||||
* single subsection (and its children).
|
||||
*
|
||||
* we only ever show separators if we have _actual_ children (ie: we do
|
||||
* not show a separator if the section contains only empty child
|
||||
* sections). it's difficult to determine this on-the-fly, so we have
|
||||
* this separate function to come back later and figure it out.
|
||||
*
|
||||
* 'section' is that section.
|
||||
*
|
||||
* 'tracker' is passed in so that we can emit callbacks when we decide
|
||||
* to add/remove separators.
|
||||
*
|
||||
* 'offset' is passed in so we know which position to emit in our
|
||||
* callbacks. ie: if we add a separator right at the top of this
|
||||
* section then we would emit it with this offset. deeper inside, we
|
||||
* adjust accordingly.
|
||||
*
|
||||
* could_have_separator is true in two situations:
|
||||
*
|
||||
* - our parent section had with_separators defined and there are items
|
||||
* before us (ie: we should add a separator if we have content in
|
||||
* order to divide us from the items above)
|
||||
*
|
||||
* - if we had a 'label' attribute set for this section
|
||||
*
|
||||
* parent_model and parent_index are passed in so that we can give them
|
||||
* to the insertion callback so that it can see the label (and anything
|
||||
* else that happens to be defined on the section).
|
||||
*
|
||||
* we iterate each item in ourselves. for subsections, we recursively
|
||||
* run ourselves to sync separators. after we are done, we notice if we
|
||||
* have any items in us or if we are completely empty and sync if our
|
||||
* separator is shown or not.
|
||||
*/
|
||||
static gint
|
||||
gtk_menu_tracker_section_sync_separators (GtkMenuTrackerSection *section,
|
||||
GtkMenuTracker *tracker,
|
||||
gint offset,
|
||||
gboolean could_have_separator,
|
||||
GMenuModel *parent_model,
|
||||
gint parent_index)
|
||||
{
|
||||
gboolean should_have_separator;
|
||||
gint n_items = 0;
|
||||
GSList *item;
|
||||
gint i = 0;
|
||||
|
||||
for (item = section->items; item; item = item->next)
|
||||
{
|
||||
GtkMenuTrackerSection *subsection = item->data;
|
||||
|
||||
if (subsection)
|
||||
{
|
||||
gboolean section_could_have_separator;
|
||||
|
||||
section_could_have_separator = (section->with_separators && n_items > 0) || subsection->separator_label;
|
||||
|
||||
/* Only pass the parent_model and parent_index in case they may be used to create the separator. */
|
||||
n_items += gtk_menu_tracker_section_sync_separators (subsection, tracker, offset + n_items,
|
||||
section_could_have_separator,
|
||||
section_could_have_separator ? section->model : NULL,
|
||||
section_could_have_separator ? i : 0);
|
||||
}
|
||||
else
|
||||
n_items++;
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
should_have_separator = !section->is_fake && could_have_separator && n_items != 0;
|
||||
|
||||
if (should_have_separator > section->has_separator)
|
||||
{
|
||||
/* Add a separator */
|
||||
GtkMenuTrackerItem *menuitem;
|
||||
|
||||
menuitem = _gtk_menu_tracker_item_new (tracker->observable, parent_model, parent_index, NULL, TRUE);
|
||||
(* tracker->insert_func) (menuitem, offset, tracker->user_data);
|
||||
g_object_unref (menuitem);
|
||||
|
||||
section->has_separator = TRUE;
|
||||
}
|
||||
else if (should_have_separator < section->has_separator)
|
||||
{
|
||||
/* Remove a separator */
|
||||
(* tracker->remove_func) (offset, tracker->user_data);
|
||||
section->has_separator = FALSE;
|
||||
}
|
||||
|
||||
n_items += section->has_separator;
|
||||
|
||||
return n_items;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_item_visibility_changed (GtkMenuTrackerItem *item,
|
||||
gboolean is_now_visible,
|
||||
gpointer user_data)
|
||||
{
|
||||
GtkMenuTracker *tracker = user_data;
|
||||
GtkMenuTrackerSection *section;
|
||||
gboolean was_visible;
|
||||
gint offset = 0;
|
||||
|
||||
/* remember: the item is our model */
|
||||
section = gtk_menu_tracker_section_find_model (tracker->toplevel, item, &offset);
|
||||
|
||||
was_visible = section->items != NULL;
|
||||
|
||||
if (is_now_visible == was_visible)
|
||||
return;
|
||||
|
||||
if (is_now_visible)
|
||||
{
|
||||
section->items = g_slist_prepend (NULL, NULL);
|
||||
(* tracker->insert_func) (section->model, offset, tracker->user_data);
|
||||
}
|
||||
else
|
||||
{
|
||||
section->items = g_slist_delete_link (section->items, section->items);
|
||||
(* tracker->remove_func) (offset, tracker->user_data);
|
||||
}
|
||||
|
||||
gtk_menu_tracker_section_sync_separators (tracker->toplevel, tracker, 0, FALSE, NULL, 0);
|
||||
}
|
||||
|
||||
static gint
|
||||
gtk_menu_tracker_section_measure (GtkMenuTrackerSection *section)
|
||||
{
|
||||
GSList *item;
|
||||
gint n_items;
|
||||
|
||||
if (section == NULL)
|
||||
return 1;
|
||||
|
||||
n_items = 0;
|
||||
|
||||
if (section->has_separator)
|
||||
n_items++;
|
||||
|
||||
for (item = section->items; item; item = item->next)
|
||||
n_items += gtk_menu_tracker_section_measure (item->data);
|
||||
|
||||
return n_items;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_remove_items (GtkMenuTracker *tracker,
|
||||
GSList **change_point,
|
||||
gint offset,
|
||||
gint n_items)
|
||||
{
|
||||
gint i;
|
||||
|
||||
for (i = 0; i < n_items; i++)
|
||||
{
|
||||
GtkMenuTrackerSection *subsection;
|
||||
gint n;
|
||||
|
||||
subsection = (*change_point)->data;
|
||||
*change_point = g_slist_delete_link (*change_point, *change_point);
|
||||
|
||||
n = gtk_menu_tracker_section_measure (subsection);
|
||||
gtk_menu_tracker_section_free (subsection);
|
||||
|
||||
while (n--)
|
||||
(* tracker->remove_func) (offset, tracker->user_data);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_add_items (GtkMenuTracker *tracker,
|
||||
GtkMenuTrackerSection *section,
|
||||
GSList **change_point,
|
||||
gint offset,
|
||||
GMenuModel *model,
|
||||
gint position,
|
||||
gint n_items)
|
||||
{
|
||||
while (n_items--)
|
||||
{
|
||||
GMenuModel *submenu;
|
||||
|
||||
submenu = g_menu_model_get_item_link (model, position + n_items, G_MENU_LINK_SECTION);
|
||||
g_assert (submenu != model);
|
||||
if (submenu != NULL)
|
||||
{
|
||||
GtkMenuTrackerSection *subsection;
|
||||
gchar *action_namespace = NULL;
|
||||
gboolean has_label;
|
||||
|
||||
has_label = g_menu_model_get_item_attribute (model, position + n_items,
|
||||
G_MENU_ATTRIBUTE_LABEL, "s", NULL);
|
||||
|
||||
g_menu_model_get_item_attribute (model, position + n_items,
|
||||
G_MENU_ATTRIBUTE_ACTION_NAMESPACE, "s", &action_namespace);
|
||||
|
||||
if (section->action_namespace)
|
||||
{
|
||||
gchar *namespace;
|
||||
|
||||
namespace = g_strjoin (".", section->action_namespace, action_namespace, NULL);
|
||||
subsection = gtk_menu_tracker_section_new (tracker, submenu, FALSE, has_label, offset, namespace);
|
||||
g_free (namespace);
|
||||
}
|
||||
else
|
||||
subsection = gtk_menu_tracker_section_new (tracker, submenu, FALSE, has_label, offset, action_namespace);
|
||||
|
||||
*change_point = g_slist_prepend (*change_point, subsection);
|
||||
g_free (action_namespace);
|
||||
g_object_unref (submenu);
|
||||
}
|
||||
else
|
||||
{
|
||||
GtkMenuTrackerItem *item;
|
||||
|
||||
item = _gtk_menu_tracker_item_new (tracker->observable, model, position + n_items,
|
||||
section->action_namespace, FALSE);
|
||||
|
||||
/* In the case that the item may disappear we handle that by
|
||||
* treating the item that we just created as being its own
|
||||
* subsection. This happens as so:
|
||||
*
|
||||
* - the subsection is created without the possibility of
|
||||
* showing a separator
|
||||
*
|
||||
* - the subsection will have either 0 or 1 item in it at all
|
||||
* times: either the shown item or not (in the case it is
|
||||
* hidden)
|
||||
*
|
||||
* - the created item acts as the "model" for this section
|
||||
* and we use its "visiblity-changed" signal in the same
|
||||
* way that we use the "items-changed" signal from a real
|
||||
* GMenuModel
|
||||
*
|
||||
* We almost never use the '->model' stored in the section for
|
||||
* anything other than lookups and for dropped the ref and
|
||||
* disconnecting the signal when we destroy the menu, and we
|
||||
* need to do exactly those things in this case as well.
|
||||
*
|
||||
* The only other thing that '->model' is used for is in the
|
||||
* case that we want to show a separator, but we will never do
|
||||
* that because separators are not shown for this fake section.
|
||||
*/
|
||||
if (_gtk_menu_tracker_item_may_disappear (item))
|
||||
{
|
||||
GtkMenuTrackerSection *fake_section;
|
||||
|
||||
fake_section = g_slice_new0 (GtkMenuTrackerSection);
|
||||
fake_section->is_fake = TRUE;
|
||||
fake_section->model = g_object_ref (item);
|
||||
fake_section->handler = g_signal_connect (item, "visibility-changed",
|
||||
G_CALLBACK (gtk_menu_tracker_item_visibility_changed),
|
||||
tracker);
|
||||
*change_point = g_slist_prepend (*change_point, fake_section);
|
||||
|
||||
if (_gtk_menu_tracker_item_is_visible (item))
|
||||
{
|
||||
(* tracker->insert_func) (item, offset, tracker->user_data);
|
||||
fake_section->items = g_slist_prepend (NULL, NULL);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* In the normal case, we store NULL in the linked list.
|
||||
* The measurement and lookup code count NULL always as
|
||||
* exactly 1: an item that will always be there.
|
||||
*/
|
||||
(* tracker->insert_func) (item, offset, tracker->user_data);
|
||||
*change_point = g_slist_prepend (*change_point, NULL);
|
||||
}
|
||||
|
||||
g_object_unref (item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_model_changed (GMenuModel *model,
|
||||
gint position,
|
||||
gint removed,
|
||||
gint added,
|
||||
gpointer user_data)
|
||||
{
|
||||
GtkMenuTracker *tracker = user_data;
|
||||
GtkMenuTrackerSection *section;
|
||||
GSList **change_point;
|
||||
gint offset = 0;
|
||||
gint i;
|
||||
|
||||
/* First find which section the changed model corresponds to, and the
|
||||
* position of that section within the overall menu.
|
||||
*/
|
||||
section = gtk_menu_tracker_section_find_model (tracker->toplevel, model, &offset);
|
||||
|
||||
/* Next, seek through that section to the change point. This gives us
|
||||
* the correct GSList** to make the change to and also finds the final
|
||||
* offset at which we will make the changes (by measuring the number
|
||||
* of items within each item of the section before the change point).
|
||||
*/
|
||||
change_point = §ion->items;
|
||||
for (i = 0; i < position; i++)
|
||||
{
|
||||
offset += gtk_menu_tracker_section_measure ((*change_point)->data);
|
||||
change_point = &(*change_point)->next;
|
||||
}
|
||||
|
||||
/* We remove items in order and add items in reverse order. This
|
||||
* means that the offset used for all inserts and removes caused by a
|
||||
* single change will be the same.
|
||||
*
|
||||
* This also has a performance advantage: GtkMenuShell stores the
|
||||
* menu items in a linked list. In the case where we are creating a
|
||||
* menu for the first time, adding the items in reverse order means
|
||||
* that we only ever insert at index zero, prepending the list. This
|
||||
* means that we can populate in O(n) time instead of O(n^2) that we
|
||||
* would do by appending.
|
||||
*/
|
||||
gtk_menu_tracker_remove_items (tracker, change_point, offset, removed);
|
||||
gtk_menu_tracker_add_items (tracker, section, change_point, offset, model, position, added);
|
||||
|
||||
/* The offsets for insertion/removal of separators will be all over
|
||||
* the place, however...
|
||||
*/
|
||||
gtk_menu_tracker_section_sync_separators (tracker->toplevel, tracker, 0, FALSE, NULL, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_section_free (GtkMenuTrackerSection *section)
|
||||
{
|
||||
if (section == NULL)
|
||||
return;
|
||||
|
||||
g_signal_handler_disconnect (section->model, section->handler);
|
||||
g_slist_free_full (section->items, (GDestroyNotify) gtk_menu_tracker_section_free);
|
||||
g_free (section->action_namespace);
|
||||
g_object_unref (section->model);
|
||||
g_slice_free (GtkMenuTrackerSection, section);
|
||||
}
|
||||
|
||||
static GtkMenuTrackerSection *
|
||||
gtk_menu_tracker_section_new (GtkMenuTracker *tracker,
|
||||
GMenuModel *model,
|
||||
gboolean with_separators,
|
||||
gboolean separator_label,
|
||||
gint offset,
|
||||
const gchar *action_namespace)
|
||||
{
|
||||
GtkMenuTrackerSection *section;
|
||||
|
||||
section = g_slice_new0 (GtkMenuTrackerSection);
|
||||
section->model = g_object_ref (model);
|
||||
section->with_separators = with_separators;
|
||||
section->action_namespace = g_strdup (action_namespace);
|
||||
section->separator_label = separator_label;
|
||||
|
||||
gtk_menu_tracker_add_items (tracker, section, §ion->items, offset, model, 0, g_menu_model_get_n_items (model));
|
||||
section->handler = g_signal_connect (model, "items-changed", G_CALLBACK (gtk_menu_tracker_model_changed), tracker);
|
||||
|
||||
return section;
|
||||
}
|
||||
|
||||
/*< private >
|
||||
* gtk_menu_tracker_new:
|
||||
* @model: the model to flatten
|
||||
* @with_separators: if the toplevel should have separators (ie: TRUE
|
||||
* for menus, FALSE for menubars)
|
||||
* @action_namespace: the passed-in action namespace
|
||||
* @insert_func: insert callback
|
||||
* @remove_func: remove callback
|
||||
* @user_data user data for callbacks
|
||||
*
|
||||
* Creates a GtkMenuTracker for @model, holding a ref on @model for as
|
||||
* long as the tracker is alive.
|
||||
*
|
||||
* This flattens out the model, merging sections and inserting
|
||||
* separators where appropriate. It monitors for changes and performs
|
||||
* updates on the fly. It also handles action_namespace for subsections
|
||||
* (but you will need to handle it yourself for submenus).
|
||||
*
|
||||
* When the tracker is first created, @insert_func will be called many
|
||||
* times to populate the menu with the initial contents of @model
|
||||
* (unless it is empty), before gtk_menu_tracker_new() returns. For
|
||||
* this reason, the menu that is using the tracker ought to be empty
|
||||
* when it creates the tracker.
|
||||
*
|
||||
* Future changes to @model will result in more calls to @insert_func
|
||||
* and @remove_func.
|
||||
*
|
||||
* The position argument to both functions is the linear 0-based
|
||||
* position in the menu at which the item in question should be inserted
|
||||
* or removed.
|
||||
*
|
||||
* For @insert_func, @model and @item_index are used to get the
|
||||
* information about the menu item to insert. @action_namespace is the
|
||||
* action namespace that actions referred to from that item should place
|
||||
* themselves in. Note that if the item is a submenu and the
|
||||
* "action-namespace" attribute is defined on the item, it will _not_ be
|
||||
* applied to the @action_namespace argument as it is meant for the
|
||||
* items inside of the submenu, not the submenu item itself.
|
||||
*
|
||||
* @is_separator is set to %TRUE in case the item being added is a
|
||||
* separator. @model and @item_index will still be meaningfully set in
|
||||
* this case -- to the section menu item corresponding to the separator.
|
||||
* This is useful if the section specifies a label, for example. If
|
||||
* there is an "action-namespace" attribute on this menu item then it
|
||||
* should be ignored by the consumer because #GtkMenuTracker has already
|
||||
* handled it.
|
||||
*
|
||||
* When using #GtkMenuTracker there is no need to hold onto @model or
|
||||
* monitor it for changes. The model will be unreffed when
|
||||
* gtk_menu_tracker_free() is called.
|
||||
*/
|
||||
GtkMenuTracker *
|
||||
gtk_menu_tracker_new (GtkActionObservable *observable,
|
||||
GMenuModel *model,
|
||||
gboolean with_separators,
|
||||
const gchar *action_namespace,
|
||||
GtkMenuTrackerInsertFunc insert_func,
|
||||
GtkMenuTrackerRemoveFunc remove_func,
|
||||
gpointer user_data)
|
||||
{
|
||||
GtkMenuTracker *tracker;
|
||||
|
||||
tracker = g_slice_new (GtkMenuTracker);
|
||||
tracker->observable = g_object_ref (observable);
|
||||
tracker->insert_func = insert_func;
|
||||
tracker->remove_func = remove_func;
|
||||
tracker->user_data = user_data;
|
||||
|
||||
tracker->toplevel = gtk_menu_tracker_section_new (tracker, model, with_separators, FALSE, 0, action_namespace);
|
||||
gtk_menu_tracker_section_sync_separators (tracker->toplevel, tracker, 0, FALSE, NULL, 0);
|
||||
|
||||
return tracker;
|
||||
}
|
||||
|
||||
GtkMenuTracker *
|
||||
gtk_menu_tracker_new_for_item_submenu (GtkMenuTrackerItem *item,
|
||||
GtkMenuTrackerInsertFunc insert_func,
|
||||
GtkMenuTrackerRemoveFunc remove_func,
|
||||
gpointer user_data)
|
||||
{
|
||||
GtkMenuTracker *tracker;
|
||||
GMenuModel *submenu;
|
||||
gchar *namespace;
|
||||
|
||||
submenu = _gtk_menu_tracker_item_get_submenu (item);
|
||||
namespace = _gtk_menu_tracker_item_get_submenu_namespace (item);
|
||||
|
||||
tracker = gtk_menu_tracker_new (_gtk_menu_tracker_item_get_observable (item), submenu,
|
||||
TRUE, namespace, insert_func, remove_func, user_data);
|
||||
|
||||
g_object_unref (submenu);
|
||||
g_free (namespace);
|
||||
|
||||
return tracker;
|
||||
}
|
||||
|
||||
/*< private >
|
||||
* gtk_menu_tracker_free:
|
||||
* @tracker: a #GtkMenuTracker
|
||||
*
|
||||
* Frees the tracker, ...
|
||||
*/
|
||||
void
|
||||
gtk_menu_tracker_free (GtkMenuTracker *tracker)
|
||||
{
|
||||
gtk_menu_tracker_section_free (tracker->toplevel);
|
||||
g_object_unref (tracker->observable);
|
||||
g_slice_free (GtkMenuTracker, tracker);
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2013 Canonical Limited
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the licence, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Ryan Lortie <desrt@desrt.ca>
|
||||
*/
|
||||
|
||||
#ifndef __GTK_MENU_TRACKER_H__
|
||||
#define __GTK_MENU_TRACKER_H__
|
||||
|
||||
#include "gtkmenutrackeritem.h"
|
||||
|
||||
typedef struct _GtkMenuTracker GtkMenuTracker;
|
||||
|
||||
typedef void (* GtkMenuTrackerInsertFunc) (GtkMenuTrackerItem *item,
|
||||
gint position,
|
||||
gpointer user_data);
|
||||
|
||||
typedef void (* GtkMenuTrackerRemoveFunc) (gint position,
|
||||
gpointer user_data);
|
||||
|
||||
|
||||
GtkMenuTracker * gtk_menu_tracker_new (GtkActionObservable *observer,
|
||||
GMenuModel *model,
|
||||
gboolean with_separators,
|
||||
const gchar *action_namespace,
|
||||
GtkMenuTrackerInsertFunc insert_func,
|
||||
GtkMenuTrackerRemoveFunc remove_func,
|
||||
gpointer user_data);
|
||||
|
||||
GtkMenuTracker * gtk_menu_tracker_new_for_item_submenu (GtkMenuTrackerItem *item,
|
||||
GtkMenuTrackerInsertFunc insert_func,
|
||||
GtkMenuTrackerRemoveFunc remove_func,
|
||||
gpointer user_data);
|
||||
|
||||
void gtk_menu_tracker_free (GtkMenuTracker *tracker);
|
||||
|
||||
#endif /* __GTK_MENU_TRACKER_H__ */
|
@ -1,894 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2013 Canonical Limited
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the licence, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Ryan Lortie <desrt@desrt.ca>
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "gtkmenutrackeritem.h"
|
||||
#include "gtkactionmuxer.h"
|
||||
|
||||
#include "gtkactionmuxer.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* SECTION:gtkmenutrackeritem
|
||||
* @Title: GtkMenuTrackerItem
|
||||
* @Short_description: Small helper for model menu items
|
||||
*
|
||||
* A #GtkMenuTrackerItem is a small helper class used by #GtkMenuTracker to
|
||||
* represent menu items. It has one of three classes: normal item, separator,
|
||||
* or submenu.
|
||||
*
|
||||
* If an item is one of the non-normal classes (submenu, separator), only the
|
||||
* label of the item needs to be respected. Otherwise, all the properties
|
||||
* of the item contribute to the item's appearance and state.
|
||||
*
|
||||
* Implementing the appearance of the menu item is up to toolkits, and certain
|
||||
* toolkits may choose to ignore certain properties, like icon or accel. The
|
||||
* role of the item determines its accessibility role, along with its
|
||||
* decoration if the GtkMenuTrackerItem::toggled property is true. As an
|
||||
* example, if the item has the role %GTK_MENU_TRACKER_ITEM_ROLE_CHECK and
|
||||
* GtkMenuTrackerItem::toggled is %FALSE, its accessible role should be that of
|
||||
* a check menu item, and no decoration should be drawn. But if
|
||||
* GtkMenuTrackerItem::toggled is %TRUE, a checkmark should be drawn.
|
||||
*
|
||||
* All properties except for the two class-determining properties,
|
||||
* GtkMenuTrackerItem::is-separator and GtkMenuTrackerItem::has-submenu are
|
||||
* allowed to change, so listen to the notify signals to update your item's
|
||||
* appearance. When using a GObject library, this can conveniently be done
|
||||
* with g_object_bind_property() and #GBinding, and this is how this is
|
||||
* implemented in GTK+; the appearance side is implemented in #GtkModelMenuItem.
|
||||
*
|
||||
* When an item is clicked, simply call gtk_menu_tracker_item_activated() in
|
||||
* response. The #GtkMenuTrackerItem will take care of everything related to
|
||||
* activating the item and will itself update the state of all items in
|
||||
* response.
|
||||
*
|
||||
* Submenus are a special case of menu item. When an item is a submenu, you
|
||||
* should create a submenu for it with gtk_menu_tracker_new_item_for_submenu(),
|
||||
* and apply the same menu tracking logic you would for a toplevel menu.
|
||||
* Applications using submenus may want to lazily build their submenus in
|
||||
* response to the user clicking on it, as building a submenu may be expensive.
|
||||
*
|
||||
* Thus, the submenu has two special controls -- the submenu's visibility
|
||||
* should be controlled by the GtkMenuTrackerItem::submenu-shown property,
|
||||
* and if a user clicks on the submenu, do not immediately show the menu,
|
||||
* but call gtk_menu_tracker_item_request_submenu_shown() and wait for the
|
||||
* GtkMenuTrackerItem::submenu-shown property to update. If the user navigates,
|
||||
* the application may want to be notified so it can cancel the expensive
|
||||
* operation that it was using to build the submenu. Thus,
|
||||
* gtk_menu_tracker_item_request_submenu_shown() takes a boolean parameter.
|
||||
* Use %TRUE when the user wants to open the submenu, and %FALSE when the
|
||||
* user wants to close the submenu.
|
||||
*/
|
||||
|
||||
typedef GObjectClass GtkMenuTrackerItemClass;
|
||||
|
||||
struct _GtkMenuTrackerItem
|
||||
{
|
||||
GObject parent_instance;
|
||||
|
||||
GtkActionObservable *observable;
|
||||
gchar *action_namespace;
|
||||
gchar *action_and_target;
|
||||
GMenuItem *item;
|
||||
GtkMenuTrackerItemRole role : 4;
|
||||
guint is_separator : 1;
|
||||
guint can_activate : 1;
|
||||
guint sensitive : 1;
|
||||
guint toggled : 1;
|
||||
guint submenu_shown : 1;
|
||||
guint submenu_requested : 1;
|
||||
guint hidden_when : 2;
|
||||
guint is_visible : 1;
|
||||
};
|
||||
|
||||
#define HIDDEN_NEVER 0
|
||||
#define HIDDEN_WHEN_MISSING 1
|
||||
#define HIDDEN_WHEN_DISABLED 2
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_IS_SEPARATOR,
|
||||
PROP_HAS_SUBMENU,
|
||||
PROP_LABEL,
|
||||
PROP_ICON,
|
||||
PROP_SENSITIVE,
|
||||
PROP_VISIBLE,
|
||||
PROP_ROLE,
|
||||
PROP_TOGGLED,
|
||||
PROP_ACCEL,
|
||||
PROP_SUBMENU_SHOWN,
|
||||
N_PROPS
|
||||
};
|
||||
|
||||
static GParamSpec *gtk_menu_tracker_item_pspecs[N_PROPS];
|
||||
static guint gtk_menu_tracker_visibility_changed_signal;
|
||||
|
||||
static void gtk_menu_tracker_item_init_observer_iface (GtkActionObserverInterface *iface);
|
||||
G_DEFINE_TYPE_WITH_CODE (GtkMenuTrackerItem, gtk_menu_tracker_item, G_TYPE_OBJECT,
|
||||
G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTION_OBSERVER, gtk_menu_tracker_item_init_observer_iface))
|
||||
|
||||
GType
|
||||
gtk_menu_tracker_item_role_get_type (void)
|
||||
{
|
||||
static gsize gtk_menu_tracker_item_role_type;
|
||||
|
||||
if (g_once_init_enter (>k_menu_tracker_item_role_type))
|
||||
{
|
||||
static const GEnumValue values[] = {
|
||||
{ GTK_MENU_TRACKER_ITEM_ROLE_NORMAL, "GTK_MENU_TRACKER_ITEM_ROLE_NORMAL", "normal" },
|
||||
{ GTK_MENU_TRACKER_ITEM_ROLE_CHECK, "GTK_MENU_TRACKER_ITEM_ROLE_CHECK", "check" },
|
||||
{ GTK_MENU_TRACKER_ITEM_ROLE_RADIO, "GTK_MENU_TRACKER_ITEM_ROLE_RADIO", "radio" },
|
||||
{ 0, NULL, NULL }
|
||||
};
|
||||
GType type;
|
||||
|
||||
type = g_enum_register_static ("GtkMenuTrackerItemRole", values);
|
||||
|
||||
g_once_init_leave (>k_menu_tracker_item_role_type, type);
|
||||
}
|
||||
|
||||
return gtk_menu_tracker_item_role_type;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_item_get_property (GObject *object,
|
||||
guint prop_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
GtkMenuTrackerItem *self = GTK_MENU_TRACKER_ITEM (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_IS_SEPARATOR:
|
||||
g_value_set_boolean (value, gtk_menu_tracker_item_get_is_separator (self));
|
||||
break;
|
||||
case PROP_HAS_SUBMENU:
|
||||
g_value_set_boolean (value, gtk_menu_tracker_item_get_has_submenu (self));
|
||||
break;
|
||||
case PROP_LABEL:
|
||||
g_value_set_string (value, gtk_menu_tracker_item_get_label (self));
|
||||
break;
|
||||
case PROP_ICON:
|
||||
g_value_set_object (value, gtk_menu_tracker_item_get_icon (self));
|
||||
break;
|
||||
case PROP_SENSITIVE:
|
||||
g_value_set_boolean (value, gtk_menu_tracker_item_get_sensitive (self));
|
||||
break;
|
||||
case PROP_VISIBLE:
|
||||
g_value_set_boolean (value, gtk_menu_tracker_item_get_visible (self));
|
||||
break;
|
||||
case PROP_ROLE:
|
||||
g_value_set_enum (value, gtk_menu_tracker_item_get_role (self));
|
||||
break;
|
||||
case PROP_TOGGLED:
|
||||
g_value_set_boolean (value, gtk_menu_tracker_item_get_toggled (self));
|
||||
break;
|
||||
case PROP_ACCEL:
|
||||
g_value_set_string (value, gtk_menu_tracker_item_get_accel (self));
|
||||
break;
|
||||
case PROP_SUBMENU_SHOWN:
|
||||
g_value_set_boolean (value, gtk_menu_tracker_item_get_submenu_shown (self));
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_item_finalize (GObject *object)
|
||||
{
|
||||
GtkMenuTrackerItem *self = GTK_MENU_TRACKER_ITEM (object);
|
||||
|
||||
g_free (self->action_namespace);
|
||||
g_free (self->action_and_target);
|
||||
|
||||
if (self->observable)
|
||||
g_object_unref (self->observable);
|
||||
|
||||
g_object_unref (self->item);
|
||||
|
||||
G_OBJECT_CLASS (gtk_menu_tracker_item_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_item_init (GtkMenuTrackerItem * self)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_item_class_init (GtkMenuTrackerItemClass *class)
|
||||
{
|
||||
class->get_property = gtk_menu_tracker_item_get_property;
|
||||
class->finalize = gtk_menu_tracker_item_finalize;
|
||||
|
||||
gtk_menu_tracker_item_pspecs[PROP_IS_SEPARATOR] =
|
||||
g_param_spec_boolean ("is-separator", "", "", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
|
||||
gtk_menu_tracker_item_pspecs[PROP_HAS_SUBMENU] =
|
||||
g_param_spec_boolean ("has-submenu", "", "", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
|
||||
gtk_menu_tracker_item_pspecs[PROP_LABEL] =
|
||||
g_param_spec_string ("label", "", "", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
|
||||
gtk_menu_tracker_item_pspecs[PROP_ICON] =
|
||||
g_param_spec_object ("icon", "", "", G_TYPE_ICON, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
|
||||
gtk_menu_tracker_item_pspecs[PROP_SENSITIVE] =
|
||||
g_param_spec_boolean ("sensitive", "", "", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
|
||||
gtk_menu_tracker_item_pspecs[PROP_VISIBLE] =
|
||||
g_param_spec_boolean ("visible", "", "", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
|
||||
gtk_menu_tracker_item_pspecs[PROP_ROLE] =
|
||||
g_param_spec_enum ("role", "", "",
|
||||
GTK_TYPE_MENU_TRACKER_ITEM_ROLE, GTK_MENU_TRACKER_ITEM_ROLE_NORMAL,
|
||||
G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
|
||||
gtk_menu_tracker_item_pspecs[PROP_TOGGLED] =
|
||||
g_param_spec_boolean ("toggled", "", "", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
|
||||
gtk_menu_tracker_item_pspecs[PROP_ACCEL] =
|
||||
g_param_spec_string ("accel", "", "", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
|
||||
gtk_menu_tracker_item_pspecs[PROP_SUBMENU_SHOWN] =
|
||||
g_param_spec_boolean ("submenu-shown", "", "", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
|
||||
|
||||
g_object_class_install_properties (class, N_PROPS, gtk_menu_tracker_item_pspecs);
|
||||
|
||||
gtk_menu_tracker_visibility_changed_signal = g_signal_new ("visibility-changed", GTK_TYPE_MENU_TRACKER_ITEM,
|
||||
G_SIGNAL_RUN_FIRST, 0, NULL, NULL,
|
||||
g_cclosure_marshal_VOID__BOOLEAN, G_TYPE_NONE,
|
||||
1, G_TYPE_BOOLEAN);
|
||||
}
|
||||
|
||||
/* This syncs up the visibility for the hidden-when='' case. We call it
|
||||
* from the action observer functions on changes to the action group and
|
||||
* on initialisation (via the action observer functions that are invoked
|
||||
* at that time).
|
||||
*/
|
||||
static void
|
||||
gtk_menu_tracker_item_update_visibility (GtkMenuTrackerItem *self)
|
||||
{
|
||||
gboolean visible;
|
||||
|
||||
switch (self->hidden_when)
|
||||
{
|
||||
case HIDDEN_NEVER:
|
||||
visible = TRUE;
|
||||
break;
|
||||
|
||||
case HIDDEN_WHEN_MISSING:
|
||||
visible = self->can_activate;
|
||||
break;
|
||||
|
||||
case HIDDEN_WHEN_DISABLED:
|
||||
visible = self->sensitive;
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
|
||||
if (visible != self->is_visible)
|
||||
{
|
||||
self->is_visible = visible;
|
||||
g_signal_emit (self, gtk_menu_tracker_visibility_changed_signal, 0, visible);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_item_action_added (GtkActionObserver *observer,
|
||||
GtkActionObservable *observable,
|
||||
const gchar *action_name,
|
||||
const GVariantType *parameter_type,
|
||||
gboolean enabled,
|
||||
GVariant *state)
|
||||
{
|
||||
GtkMenuTrackerItem *self = GTK_MENU_TRACKER_ITEM (observer);
|
||||
GVariant *action_target;
|
||||
|
||||
action_target = g_menu_item_get_attribute_value (self->item, G_MENU_ATTRIBUTE_TARGET, NULL);
|
||||
|
||||
self->can_activate = (action_target == NULL && parameter_type == NULL) ||
|
||||
(action_target != NULL && parameter_type != NULL &&
|
||||
g_variant_is_of_type (action_target, parameter_type));
|
||||
|
||||
if (!self->can_activate)
|
||||
{
|
||||
if (action_target)
|
||||
g_variant_unref (action_target);
|
||||
return;
|
||||
}
|
||||
|
||||
self->sensitive = enabled;
|
||||
|
||||
if (action_target != NULL && state != NULL)
|
||||
{
|
||||
self->toggled = g_variant_equal (state, action_target);
|
||||
self->role = GTK_MENU_TRACKER_ITEM_ROLE_RADIO;
|
||||
}
|
||||
|
||||
else if (state != NULL && g_variant_is_of_type (state, G_VARIANT_TYPE_BOOLEAN))
|
||||
{
|
||||
self->toggled = g_variant_get_boolean (state);
|
||||
self->role = GTK_MENU_TRACKER_ITEM_ROLE_CHECK;
|
||||
}
|
||||
|
||||
g_object_freeze_notify (G_OBJECT (self));
|
||||
|
||||
if (self->sensitive)
|
||||
g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_SENSITIVE]);
|
||||
|
||||
if (self->toggled)
|
||||
g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_TOGGLED]);
|
||||
|
||||
if (self->role != GTK_MENU_TRACKER_ITEM_ROLE_NORMAL)
|
||||
g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_ROLE]);
|
||||
|
||||
g_object_thaw_notify (G_OBJECT (self));
|
||||
|
||||
if (action_target)
|
||||
g_variant_unref (action_target);
|
||||
|
||||
/* In case of hidden-when='', we want to Wait until after refreshing
|
||||
* all of the properties to emit the signal that will cause the
|
||||
* tracker to expose us (to prevent too much thrashing).
|
||||
*/
|
||||
gtk_menu_tracker_item_update_visibility (self);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_item_action_enabled_changed (GtkActionObserver *observer,
|
||||
GtkActionObservable *observable,
|
||||
const gchar *action_name,
|
||||
gboolean enabled)
|
||||
{
|
||||
GtkMenuTrackerItem *self = GTK_MENU_TRACKER_ITEM (observer);
|
||||
|
||||
if (!self->can_activate)
|
||||
return;
|
||||
|
||||
if (self->sensitive == enabled)
|
||||
return;
|
||||
|
||||
self->sensitive = enabled;
|
||||
|
||||
g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_SENSITIVE]);
|
||||
|
||||
gtk_menu_tracker_item_update_visibility (self);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_item_action_state_changed (GtkActionObserver *observer,
|
||||
GtkActionObservable *observable,
|
||||
const gchar *action_name,
|
||||
GVariant *state)
|
||||
{
|
||||
GtkMenuTrackerItem *self = GTK_MENU_TRACKER_ITEM (observer);
|
||||
GVariant *action_target;
|
||||
gboolean was_toggled;
|
||||
|
||||
if (!self->can_activate)
|
||||
return;
|
||||
|
||||
action_target = g_menu_item_get_attribute_value (self->item, G_MENU_ATTRIBUTE_TARGET, NULL);
|
||||
was_toggled = self->toggled;
|
||||
|
||||
if (action_target)
|
||||
{
|
||||
self->toggled = g_variant_equal (state, action_target);
|
||||
g_variant_unref (action_target);
|
||||
}
|
||||
|
||||
else if (g_variant_is_of_type (state, G_VARIANT_TYPE_BOOLEAN))
|
||||
self->toggled = g_variant_get_boolean (state);
|
||||
|
||||
else
|
||||
self->toggled = FALSE;
|
||||
|
||||
if (self->toggled != was_toggled)
|
||||
g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_TOGGLED]);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_item_action_removed (GtkActionObserver *observer,
|
||||
GtkActionObservable *observable,
|
||||
const gchar *action_name)
|
||||
{
|
||||
GtkMenuTrackerItem *self = GTK_MENU_TRACKER_ITEM (observer);
|
||||
gboolean was_sensitive, was_toggled;
|
||||
GtkMenuTrackerItemRole old_role;
|
||||
|
||||
if (!self->can_activate)
|
||||
return;
|
||||
|
||||
was_sensitive = self->sensitive;
|
||||
was_toggled = self->toggled;
|
||||
old_role = self->role;
|
||||
|
||||
self->can_activate = FALSE;
|
||||
self->sensitive = FALSE;
|
||||
self->toggled = FALSE;
|
||||
self->role = GTK_MENU_TRACKER_ITEM_ROLE_NORMAL;
|
||||
|
||||
/* Backwards from adding: we want to remove ourselves from the menu
|
||||
* -before- thrashing the properties.
|
||||
*/
|
||||
gtk_menu_tracker_item_update_visibility (self);
|
||||
|
||||
g_object_freeze_notify (G_OBJECT (self));
|
||||
|
||||
if (was_sensitive)
|
||||
g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_SENSITIVE]);
|
||||
|
||||
if (was_toggled)
|
||||
g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_TOGGLED]);
|
||||
|
||||
if (old_role != GTK_MENU_TRACKER_ITEM_ROLE_NORMAL)
|
||||
g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_ROLE]);
|
||||
|
||||
g_object_thaw_notify (G_OBJECT (self));
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_item_primary_accel_changed (GtkActionObserver *observer,
|
||||
GtkActionObservable *observable,
|
||||
const gchar *action_name,
|
||||
const gchar *action_and_target)
|
||||
{
|
||||
GtkMenuTrackerItem *self = GTK_MENU_TRACKER_ITEM (observer);
|
||||
|
||||
if (g_str_equal (action_and_target, self->action_and_target))
|
||||
g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_ACCEL]);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_item_init_observer_iface (GtkActionObserverInterface *iface)
|
||||
{
|
||||
iface->action_added = gtk_menu_tracker_item_action_added;
|
||||
iface->action_enabled_changed = gtk_menu_tracker_item_action_enabled_changed;
|
||||
iface->action_state_changed = gtk_menu_tracker_item_action_state_changed;
|
||||
iface->action_removed = gtk_menu_tracker_item_action_removed;
|
||||
iface->primary_accel_changed = gtk_menu_tracker_item_primary_accel_changed;
|
||||
}
|
||||
|
||||
GtkMenuTrackerItem *
|
||||
_gtk_menu_tracker_item_new (GtkActionObservable *observable,
|
||||
GMenuModel *model,
|
||||
gint item_index,
|
||||
const gchar *action_namespace,
|
||||
gboolean is_separator)
|
||||
{
|
||||
GtkMenuTrackerItem *self;
|
||||
const gchar *action_name;
|
||||
const gchar *hidden_when;
|
||||
|
||||
g_return_val_if_fail (GTK_IS_ACTION_OBSERVABLE (observable), NULL);
|
||||
g_return_val_if_fail (G_IS_MENU_MODEL (model), NULL);
|
||||
|
||||
self = g_object_new (GTK_TYPE_MENU_TRACKER_ITEM, NULL);
|
||||
self->item = g_menu_item_new_from_model (model, item_index);
|
||||
self->action_namespace = g_strdup (action_namespace);
|
||||
self->observable = g_object_ref (observable);
|
||||
self->is_separator = is_separator;
|
||||
|
||||
if (!is_separator && g_menu_item_get_attribute (self->item, "hidden-when", "&s", &hidden_when))
|
||||
{
|
||||
if (g_str_equal (hidden_when, "action-disabled"))
|
||||
self->hidden_when = HIDDEN_WHEN_DISABLED;
|
||||
else if (g_str_equal (hidden_when, "action-missing"))
|
||||
self->hidden_when = HIDDEN_WHEN_MISSING;
|
||||
|
||||
/* Ignore other values -- this code may be running in context of a
|
||||
* desktop shell or the like and should not spew criticals due to
|
||||
* application bugs...
|
||||
*
|
||||
* Note: if we just set a hidden-when state, but don't get the
|
||||
* action_name below then our visibility will be FALSE forever.
|
||||
* That's to be expected since the action is missing...
|
||||
*/
|
||||
}
|
||||
|
||||
if (!is_separator && g_menu_item_get_attribute (self->item, "action", "&s", &action_name))
|
||||
{
|
||||
GActionGroup *group = G_ACTION_GROUP (observable);
|
||||
const GVariantType *parameter_type;
|
||||
GVariant *target;
|
||||
gboolean enabled;
|
||||
GVariant *state;
|
||||
gboolean found;
|
||||
|
||||
target = g_menu_item_get_attribute_value (self->item, "target", NULL);
|
||||
|
||||
self->action_and_target = gtk_print_action_and_target (action_namespace, action_name, target);
|
||||
|
||||
if (target)
|
||||
g_variant_unref (target);
|
||||
|
||||
action_name = strrchr (self->action_and_target, '|') + 1;
|
||||
|
||||
state = NULL;
|
||||
|
||||
gtk_action_observable_register_observer (self->observable, action_name, GTK_ACTION_OBSERVER (self));
|
||||
found = g_action_group_query_action (group, action_name, &enabled, ¶meter_type, NULL, NULL, &state);
|
||||
|
||||
if (found)
|
||||
gtk_menu_tracker_item_action_added (GTK_ACTION_OBSERVER (self), observable, NULL, parameter_type, enabled, state);
|
||||
else
|
||||
gtk_menu_tracker_item_action_removed (GTK_ACTION_OBSERVER (self), observable, NULL);
|
||||
|
||||
if (state)
|
||||
g_variant_unref (state);
|
||||
}
|
||||
else
|
||||
self->sensitive = TRUE;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
GtkActionObservable *
|
||||
_gtk_menu_tracker_item_get_observable (GtkMenuTrackerItem *self)
|
||||
{
|
||||
return self->observable;
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_menu_tracker_item_get_is_separator:
|
||||
* @self: A #GtkMenuTrackerItem instance
|
||||
*
|
||||
* Returns whether the menu item is a separator. If so, only
|
||||
* certain properties may need to be obeyed. See the documentation
|
||||
* for #GtkMenuTrackerItem.
|
||||
*/
|
||||
gboolean
|
||||
gtk_menu_tracker_item_get_is_separator (GtkMenuTrackerItem *self)
|
||||
{
|
||||
return self->is_separator;
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_menu_tracker_item_get_has_submenu:
|
||||
* @self: A #GtkMenuTrackerItem instance
|
||||
*
|
||||
* Returns whether the menu item has a submenu. If so, only
|
||||
* certain properties may need to be obeyed. See the documentation
|
||||
* for #GtkMenuTrackerItem.
|
||||
*/
|
||||
gboolean
|
||||
gtk_menu_tracker_item_get_has_submenu (GtkMenuTrackerItem *self)
|
||||
{
|
||||
GMenuModel *link;
|
||||
|
||||
link = g_menu_item_get_link (self->item, G_MENU_LINK_SUBMENU);
|
||||
|
||||
if (link)
|
||||
{
|
||||
g_object_unref (link);
|
||||
return TRUE;
|
||||
}
|
||||
else
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
const gchar *
|
||||
gtk_menu_tracker_item_get_label (GtkMenuTrackerItem *self)
|
||||
{
|
||||
const gchar *label = NULL;
|
||||
|
||||
g_menu_item_get_attribute (self->item, G_MENU_ATTRIBUTE_LABEL, "&s", &label);
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_menu_tracker_item_get_icon:
|
||||
*
|
||||
* Returns: (transfer full):
|
||||
*/
|
||||
GIcon *
|
||||
gtk_menu_tracker_item_get_icon (GtkMenuTrackerItem *self)
|
||||
{
|
||||
GVariant *icon_data;
|
||||
GIcon *icon;
|
||||
|
||||
icon_data = g_menu_item_get_attribute_value (self->item, "icon", NULL);
|
||||
|
||||
if (icon_data == NULL)
|
||||
return NULL;
|
||||
|
||||
icon = g_icon_deserialize (icon_data);
|
||||
g_variant_unref (icon_data);
|
||||
|
||||
return icon;
|
||||
}
|
||||
|
||||
gboolean
|
||||
gtk_menu_tracker_item_get_sensitive (GtkMenuTrackerItem *self)
|
||||
{
|
||||
return self->sensitive;
|
||||
}
|
||||
|
||||
gboolean
|
||||
gtk_menu_tracker_item_get_visible (GtkMenuTrackerItem *self)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
GtkMenuTrackerItemRole
|
||||
gtk_menu_tracker_item_get_role (GtkMenuTrackerItem *self)
|
||||
{
|
||||
return self->role;
|
||||
}
|
||||
|
||||
gboolean
|
||||
gtk_menu_tracker_item_get_toggled (GtkMenuTrackerItem *self)
|
||||
{
|
||||
return self->toggled;
|
||||
}
|
||||
|
||||
const gchar *
|
||||
gtk_menu_tracker_item_get_accel (GtkMenuTrackerItem *self)
|
||||
{
|
||||
const gchar *accel;
|
||||
|
||||
if (!self->action_and_target)
|
||||
return NULL;
|
||||
|
||||
if (g_menu_item_get_attribute (self->item, "accel", "&s", &accel))
|
||||
return accel;
|
||||
|
||||
if (!GTK_IS_ACTION_MUXER (self->observable))
|
||||
return NULL;
|
||||
|
||||
return gtk_action_muxer_get_primary_accel (GTK_ACTION_MUXER (self->observable), self->action_and_target);
|
||||
}
|
||||
|
||||
GMenuModel *
|
||||
_gtk_menu_tracker_item_get_submenu (GtkMenuTrackerItem *self)
|
||||
{
|
||||
return g_menu_item_get_link (self->item, "submenu");
|
||||
}
|
||||
|
||||
gchar *
|
||||
_gtk_menu_tracker_item_get_submenu_namespace (GtkMenuTrackerItem *self)
|
||||
{
|
||||
const gchar *namespace;
|
||||
|
||||
if (g_menu_item_get_attribute (self->item, "action-namespace", "&s", &namespace))
|
||||
{
|
||||
if (self->action_namespace)
|
||||
return g_strjoin (".", self->action_namespace, namespace, NULL);
|
||||
else
|
||||
return g_strdup (namespace);
|
||||
}
|
||||
else
|
||||
return g_strdup (self->action_namespace);
|
||||
}
|
||||
|
||||
gboolean
|
||||
gtk_menu_tracker_item_get_should_request_show (GtkMenuTrackerItem *self)
|
||||
{
|
||||
return g_menu_item_get_attribute (self->item, "submenu-action", "&s", NULL);
|
||||
}
|
||||
|
||||
gboolean
|
||||
gtk_menu_tracker_item_get_submenu_shown (GtkMenuTrackerItem *self)
|
||||
{
|
||||
return self->submenu_shown;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_item_set_submenu_shown (GtkMenuTrackerItem *self,
|
||||
gboolean submenu_shown)
|
||||
{
|
||||
if (submenu_shown == self->submenu_shown)
|
||||
return;
|
||||
|
||||
self->submenu_shown = submenu_shown;
|
||||
g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_SUBMENU_SHOWN]);
|
||||
}
|
||||
|
||||
void
|
||||
gtk_menu_tracker_item_activated (GtkMenuTrackerItem *self)
|
||||
{
|
||||
const gchar *action_name;
|
||||
GVariant *action_target;
|
||||
|
||||
g_return_if_fail (GTK_IS_MENU_TRACKER_ITEM (self));
|
||||
|
||||
if (!self->can_activate)
|
||||
return;
|
||||
|
||||
action_name = strrchr (self->action_and_target, '|') + 1;
|
||||
action_target = g_menu_item_get_attribute_value (self->item, G_MENU_ATTRIBUTE_TARGET, NULL);
|
||||
|
||||
g_action_group_activate_action (G_ACTION_GROUP (self->observable), action_name, action_target);
|
||||
|
||||
if (action_target)
|
||||
g_variant_unref (action_target);
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GtkMenuTrackerItem *item;
|
||||
gchar *submenu_action;
|
||||
gboolean first_time;
|
||||
} GtkMenuTrackerOpener;
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_opener_update (GtkMenuTrackerOpener *opener)
|
||||
{
|
||||
GActionGroup *group = G_ACTION_GROUP (opener->item->observable);
|
||||
gboolean is_open = TRUE;
|
||||
|
||||
/* We consider the menu as being "open" if the action does not exist
|
||||
* or if there is another problem (no state, wrong state type, etc.).
|
||||
* If the action exists, with the correct state then we consider it
|
||||
* open if we have ever seen this state equal to TRUE.
|
||||
*
|
||||
* In the event that we see the state equal to FALSE, we force it back
|
||||
* to TRUE. We do not signal that the menu was closed because this is
|
||||
* likely to create UI thrashing.
|
||||
*
|
||||
* The only way the menu can have a true-to-false submenu-shown
|
||||
* transition is if the user calls _request_submenu_shown (FALSE).
|
||||
* That is handled in _free() below.
|
||||
*/
|
||||
|
||||
if (g_action_group_has_action (group, opener->submenu_action))
|
||||
{
|
||||
GVariant *state = g_action_group_get_action_state (group, opener->submenu_action);
|
||||
|
||||
if (state)
|
||||
{
|
||||
if (g_variant_is_of_type (state, G_VARIANT_TYPE_BOOLEAN))
|
||||
is_open = g_variant_get_boolean (state);
|
||||
g_variant_unref (state);
|
||||
}
|
||||
}
|
||||
|
||||
/* If it is already open, signal that.
|
||||
*
|
||||
* If it is not open, ask it to open.
|
||||
*/
|
||||
if (is_open)
|
||||
gtk_menu_tracker_item_set_submenu_shown (opener->item, TRUE);
|
||||
|
||||
if (!is_open || opener->first_time)
|
||||
{
|
||||
g_action_group_change_action_state (group, opener->submenu_action, g_variant_new_boolean (TRUE));
|
||||
opener->first_time = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_opener_added (GActionGroup *group,
|
||||
const gchar *action_name,
|
||||
gpointer user_data)
|
||||
{
|
||||
GtkMenuTrackerOpener *opener = user_data;
|
||||
|
||||
if (g_str_equal (action_name, opener->submenu_action))
|
||||
gtk_menu_tracker_opener_update (opener);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_opener_removed (GActionGroup *action_group,
|
||||
const gchar *action_name,
|
||||
gpointer user_data)
|
||||
{
|
||||
GtkMenuTrackerOpener *opener = user_data;
|
||||
|
||||
if (g_str_equal (action_name, opener->submenu_action))
|
||||
gtk_menu_tracker_opener_update (opener);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_opener_changed (GActionGroup *action_group,
|
||||
const gchar *action_name,
|
||||
GVariant *new_state,
|
||||
gpointer user_data)
|
||||
{
|
||||
GtkMenuTrackerOpener *opener = user_data;
|
||||
|
||||
if (g_str_equal (action_name, opener->submenu_action))
|
||||
gtk_menu_tracker_opener_update (opener);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_opener_free (gpointer data)
|
||||
{
|
||||
GtkMenuTrackerOpener *opener = data;
|
||||
|
||||
g_signal_handlers_disconnect_by_func (opener->item->observable, gtk_menu_tracker_opener_added, opener);
|
||||
g_signal_handlers_disconnect_by_func (opener->item->observable, gtk_menu_tracker_opener_removed, opener);
|
||||
g_signal_handlers_disconnect_by_func (opener->item->observable, gtk_menu_tracker_opener_changed, opener);
|
||||
|
||||
g_action_group_change_action_state (G_ACTION_GROUP (opener->item->observable),
|
||||
opener->submenu_action,
|
||||
g_variant_new_boolean (FALSE));
|
||||
|
||||
gtk_menu_tracker_item_set_submenu_shown (opener->item, FALSE);
|
||||
|
||||
g_free (opener->submenu_action);
|
||||
|
||||
g_slice_free (GtkMenuTrackerOpener, opener);
|
||||
}
|
||||
|
||||
static GtkMenuTrackerOpener *
|
||||
gtk_menu_tracker_opener_new (GtkMenuTrackerItem *item,
|
||||
const gchar *submenu_action)
|
||||
{
|
||||
GtkMenuTrackerOpener *opener;
|
||||
|
||||
opener = g_slice_new (GtkMenuTrackerOpener);
|
||||
opener->first_time = TRUE;
|
||||
opener->item = item;
|
||||
|
||||
if (item->action_namespace)
|
||||
opener->submenu_action = g_strjoin (".", item->action_namespace, submenu_action, NULL);
|
||||
else
|
||||
opener->submenu_action = g_strdup (submenu_action);
|
||||
|
||||
g_signal_connect (item->observable, "action-added", G_CALLBACK (gtk_menu_tracker_opener_added), opener);
|
||||
g_signal_connect (item->observable, "action-removed", G_CALLBACK (gtk_menu_tracker_opener_removed), opener);
|
||||
g_signal_connect (item->observable, "action-state-changed", G_CALLBACK (gtk_menu_tracker_opener_changed), opener);
|
||||
|
||||
gtk_menu_tracker_opener_update (opener);
|
||||
|
||||
return opener;
|
||||
}
|
||||
|
||||
void
|
||||
gtk_menu_tracker_item_request_submenu_shown (GtkMenuTrackerItem *self,
|
||||
gboolean shown)
|
||||
{
|
||||
const gchar *submenu_action;
|
||||
gboolean has_submenu_action;
|
||||
|
||||
if (shown == self->submenu_requested)
|
||||
return;
|
||||
|
||||
has_submenu_action = g_menu_item_get_attribute (self->item, "submenu-action", "&s", &submenu_action);
|
||||
|
||||
self->submenu_requested = shown;
|
||||
|
||||
/* If we have a submenu action, start a submenu opener and wait
|
||||
* for the reply from the client. Otherwise, simply open the
|
||||
* submenu immediately.
|
||||
*/
|
||||
if (has_submenu_action)
|
||||
{
|
||||
if (shown)
|
||||
g_object_set_data_full (G_OBJECT (self), "submenu-opener",
|
||||
gtk_menu_tracker_opener_new (self, submenu_action),
|
||||
gtk_menu_tracker_opener_free);
|
||||
else
|
||||
g_object_set_data (G_OBJECT (self), "submenu-opener", NULL);
|
||||
}
|
||||
else
|
||||
gtk_menu_tracker_item_set_submenu_shown (self, shown);
|
||||
}
|
||||
|
||||
gboolean
|
||||
_gtk_menu_tracker_item_is_visible (GtkMenuTrackerItem *self)
|
||||
{
|
||||
return self->is_visible;
|
||||
}
|
||||
|
||||
gboolean
|
||||
_gtk_menu_tracker_item_may_disappear (GtkMenuTrackerItem *self)
|
||||
{
|
||||
return self->hidden_when != HIDDEN_NEVER;
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2011, 2013 Canonical Limited
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the licence, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Ryan Lortie <desrt@desrt.ca>
|
||||
*/
|
||||
|
||||
#ifndef __GTK_MENU_TRACKER_ITEM_H__
|
||||
#define __GTK_MENU_TRACKER_ITEM_H__
|
||||
|
||||
#include "gtkactionobservable.h"
|
||||
|
||||
#define GTK_TYPE_MENU_TRACKER_ITEM (gtk_menu_tracker_item_get_type ())
|
||||
#define GTK_MENU_TRACKER_ITEM(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), \
|
||||
GTK_TYPE_MENU_TRACKER_ITEM, GtkMenuTrackerItem))
|
||||
#define GTK_IS_MENU_TRACKER_ITEM(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), \
|
||||
GTK_TYPE_MENU_TRACKER_ITEM))
|
||||
|
||||
typedef struct _GtkMenuTrackerItem GtkMenuTrackerItem;
|
||||
|
||||
#define GTK_TYPE_MENU_TRACKER_ITEM_ROLE (gtk_menu_tracker_item_role_get_type ())
|
||||
|
||||
typedef enum {
|
||||
GTK_MENU_TRACKER_ITEM_ROLE_NORMAL,
|
||||
GTK_MENU_TRACKER_ITEM_ROLE_CHECK,
|
||||
GTK_MENU_TRACKER_ITEM_ROLE_RADIO,
|
||||
} GtkMenuTrackerItemRole;
|
||||
|
||||
GType gtk_menu_tracker_item_get_type (void) G_GNUC_CONST;
|
||||
|
||||
GType gtk_menu_tracker_item_role_get_type (void) G_GNUC_CONST;
|
||||
|
||||
GtkMenuTrackerItem * _gtk_menu_tracker_item_new (GtkActionObservable *observable,
|
||||
GMenuModel *model,
|
||||
gint item_index,
|
||||
const gchar *action_namespace,
|
||||
gboolean is_separator);
|
||||
|
||||
GtkActionObservable * _gtk_menu_tracker_item_get_observable (GtkMenuTrackerItem *self);
|
||||
|
||||
gboolean gtk_menu_tracker_item_get_is_separator (GtkMenuTrackerItem *self);
|
||||
|
||||
gboolean gtk_menu_tracker_item_get_has_submenu (GtkMenuTrackerItem *self);
|
||||
|
||||
const gchar * gtk_menu_tracker_item_get_label (GtkMenuTrackerItem *self);
|
||||
|
||||
GIcon * gtk_menu_tracker_item_get_icon (GtkMenuTrackerItem *self);
|
||||
|
||||
gboolean gtk_menu_tracker_item_get_sensitive (GtkMenuTrackerItem *self);
|
||||
|
||||
gboolean gtk_menu_tracker_item_get_visible (GtkMenuTrackerItem *self);
|
||||
|
||||
GtkMenuTrackerItemRole gtk_menu_tracker_item_get_role (GtkMenuTrackerItem *self);
|
||||
|
||||
gboolean gtk_menu_tracker_item_get_toggled (GtkMenuTrackerItem *self);
|
||||
|
||||
const gchar * gtk_menu_tracker_item_get_accel (GtkMenuTrackerItem *self);
|
||||
|
||||
GMenuModel * _gtk_menu_tracker_item_get_submenu (GtkMenuTrackerItem *self);
|
||||
|
||||
gchar * _gtk_menu_tracker_item_get_submenu_namespace (GtkMenuTrackerItem *self);
|
||||
|
||||
gboolean _gtk_menu_tracker_item_may_disappear (GtkMenuTrackerItem *self);
|
||||
|
||||
gboolean _gtk_menu_tracker_item_is_visible (GtkMenuTrackerItem *self);
|
||||
|
||||
gboolean gtk_menu_tracker_item_get_should_request_show (GtkMenuTrackerItem *self);
|
||||
|
||||
void gtk_menu_tracker_item_activated (GtkMenuTrackerItem *self);
|
||||
|
||||
void gtk_menu_tracker_item_request_submenu_shown (GtkMenuTrackerItem *self,
|
||||
gboolean shown);
|
||||
|
||||
gboolean gtk_menu_tracker_item_get_submenu_shown (GtkMenuTrackerItem *self);
|
||||
|
||||
#endif
|
@ -60,24 +60,17 @@ gnome_shell_deps += recorder_deps
|
||||
tools_cflags = '-DLOCALEDIR="@0@"'.format(localedir)
|
||||
tools_deps = [gio_dep, gjs_dep]
|
||||
|
||||
libshell_menu_gir_sources = [
|
||||
libshell_menu_sources = [
|
||||
'gtkactionmuxer.h',
|
||||
'gtkactionmuxer.c',
|
||||
'gtkactionobservable.h',
|
||||
'gtkactionobservable.c',
|
||||
'gtkactionobserver.h',
|
||||
'gtkactionobserver.c',
|
||||
'gtkmenutrackeritem.c',
|
||||
'gtkmenutrackeritem.h'
|
||||
]
|
||||
|
||||
libshell_menu_no_gir_sources= [
|
||||
'gtkmenutracker.c',
|
||||
'gtkmenutracker.h'
|
||||
'gtkactionobserver.c'
|
||||
]
|
||||
|
||||
libshell_menu = library('gnome-shell-menu',
|
||||
sources: libshell_menu_gir_sources + libshell_menu_no_gir_sources,
|
||||
sources: libshell_menu_sources,
|
||||
dependencies: [gio_dep, clutter_dep],
|
||||
include_directories: conf_inc,
|
||||
build_rpath: mutter_typelibdir,
|
||||
@ -86,20 +79,6 @@ libshell_menu = library('gnome-shell-menu',
|
||||
install: true
|
||||
)
|
||||
|
||||
libshell_menu_gir = gnome.generate_gir(libshell_menu,
|
||||
sources: libshell_menu_gir_sources,
|
||||
nsversion: '0.1',
|
||||
namespace: 'ShellMenu',
|
||||
identifier_prefix: 'Gtk',
|
||||
symbol_prefix: 'gtk',
|
||||
includes: ['Gio-2.0', libst_gir[0]],
|
||||
dependencies: [mutter_dep],
|
||||
extra_args: ['--quiet'],
|
||||
install_dir_gir: pkgdatadir,
|
||||
install_dir_typelib: pkglibdir,
|
||||
install: true
|
||||
)
|
||||
|
||||
libshell_menu_dep = declare_dependency(link_with: libshell_menu)
|
||||
|
||||
libshell_public_headers = [
|
||||
@ -148,8 +127,6 @@ libshell_sources = [
|
||||
'shell-invert-lightness-effect.c',
|
||||
'shell-keyring-prompt.c',
|
||||
'shell-keyring-prompt.h',
|
||||
'shell-menu-tracker.c',
|
||||
'shell-menu-tracker.h',
|
||||
'shell-mount-operation.c',
|
||||
'shell-perf-log.c',
|
||||
'shell-polkit-authentication-agent.c',
|
||||
@ -231,7 +208,6 @@ endif
|
||||
|
||||
libshell_gir_includes += [
|
||||
libgvc_gir[0],
|
||||
libshell_menu_gir[0],
|
||||
libst_gir[0]
|
||||
]
|
||||
|
||||
|
@ -51,7 +51,6 @@ typedef struct {
|
||||
guint window_sort_stale : 1;
|
||||
|
||||
/* See GApplication documentation */
|
||||
GDBusMenuModel *remote_menu;
|
||||
GtkActionMuxer *muxer;
|
||||
char *unique_bus_name;
|
||||
GDBusConnection *session;
|
||||
@ -98,7 +97,6 @@ enum {
|
||||
PROP_ID,
|
||||
PROP_DBUS_ID,
|
||||
PROP_ACTION_GROUP,
|
||||
PROP_MENU,
|
||||
PROP_APP_INFO
|
||||
};
|
||||
|
||||
@ -137,10 +135,6 @@ shell_app_get_property (GObject *gobject,
|
||||
if (app->running_state)
|
||||
g_value_set_object (value, app->running_state->muxer);
|
||||
break;
|
||||
case PROP_MENU:
|
||||
if (app->running_state)
|
||||
g_value_set_object (value, app->running_state->remote_menu);
|
||||
break;
|
||||
case PROP_APP_INFO:
|
||||
if (app->info)
|
||||
g_value_set_object (value, app->info);
|
||||
@ -608,6 +602,7 @@ gboolean
|
||||
shell_app_can_open_new_window (ShellApp *app)
|
||||
{
|
||||
ShellAppRunningState *state;
|
||||
MetaWindow *window;
|
||||
|
||||
/* Apps that are not running can always open new windows, because
|
||||
activating them would open the first one */
|
||||
@ -641,11 +636,13 @@ shell_app_can_open_new_window (ShellApp *app)
|
||||
Activate() knows nothing about the other instances, so it will show a
|
||||
new window.
|
||||
*/
|
||||
if (state->remote_menu)
|
||||
|
||||
window = g_slist_nth_data (state->windows, 0);
|
||||
|
||||
if (state->unique_bus_name != NULL &&
|
||||
meta_window_get_gtk_application_object_path (window) != NULL)
|
||||
{
|
||||
const char *application_id;
|
||||
application_id = meta_window_get_gtk_application_id (state->windows->data);
|
||||
if (application_id != NULL)
|
||||
if (meta_window_get_gtk_application_id (window) != NULL)
|
||||
return FALSE;
|
||||
else
|
||||
return TRUE;
|
||||
@ -1078,7 +1075,7 @@ _shell_app_add_window (ShellApp *app,
|
||||
g_signal_connect (window, "notify::user-time", G_CALLBACK(shell_app_on_user_time_changed), app);
|
||||
g_signal_connect (window, "notify::skip-taskbar", G_CALLBACK(shell_app_on_skip_taskbar_changed), app);
|
||||
|
||||
shell_app_update_app_menu (app, window);
|
||||
shell_app_update_app_actions (app, window);
|
||||
shell_app_ensure_busy_watch (app);
|
||||
|
||||
if (!meta_window_is_skip_taskbar (window))
|
||||
@ -1380,8 +1377,8 @@ create_running_state (ShellApp *app)
|
||||
}
|
||||
|
||||
void
|
||||
shell_app_update_app_menu (ShellApp *app,
|
||||
MetaWindow *window)
|
||||
shell_app_update_app_actions (ShellApp *app,
|
||||
MetaWindow *window)
|
||||
{
|
||||
const gchar *unique_bus_name;
|
||||
|
||||
@ -1397,23 +1394,18 @@ shell_app_update_app_menu (ShellApp *app,
|
||||
|
||||
unique_bus_name = meta_window_get_gtk_unique_bus_name (window);
|
||||
|
||||
if (app->running_state->remote_menu == NULL ||
|
||||
g_strcmp0 (app->running_state->unique_bus_name, unique_bus_name) != 0)
|
||||
if (g_strcmp0 (app->running_state->unique_bus_name, unique_bus_name) != 0)
|
||||
{
|
||||
const gchar *application_object_path;
|
||||
const gchar *app_menu_object_path;
|
||||
GDBusActionGroup *actions;
|
||||
|
||||
application_object_path = meta_window_get_gtk_application_object_path (window);
|
||||
app_menu_object_path = meta_window_get_gtk_app_menu_object_path (window);
|
||||
|
||||
if (application_object_path == NULL || app_menu_object_path == NULL || unique_bus_name == NULL)
|
||||
if (application_object_path == NULL || unique_bus_name == NULL)
|
||||
return;
|
||||
|
||||
g_clear_pointer (&app->running_state->unique_bus_name, g_free);
|
||||
app->running_state->unique_bus_name = g_strdup (unique_bus_name);
|
||||
g_clear_object (&app->running_state->remote_menu);
|
||||
app->running_state->remote_menu = g_dbus_menu_model_get (app->running_state->session, unique_bus_name, app_menu_object_path);
|
||||
actions = g_dbus_action_group_get (app->running_state->session, unique_bus_name, application_object_path);
|
||||
gtk_action_muxer_insert (app->running_state->muxer, "app", G_ACTION_GROUP (actions));
|
||||
g_object_unref (actions);
|
||||
@ -1443,11 +1435,9 @@ unref_running_state (ShellAppRunningState *state)
|
||||
g_clear_object (&state->cancellable);
|
||||
}
|
||||
|
||||
g_clear_object (&state->remote_menu);
|
||||
g_clear_object (&state->muxer);
|
||||
g_clear_object (&state->session);
|
||||
g_clear_pointer (&state->unique_bus_name, g_free);
|
||||
g_clear_pointer (&state->remote_menu, g_free);
|
||||
|
||||
g_slice_free (ShellAppRunningState, state);
|
||||
}
|
||||
@ -1575,19 +1565,6 @@ shell_app_class_init(ShellAppClass *klass)
|
||||
"The action group exported by the remote application",
|
||||
G_TYPE_ACTION_GROUP,
|
||||
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
||||
/**
|
||||
* ShellApp:menu:
|
||||
*
|
||||
* The #GMenuProxy associated with this ShellApp, if any. See the
|
||||
* documentation of #GMenuModel for details.
|
||||
*/
|
||||
g_object_class_install_property (gobject_class,
|
||||
PROP_MENU,
|
||||
g_param_spec_object ("menu",
|
||||
"Application Menu",
|
||||
"The primary menu exported by the remote application",
|
||||
G_TYPE_MENU_MODEL,
|
||||
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
||||
/**
|
||||
* ShellApp:app-info:
|
||||
*
|
||||
|
@ -67,7 +67,7 @@ int shell_app_compare_by_name (ShellApp *app, ShellApp *other);
|
||||
int shell_app_compare (ShellApp *app, ShellApp *other);
|
||||
|
||||
void shell_app_update_window_actions (ShellApp *app, MetaWindow *window);
|
||||
void shell_app_update_app_menu (ShellApp *app, MetaWindow *window);
|
||||
void shell_app_update_app_actions (ShellApp *app, MetaWindow *window);
|
||||
|
||||
gboolean shell_app_get_busy (ShellApp *app);
|
||||
|
||||
|
@ -1,166 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Written by:
|
||||
* Jasper St. Pierre <jstpierre@mecheye.net>
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "shell-menu-tracker.h"
|
||||
#include "gtkmenutracker.h"
|
||||
|
||||
/**
|
||||
* SECTION:shell-menu-tracker
|
||||
* @short_description: a simple wrapper around #GtkMenuTracker
|
||||
* to make it bindable.
|
||||
*/
|
||||
|
||||
struct _ShellMenuTracker
|
||||
{
|
||||
guint ref_count;
|
||||
|
||||
GtkMenuTracker *tracker;
|
||||
|
||||
ShellMenuTrackerInsertFunc insert_func;
|
||||
gpointer insert_user_data;
|
||||
GDestroyNotify insert_notify;
|
||||
ShellMenuTrackerRemoveFunc remove_func;
|
||||
gpointer remove_user_data;
|
||||
GDestroyNotify remove_notify;
|
||||
};
|
||||
|
||||
static void
|
||||
shell_menu_tracker_insert_func (GtkMenuTrackerItem *item,
|
||||
gint position,
|
||||
gpointer user_data)
|
||||
{
|
||||
ShellMenuTracker *tracker = (ShellMenuTracker *) user_data;
|
||||
tracker->insert_func (item, position, tracker->insert_user_data);
|
||||
}
|
||||
|
||||
static void
|
||||
shell_menu_tracker_remove_func (gint position,
|
||||
gpointer user_data)
|
||||
{
|
||||
ShellMenuTracker *tracker = (ShellMenuTracker *) user_data;
|
||||
tracker->remove_func (position, tracker->remove_user_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* shell_menu_tracker_new:
|
||||
* @observable:
|
||||
* @model:
|
||||
* @action_namespace: (nullable):
|
||||
* @insert_func:
|
||||
* @insert_user_data:
|
||||
* @insert_notify:
|
||||
* @remove_func:
|
||||
* @remove_user_data:
|
||||
* @remove_notify:
|
||||
*/
|
||||
ShellMenuTracker *
|
||||
shell_menu_tracker_new (GtkActionObservable *observable,
|
||||
GMenuModel *model,
|
||||
const gchar *action_namespace,
|
||||
ShellMenuTrackerInsertFunc insert_func,
|
||||
gpointer insert_user_data,
|
||||
GDestroyNotify insert_notify,
|
||||
ShellMenuTrackerRemoveFunc remove_func,
|
||||
gpointer remove_user_data,
|
||||
GDestroyNotify remove_notify)
|
||||
{
|
||||
ShellMenuTracker *tracker = g_slice_new0 (ShellMenuTracker);
|
||||
|
||||
tracker->ref_count = 1;
|
||||
tracker->insert_func = insert_func;
|
||||
tracker->insert_user_data = insert_user_data;
|
||||
tracker->insert_notify = insert_notify;
|
||||
tracker->remove_func = remove_func;
|
||||
tracker->remove_user_data = remove_user_data;
|
||||
tracker->remove_notify = remove_notify;
|
||||
|
||||
tracker->tracker = gtk_menu_tracker_new (observable,
|
||||
model,
|
||||
TRUE, /* with separators */
|
||||
action_namespace,
|
||||
shell_menu_tracker_insert_func,
|
||||
shell_menu_tracker_remove_func,
|
||||
tracker);
|
||||
|
||||
return tracker;
|
||||
}
|
||||
|
||||
ShellMenuTracker *
|
||||
shell_menu_tracker_new_for_item_submenu (GtkMenuTrackerItem *item,
|
||||
ShellMenuTrackerInsertFunc insert_func,
|
||||
gpointer insert_user_data,
|
||||
GDestroyNotify insert_notify,
|
||||
ShellMenuTrackerRemoveFunc remove_func,
|
||||
gpointer remove_user_data,
|
||||
GDestroyNotify remove_notify)
|
||||
{
|
||||
ShellMenuTracker *tracker = g_slice_new0 (ShellMenuTracker);
|
||||
|
||||
tracker->ref_count = 1;
|
||||
tracker->insert_func = insert_func;
|
||||
tracker->insert_user_data = insert_user_data;
|
||||
tracker->insert_notify = insert_notify;
|
||||
tracker->remove_func = remove_func;
|
||||
tracker->remove_user_data = remove_user_data;
|
||||
tracker->remove_notify = remove_notify;
|
||||
|
||||
tracker->tracker = gtk_menu_tracker_new_for_item_submenu (item,
|
||||
shell_menu_tracker_insert_func,
|
||||
shell_menu_tracker_remove_func,
|
||||
tracker);
|
||||
|
||||
return tracker;
|
||||
}
|
||||
|
||||
ShellMenuTracker *
|
||||
shell_menu_tracker_ref (ShellMenuTracker *tracker)
|
||||
{
|
||||
tracker->ref_count++;
|
||||
return tracker;
|
||||
}
|
||||
|
||||
void
|
||||
shell_menu_tracker_unref (ShellMenuTracker *tracker)
|
||||
{
|
||||
if (tracker->ref_count-- <= 0)
|
||||
{
|
||||
shell_menu_tracker_destroy (tracker);
|
||||
g_slice_free (ShellMenuTracker, tracker);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
shell_menu_tracker_destroy (ShellMenuTracker *tracker)
|
||||
{
|
||||
if (tracker->tracker != NULL)
|
||||
{
|
||||
gtk_menu_tracker_free (tracker->tracker);
|
||||
tracker->tracker = NULL;
|
||||
tracker->insert_notify (tracker->insert_user_data);
|
||||
tracker->remove_notify (tracker->remove_user_data);
|
||||
}
|
||||
}
|
||||
|
||||
G_DEFINE_BOXED_TYPE(ShellMenuTracker,
|
||||
shell_menu_tracker,
|
||||
shell_menu_tracker_ref,
|
||||
shell_menu_tracker_unref)
|
@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Written by:
|
||||
* Jasper St. Pierre <jstpierre@mecheye.net>
|
||||
*/
|
||||
|
||||
#ifndef __SHELL_MENU_TRACKER_H__
|
||||
#define __SHELL_MENU_TRACKER_H__
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
#include "gtkmenutrackeritem.h"
|
||||
|
||||
typedef struct _ShellMenuTracker ShellMenuTracker;
|
||||
|
||||
GType shell_menu_tracker_get_type (void) G_GNUC_CONST;
|
||||
|
||||
typedef void (* ShellMenuTrackerInsertFunc) (GtkMenuTrackerItem *item,
|
||||
gint position,
|
||||
gpointer user_data);
|
||||
|
||||
typedef void (* ShellMenuTrackerRemoveFunc) (gint position,
|
||||
gpointer user_data);
|
||||
|
||||
ShellMenuTracker * shell_menu_tracker_new (GtkActionObservable *observable,
|
||||
GMenuModel *model,
|
||||
const gchar *action_namespace,
|
||||
ShellMenuTrackerInsertFunc insert_func,
|
||||
gpointer insert_user_data,
|
||||
GDestroyNotify insert_notify,
|
||||
ShellMenuTrackerRemoveFunc remove_func,
|
||||
gpointer remove_user_data,
|
||||
GDestroyNotify remove_notify);
|
||||
ShellMenuTracker * shell_menu_tracker_new_for_item_submenu (GtkMenuTrackerItem *item,
|
||||
ShellMenuTrackerInsertFunc insert_func,
|
||||
gpointer insert_user_data,
|
||||
GDestroyNotify insert_notify,
|
||||
ShellMenuTrackerRemoveFunc remove_func,
|
||||
gpointer remove_user_data,
|
||||
GDestroyNotify remove_notify);
|
||||
|
||||
ShellMenuTracker * shell_menu_tracker_ref (ShellMenuTracker *tracker);
|
||||
void shell_menu_tracker_unref (ShellMenuTracker *tracker);
|
||||
void shell_menu_tracker_destroy (ShellMenuTracker *tracker);
|
||||
|
||||
#endif /* __SHELL_MENU_TRACKER_H__ */
|
@ -478,7 +478,7 @@ update_focus_app (ShellWindowTracker *self)
|
||||
if (new_focus_app)
|
||||
{
|
||||
shell_app_update_window_actions (new_focus_app, new_focus_win);
|
||||
shell_app_update_app_menu (new_focus_app, new_focus_win);
|
||||
shell_app_update_app_actions (new_focus_app, new_focus_win);
|
||||
}
|
||||
|
||||
set_focus_app (self, new_focus_app);
|
||||
|
Loading…
Reference in New Issue
Block a user