From 9659d934ac190a6363fc5b59f3c62ef305f56c52 Mon Sep 17 00:00:00 2001 From: Rui Matos Date: Sat, 1 Sep 2012 23:57:53 +0200 Subject: [PATCH] status/keyboard: Add menu items for IBus Anthy's InputMode, TypingMode IBus has a properties API which are basically generic knobs into the engine which are serialized and presented in a way that allows us to easily build actionable UI elements with them. Instead of implementing the whole generic system and accepting everything coming out of the engines, for now, this patch just adds support for a couple of important IBus Anthy properties. https://bugzilla.gnome.org/show_bug.cgi?id=682314 --- js/ui/status/keyboard.js | 229 +++++++++++++++++++++++++++++++++++---- 1 file changed, 207 insertions(+), 22 deletions(-) diff --git a/js/ui/status/keyboard.js b/js/ui/status/keyboard.js index ce5824e42..27dac0c53 100644 --- a/js/ui/status/keyboard.js +++ b/js/ui/status/keyboard.js @@ -5,6 +5,7 @@ const GLib = imports.gi.GLib; const GnomeDesktop = imports.gi.GnomeDesktop; const Lang = imports.lang; const Shell = imports.gi.Shell; +const Signals = imports.signals; const St = imports.gi.St; try { @@ -45,6 +46,7 @@ const IBusManager = new Lang.Class({ this._panelService = null; this._engines = {}; this._ready = false; + this._registerPropertiesId = 0; this._nameWatcherId = Gio.DBus.session.watch_name(IBus.SERVICE_IBUS, Gio.BusNameWatcherFlags.NONE, @@ -63,6 +65,7 @@ const IBusManager = new Lang.Class({ this._candidatePopup.setPanelService(null); this._engines = {}; this._ready = false; + this._registerPropertiesId = 0; }, _onNameAppeared: function() { @@ -100,6 +103,11 @@ const IBusManager = new Lang.Class({ this._panelService = new IBus.PanelService({ connection: this._ibus.get_connection(), object_path: IBus.PATH_PANEL }); this._candidatePopup.setPanelService(this._panelService); + // Need to set this to get 'global-engine-changed' emitions + this._ibus.set_watch_ibus_signal(true); + this._ibus.connect('global-engine-changed', Lang.bind(this, this._resetProperties)); + this._panelService.connect('update-property', Lang.bind(this, this._updateProperty)); + this._resetProperties(); } else { this._clear(); return; @@ -116,6 +124,39 @@ const IBusManager = new Lang.Class({ this._readyCallback(); }, + _resetProperties: function() { + this.emit('properties-registered', null); + + if (this._registerPropertiesId != 0) + return; + + this._registerPropertiesId = + this._panelService.connect('register-properties', Lang.bind(this, function(p, props) { + if (!props.get(0)) + return; + + this._panelService.disconnect(this._registerPropertiesId); + this._registerPropertiesId = 0; + + this.emit('properties-registered', props); + })); + }, + + _updateProperty: function(panel, prop) { + this.emit('property-updated', prop); + }, + + activateProperty: function(key, state) { + this._panelService.property_activate(key, state); + }, + + hasProperties: function(id) { + if (id == 'anthy') + return true; + + return false; + }, + getEngineDesc: function(id) { if (!IBus || !this._ready) return null; @@ -123,6 +164,7 @@ const IBusManager = new Lang.Class({ return this._engines[id]; } }); +Signals.addSignalMethods(IBusManager.prototype); const LayoutMenuItem = new Lang.Class({ Name: 'LayoutMenuItem', @@ -142,6 +184,12 @@ const InputSourceIndicator = new Lang.Class({ Name: 'InputSourceIndicator', Extends: PanelMenu.Button, + _propertiesWhitelist: [ + 'InputMode', + 'TypingMode', + 'DictMode' + ], + _init: function() { this.parent(0.0, _("Keyboard")); @@ -162,8 +210,16 @@ const InputSourceIndicator = new Lang.Class({ this._currentSourceIndex = this._settings.get_uint(KEY_CURRENT_INPUT_SOURCE); this._xkbInfo = new GnomeDesktop.XkbInfo(); - this._ibusManager = new IBusManager(Lang.bind(this, this._inputSourcesChanged)); + this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); + this._propSection = new PopupMenu.PopupMenuSection(); + this.menu.addMenuItem(this._propSection); + this._propSection.actor.hide(); + this._properties = null; + + this._ibusManager = new IBusManager(Lang.bind(this, this._inputSourcesChanged)); + this._ibusManager.connect('properties-registered', Lang.bind(this, this._ibusPropertiesRegistered)); + this._ibusManager.connect('property-updated', Lang.bind(this, this._ibusPropertyUpdated)); this._inputSourcesChanged(); this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); @@ -185,32 +241,41 @@ const InputSourceIndicator = new Lang.Class({ _currentInputSourceChanged: function() { let nVisibleSources = Object.keys(this._layoutItems).length; - if (nVisibleSources < 2) - return; - - let nSources = this._settings.get_value(KEY_INPUT_SOURCES).n_children(); let newCurrentSourceIndex = this._settings.get_uint(KEY_CURRENT_INPUT_SOURCE); - if (newCurrentSourceIndex >= nSources) - return; + let newLayoutItem = this._layoutItems[newCurrentSourceIndex]; + let hasProperties; - if (!this._layoutItems[newCurrentSourceIndex]) { - // This source index is invalid as we weren't able to - // build a menu item for it, so we hide ourselves since we - // can't fix it here. *shrug* + if (newLayoutItem) + hasProperties = this._ibusManager.hasProperties(newLayoutItem.ibusEngineId); + else + hasProperties = false; + + if (!newLayoutItem || (nVisibleSources < 2 && !hasProperties)) { + // This source index might be invalid if we weren't able + // to build a menu item for it, so we hide ourselves since + // we can't fix it here. *shrug* + + // We also hide if we have only one visible source unless + // it's an IBus source with properties. this.menu.close(); this.actor.hide(); return; - } else { - this.actor.show(); } + this.actor.show(); + if (this._layoutItems[this._currentSourceIndex]) { this._layoutItems[this._currentSourceIndex].setShowDot(false); this._container.set_skip_paint(this._labelActors[this._currentSourceIndex], true); } - this._layoutItems[newCurrentSourceIndex].setShowDot(true); - this._container.set_skip_paint(this._labelActors[newCurrentSourceIndex], false); + newLayoutItem.setShowDot(true); + + let newLabelActor = this._labelActors[newCurrentSourceIndex]; + this._container.set_skip_paint(newLabelActor, false); + + if (hasProperties) + newLabelActor.set_text(newLayoutItem.indicator.get_text()); this._currentSourceIndex = newCurrentSourceIndex; }, @@ -246,6 +311,7 @@ const InputSourceIndicator = new Lang.Class({ info.exists = true; info.displayName = language + ' (' + engineDesc.get_longname() + ')'; info.shortName = this._makeEngineShortName(engineDesc); + info.ibusEngineId = id; } } @@ -260,13 +326,6 @@ const InputSourceIndicator = new Lang.Class({ infos.push(info); } - if (infos.length > 1) { - this.actor.show(); - } else { - this.menu.close(); - this.actor.hide(); - } - for (let i = 0; i < infos.length; i++) { let info = infos[i]; if (infosByShortName[info.shortName].length > 1) { @@ -275,6 +334,7 @@ const InputSourceIndicator = new Lang.Class({ } let item = new LayoutMenuItem(info.displayName, info.shortName); + item.ibusEngineId = info.ibusEngineId; this._layoutItems[info.sourceIndex] = item; this.menu.addMenuItem(item, i); item.connect('activate', Lang.bind(this, function() { @@ -332,6 +392,131 @@ const InputSourceIndicator = new Lang.Class({ return String.fromCharCode(0x2328); // keyboard glyph }, + _propertyWhitelisted: function(prop) { + for (let i = 0; i < this._propertiesWhitelist.length; ++i) { + let key = prop.get_key(); + if (key.substr(0, this._propertiesWhitelist[i].length) == this._propertiesWhitelist[i]) + return true; + } + return false; + }, + + _ibusPropertiesRegistered: function(im, props) { + this._properties = props; + this._buildPropSection(); + }, + + _ibusPropertyUpdated: function(im, prop) { + if (!this._propertyWhitelisted(prop)) + return; + + if (this._updateSubProperty(this._properties, prop)) + this._buildPropSection(); + }, + + _updateSubProperty: function(props, prop) { + if (!props) + return false; + + let p; + for (let i = 0; (p = props.get(i)) != null; ++i) { + if (p.get_key() == prop.get_key() && p.get_prop_type() == prop.get_prop_type()) { + p.update(prop); + return true; + } else if (p.get_prop_type() == IBus.PropType.MENU) { + if (this._updateSubProperty(p.get_sub_props(), prop)) + return true; + } + } + return false; + }, + + _updateIndicatorLabel: function(text) { + let layoutItem = this._layoutItems[this._currentSourceIndex]; + let hasProperties; + + if (layoutItem) + hasProperties = this._ibusManager.hasProperties(layoutItem.ibusEngineId); + else + hasProperties = false; + + if (hasProperties) + this._labelActors[this._currentSourceIndex].set_text(text); + }, + + _buildPropSection: function() { + this._propSection.actor.hide(); + this._propSection.removeAll(); + + this._buildPropSubMenu(this._propSection, this._properties); + + if (!this._propSection.isEmpty()) + this._propSection.actor.show(); + }, + + _buildPropSubMenu: function(menu, props) { + if (!props) + return; + + let radioGroup = []; + let p; + for (let i = 0; (p = props.get(i)) != null; ++i) { + let prop = p; + + if (!this._propertyWhitelisted(prop) || + !prop.get_visible()) + continue; + + if (prop.get_key() == 'InputMode') { + let text; + if (prop.get_symbol) + text = prop.get_symbol().get_text(); + else + text = prop.get_label().get_text(); + + if (text && text.length > 0 && text.length < 3) + this._updateIndicatorLabel(text); + } + + let item; + let type = prop.get_prop_type(); + if (type == IBus.PropType.MENU) { + item = new PopupMenu.PopupSubMenuMenuItem(prop.get_label().get_text()); + this._buildPropSubMenu(item.menu, prop.get_sub_props()); + } else if (type == IBus.PropType.RADIO) { + item = new PopupMenu.PopupMenuItem(prop.get_label().get_text()); + item.prop = prop; + radioGroup.push(item); + item.radioGroup = radioGroup; + item.setShowDot(prop.get_state() == IBus.PropState.CHECKED); + item.connect('activate', Lang.bind(this, function() { + if (item.prop.get_state() == IBus.PropState.CHECKED) + return; + + let group = item.radioGroup; + for (let i = 0; i < group.length; ++i) { + if (group[i] == item) { + item.setShowDot(true); + item.prop.set_state(IBus.PropState.CHECKED); + this._ibusManager.activateProperty(item.prop.get_key(), + IBus.PropState.CHECKED); + } else { + group[i].setShowDot(false); + group[i].prop.set_state(IBus.PropState.UNCHECKED); + this._ibusManager.activateProperty(group[i].prop.get_key(), + IBus.PropState.UNCHECKED); + } + } + })); + } else { + continue; + } + + item.setSensitive(prop.get_sensitive()); + menu.addMenuItem(item); + } + }, + _containerGetPreferredWidth: function(container, for_height, alloc) { // Here, and in _containerGetPreferredHeight, we need to query // for the height of all children, but we ignore the results