Compare commits

...

7 Commits

Author SHA1 Message Date
Rui Matos
d4802861ed status/keyboard: Adapt to changes in the settings schema 2012-05-15 02:24:23 +02:00
Rui Matos
a8fa0b8146 candidatePanel: Use the Lang.Class framework 2012-05-03 13:20:01 +02:00
Rui Matos
cf0bb62f40 candidatePanel: Remove the PopupSeparatorMenuItem copy
No reason to not use the real deal.
2012-05-03 13:20:01 +02:00
Rui Matos
de8106967d candidatePanel: Remove hardcoded styling
The styling should be done in the CSS file.
2012-05-03 13:19:58 +02:00
Rui Matos
1a24f061cf candidatePanel: A candidate popup for IBus input methods
This is an implementation of the org.freedesktop.IBus.Panel API which
shows a shell style popup (BoxPointer) when using an IBus input
method.

The original code is from the ibus-gjs project[1] with minor
modifications.

[1] https://github.com/fujiwarat/ibus-gjs
2012-05-03 13:19:53 +02:00
Rui Matos
e038845458 status/keyboard: Add the input source switcher keybindings 2012-05-03 13:19:53 +02:00
Rui Matos
b03273c765 status/keyboard: Initial port to the new input sources settings 2012-05-03 13:19:53 +02:00
5 changed files with 899 additions and 96 deletions

View File

@ -2040,3 +2040,30 @@ StScrollBar StButton#vhandle:hover
-arrow-rise: 10px; -arrow-rise: 10px;
-boxpointer-gap: 5px; -boxpointer-gap: 5px;
} }
/* Candidate Window */
.candidate-panel {
min-width: 100px;
padding: .5em;
spacing: 0;
}
.candidate-area {
padding-top: 5px;
}
.candidate-label {
cursor: pointer;
}
.candidate-hlabel-content {
padding: 0em .5em 0em 0em;
}
.candidate-htext-content {
padding: 0em;
}
.candidate-vcontent {
padding: 0em .5em 0em 0em;
}

View File

@ -84,6 +84,7 @@ nobase_dist_js_DATA = \
ui/shellDBus.js \ ui/shellDBus.js \
ui/statusIconDispatcher.js \ ui/statusIconDispatcher.js \
ui/status/accessibility.js \ ui/status/accessibility.js \
ui/status/candidatePanel.js \
ui/status/keyboard.js \ ui/status/keyboard.js \
ui/status/network.js \ ui/status/network.js \
ui/status/power.js \ ui/status/power.js \

View File

@ -37,7 +37,7 @@ const STANDARD_STATUS_AREA_SHELL_IMPLEMENTATION = {
'a11y': imports.ui.status.accessibility.ATIndicator, 'a11y': imports.ui.status.accessibility.ATIndicator,
'volume': imports.ui.status.volume.Indicator, 'volume': imports.ui.status.volume.Indicator,
'battery': imports.ui.status.power.Indicator, 'battery': imports.ui.status.power.Indicator,
'keyboard': imports.ui.status.keyboard.XKBIndicator, 'keyboard': imports.ui.status.keyboard.InputSourceIndicator,
'userMenu': imports.ui.userMenu.UserMenuButton 'userMenu': imports.ui.userMenu.UserMenuButton
}; };
@ -55,7 +55,7 @@ const GDM_STATUS_AREA_SHELL_IMPLEMENTATION = {
'a11y': imports.ui.status.accessibility.ATIndicator, 'a11y': imports.ui.status.accessibility.ATIndicator,
'volume': imports.ui.status.volume.Indicator, 'volume': imports.ui.status.volume.Indicator,
'battery': imports.ui.status.power.Indicator, 'battery': imports.ui.status.power.Indicator,
'keyboard': imports.ui.status.keyboard.XKBIndicator, 'keyboard': imports.ui.status.keyboard.InputSourceIndicator,
'powerMenu': imports.gdm.powerMenu.PowerMenuButton 'powerMenu': imports.gdm.powerMenu.PowerMenuButton
}; };

View File

@ -0,0 +1,584 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/*
* Copyright 2012 Red Hat, Inc.
* Copyright 2012 Peng Huang <shawn.p.huang@gmail.com>
* Copyright 2012 Takao Fujiwara <tfujiwar@redhat.com>
* Copyright 2012 Tiger Soldier <tigersoldi@gmail.com>
*
* This program 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.1 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope 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 program. If not, see <http://www.gnu.org/licenses/>.
*/
const St = imports.gi.St;
const GLib = imports.gi.GLib;
const IBus = imports.gi.IBus;
const Lang = imports.lang;
const Signals = imports.signals;
const Shell = imports.gi.Shell;
const BoxPointer = imports.ui.boxpointer;
const Main = imports.ui.main;
const PopupMenu = imports.ui.popupMenu;
const ORIENTATION_HORIZONTAL = 0;
const ORIENTATION_VERTICAL = 1;
const ORIENTATION_SYSTEM = 2;
const StCandidateArea = new Lang.Class({
Name: 'StCandidateArea',
_init: function(orientation) {
this.actor = new St.BoxLayout({ style_class: 'candidate-area' });
this._orientation = orientation;
this._labels = [];
this._labelBoxes = [];
this._createUI();
},
_removeOldWidgets: function() {
this.actor.destroy_all_children();
this._labels = [];
this._labelBoxes = [];
},
_createUI: function() {
let vbox = null;
let hbox = null;
if (this._orientation == ORIENTATION_VERTICAL) {
vbox = new St.BoxLayout({ vertical: true,
style_class: 'candidate-vertical' });
this.actor.add_child(vbox,
{ expand: true,
x_fill: true,
y_fill: true
});
} else {
hbox = new St.BoxLayout({ vertical: false,
style_class: 'candidate-horizontal' });
this.actor.add_child(hbox,
{ expand: true,
x_fill: true,
y_fill: true
});
}
for (let i = 0; i < 16; i++) {
let label1 = new St.Label({ text: '1234567890abcdef'.charAt(i) + '.',
style_class: 'popup-menu-item',
reactive: true });
let label2 = new St.Label({ text: '' ,
style_class: 'popup-menu-item',
reactive: true });
if (this._orientation == ORIENTATION_VERTICAL) {
let candidateHBox = new St.BoxLayout({vertical: false});
let labelBox = new St.Bin({ style_class: 'candidate-hlabel-content' });
labelBox.set_child(label1);
labelBox.set_fill(true, true);
let textBox = new St.Bin({ style_class: 'candidate-htext-content' });
textBox.set_child(label2);
textBox.set_fill(true, true);
candidateHBox.add_child(labelBox,
{ expand: false,
x_fill: false,
y_fill: true
});
candidateHBox.add_child(textBox,
{ expand: true,
x_fill: true,
y_fill: true
});
vbox.add_child(candidateHBox);
this._labelBoxes.push(candidateHBox);
} else {
let candidateHBox = new St.BoxLayout({ style_class: 'candidate-vcontent',
vertical: false });
candidateHBox.add_child(label1);
candidateHBox.add_child(label2);
hbox.add_child(candidateHBox);
this._labelBoxes.push(candidateHBox);
}
this._labels.push([label1, label2]);
}
for (let i = 0; i < this._labels.length; i++) {
for(let j = 0; j < this._labels[i].length; j++) {
let widget = this._labels[i][j];
widget.candidateIndex = i;
widget.connect('button-press-event',
Lang.bind(this, function (widget, event) {
this._candidateClickedCB(widget, event);
}));
widget.connect('enter-event',
function(widget, event) {
widget.add_style_pseudo_class('hover');
});
widget.connect('leave-event',
function(widget, event) {
widget.remove_style_pseudo_class('hover');
});
}
}
},
_recreateUI: function() {
this._removeOldWidgets();
this._createUI();
},
_candidateClickedCB: function(widget, event) {
this.emit('candidate-clicked',
widget.candidateIndex,
event.get_button(),
event.get_state());
},
setLabels: function(labels) {
if (!labels || labels.length == 0) {
for (let i = 0; i < 16; i++) {
this._labels[i][0].set_text('1234567890abcdef'.charAt(i) + '.');
}
return;
}
for (let i = 0; i < labels.length && i < this._labels.length; i++) {
/* Use a ClutterActor attribute of Shell's theme instead of
* Pango.AttrList for the lookup window GUI and
* can ignore 'attrs' simply from IBus engines?
*/
let [text, attrs] = labels[i];
this._labels[i][0].set_text(text);
}
},
setCandidates: function(candidates, focusCandidate, showCursor) {
if (focusCandidate == undefined) {
focusCandidate = 0;
}
if (showCursor == undefined) {
showCursor = true;
}
if (candidates.length > this._labels.length) {
assert();
}
for (let i = 0; i < candidates.length; i++) {
/* Use a ClutterActor attribute of Shell's theme instead of
* Pango.AttrList for the lookup window GUI and
* can ignore 'attrs' simply from IBus engines?
*/
let [text, attrs] = candidates[i];
if (i == focusCandidate && showCursor) {
this._labels[i][1].add_style_pseudo_class('active');
} else {
this._labels[i][1].remove_style_pseudo_class('active');
}
this._labels[i][1].set_text(text);
this._labelBoxes[i].show();
}
for (let i = this._labelBoxes.length - 1; i >= candidates.length; i--) {
this._labelBoxes[i].hide();
}
},
setOrientation: function(orientation) {
if (orientation == this._orientation)
return;
this._orientation = orientation;
this._recreateUI();
},
showAll: function() {
this.actor.show();
},
hideAll: function() {
this.actor.hide();
},
});
Signals.addSignalMethods(StCandidateArea.prototype);
const CandidatePanel = new Lang.Class({
Name: 'CandidatePanel',
_init: function() {
this._orientation = ORIENTATION_VERTICAL;
this._currentOrientation = this._orientation;
this._preeditVisible = false;
this._auxStringVisible = false;
this._lookupTableVisible = false;
this._lookupTable = null;
this._cursorLocation = [0, 0, 0, 0];
this._movedCursorLocation = null;
this._initSt();
},
_initSt: function() {
this._arrowSide = St.Side.TOP;
this._arrowAlignment = 0.0;
this._boxPointer = new BoxPointer.BoxPointer(this._arrowSide,
{ x_fill: true,
y_fill: true,
x_align: St.Align.START });
this.actor = this._boxPointer.actor;
this.actor._delegate = this;
this.actor.style_class = 'popup-menu-boxpointer';
this.actor.add_style_class_name('popup-menu');
this.actor.add_style_class_name('candidate-panel');
this._cursorActor = new Shell.GenericContainer();
Main.uiGroup.add_actor(this.actor);
Main.uiGroup.add_actor(this._cursorActor);
this._stCandidatePanel = new St.BoxLayout({ style_class: 'candidate-panel',
vertical: true });
this._boxPointer.bin.set_child(this._stCandidatePanel);
this._stPreeditLabel = new St.Label({ style_class: 'popup-menu-item',
text: '' });
if (!this._preeditVisible) {
this._stPreeditLabel.hide();
}
this._stAuxLabel = new St.Label({ style_class: 'popup-menu-item',
text: '' });
if (!this._auxVisible) {
this._stAuxLabel.hide();
}
this._separator = new PopupMenu.PopupSeparatorMenuItem();
if (!this._preeditVisible && !this._auxVisible) {
this._separator.actor.hide();
}
// create candidates area
this._stCandidateArea = new StCandidateArea(this._currentOrientation);
this._stCandidateArea.connect('candidate-clicked',
Lang.bind(this, function(x, i, b, s) {
this.emit('candidate-clicked', i, b, s);}));
this.updateLookupTable(this._lookupTable, this._lookupTableVisible);
// TODO: page up/down GUI
this._packAllStWidgets();
this._isVisible = true;
this.hideAll();
this._checkShowStates();
},
_packAllStWidgets: function() {
this._stCandidatePanel.add_child(this._stPreeditLabel,
{ x_fill: true,
y_fill: false,
x_align: St.Align.MIDDLE,
y_align: St.Align.START });
this._stCandidatePanel.add_child(this._stAuxLabel,
{ x_fill: true,
y_fill: false,
x_align: St.Align.MIDDLE,
y_align: St.Align.MIDDLE });
this._stCandidatePanel.add_child(this._separator.actor,
{ x_fill: true,
y_fill: false,
x_align: St.Align.MIDDLE,
y_align: St.Align.MIDDLE });
this._stCandidatePanel.add_child(this._stCandidateArea.actor,
{ x_fill: true,
y_fill: false,
x_align: St.Align.MIDDLE,
y_align: St.Align.END });
},
showPreeditText: function() {
this._preeditVisible = true;
this._stPreeditLabel.show();
this._checkShowStates();
},
hidePreeditText: function() {
this._preeditVisible = false;
this._checkShowStates();
this._stPreeditLabel.hide();
},
updatePreeditText: function(text, cursorPos, visible) {
if (visible) {
this.showPreeditText();
} else {
this.hidePreeditText();
}
let str = text.get_text();
this._stPreeditLabel.set_text(str);
let attrs = text.get_attributes();
for (let i = 0; attrs != null && attrs.get(i) != null; i++) {
let attr = attrs.get(i);
if (attr.get_attr_type() == IBus.AttrType.BACKGROUND) {
let startIndex = attr.get_start_index();
let endIndex = attr.get_end_index();
let len = GLib.utf8_strlen(str, -1);
let markup = '';
if (startIndex == 0 &&
endIndex == GLib.utf8_strlen(str, -1)) {
markup = markup.concat(str);
} else {
if (startIndex > 0) {
markup = markup.concat(GLib.utf8_substring(str,
0,
startIndex));
}
if (startIndex != endIndex) {
markup = markup.concat('<span background=\"#555555\">');
markup = markup.concat(GLib.utf8_substring(str,
startIndex,
endIndex));
markup = markup.concat('</span>');
}
if (endIndex < len) {
markup = markup.concat(GLib.utf8_substring(str,
endIndex,
len));
}
}
let clutter_text = this._stPreeditLabel.get_clutter_text();
clutter_text.set_markup(markup);
clutter_text.queue_redraw();
}
}
},
showAuxiliaryText: function() {
this._auxStringVisible = true;
this._stAuxLabel.show();
this._checkShowStates();
},
hideAuxiliaryText: function() {
this._auxStringVisible = false;
this._checkShowStates();
this._stAuxLabel.hide();
},
updateAuxiliaryText: function(text, show) {
if (show) {
this.showAuxiliaryText();
} else {
this.hideAuxiliaryText();
}
this._stAuxLabel.set_text(text.get_text());
},
_refreshLabels: function() {
let newLabels = [];
for (let i = 0; this._lookupTable.get_label(i) != null; i++) {
let label = this._lookupTable.get_label(i);
newLabels.push([label.get_text(), label.get_attributes()]);
}
this._stCandidateArea.setLabels(newLabels);
},
_getCandidatesInCurrentPage: function() {
let cursorPos = this._lookupTable.get_cursor_pos();
let pageSize = this._lookupTable.get_page_size();
let page = ((cursorPos == 0) ? 0 : Math.floor(cursorPos / pageSize));
let startIndex = page * pageSize;
let endIndex = Math.min((page + 1) * pageSize,
this._lookupTable.get_number_of_candidates());
let candidates = [];
for (let i = startIndex; i < endIndex; i++) {
candidates.push(this._lookupTable.get_candidate(i));
}
return candidates;
},
_getCursorPosInCurrentPage: function() {
let cursorPos = this._lookupTable.get_cursor_pos();
let pageSize = this._lookupTable.get_page_size();
let posInPage = cursorPos % pageSize;
return posInPage;
},
_refreshCandidates: function() {
let candidates = this._getCandidatesInCurrentPage();
let newCandidates = [];
for (let i = 0; i < candidates.length; i++) {
let candidate = candidates[i];
newCandidates.push([candidate.get_text(),
candidate.get_attributes()]);
}
this._stCandidateArea.setCandidates(newCandidates,
this._getCursorPosInCurrentPage(),
this._lookupTable.is_cursor_visible());
},
updateLookupTable: function(lookupTable, visible) {
// hide lookup table
if (!visible) {
this.hideLookupTable();
}
this._lookupTable = lookupTable || new IBus.LookupTable();
let orientation = this._lookupTable.get_orientation();
if (orientation != ORIENTATION_HORIZONTAL &&
orientation != ORIENTATION_VERTICAL) {
orientation = this._orientation;
}
this.setCurrentOrientation(orientation);
this._refreshCandidates();
this._refreshLabels();
// show lookup table
if (visible) {
this.showLookupTable();
}
},
showLookupTable: function() {
this._lookupTableVisible = true;
this._stCandidateArea.showAll();
this._checkShowStates();
},
hideLookupTable: function() {
this._lookupTableVisible = false;
this._checkShowStates();
this._stCandidateArea.hideAll();
},
pageUpLookupTable: function() {
this._lookupTable.page_up();
this._refreshCandidates();
},
pageDownLookup_table: function() {
this._lookupTable.page_down();
this._refreshCandidates();
},
cursorUpLookupTable: function() {
this._lookupTable.cursor_up();
this._refreshCandidates();
},
cursorDownLookupTable: function() {
this._lookupTable.cursor_down();
this._refreshCandidates();
},
setCursorLocation: function(x, y, w, h) {
// if cursor location is changed, we reset the moved cursor location
if (this._cursorLocation.join() != [x, y, w, h].join()) {
this._cursorLocation = [x, y, w, h];
this._movedCursorLocation = null;
this._checkPosition();
}
},
_checkShowStates: function() {
this._checkSeparatorShowStates();
if (this._preeditVisible ||
this._auxStringVisible ||
this._lookupTableVisible) {
this._checkPosition();
this.showAll();
this.emit('show');
} else {
this.hideAll();
this.emit('hide');
}
},
_checkSeparatorShowStates: function() {
if (this._preeditVisible || this._auxStringVisible) {
this._separator.actor.show();
}
else
this._separator.actor.hide();
},
reset: function() {
let text = IBus.Text.new_from_string('');
this.updatePreeditText(text, 0, false);
text = IBus.Text.new_from_string('');
this.updateAuxiliaryText(text, false);
this.updateLookupTable(null, false);
this.hideAll();
},
setCurrentOrientation: function(orientation) {
if (this._currentOrientation == orientation) {
return;
}
this._currentOrientation = orientation;
this._stCandidateArea.setOrientation(orientation);
},
setOrientation: function(orientation) {
this._orientation = orientation;
this.updateLookupTable(this._lookupTable, this._lookupTableVisible);
},
getCurrentOrientation: function() {
return this._currentOrientation;
},
_checkPosition: function() {
let cursorLocation = this._movedCursorLocation || this._cursorLocation;
let [cursorX, cursorY, cursorWidth, cursorHeight] = cursorLocation;
let windowRight = cursorX + cursorWidth + this.actor.get_width();
let windowBottom = cursorY + cursorHeight + this.actor.get_height();
this._cursorActor.set_position(cursorX, cursorY);
this._cursorActor.set_size(cursorWidth, cursorHeight);
let monitor = Main.layoutManager.findMonitorForActor(this._cursorActor);
let [sx, sy] = [monitor.x + monitor.width, monitor.y + monitor.height];
if (windowBottom > sy) {
this._arrowSide = St.Side.BOTTOM;
} else {
this._arrowSide = St.Side.TOP;
}
this._boxPointer._arrowSide = this._arrowSide;
this._boxPointer.setArrowOrigin(this._arrowSide);
this._boxPointer.setPosition(this._cursorActor, this._arrowAlignment);
},
showAll: function() {
if (!this._isVisible) {
this.actor.opacity = 255;
this.actor.show();
this._isVisible = true;
}
},
hideAll: function() {
if (this._isVisible) {
this.actor.opacity = 0;
this.actor.hide();
this._isVisible = false;
}
},
move: function(x, y) {
this.actor.set_position(x, y);
}
});
Signals.addSignalMethods(CandidatePanel.prototype);

View File

@ -2,42 +2,52 @@
const Clutter = imports.gi.Clutter; const Clutter = imports.gi.Clutter;
const GdkPixbuf = imports.gi.GdkPixbuf; const GdkPixbuf = imports.gi.GdkPixbuf;
const Gkbd = imports.gi.Gkbd;
const Gio = imports.gi.Gio; const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib; const GLib = imports.gi.GLib;
const Lang = imports.lang; const Lang = imports.lang;
const Meta = imports.gi.Meta;
const Shell = imports.gi.Shell; const Shell = imports.gi.Shell;
const St = imports.gi.St; const St = imports.gi.St;
try {
var IBus = imports.gi.IBus;
const CandidatePanel = imports.ui.status.candidatePanel;
} catch (e) {
var IBus = null;
}
const Main = imports.ui.main; const Main = imports.ui.main;
const PopupMenu = imports.ui.popupMenu; const PopupMenu = imports.ui.popupMenu;
const PanelMenu = imports.ui.panelMenu; const PanelMenu = imports.ui.panelMenu;
const Util = imports.misc.util; const Util = imports.misc.util;
const DESKTOP_INPUT_SOURCES_KEYBINDINGS_SCHEMA = 'org.gnome.desktop.input-sources.keybindings';
const DESKTOP_INPUT_SOURCES_SCHEMA = 'org.gnome.desktop.input-sources';
const KEY_CURRENT_IS = 'current';
const KEY_INPUT_SOURCES = 'sources';
const LayoutMenuItem = new Lang.Class({ const LayoutMenuItem = new Lang.Class({
Name: 'LayoutMenuItem', Name: 'LayoutMenuItem',
Extends: PopupMenu.PopupBaseMenuItem, Extends: PopupMenu.PopupBaseMenuItem,
_init: function(config, id, indicator, long_name) { _init: function(name, shortName, xkbLayout, xkbVariant, ibusEngine) {
this.parent(); this.parent();
this._config = config; this.label = new St.Label({ text: name });
this._id = id; this.indicator = new St.Label({ text: shortName });
this.label = new St.Label({ text: long_name });
this.indicator = indicator;
this.addActor(this.label); this.addActor(this.label);
this.addActor(this.indicator); this.addActor(this.indicator);
},
activate: function(event) { this.sourceName = name;
this.parent(event); this.shortName = shortName;
this.xkbLayout = xkbLayout;
this._config.lock_group(this._id); this.xkbVariant = xkbVariant;
this.ibusEngine = ibusEngine;
} }
}); });
const XKBIndicator = new Lang.Class({ const InputSourceIndicator = new Lang.Class({
Name: 'XKBIndicator', Name: 'InputSourceIndicator',
Extends: PanelMenu.Button, Extends: PanelMenu.Button,
_init: function() { _init: function() {
@ -50,61 +60,237 @@ const XKBIndicator = new Lang.Class({
this.actor.add_actor(this._container); this.actor.add_actor(this._container);
this.actor.add_style_class_name('panel-status-button'); this.actor.add_style_class_name('panel-status-button');
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._labelActors = [ ];
this._layoutItems = [ ]; this._layoutItems = [ ];
this._showFlags = false; this._settings = new Gio.Settings({ schema: DESKTOP_INPUT_SOURCES_SCHEMA });
this._config = Gkbd.Configuration.get(); this._settings.connect('changed::' + KEY_CURRENT_IS, Lang.bind(this, this._currentISChanged));
this._config.connect('changed', Lang.bind(this, this._syncConfig)); this._settings.connect('changed::' + KEY_INPUT_SOURCES, Lang.bind(this, this._inputSourcesChanged));
this._config.connect('group-changed', Lang.bind(this, this._syncGroup));
this._config.start_listen();
this._syncConfig(); if (IBus)
this._ibusInit();
this._inputSourcesChanged();
if (global.session_type == Shell.SessionType.USER) { if (global.session_type == Shell.SessionType.USER) {
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
this.menu.addAction(_("Show Keyboard Layout"), Lang.bind(this, function() { this.menu.addAction(_("Show Keyboard Layout"), Lang.bind(this, function() {
Main.overview.hide(); Main.overview.hide();
Util.spawn(['gkbd-keyboard-display', '-g', String(this._config.get_current_group() + 1)]); let description = this._selectedLayout.xkbLayout;
if (this._selectedLayout.xkbVariant.length > 0)
description = description + '\t' + this._selectedLayout.xkbVariant;
Util.spawn(['gkbd-keyboard-display', '-l', description]);
})); }));
} }
this.menu.addSettingsAction(_("Region and Language Settings"), 'gnome-region-panel.desktop'); this.menu.addSettingsAction(_("Region and Language Settings"), 'gnome-region-panel.desktop');
global.display.add_keybinding('switch-next',
new Gio.Settings({ schema: DESKTOP_INPUT_SOURCES_KEYBINDINGS_SCHEMA }),
Meta.KeyBindingFlags.NONE,
Lang.bind(this, this._switchNext));
global.display.add_keybinding('switch-previous',
new Gio.Settings({ schema: DESKTOP_INPUT_SOURCES_KEYBINDINGS_SCHEMA }),
Meta.KeyBindingFlags.NONE,
Lang.bind(this, this._switchPrevious));
}, },
_adjustGroupNames: function(names) { _ibusInit: function() {
// Disambiguate duplicate names with a subscript IBus.init();
// This is O(N^2) to avoid sorting names this._ibus = new IBus.Bus();
// but N <= 4 so who cares? if (!this._ibus.is_connected()) {
log('ibus-daemon is not running');
for (let i = 0; i < names.length; i++) { return;
let name = names[i];
let cnt = 0;
for (let j = i + 1; j < names.length; j++) {
if (names[j] == name) {
cnt++;
// U+2081 SUBSCRIPT ONE
names[j] = name + String.fromCharCode(0x2081 + cnt);
}
}
if (cnt != 0)
names[i] = name + '\u2081';
} }
return names; this._ibus.request_name(IBus.SERVICE_PANEL,
IBus.BusNameFlag.ALLOW_REPLACEMENT |
IBus.BusNameFlag.REPLACE_EXISTING);
this._panel = new IBus.PanelService({ connection: this._ibus.get_connection(),
object_path: IBus.PATH_PANEL });
this._ibusInitPanelService();
this._candidatePanel = new CandidatePanel.CandidatePanel();
this._ibusInitCandidatePanel();
}, },
_syncConfig: function() { _ibusInitCandidatePanel: function() {
this._showFlags = this._config.if_flags_shown(); this._candidatePanel.connect('cursor-up',
if (this._showFlags) { Lang.bind(this, function(widget) {
this._container.set_skip_paint(this._iconActor, false); this.cursorUp();
} else { }));
this._container.set_skip_paint(this._iconActor, true); this._candidatePanel.connect('cursor-down',
Lang.bind(this, function(widget) {
this.cursorDown();
}));
this._candidatePanel.connect('page-up',
Lang.bind(this, function(widget) {
this.pageUp();
}));
this._candidatePanel.connect('page-down',
Lang.bind(this, function(widget) {
this.pageDown();
}));
this._candidatePanel.connect('candidate-clicked',
Lang.bind(this, function(widget, index, button, state) {
this.candidateClicked(index, button, state);
}));
},
_ibusInitPanelService: function() {
this._panel.connect('set-cursor-location',
Lang.bind(this, this.setCursorLocation));
this._panel.connect('update-preedit-text',
Lang.bind(this, this.updatePreeditText));
this._panel.connect('show-preedit-text',
Lang.bind(this, this.showPreeditText));
this._panel.connect('hide-preedit-text',
Lang.bind(this, this.hidePreeditText));
this._panel.connect('update-auxiliary-text',
Lang.bind(this, this.updateAuxiliaryText));
this._panel.connect('show-auxiliary-text',
Lang.bind(this, this.showAuxiliaryText));
this._panel.connect('hide-auxiliary-text',
Lang.bind(this, this.hideAuxiliaryText));
this._panel.connect('update-lookup-table',
Lang.bind(this, this.updateLookupTable));
this._panel.connect('show-lookup-table',
Lang.bind(this, this.showLookupTable));
this._panel.connect('hide-lookup-table',
Lang.bind(this, this.hideLookupTable));
this._panel.connect('page-up-lookup-table',
Lang.bind(this, this.pageUpLookupTable));
this._panel.connect('page-down-lookup-table',
Lang.bind(this, this.pageDownLookupTable));
this._panel.connect('cursor-up-lookup-table',
Lang.bind(this, this.cursorUpLookupTable));
this._panel.connect('cursor-down-lookup-table',
Lang.bind(this, this.cursorDownLookupTable));
this._panel.connect('focus-in', Lang.bind(this, this.focusIn));
this._panel.connect('focus-out', Lang.bind(this, this.focusOut));
},
setCursorLocation: function(panel, x, y, w, h) {
this._candidatePanel.setCursorLocation(x, y, w, h);
},
updatePreeditText: function(panel, text, cursorPos, visible) {
this._candidatePanel.updatePreeditText(text, cursorPos, visible);
},
showPreeditText: function(panel) {
this._candidatePanel.showPreeditText();
},
hidePreeditText: function(panel) {
this._candidatePanel.hidePreeditText();
},
updateAuxiliaryText: function(panel, text, visible) {
this._candidatePanel.updateAuxiliaryText(text, visible);
},
showAuxiliaryText: function(panel) {
this._candidatePanel.showAuxiliaryText();
},
hideAuxiliaryText: function(panel) {
this._candidatePanel.hideAuxiliaryText();
},
updateLookupTable: function(panel, lookupTable, visible) {
this._candidatePanel.updateLookupTable(lookupTable, visible);
},
showLookupTable: function(panel) {
this._candidatePanel.showLookupTable();
},
hideLookupTable: function(panel) {
this._candidatePanel.hideLookupTable();
},
pageUpLookupTable: function(panel) {
this._candidatePanel.pageUpLookupTable();
},
pageDownLookupTable: function(panel) {
this._candidatePanel.pageDownLookupTable();
},
cursorUpLookupTable: function(panel) {
this._candidatePanel.cursorUpLookupTable();
},
cursorDownLookupTable: function(panel) {
this._candidatePanel.cursorDownLookupTable();
},
focusIn: function(panel, path) {
},
focusOut: function(panel, path) {
this._candidatePanel.reset();
},
cursorUp: function() {
this._panel.cursor_up();
},
cursorDown: function() {
this._panel.cursor_down();
},
pageUp: function() {
this._panel.page_up();
},
pageDown: function() {
this._panel.page_down();
},
candidateClicked: function(index, button, state) {
this._panel.candidate_clicked(index, button, state);
},
_currentISChanged: function() {
let source = this._settings.get_value(KEY_CURRENT_IS);
let name = source.get_child_value(0).get_string()[0];
if (this._selectedLayout) {
this._selectedLayout.setShowDot(false);
this._selectedLayout = null;
} }
let groups = this._config.get_group_names(); if (this._selectedLabel) {
if (groups.length > 1) { this._container.set_skip_paint(this._selectedLabel, true);
this._selectedLabel = null;
}
for (let i = 0; i < this._layoutItems.length; ++i) {
let item = this._layoutItems[i];
if (item.sourceName == name) {
item.setShowDot(true);
this._selectedLayout = item;
break;
}
}
for (let i = 0; i < this._labelActors.length; ++i) {
let actor = this._labelActors[i];
if (actor.sourceName == name) {
this._selectedLabel = actor;
this._container.set_skip_paint(actor, false);
break;
}
}
if (!this._selectedLayout || !this._selectedLabel)
this._layoutItems[0].activate();
},
_inputSourcesChanged: function() {
let sources = this._settings.get_value(KEY_INPUT_SOURCES);
if (sources.n_children() > 1) {
this.actor.show(); this.actor.show();
} else { } else {
this.menu.close(); this.menu.close();
@ -117,55 +303,69 @@ const XKBIndicator = new Lang.Class({
for (let i = 0; i < this._labelActors.length; i++) for (let i = 0; i < this._labelActors.length; i++)
this._labelActors[i].destroy(); this._labelActors[i].destroy();
let short_names = this._adjustGroupNames(this._config.get_short_group_names());
this._selectedLayout = null; this._selectedLayout = null;
this._layoutItems = [ ]; this._layoutItems = [ ];
this._selectedLabel = null; this._selectedLabel = null;
this._labelActors = [ ]; this._labelActors = [ ];
for (let i = 0; i < groups.length; i++) {
let icon_name = this._config.get_group_name(i); for (let i = 0; i < sources.n_children(); ++i) {
let actor; let name = sources.get_child_value(i).get_child_value(0).get_string()[0];
if (this._showFlags) let shortName = sources.get_child_value(i).get_child_value(1).get_string()[0];
actor = new St.Icon({ icon_name: icon_name, icon_type: St.IconType.SYMBOLIC, style_class: 'popup-menu-icon' }); let xkbLayout = sources.get_child_value(i).get_child_value(2).get_string()[0];
else let xkbVariant = sources.get_child_value(i).get_child_value(3).get_string()[0];
actor = new St.Label({ text: short_names[i] }); let ibusEngine = sources.get_child_value(i).get_child_value(4).get_string()[0];
let item = new LayoutMenuItem(this._config, i, actor, groups[i]);
item._short_group_name = short_names[i]; let item = new LayoutMenuItem(name, shortName, xkbLayout, xkbVariant, ibusEngine);
item._icon_name = icon_name;
this._layoutItems.push(item); this._layoutItems.push(item);
this.menu.addMenuItem(item, i); this.menu.addMenuItem(item, i);
item.connect('activate', Lang.bind(this, function() {
if (this._selectedLayout == null || item.sourceName != this._selectedLayout.sourceName) {
let name = GLib.Variant.new_string(item.sourceName);
let shortName = GLib.Variant.new_string(item.shortName);
let xkbLayout = GLib.Variant.new_string(item.xkbLayout);
let xkbVariant = GLib.Variant.new_string(item.xkbVariant);
let ibusEngine = GLib.Variant.new_string(item.ibusEngine);
let tuple = GLib.Variant.new_tuple([name, shortName, xkbLayout, xkbVariant, ibusEngine], 5);
this._settings.set_value(KEY_CURRENT_IS, tuple);
}
}));
let shortLabel = new St.Label({ text: short_names[i] }); let shortLabel = new St.Label({ text: shortName });
shortLabel.sourceName = name;
this._labelActors.push(shortLabel); this._labelActors.push(shortLabel);
this._container.add_actor(shortLabel); this._container.add_actor(shortLabel);
this._container.set_skip_paint(shortLabel, true); this._container.set_skip_paint(shortLabel, true);
} }
this._syncGroup(); this._currentISChanged();
}, },
_syncGroup: function() { _switchNext: function() {
let selected = this._config.get_current_group(); if (!this._selectedLayout || !this._selectedLabel) {
this._layoutItems[0].activate();
if (this._selectedLayout) { return;
this._selectedLayout.setShowDot(false);
this._selectedLayout = null;
} }
for (let i = 0; i < this._layoutItems.length; ++i) {
if (this._selectedLabel) { let item = this._layoutItems[i];
this._container.set_skip_paint(this._selectedLabel, true); if (item.sourceName == this._selectedLayout.sourceName) {
this._selectedLabel = null; this._layoutItems[(++i == this._layoutItems.length) ? 0 : i].activate();
break;
} }
}
},
let item = this._layoutItems[selected]; _switchPrevious: function() {
item.setShowDot(true); if (!this._selectedLayout || !this._selectedLabel) {
this._layoutItems[0].activate();
this._iconActor.icon_name = item._icon_name; return;
this._selectedLabel = this._labelActors[selected]; }
this._container.set_skip_paint(this._selectedLabel, this._showFlags); for (let i = 0; i < this._layoutItems.length; ++i) {
let item = this._layoutItems[i];
this._selectedLayout = item; if (item.sourceName == this._selectedLayout.sourceName) {
this._layoutItems[(--i == -1) ? (this._layoutItems.length - 1) : i].activate();
break;
}
}
}, },
_containerGetPreferredWidth: function(container, for_height, alloc) { _containerGetPreferredWidth: function(container, for_height, alloc) {
@ -173,16 +373,12 @@ const XKBIndicator = new Lang.Class({
// for the height of all children, but we ignore the results // for the height of all children, but we ignore the results
// for those we don't actually display. // for those we don't actually display.
let max_min_width = 0, max_natural_width = 0; 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++) { for (let i = 0; i < this._labelActors.length; i++) {
let [min_width, natural_width] = this._labelActors[i].get_preferred_width(for_height); 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_min_width = Math.max(max_min_width, min_width);
max_natural_width = Math.max(max_natural_width, natural_width); max_natural_width = Math.max(max_natural_width, natural_width);
} }
}
alloc.min_size = max_min_width; alloc.min_size = max_min_width;
alloc.natural_size = max_natural_width; alloc.natural_size = max_natural_width;
@ -190,16 +386,12 @@ const XKBIndicator = new Lang.Class({
_containerGetPreferredHeight: function(container, for_width, alloc) { _containerGetPreferredHeight: function(container, for_width, alloc) {
let max_min_height = 0, max_natural_height = 0; 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++) { for (let i = 0; i < this._labelActors.length; i++) {
let [min_height, natural_height] = this._labelActors[i].get_preferred_height(for_width); 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_min_height = Math.max(max_min_height, min_height);
max_natural_height = Math.max(max_natural_height, natural_height); max_natural_height = Math.max(max_natural_height, natural_height);
} }
}
alloc.min_size = max_min_height; alloc.min_size = max_min_height;
alloc.natural_size = max_natural_height; alloc.natural_size = max_natural_height;
@ -212,7 +404,6 @@ const XKBIndicator = new Lang.Class({
box.y2 -= box.y1; box.y2 -= box.y1;
box.y1 = 0; box.y1 = 0;
this._iconActor.allocate_align_fill(box, 0.5, 0, false, false, flags);
for (let i = 0; i < this._labelActors.length; i++) for (let i = 0; i < this._labelActors.length; i++)
this._labelActors[i].allocate_align_fill(box, 0.5, 0, false, false, flags); this._labelActors[i].allocate_align_fill(box, 0.5, 0, false, false, flags);
} }