diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index 747639053..248bb0c6a 100644 --- a/data/theme/gnome-shell.css +++ b/data/theme/gnome-shell.css @@ -140,6 +140,10 @@ StTooltip StLabel { height: 1em; } +.popup-alternating-menu-item:alternate { + font-weight: bold; +} + .popup-slider-menu-item { height: 1em; min-width: 15em; diff --git a/js/ui/popupMenu.js b/js/ui/popupMenu.js index 00cc02082..0a26ba71e 100644 --- a/js/ui/popupMenu.js +++ b/js/ui/popupMenu.js @@ -328,6 +328,107 @@ PopupSeparatorMenuItem.prototype = { } }; +const PopupAlternatingMenuItemState = { + DEFAULT: 0, + ALTERNATIVE: 1 +} + +function PopupAlternatingMenuItem() { + this._init.apply(this, arguments); +} + +PopupAlternatingMenuItem.prototype = { + __proto__: PopupBaseMenuItem.prototype, + + _init: function(text, alternateText, params) { + PopupBaseMenuItem.prototype._init.call(this, params); + this.actor.add_style_class_name('popup-alternating-menu-item'); + + this._text = text; + this._alternateText = alternateText; + this.label = new St.Label({ text: text }); + this.state = PopupAlternatingMenuItemState.DEFAULT; + this.addActor(this.label); + + this.actor.connect('notify::mapped', Lang.bind(this, this._onMapped)); + }, + + _onMapped: function() { + if (this.actor.mapped) { + this._capturedEventId = global.stage.connect('captured-event', + Lang.bind(this, this._onCapturedEvent)); + this._updateStateFromModifiers(); + } else { + if (this._capturedEventId != 0) { + global.stage.disconnect(this._capturedEventId); + this._capturedEventId = 0; + } + } + }, + + _setState: function(state) { + if (this.state != state) { + if (state == PopupAlternatingMenuItemState.ALTERNATIVE && !this._canAlternate()) + return; + + this.state = state; + this._updateLabel(); + } + }, + + _updateStateFromModifiers: function() { + let [x, y, mods] = global.get_pointer(); + let state; + + if ((mods & Clutter.ModifierType.MOD1_MASK) == 0) { + state = PopupAlternatingMenuItemState.DEFAULT; + } else { + state = PopupAlternatingMenuItemState.ALTERNATIVE; + } + + this._setState(state); + }, + + _onCapturedEvent: function(actor, event) { + if (event.type() != Clutter.EventType.KEY_PRESS && + event.type() != Clutter.EventType.KEY_RELEASE) + return false; + + let key = event.get_key_symbol(); + + if (key == Clutter.KEY_Alt_L || key == Clutter.KEY_Alt_R) + this._updateStateFromModifiers(); + + return false; + }, + + _updateLabel: function() { + if (this.state == PopupAlternatingMenuItemState.ALTERNATIVE) { + this.actor.add_style_pseudo_class('alternate'); + this.label.set_text(this._alternateText); + } else { + this.actor.remove_style_pseudo_class('alternate'); + this.label.set_text(this._text); + } + }, + + _canAlternate: function() { + if (this.state == PopupAlternatingMenuItemState.DEFAULT && !this._alternateText) + return false; + return true; + }, + + updateText: function(text, alternateText) { + this._text = text; + this._alternateText = alternateText; + + if (!this._canAlternate()) + this._setState(PopupAlternatingMenuItemState.DEFAULT); + + this._updateLabel(); + } +}; + function PopupSliderMenuItem() { this._init.apply(this, arguments); }