From 420753637707d5af55acfa4172b26e09329ac577 Mon Sep 17 00:00:00 2001 From: Giovanni Campagna Date: Tue, 4 Jan 2011 23:04:56 +0100 Subject: [PATCH] Status Area: add keyboard layout selector Add an indicator for the current keyboard layout, based on libgnomekbd. The indicator is shown when more than one group is loaded in X and it is not disabled in GSettings. https://bugzilla.gnome.org/show_bug.cgi?id=600771 --- js/Makefile.am | 1 + js/ui/panel.js | 3 +- js/ui/status/keyboard.js | 205 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 js/ui/status/keyboard.js diff --git a/js/Makefile.am b/js/Makefile.am index 49de2ff2a..b2975fc27 100644 --- a/js/Makefile.am +++ b/js/Makefile.am @@ -46,6 +46,7 @@ nobase_dist_js_DATA = \ ui/statusIconDispatcher.js \ ui/statusMenu.js \ ui/status/accessibility.js \ + ui/status/keyboard.js \ ui/status/power.js \ ui/status/volume.js \ ui/status/bluetooth.js \ diff --git a/js/ui/panel.js b/js/ui/panel.js index 4fe60a95d..97789d802 100644 --- a/js/ui/panel.js +++ b/js/ui/panel.js @@ -36,7 +36,8 @@ const STANDARD_TRAY_ICON_ORDER = ['a11y', 'display', 'keyboard', 'volume', 'blue const STANDARD_TRAY_ICON_SHELL_IMPLEMENTATION = { 'a11y': imports.ui.status.accessibility.ATIndicator, 'volume': imports.ui.status.volume.Indicator, - 'battery': imports.ui.status.power.Indicator + 'battery': imports.ui.status.power.Indicator, + 'keyboard': imports.ui.status.keyboard.XKBIndicator }; if (Config.HAVE_BLUETOOTH) diff --git a/js/ui/status/keyboard.js b/js/ui/status/keyboard.js new file mode 100644 index 000000000..b01e28b69 --- /dev/null +++ b/js/ui/status/keyboard.js @@ -0,0 +1,205 @@ +/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ + +const Clutter = imports.gi.Clutter; +const GdkPixbuf = imports.gi.GdkPixbuf; +const Gkbd = imports.gi.Gkbd; +const Gio = imports.gi.Gio; +const GLib = imports.gi.GLib; +const Lang = imports.lang; +const Shell = imports.gi.Shell; +const St = imports.gi.St; + +const PopupMenu = imports.ui.popupMenu; +const PanelMenu = imports.ui.panelMenu; + +const Gettext = imports.gettext.domain('gnome-shell'); +const _ = Gettext.gettext; + +const INDICATOR_SCHEMA = 'org.gnome.settings-daemon.plugins.keyboard'; + +function LayoutMenuItem() { + this._init.apply(this, arguments); +} + +LayoutMenuItem.prototype = { + __proto__: PopupMenu.PopupBaseMenuItem.prototype, + + _init: function(config, id, indicator, long_name) { + PopupMenu.PopupBaseMenuItem.prototype._init.call(this); + + this._config = config; + this._id = id; + this.label = new St.Label({ text: long_name }); + this.indicator = indicator; + this.addActor(this.label); + this.addActor(this.indicator); + }, + + activate: function(event) { + this._config.lock_group(this._id); + } +}; + +function XKBIndicator() { + this._init.apply(this, arguments); +} + +XKBIndicator.prototype = { + __proto__: PanelMenu.Button.prototype, + + _init: function() { + PanelMenu.Button.prototype._init.call(this, St.Align.START); + + this._container = new Shell.GenericContainer(); + this._container.connect('get-preferred-width', Lang.bind(this, this._get_preferred_width)); + this._container.connect('get-preferred-height', Lang.bind(this, this._get_preferred_height)); + this._container.connect('allocate', Lang.bind(this, this._allocate)); + this.actor.set_child(this._container); + + this._iconActor = new St.Icon({ icon_name: 'keyboard', icon_type: St.IconType.SYMBOLIC, style_class: 'system-status-icon' }); + this._container.add_actor(this._iconActor); + this._labelActors = [ ]; + this._layoutItems = [ ]; + + this._indicatorSettings = new Gio.Settings({ schema: INDICATOR_SCHEMA }); + this._indicatorSettings.connect('changed::disable-indicator', Lang.bind(this, this._sync_config)); + + this._disableIndicator = false; + this._showFlags = false; + this._config = Gkbd.Configuration.get(); + this._config.connect('changed', Lang.bind(this, this._sync_config)); + this._config.connect('group-changed', Lang.bind(this, this._sync_group)); + this._config.start_listen(); + + this._sync_config(); + + this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); + this.menu.addAction(_("Keyboard Preferences"), function() { + GLib.spawn_command_line_async('gnome-control-center region'); + }); + }, + + _sync_config: function() { + this._disableIndicator = this._indicatorSettings.get_boolean('disable-indicator'); + + this._showFlags = this._config.if_flags_shown(); + if (this._showFlags) { + this._container.set_skip_paint(this._iconActor, false); + } else { + this._container.set_skip_paint(this._iconActor, true); + } + + let groups = this._config.get_group_names(); + if (groups.length > 1 && !this._disableIndicator) { + this.actor.show(); + } else { + this.menu.close(); + this.actor.hide(); + } + + for (let i = 0; i < this._layoutItems.length; i++) + this._layoutItems[i].destroy(); + + for (let i = 0; i < this._labelActors.length; i++) + this._labelActors[i].destroy(); + + let short_names = this._config.get_short_group_names(); + + this._selectedLayout = null; + this._layoutItems = [ ]; + this._selectedLabel = null; + this._labelActors = [ ]; + for (let i = 0; i < groups.length; i++) { + let icon_name = this._config.get_group_name(i); + let actor; + if (this._showFlags) + actor = new St.Icon({ icon_name: icon_name, icon_type: St.IconType.SYMBOLIC, style_class: 'popup-menu-icon' }); + else + actor = new St.Label({ text: short_names[i] }); + let item = new LayoutMenuItem(this._config, i, actor, groups[i]); + item._short_group_name = short_names[i]; + item._icon_name = icon_name; + this._layoutItems.push(item); + this.menu.addMenuItem(item, i); + + let shortLabel = new St.Label({ text: short_names[i] }); + this._labelActors.push(shortLabel); + this._container.add_actor(shortLabel); + this._container.set_skip_paint(shortLabel, true); + } + + this._sync_group(); + }, + + _sync_group: function() { + let selected = this._config.get_current_group(); + + if (this._selectedLayout) { + this._selectedLayout.setShowDot(false); + this._selectedLayout = null; + } + + if (this._selectedLabel) { + this._container.set_skip_paint(this._selectedLabel, true); + this._selectedLabel = null; + } + + let item = this._layoutItems[selected]; + item.setShowDot(true); + + this._iconActor.icon_name = item._icon_name; + this._selectedLabel = this._labelActors[selected]; + this._container.set_skip_paint(this._selectedLabel, this._showFlags); + + this._selectedLayout = item; + }, + + _get_preferred_width: function(container, for_height, alloc) { + /* Here, and in _get_preferred_height, we need to query for the + height of all children, but we ignore the results for those + we don't actually display. */ + let max_min_width = 0, max_natural_width = 0; + if (this._showFlags) + [max_min_width, max_natural_width] = this._iconActor.get_preferred_width(for_height); + + for (let i = 0; i < this._labelActors.length; i++) { + let [min_width, natural_width] = this._labelActors[i].get_preferred_width(for_height); + if (!this._showFlags) { + max_min_width = Math.max(max_min_width, min_width); + max_natural_width = Math.max(max_natural_width, natural_width); + } + } + + alloc.min_size = max_min_width; + alloc.natural_size = max_natural_width; + }, + + _get_preferred_height: function(container, for_width, alloc) { + let max_min_height = 0, max_natural_height = 0; + if (this._showFlags) + [max_min_height, max_natural_height] = this._iconActor.get_preferred_height(for_width); + + for (let i = 0; i < this._labelActors.length; i++) { + let [min_height, natural_height] = this._labelActors[i].get_preferred_height(for_width); + if (!this._showFlags) { + max_min_height = Math.max(max_min_height, min_height); + max_natural_height = Math.max(max_natural_height, natural_height); + } + } + + alloc.min_size = max_min_height; + alloc.natural_size = max_natural_height; + }, + + _allocate: function(container, box, flags) { + // translate box to (0, 0) + box.x2 -= box.x1; + box.x1 = 0; + box.y2 -= box.y1; + box.y1 = 0; + + this._iconActor.allocate_align_fill(box, 0.5, 0, false, false, flags); + for (let i = 0; i < this._labelActors.length; i++) + this._labelActors[i].allocate_align_fill(box, 0.5, 0, false, false, flags); + } +}; \ No newline at end of file