[gnome-shell/wip/carlosg/osk-cldr: 22/43] keyboard: Rework OSK



commit fe1596caca5e90172860507e1baf5f64304ac366
Author: Carlos Garnacho <carlosg gnome org>
Date:   Sat Aug 5 12:21:30 2017 +0200

    keyboard: Rework OSK
    
    Caribou is no longer used to load keyboard layouts,
    gnome-shell uses the JSON files extracted from Unicode
    CLDR instead.

 data/theme/gnome-shell-high-contrast.css |  29 +-
 data/theme/gnome-shell-sass              |   2 +-
 data/theme/gnome-shell.css               |  29 +-
 js/ui/keyboard.js                        | 512 ++++++++++++++++++++-----------
 4 files changed, 352 insertions(+), 220 deletions(-)
---
diff --git a/data/theme/gnome-shell-high-contrast.css b/data/theme/gnome-shell-high-contrast.css
index ff0ebcabe..bab0101a7 100644
--- a/data/theme/gnome-shell-high-contrast.css
+++ b/data/theme/gnome-shell-high-contrast.css
@@ -1506,27 +1506,18 @@ StScrollBar {
 #keyboard {
   background-color: rgba(46, 52, 54, 0.7); }
 
-.keyboard-layout {
-  spacing: 10px;
-  padding: 10px; }
-
-.keyboard-row {
-  spacing: 15px; }
+.key-container {
+  padding: 4px;
+  spacing: 4px; }
 
 .keyboard-key {
-  color: #eeeeec;
-  background-color: #2e3436;
-  border-color: rgba(0, 0, 0, 0.7);
-  box-shadow: inset 0 1px #454f52;
-  text-shadow: 0 1px black;
-  icon-shadow: 0 1px black;
+  background-color: #393f3f;
   min-height: 2em;
   min-width: 2em;
   font-size: 14pt;
-  font-weight: bold;
-  border-radius: 5px;
-  border: 1px solid black;
-  color: white; }
+  border-radius: 3px;
+  border: 1px solid #464d4d;
+  color: #e5e5e5; }
   .keyboard-key:focus {
     color: #eeeeec;
     text-shadow: 0 1px black;
@@ -1550,6 +1541,12 @@ StScrollBar {
     background-color: #2e3436;
     color: #eeeeec;
     border-color: rgba(0, 0, 0, 0.7); }
+  .keyboard-key.default-key {
+    border-color: #2d3232;
+    background-color: #1d2020; }
+  .keyboard-key.enter-key {
+    border-color: #005684;
+    background-color: #006098; }
 
 .keyboard-subkeys {
   color: white;
diff --git a/data/theme/gnome-shell-sass b/data/theme/gnome-shell-sass
index 57a2e5bfe..08973e0e1 160000
--- a/data/theme/gnome-shell-sass
+++ b/data/theme/gnome-shell-sass
@@ -1 +1 @@
-Subproject commit 57a2e5bfe179d9db1e05c3edaffdcb3fee307be0
+Subproject commit 08973e0e16de468bc7a1cc1085f2e17153b74609
diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css
index 61a78d379..9eb44a53d 100644
--- a/data/theme/gnome-shell.css
+++ b/data/theme/gnome-shell.css
@@ -1506,27 +1506,18 @@ StScrollBar {
 #keyboard {
   background-color: rgba(46, 52, 54, 0.7); }
 
-.keyboard-layout {
-  spacing: 10px;
-  padding: 10px; }
-
-.keyboard-row {
-  spacing: 15px; }
+.key-container {
+  padding: 4px;
+  spacing: 4px; }
 
 .keyboard-key {
-  color: #eeeeec;
-  background-color: #2e3436;
-  border-color: rgba(0, 0, 0, 0.7);
-  box-shadow: inset 0 1px #454f52;
-  text-shadow: 0 1px black;
-  icon-shadow: 0 1px black;
+  background-color: #393f3f;
   min-height: 2em;
   min-width: 2em;
   font-size: 14pt;
-  font-weight: bold;
-  border-radius: 5px;
-  border: 1px solid #1c1f1f;
-  color: white; }
+  border-radius: 3px;
+  border: 1px solid #464d4d;
+  color: #e5e5e5; }
   .keyboard-key:focus {
     color: #eeeeec;
     text-shadow: 0 1px black;
@@ -1550,6 +1541,12 @@ StScrollBar {
     background-color: #2e3436;
     color: #eeeeec;
     border-color: rgba(0, 0, 0, 0.7); }
+  .keyboard-key.default-key {
+    border-color: #2d3232;
+    background-color: #1d2020; }
+  .keyboard-key.enter-key {
+    border-color: #005684;
+    background-color: #006098; }
 
 .keyboard-subkeys {
   color: white;
diff --git a/js/ui/keyboard.js b/js/ui/keyboard.js
index 67e436f2c..a430b82e4 100644
--- a/js/ui/keyboard.js
+++ b/js/ui/keyboard.js
@@ -2,7 +2,6 @@
 
 const FocusCaretTracker = imports.ui.focusCaretTracker;
 const Atspi = imports.gi.Atspi;
-const Caribou = imports.gi.Caribou;
 const Clutter = imports.gi.Clutter;
 const Gdk = imports.gi.Gdk;
 const Gio = imports.gi.Gio;
@@ -19,40 +18,54 @@ const Layout = imports.ui.layout;
 const Main = imports.ui.main;
 
 var KEYBOARD_REST_TIME = Layout.KEYBOARD_ANIMATION_TIME * 2 * 1000;
-
-const KEYBOARD_SCHEMA = 'org.gnome.shell.keyboard';
-const KEYBOARD_TYPE = 'keyboard-type';
+var KEY_LONG_PRESS_TIME = 250;
 
 const A11Y_APPLICATIONS_SCHEMA = 'org.gnome.desktop.a11y.applications';
 const SHOW_KEYBOARD = 'screen-keyboard-enabled';
 
+const defaultKeysPre = [
+    [ [], [], [{ label: '⇧', width: 1.5, level: 1 }], [{ label: '?123', width: 1.5, level: 2 }] ],
+    [ [], [], [{ label: '⇪', width: 1.5, level: 0 }], [{ label: '?123', width: 1.5, level: 2 }] ],
+    [ [], [], [{ label: '=/<', width: 1.5, level: 3 }], [{ label: 'ABC', width: 1.5, level: 0 }] ],
+    [ [], [], [{ label: '?123', width: 1.5, level: 2 }], [{ label: 'ABC', width: 1.5, level: 0 }] ],
+];
+
+const defaultKeysPost = [
+    [ [{ label: '⌫', width: 1.5, keyval: Clutter.KEY_BackSpace }],
+      [{ label: '⏎', width: 2, keyval: Clutter.KEY_Return, extraClassName: 'enter-key' }],
+      [{ label: '⇧', width: 3, level: 1, right: true }],
+      [{ label: '🌐', width: 1.5 }, { label: '⌨', width: 1.5, action: 'hide' }] ],
+    [ [{ label: '⌫', width: 1.5, keyval: Clutter.KEY_BackSpace }],
+      [{ label: '⏎', width: 2, keyval: Clutter.KEY_Return, extraClassName: 'enter-key' }],
+      [{ label: '⇪', width: 3, level: 0, right: true }],
+      [{ label: '🌐', width: 1.5 }, { label: '⌨', width: 1.5, action: 'hide' }] ],
+    [ [{ label: '⌫', width: 1.5, keyval: Clutter.KEY_BackSpace }],
+      [{ label: '⏎', width: 2, keyval: Clutter.KEY_Return, extraClassName: 'enter-key' }],
+      [{ label: '=/<', width: 3, level: 3, right: true }],
+      [{ label: '🌐', width: 1.5 }, { label: '⌨', width: 1.5, action: 'hide' }] ],
+    [ [{ label: '⌫', width: 1.5, keyval: Clutter.KEY_BackSpace }],
+      [{ label: '⏎', width: 2, keyval: Clutter.KEY_Return, extraClassName: 'enter-key' }],
+      [{ label: '?123', width: 3, level: 2, right: true }],
+      [{ label: '🌐', width: 1.5 }, { label: '⌨', width: 1.5, action: 'hide' }] ],
+];
+
 var Key = new Lang.Class({
     Name: 'Key',
 
-    _init : function(key) {
-        this._key = key;
-        this.actor = this._makeKey(key, GLib.markup_escape_text(key.label, -1));
-        this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
+    _init : function(key, extendedKeys) {
+        this.key = key;
+        this.actor = this._makeKey(this.key);
 
-        this._extended_keys = this._key.get_extended_keys();
-        this._extended_keyboard = null;
+        /* Add the key in a container, so keys can be padded without losing
+         * logical proportions between those.
+         */
+        this.container = new St.BoxLayout ({ style_class: 'key-container' });
+        this.container.add(this.actor, { expand: true, x_fill: true });
+        this.container.connect('destroy', Lang.bind(this, this._onDestroy));
 
-        if (this._key.name == 'Control_L' || this._key.name == 'Alt_L')
-            this._key.latch = true;
-
-        if (this._extended_keys.length > 0) {
-            this._key.connect('notify::show-subkeys', Lang.bind(this, this._onShowSubkeysChanged));
-            this._boxPointer = new BoxPointer.BoxPointer(St.Side.BOTTOM,
-                                                         { x_fill: true,
-                                                           y_fill: true,
-                                                           x_align: St.Align.START });
-            // Adds style to existing keyboard style to avoid repetition
-            this._boxPointer.actor.add_style_class_name('keyboard-subkeys');
-            this._getExtendedKeys();
-            this.actor._extended_keys = this._extended_keyboard;
-            this._boxPointer.actor.hide();
-            Main.layoutManager.addChrome(this._boxPointer.actor);
-        }
+        this._extended_keys = extendedKeys;
+        this._extended_keyboard = null;
+        this._pressTimeoutId = 0;
     },
 
     _onDestroy: function() {
@@ -62,19 +75,73 @@ var Key = new Lang.Class({
         }
     },
 
-    _makeKey: function (key, label) {
+    _ensureExtendedKeysPopup: function() {
+        if (this._extended_keys.length == 0)
+            return;
+
+        this._boxPointer = new BoxPointer.BoxPointer(St.Side.BOTTOM,
+                                                     { x_fill: true,
+                                                       y_fill: true,
+                                                       x_align: St.Align.START });
+        this._boxPointer.actor.hide();
+        Main.layoutManager.addChrome(this._boxPointer.actor);
+        this._boxPointer.setPosition(this.actor, 0.5);
+
+        // Adds style to existing keyboard style to avoid repetition
+        this._boxPointer.actor.add_style_class_name('keyboard-subkeys');
+        this._getExtendedKeys();
+        this.actor._extended_keys = this._extended_keyboard;
+    },
+
+    _getKeyval: function(key) {
+        let unicode = String.charCodeAt(key, 0);
+        return Gdk.unicode_to_keyval(unicode);
+    },
+
+    _press: function(key) {
+        if (key != this.key || this._extended_keys.length == 0) {
+            this.emit('pressed', this._getKeyval(key));
+        } else if (key == this.key) {
+            this._pressTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT,
+                                                    KEY_LONG_PRESS_TIME,
+                                                    Lang.bind(this, function() {
+                                                        this.actor.set_hover(false);
+                                                        this.actor.fake_release();
+                                                        this._pressTimeoutId = 0;
+                                                        this._touchPressed = false;
+                                                        this._ensureExtendedKeysPopup();
+                                                        this.actor.fake_release();
+                                                        this.actor.set_hover(false);
+                                                        this.emit('show-subkeys');
+                                                        return GLib.SOURCE_REMOVE;
+                                                    }));
+        }
+    },
+
+    _release: function(key) {
+        if (this._pressTimeoutId != 0) {
+            GLib.source_remove(this._pressTimeoutId);
+            this._pressTimeoutId = 0;
+            this.emit('pressed', this._getKeyval(key));
+        }
+
+        this.emit('released', this._getKeyval(key));
+    },
+
+    _makeKey: function (key) {
+        let label = GLib.markup_escape_text(key, -1);
         let button = new St.Button ({ label: label,
                                       style_class: 'keyboard-key' });
 
-        button.key_width = this._key.width;
+        button.keyWidth = 1;
         button.connect('button-press-event', Lang.bind(this,
             function () {
-                key.press();
+                this._press(key);
                 return Clutter.EVENT_PROPAGATE;
             }));
         button.connect('button-release-event', Lang.bind(this,
             function () {
-                key.release();
+                this._release(key);
                 return Clutter.EVENT_PROPAGATE;
             }));
         button.connect('touch-event', Lang.bind(this,
@@ -96,13 +163,13 @@ var Key = new Lang.Class({
                     event.type() == Clutter.EventType.TOUCH_BEGIN) {
                     device.sequence_grab(sequence, actor);
                     this._touchPressed = true;
-                    key.press();
+                    this._press(key);
                 } else if (this._touchPressed &&
                            event.type() == Clutter.EventType.TOUCH_END &&
                            device.sequence_get_grabbed_actor(sequence) == actor) {
                     device.sequence_ungrab(sequence);
                     this._touchPressed = false;
-                    key.release();
+                    this._release(key);
                 }
                 return Clutter.EVENT_PROPAGATE;
             }));
@@ -110,26 +177,18 @@ var Key = new Lang.Class({
         return button;
     },
 
-    _getUnichar: function(key) {
-        let keyval = key.keyval;
-        let unichar = Gdk.keyval_to_unicode(keyval);
-        if (unichar) {
-            return String.fromCharCode(unichar);
-        } else {
-            return key.name;
-        }
-    },
-
     _getExtendedKeys: function () {
-        this._extended_keyboard = new St.BoxLayout({ style_class: 'keyboard-layout',
+        this._extended_keyboard = new St.BoxLayout({ style_class: 'key-container',
                                                      vertical: false });
         for (let i = 0; i < this._extended_keys.length; ++i) {
-            let extended_key = this._extended_keys[i];
-            let label = this._getUnichar(extended_key);
-            let key = this._makeKey(extended_key, label);
+            let extendedKey = this._extended_keys[i];
+            let key = this._makeKey(extendedKey);
 
-            key.extended_key = extended_key;
+            key.extended_key = extendedKey;
             this._extended_keyboard.add(key);
+
+            key.width = this.actor.width;
+            key.height = this.actor.height;
         }
         this._boxPointer.bin.add_actor(this._extended_keyboard);
     },
@@ -138,19 +197,38 @@ var Key = new Lang.Class({
         return this._boxPointer;
     },
 
-    _onShowSubkeysChanged: function () {
-        if (this._key.show_subkeys) {
-            this._boxPointer.actor.raise_top();
-            this._boxPointer.setPosition(this.actor, 0.5);
-            this.emit('show-subkeys');
-            this.actor.fake_release();
-            this.actor.set_hover(false);
-        } else {
-            this.emit('hide-subkeys');
+    setWidth: function (width) {
+        this.actor.keyWidth = width;
+    },
+});
+Signals.addSignalMethods(Key.prototype);
+
+var KeyboardModel = new Lang.Class({
+    Name: 'KeyboardModel',
+
+    _init: function (groupName) {
+        try {
+            this._model = this._loadModel(groupName);
+        } catch (e) {
+            this._model = this._loadModel('us');
         }
+    },
+
+    _loadModel: function(groupName) {
+        let file = Gio.File.new_for_uri('resource:///org/gnome/shell/osk-layouts/%s.json'.format(groupName));
+        let [success, contents] = file.load_contents(null);
+
+        return JSON.parse(contents);
+    },
+
+    getLevels: function() {
+        return this._model.levels;
+    },
+
+    getKeysForLevel: function(levelName) {
+        return this._model.levels.find(level => level == levelName);
     }
 });
-Signals.addSignalMethods(Key.prototype);
 
 var Keyboard = new Lang.Class({
     Name: 'Keyboard',
@@ -169,14 +247,10 @@ var Keyboard = new Lang.Class({
         this._enableKeyboard = false; // a11y settings value
         this._enabled = false; // enabled state (by setting or device type)
 
-        this._keyboardSettings = new Gio.Settings({ schema_id: KEYBOARD_SCHEMA });
-        this._keyboardSettings.connect('changed', Lang.bind(this, this._sync));
         this._a11yApplicationsSettings = new Gio.Settings({ schema_id: A11Y_APPLICATIONS_SCHEMA });
         this._a11yApplicationsSettings.connect('changed', Lang.bind(this, this._syncEnabled));
         this._lastDeviceId = null;
 
-        Caribou.DisplayAdapter.set_default(new LocalAdapter());
-
         Meta.get_backend().connect('last-device-changed', Lang.bind(this,
             function (backend, deviceId) {
                 let manager = Clutter.DeviceManager.get_default();
@@ -187,7 +261,7 @@ var Keyboard = new Lang.Class({
                     this._syncEnabled();
                 }
             }));
-        this._sync();
+        this._syncEnabled();
 
         this._showIdleId = 0;
         this._subkeysBoxPointer = null;
@@ -309,33 +383,25 @@ var Keyboard = new Lang.Class({
         let wasEnabled = this._enabled;
         this._enableKeyboard = this._a11yApplicationsSettings.get_boolean(SHOW_KEYBOARD);
         this._enabled = this._enableKeyboard || this._lastDeviceIsTouchscreen();
-        if (!this._enabled && !this._keyboard)
+        if (!this._enabled && !this._keyboardController)
             return;
 
         this._setCaretTrackerEnabled(this._enabled);
 
-        if (this._enabled && !this._keyboard)
+        if (this._enabled && !this._keyboardController)
             this._setupKeyboard();
 
-        if (!this._enabled && wasEnabled)
+        if (!this._enabled && wasEnabled) {
+            this._hideSubkeys();
             Main.layoutManager.hideKeyboard(true);
-    },
-
-    _sync: function () {
-        if (this._keyboard &&
-            this._keyboard.keyboard_type != this._keyboardSettings.get_string(KEYBOARD_TYPE))
-            this._destroyKeyboard();
-
-        this._syncEnabled();
+        }
     },
 
     _destroyKeyboard: function() {
         if (this._keyboardNotifyId)
-            this._keyboard.disconnect(this._keyboardNotifyId);
-        if (this._keyboardGroupAddedId)
-            this._keyboard.disconnect(this._keyboardGroupAddedId);
-        if (this._keyboardGroupRemovedId)
-            this._keyboard.disconnect(this._keyboardGroupRemovedId);
+            this._keyboardController.disconnect(this._keyboardNotifyId);
+        if (this._keyboardGroupsChangedId)
+            this._keyboardController.disconnect(this._keyboardGroupsChangedId);
         if (this._focusNotifyId)
             global.stage.disconnect(this._focusNotifyId);
         this._keyboard = null;
@@ -348,7 +414,8 @@ var Keyboard = new Lang.Class({
         Main.layoutManager.keyboardBox.add_actor(this.actor);
         Main.layoutManager.trackChrome(this.actor);
 
-        this._keyboard = new Caribou.KeyboardModel({ keyboard_type: 
this._keyboardSettings.get_string(KEYBOARD_TYPE) });
+        this._keyboardController = new KeyboardController();
+
         this._groups = {};
         this._current_page = null;
 
@@ -358,14 +425,13 @@ var Keyboard = new Lang.Class({
 
         this._addKeys();
 
-        // Keys should be layout according to the group, not the
-        // locale; as Caribou already provides the expected layout,
-        // this means enforcing LTR for all locales.
+        // Keyboard models are defined in LTR, we must override
+        // the locale setting in order to avoid flipping the
+        // keyboard on RTL locales.
         this.actor.text_direction = Clutter.TextDirection.LTR;
 
-        this._keyboardNotifyId = this._keyboard.connect('notify::active-group', Lang.bind(this, 
this._onGroupChanged));
-        this._keyboardGroupAddedId = this._keyboard.connect('group-added', Lang.bind(this, 
this._onGroupAdded));
-        this._keyboardGroupRemovedId = this._keyboard.connect('group-removed', Lang.bind(this, 
this._onGroupRemoved));
+        this._keyboardNotifyId = this._keyboardController.connect('active-group', Lang.bind(this, 
this._onGroupChanged));
+        this._keyboardGroupsChangedId = this._keyboardController.connect('groups-changed', Lang.bind(this, 
this._onKeyboardGroupsChanged));
         this._focusNotifyId = global.stage.connect('notify::key-focus', Lang.bind(this, 
this._onKeyFocusChanged));
     },
 
@@ -395,18 +461,21 @@ var Keyboard = new Lang.Class({
         }
     },
 
-    _createLayersForGroup: function (gname) {
-        let group = this._keyboard.get_group(gname);
-        group.connect('notify::active-level', Lang.bind(this, this._onLevelChanged));
+    _createLayersForGroup: function (groupName) {
+        let keyboardModel = new KeyboardModel(groupName);
         let layers = {};
-        let levels = group.get_levels();
-        for (let j = 0; j < levels.length; ++j) {
-            let lname = levels[j];
-            let level = group.get_level(lname);
+        let levels = keyboardModel.getLevels();
+        for (let i = 0; i < levels.length; i++) {
+            let currentLevel = levels[i];
+            /* There are keyboard maps which consist of 3 levels (no uppercase,
+             * basically). We however make things consistent by skipping that
+             * second level.
+             */
+            let level = (i >= 1 && levels.length == 3) ? i + 1 : i;
             let layout = new St.BoxLayout({ style_class: 'keyboard-layout',
-                                                 vertical: true });
-            this._loadRows(level, layout);
-            layers[lname] = layout;
+                                            vertical: true });
+            this._loadRows(currentLevel, level, levels.length, layout);
+            layers[level] = layout;
             this.actor.add(layout, { x_fill: false });
 
             layout.hide();
@@ -415,19 +484,19 @@ var Keyboard = new Lang.Class({
     },
 
     _addKeys: function () {
-        let groups = this._keyboard.get_groups();
+        let groups = this._keyboardController.getGroups();
         for (let i = 0; i < groups.length; ++i) {
              let gname = groups[i];
              this._groups[gname] = this._createLayersForGroup(gname);
         }
 
-        this._setActiveLayer();
+        this._setActiveLayer(0);
     },
 
     _onCapturedEvent: function(actor, event) {
         let type = event.type();
-        let press = type == Clutter.EventType.BUTTON_PRESS;
-        let release = type == Clutter.EventType.BUTTON_RELEASE;
+        let press = (type == Clutter.EventType.BUTTON_PRESS || type == Clutter.EventType.TOUCH_BEGIN);
+        let release = (type == Clutter.EventType.BUTTON_RELEASE || type == Clutter.EventType.TOUCH_END);
 
         if (press)
             this._capturedPress = true;
@@ -437,68 +506,140 @@ var Keyboard = new Lang.Class({
         return Clutter.EVENT_STOP;
     },
 
-    _addRows : function (keys, layout) {
-        let keyboard_row = new St.BoxLayout();
+    _addRowKeys : function (keys, keyboardRow) {
         for (let i = 0; i < keys.length; ++i) {
-            let children = keys[i].get_children();
-            let left_box = new St.BoxLayout({ style_class: 'keyboard-row' });
-            let center_box = new St.BoxLayout({ style_class: 'keyboard-row' });
-            let right_box = new St.BoxLayout({ style_class: 'keyboard-row' });
-            for (let j = 0; j < children.length; ++j) {
-                if (this._numOfHorizKeys == 0)
-                    this._numOfHorizKeys = children.length;
-                let key = children[j];
-                let button = new Key(key);
-
-                switch (key.align) {
-                case 'right':
-                    right_box.add(button.actor);
-                    break;
-                case 'center':
-                    center_box.add(button.actor);
-                    break;
-                case 'left':
-                default:
-                    left_box.add(button.actor);
-                    break;
-                }
-                if (key.name == 'Caribou_Prefs') {
-                    key.connect('key-released', Lang.bind(this, this.hide));
-                }
+            let key = keys[i];
+            let button = new Key(key.shift(), key);
+
+            /* Space key gets special width, dependent on the number of surrounding keys */
+            if (button.key == ' ')
+                button.setWidth(keys.length <= 3 ? 5 : 3);
+
+            button.connect('show-subkeys', Lang.bind(this, function() {
+                if (this._subkeysBoxPointer)
+                    this._subkeysBoxPointer.hide(BoxPointer.PopupAnimation.FULL);
+                this._subkeysBoxPointer = button.subkeys;
+                this._subkeysBoxPointer.show(BoxPointer.PopupAnimation.FULL);
+                if (!this._capturedEventId)
+                    this._capturedEventId = this.actor.connect('captured-event',
+                                                               Lang.bind(this, this._onCapturedEvent));
+            }));
+            button.connect('hide-subkeys', Lang.bind(this, function() {
+                this._hideSubkeys();
+            }));
+            button.connect('pressed', Lang.bind(this, function(actor, keyval) {
+                this._hideSubkeys();
+                if (keyval != 0)
+                    this._keyboardController.keyvalPress(keyval);
+            }));
+            button.connect('released', Lang.bind(this, function(actor, keyval) {
+                this._hideSubkeys();
+                if (keyval != 0)
+                    this._keyboardController.keyvalRelease(keyval);
+            }));
+
+            keyboardRow.add(button.container, { expand: true, x_fill: false, x_align: St.Align.END });
+        }
+    },
 
-                button.connect('show-subkeys', Lang.bind(this, function() {
-                    if (this._subkeysBoxPointer)
-                        this._subkeysBoxPointer.hide(BoxPointer.PopupAnimation.FULL);
-                    this._subkeysBoxPointer = button.subkeys;
-                    this._subkeysBoxPointer.show(BoxPointer.PopupAnimation.FULL);
-                    if (!this._capturedEventId)
-                        this._capturedEventId = this.actor.connect('captured-event',
-                                                                   Lang.bind(this, this._onCapturedEvent));
-                }));
-                button.connect('hide-subkeys', Lang.bind(this, function() {
-                    this._hideSubkeys();
-                }));
+    _loadDefaultKeys: function(keys, rowActor, numLevels, numKeys) {
+        let extraButton;
+        for (let i = 0; i < keys.length; i++) {
+            let key = keys[i];
+            let keyval = key.keyval;
+            let switchToLevel = key.level;
+            let action = key.action;
+
+            extraButton = new Key(key.label, []);
+            rowActor.add(extraButton.container);
+
+            extraButton.actor.add_style_class_name('default-key');
+            if (key.extraClassName != null)
+                extraButton.actor.add_style_class_name(key.extraClassName);
+            if (key.width != null)
+                extraButton.setWidth(key.width);
+
+            extraButton.connect('released', Lang.bind(this, function() {
+                if (switchToLevel != null)
+                    this._onLevelChanged(switchToLevel);
+                else if (keyval != null)
+                    this._keyboardController.keyvalPress(keyval);
+            }));
+            extraButton.connect('released', Lang.bind(this, function() {
+                if (keyval != null)
+                    this._keyboardController.keyvalRelease(keyval);
+                else if (action == 'hide')
+                    this.hide();
+            }));
+
+            /* Fixup default keys based on the number of levels/keys */
+            if (key.label == '⇧' && numLevels == 3) {
+                if (key.right) {
+                    /* Only hide the key actor, so the container still takes space */
+                    extraButton.actor.hide();
+                } else {
+                    extraButton.container.hide();
+                }
+                extraButton.setWidth(1.5);
+            } else if (key.right && numKeys > 8) {
+                extraButton.setWidth(2);
+            } else if (key.label == '⏎' && numKeys > 9) {
+                extraButton.setWidth(1.5);
             }
-            keyboard_row.add(left_box, { expand: true, x_fill: false, x_align: St.Align.START });
-            keyboard_row.add(center_box, { expand: true, x_fill: false, x_align: St.Align.MIDDLE });
-            keyboard_row.add(right_box, { expand: true, x_fill: false, x_align: St.Align.END });
         }
-        layout.add(keyboard_row);
     },
 
-    _loadRows : function (level, layout) {
-        let rows = level.get_rows();
+    _getDefaultKeysForRow: function(row, numRows, level) {
+        let pre, post;
+
+        /* The first 2 rows in defaultKeysPre/Post belong together with
+         * the first 2 rows on each keymap. On keymaps that have more than
+         * 4 rows, the last 2 default key rows must be respectively
+         * assigned to the 2 last keymap ones.
+         */
+        if (row < 2) {
+            return [defaultKeysPre[level][row], defaultKeysPost[level][row]];
+        } else if (row >= numRows - 2) {
+            let defaultRow = row - (numRows - 2) + 2;
+            return [defaultKeysPre[level][defaultRow], defaultKeysPost[level][defaultRow]];
+        } else {
+            return [null, null];
+        }
+    },
+
+    _mergeRowKeys: function (keyboardRow, pre, row, post, numLevels) {
+        if (pre != null)
+            this._loadDefaultKeys(pre, keyboardRow, numLevels, row.length);
+
+        this._addRowKeys(row, keyboardRow);
+
+        if (post != null)
+            this._loadDefaultKeys(post, keyboardRow, numLevels, row.length);
+    },
+
+    _loadRows : function (model, level, numLevels, layout) {
+        let rows = model.rows;
         for (let i = 0; i < rows.length; ++i) {
             let row = rows[i];
             if (this._numOfVertKeys == 0)
                 this._numOfVertKeys = rows.length;
-            this._addRows(row.get_columns(), layout);
+
+            let keyboardRow = new St.BoxLayout({ style_class: 'keyboard-row',
+                                                 x_expand: false,
+                                                 x_align: Clutter.ActorAlign.END });
+            layout.add(keyboardRow);
+
+            let [pre, post] = this._getDefaultKeysForRow(i, rows.length, level);
+            this._mergeRowKeys (keyboardRow, pre, row, post, numLevels);
+
+            this._numOfHorizKeys = Math.max(this._numOfHorizKeys, keyboardRow.get_n_children());
         }
 
+        this._numOfVertKeys = Math.max(this._numOfVertKeys, rows.length);
     },
 
     _redraw: function () {
-        if (!this._enabled)
+        if (!this._enabled || !this._current_page)
             return;
 
         let monitor = Main.layoutManager.keyboardMonitor;
@@ -523,55 +664,48 @@ var Keyboard = new Lang.Class({
         let rows = this._current_page.get_children();
         for (let i = 0; i < rows.length; ++i) {
             let keyboard_row = rows[i];
-            let boxes = keyboard_row.get_children();
-            for (let j = 0; j < boxes.length; ++j) {
-                let keys = boxes[j].get_children();
-                for (let k = 0; k < keys.length; ++k) {
-                    let child = keys[k];
-                    child.width = keySize * child.key_width;
-                    child.height = keySize;
-                    if (child._extended_keys) {
-                        let extended_keys = child._extended_keys.get_children();
-                        for (let n = 0; n < extended_keys.length; ++n) {
-                            let extended_key = extended_keys[n];
-                            extended_key.width = keySize;
-                            extended_key.height = keySize;
-                        }
+            let keys = keyboard_row.get_children();
+            for (let j = 0; j < keys.length; ++j) {
+                let child = keys[j];
+                let keyActor = child.get_children()[0];
+                child.width = keySize * keyActor.keyWidth;
+                child.height = keySize;
+                if (keyActor._extended_keys) {
+                    let extended_keys = keyActor._extended_keys.get_children();
+                    for (let n = 0; n < extended_keys.length; ++n) {
+                        let extended_key = extended_keys[n];
+                        extended_key.width = keySize;
+                        extended_key.height = keySize;
                     }
                 }
             }
         }
     },
 
-    _onLevelChanged: function () {
-        this._setActiveLayer();
+    _onLevelChanged: function (level) {
+        this._setActiveLayer(level);
         this._redraw();
     },
 
     _onGroupChanged: function () {
-        this._setActiveLayer();
+        this._setActiveLayer(0);
         this._redraw();
     },
 
-    _onGroupAdded: function (keyboard, gname) {
-        this._groups[gname] = this._createLayersForGroup(gname);
-    },
-
-    _onGroupRemoved: function (keyboard, gname) {
-        delete this._groups[gname];
+    _onKeyboardGroupsChanged: function(keyboard) {
+        this._groups = [];
+        this._addKeys();
     },
 
-    _setActiveLayer: function () {
-        let active_group_name = this._keyboard.active_group;
-        let active_group = this._keyboard.get_group(active_group_name);
-        let active_level = active_group.active_level;
-        let layers = this._groups[active_group_name];
+    _setActiveLayer: function (activeLevel) {
+        let activeGroupName = this._keyboardController.getCurrentGroup();
+        let layers = this._groups[activeGroupName];
 
         if (this._current_page != null) {
             this._current_page.hide();
         }
 
-        this._current_page = layers[active_level];
+        this._current_page = layers[activeLevel];
         this._current_page.show();
     },
 
@@ -700,9 +834,8 @@ var Keyboard = new Lang.Class({
     },
 });
 
-var LocalAdapter = new Lang.Class({
-    Name: 'LocalAdapter',
-    Extends: Caribou.XAdapter,
+var KeyboardController = new Lang.Class({
+    Name: 'KeyboardController',
 
     _init: function () {
         this.parent();
@@ -714,38 +847,43 @@ var LocalAdapter = new Lang.Class({
                                                                  Lang.bind(this, this._onSourceChanged));
         this._sourcesModifiedId = this._inputSourceManager.connect ('sources-changed',
                                                                     Lang.bind(this, 
this._onSourcesModified));
+        this._currentSource = this._inputSourceManager.currentSource;
     },
 
     _onSourcesModified: function () {
-        this.emit('config-changed');
+        this.emit('groups-changed');
     },
 
     _onSourceChanged: function (inputSourceManager, oldSource) {
         let source = inputSourceManager.currentSource;
-        this.emit('group-changed', source.index, source.id, '');
+        this._currentSource = source;
+        this.emit('active-group', source.id);
     },
 
-    vfunc_get_groups: function () {
+    getGroups: function () {
         let inputSources = this._inputSourceManager.inputSources;
         let groups = []
-        let variants = [];
 
         for (let i in inputSources) {
             let is = inputSources[i];
-            groups[is.index] = is.id;
-            variants[is.index] = '';
+            groups[is.index] = is.xkbId;
         }
 
-        return [groups, groups.length, variants, variants.length];
+        return groups;
+    },
+
+    getCurrentGroup: function () {
+        return this._currentSource.xkbId;
     },
 
-    vfunc_keyval_press: function(keyval) {
+    keyvalPress: function(keyval) {
         this._virtualDevice.notify_keyval(Clutter.get_current_event_time(),
                                           keyval, Clutter.KeyState.PRESSED);
     },
 
-    vfunc_keyval_release: function(keyval) {
+    keyvalRelease: function(keyval) {
         this._virtualDevice.notify_keyval(Clutter.get_current_event_time(),
                                           keyval, Clutter.KeyState.RELEASED);
     },
 });
+Signals.addSignalMethods(KeyboardController.prototype);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]