Compare commits
	
		
			7 Commits
		
	
	
		
			benzea/sys
			...
			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);
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user