diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index f790d52b7..63331fb75 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -15,7 +15,7 @@ jobs:
- name: Install Dependencies
run: |
apt update
- apt install -y meson libgranite-dev libswitchboard-2.0-dev libxml2-dev libgnomekbd-dev libxklavier-dev valac
+ apt install -y meson libgranite-dev libswitchboard-2.0-dev libxml2-dev libgnomekbd-dev libibus-1.0-dev libxklavier-dev valac
- name: Build
env:
DESTDIR: out
diff --git a/README.md b/README.md
index ee705f745..4d5f224f7 100644
--- a/README.md
+++ b/README.md
@@ -11,6 +11,7 @@ You'll need the following dependencies:
* libgnomekbd-dev
* libgranite-dev
* libgtk-3-dev
+* libibus-1.0-dev
* libxklavier-dev
* libxml2-dev
* meson
diff --git a/po/POTFILES b/po/POTFILES
index de5aa888f..12e86f617 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -1,5 +1,12 @@
src/Plug.vala
+src/InputMethod/AddEnginesList.vala
+src/InputMethod/Utils.vala
src/Dialogs/ConflictDialog.vala
+src/Dialogs/InstallEngineDialog.vala
+src/Dialogs/ProgressDialog.vala
+src/InputMethod/Installer/aptd-client.vala
+src/InputMethod/Installer/InstallList.vala
+src/InputMethod/Installer/UbuntuInstaller.vala
src/Layout/AdvancedSettingsGrid.vala
src/Layout/AdvancedSettingsPanel.vala
src/Layout/Handler.vala
@@ -11,8 +18,12 @@ src/Shortcuts/Settings.vala
src/Shortcuts/Shortcut.vala
src/Views/AbstractPage.vala
src/Views/Behavior.vala
+src/Views/InputMethod.vala
src/Views/Layout.vala
src/Views/Shortcuts.vala
+src/Widgets/InputMethod/AddEnginesPopover.vala
+src/Widgets/InputMethod/EnginesRow.vala
+src/Widgets/InputMethod/LanguagesRow.vala
src/Widgets/Layout/AddLayoutPopover.vala
src/Widgets/Layout/Display.vala
src/Widgets/Shortcuts/CustomTree.vala
diff --git a/src/Dialogs/InstallEngineDialog.vala b/src/Dialogs/InstallEngineDialog.vala
new file mode 100644
index 000000000..ffba3a82b
--- /dev/null
+++ b/src/Dialogs/InstallEngineDialog.vala
@@ -0,0 +1,140 @@
+/*
+* Copyright 2019-2020 elementary, Inc. (https://elementary.io)
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see .
+*/
+
+public class Pantheon.Keyboard.InputMethodPage.InstallEngineDialog : Granite.MessageDialog {
+ private InstallList? engines_filter;
+
+ public InstallEngineDialog (Gtk.Window parent) {
+ Object (
+ primary_text: _("Choose an engine to install"),
+ secondary_text: _("Select an engine from the list to install and use."),
+ image_icon: new ThemedIcon ("extension"),
+ transient_for: parent,
+ buttons: Gtk.ButtonsType.CANCEL
+ );
+ }
+
+ construct {
+ var languages_list = new Gtk.ListBox () {
+ activate_on_single_click = true,
+ expand = true,
+ selection_mode = Gtk.SelectionMode.NONE
+ };
+
+ foreach (var language in InstallList.get_all ()) {
+ var lang = new LanguagesRow (language);
+ languages_list.add (lang);
+ }
+
+ var back_button = new Gtk.Button.with_label (_("Languages")) {
+ halign = Gtk.Align.START,
+ margin = 6
+ };
+ back_button.get_style_context ().add_class (Granite.STYLE_CLASS_BACK_BUTTON);
+
+ var language_title = new Gtk.Label ("");
+
+ var language_header = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6);
+ language_header.pack_start (back_button);
+ language_header.set_center_widget (language_title);
+
+ var listbox = new Gtk.ListBox () {
+ expand = true
+ };
+ listbox.set_filter_func (filter_function);
+ listbox.set_sort_func (sort_function);
+
+ foreach (var language in InstallList.get_all ()) {
+ foreach (var engine in language.get_components ()) {
+ listbox.add (new EnginesRow (engine));
+ }
+ }
+
+ var scrolled = new Gtk.ScrolledWindow (null, null);
+ scrolled.add (listbox);
+
+ var engine_list_grid = new Gtk.Grid () {
+ orientation = Gtk.Orientation.VERTICAL
+ };
+ engine_list_grid.get_style_context ().add_class (Gtk.STYLE_CLASS_VIEW);
+ engine_list_grid.add (language_header);
+ engine_list_grid.add (new Gtk.Separator (Gtk.Orientation.HORIZONTAL));
+ engine_list_grid.add (scrolled);
+
+ var stack = new Gtk.Stack () {
+ height_request = 200,
+ width_request = 300,
+ transition_type = Gtk.StackTransitionType.SLIDE_LEFT_RIGHT
+ };
+ stack.add (languages_list);
+ stack.add (engine_list_grid);
+
+ var frame = new Gtk.Frame (null);
+ frame.add (stack);
+
+ custom_bin.add (frame);
+ custom_bin.show_all ();
+
+ var install_button = add_button (_("Install"), Gtk.ResponseType.OK);
+ install_button.sensitive = false;
+ install_button.get_style_context ().add_class (Gtk.STYLE_CLASS_SUGGESTED_ACTION);
+
+ languages_list.row_activated.connect ((row) => {
+ stack.visible_child = engine_list_grid;
+ language_title.label = ((LanguagesRow) row).language.get_name ();
+ engines_filter = ((LanguagesRow) row).language;
+ listbox.invalidate_filter ();
+ var adjustment = scrolled.get_vadjustment ();
+ adjustment.set_value (adjustment.lower);
+ });
+
+ back_button.clicked.connect (() => {
+ stack.visible_child = languages_list;
+ install_button.sensitive = false;
+ });
+
+ listbox.selected_rows_changed.connect (() => {
+ foreach (var engines_row in listbox.get_children ()) {
+ ((EnginesRow) engines_row).selected = false;
+ }
+
+ ((EnginesRow) listbox.get_selected_row ()).selected = true;
+ install_button.sensitive = true;
+ });
+
+ response.connect ((response_id) => {
+ if (response_id == Gtk.ResponseType.OK) {
+ string engine_to_install = ((EnginesRow) listbox.get_selected_row ()).engine_name;
+ UbuntuInstaller.get_default ().install (engine_to_install);
+ }
+ });
+ }
+
+ [CCode (instance_pos = -1)]
+ private bool filter_function (Gtk.ListBoxRow row) {
+ if (InstallList.get_language_from_engine_name (((EnginesRow) row).engine_name) == engines_filter) {
+ return true;
+ }
+
+ return false;
+ }
+
+ [CCode (instance_pos = -1)]
+ private int sort_function (Gtk.ListBoxRow row1, Gtk.ListBoxRow row2) {
+ return ((EnginesRow) row1).engine_name.collate (((EnginesRow) row1).engine_name);
+ }
+}
diff --git a/src/Dialogs/ProgressDialog.vala b/src/Dialogs/ProgressDialog.vala
new file mode 100644
index 000000000..f110acaf1
--- /dev/null
+++ b/src/Dialogs/ProgressDialog.vala
@@ -0,0 +1,82 @@
+/*
+* Copyright 2011-2020 elementary, Inc. (https://elementary.io)
+*
+* 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 3 of the
+* License, or (at your option) any later version.
+*
+* This program is distributed in the hope that it will be
+* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+* Public License for more details.
+*
+* You should have received a copy of the GNU General Public License along
+* with this program. If not, see http://www.gnu.org/licenses/.
+*/
+
+public class Pantheon.Keyboard.InputMethodPage.ProgressDialog : Gtk.Dialog {
+ public int progress {
+ set {
+ if (value >= 100) {
+ destroy ();
+ }
+
+ progress_bar.fraction = value / 100.0;
+ }
+ }
+
+ private Gtk.ProgressBar progress_bar;
+
+ construct {
+ var image = new Gtk.Image.from_icon_name ("preferences-desktop-locale", Gtk.IconSize.DIALOG) {
+ valign = Gtk.Align.START
+ };
+
+ var primary_label = new Gtk.Label (null) {
+ max_width_chars = 50,
+ wrap = true,
+ xalign = 0
+ };
+ primary_label.get_style_context ().add_class (Granite.STYLE_CLASS_PRIMARY_LABEL);
+
+ unowned UbuntuInstaller installer = UbuntuInstaller.get_default ();
+ switch (installer.transaction_mode) {
+ case UbuntuInstaller.TransactionMode.INSTALL:
+ primary_label.label = _("Installing %s").printf (installer.engine_to_address);
+ break;
+ case UbuntuInstaller.TransactionMode.REMOVE:
+ primary_label.label = _("Removing %s").printf (installer.engine_to_address);
+ break;
+ }
+
+ progress_bar = new Gtk.ProgressBar () {
+ hexpand = true,
+ valign = Gtk.Align.START,
+ width_request = 300
+ };
+
+ var cancel_button = (Gtk.Button) add_button (_("Cancel"), 0);
+
+ installer.bind_property ("install-cancellable", cancel_button, "sensitive");
+
+ var grid = new Gtk.Grid () {
+ column_spacing = 12,
+ margin = 6,
+ row_spacing = 6
+ };
+ grid.attach (image, 0, 0, 1, 2);
+ grid.attach (primary_label, 1, 0);
+ grid.attach (progress_bar, 1, 1);
+ grid.show_all ();
+
+ border_width = 6;
+ deletable = false;
+ get_content_area ().add (grid);
+
+ cancel_button.clicked.connect (() => {
+ installer.cancel_install ();
+ destroy ();
+ });
+ }
+}
diff --git a/src/InputMethod/AddEnginesList.vala b/src/InputMethod/AddEnginesList.vala
new file mode 100644
index 000000000..c46503207
--- /dev/null
+++ b/src/InputMethod/AddEnginesList.vala
@@ -0,0 +1,38 @@
+/*
+* 2019-2020 elementary, Inc. (https://elementary.io)
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see .
+*/
+
+public class Pantheon.Keyboard.InputMethodPage.AddEnginesList : Object {
+ /*
+ * Stores strings used to add/remove engines in the code and won't be shown in the UI.
+ * It consists from "",
+ * e.g. "mozc-jp" or "libpinyin"
+ */
+ public string engine_id { get; private set; }
+
+ /*
+ * Stores strings used to show in the UI.
+ * It consists from " - ",
+ * e.g. "Japanese - Mozc" or "Chinese - Intelligent Pinyin"
+ */
+ public string engine_full_name { get; private set; }
+
+ public AddEnginesList (IBus.EngineDesc engine) {
+ engine_id = engine.name;
+ engine_full_name = "%s - %s".printf (IBus.get_language_name (engine.language),
+ Utils.gettext_engine_longname (engine));
+ }
+}
diff --git a/src/InputMethod/Installer/InstallList.vala b/src/InputMethod/Installer/InstallList.vala
new file mode 100644
index 000000000..275c30205
--- /dev/null
+++ b/src/InputMethod/Installer/InstallList.vala
@@ -0,0 +1,73 @@
+/*
+* 2019-2020 elementary, Inc. (https://elementary.io)
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see .
+*/
+
+public enum Pantheon.Keyboard.InputMethodPage.InstallList {
+ JA,
+ KO,
+ ZH;
+
+ public string get_name () {
+ switch (this) {
+ case JA:
+ return _("Japanese");
+ case KO:
+ return _("Korean");
+ case ZH:
+ return _("Chinese");
+ default:
+ assert_not_reached ();
+ }
+ }
+
+ public string[] get_components () {
+ switch (this) {
+ case JA:
+ return { "ibus-anthy", "ibus-mozc", "ibus-skk" };
+ case KO:
+ return { "ibus-hangul" };
+ case ZH:
+ return { "ibus-cangjie", "ibus-chewing", "ibus-pinyin" };
+ default:
+ assert_not_reached ();
+ }
+ }
+
+ public static InstallList get_language_from_engine_name (string engine_name) {
+ switch (engine_name) {
+ case "ibus-anthy":
+ return JA;
+ case "ibus-mozc":
+ return JA;
+ case "ibus-skk":
+ return JA;
+ case "ibus-hangul":
+ return KO;
+ case "ibus-cangjie":
+ return ZH;
+ case "ibus-chewing":
+ return ZH;
+ case "ibus-pinyin":
+ return ZH;
+ default:
+ assert_not_reached ();
+ }
+ }
+
+ public static InstallList[] get_all () {
+ return { JA, KO, ZH };
+ }
+}
diff --git a/src/InputMethod/Installer/UbuntuInstaller.vala b/src/InputMethod/Installer/UbuntuInstaller.vala
new file mode 100644
index 000000000..b65aa1fa7
--- /dev/null
+++ b/src/InputMethod/Installer/UbuntuInstaller.vala
@@ -0,0 +1,142 @@
+/*
+* Copyright 2011-2020 elementary, Inc. (https://elementary.io)
+*
+* 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 3 of the
+* License, or (at your option) any later version.
+*
+* This program is distributed in the hope that it will be
+* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+* Public License for more details.
+*
+* You should have received a copy of the GNU General Public License along
+* with this program. If not, see http://www.gnu.org/licenses/.
+*/
+
+public class Pantheon.Keyboard.InputMethodPage.UbuntuInstaller : Object {
+ private AptdProxy aptd;
+ private AptdTransactionProxy proxy;
+
+ public bool install_cancellable { get; private set; }
+ public TransactionMode transaction_mode { get; private set; }
+ public string engine_to_address { get; private set; }
+
+ public signal void install_finished (string langcode);
+ public signal void install_failed ();
+ public signal void remove_finished (string langcode);
+ public signal void progress_changed (int progress);
+
+ public enum TransactionMode {
+ INSTALL,
+ REMOVE,
+ INSTALL_MISSING,
+ }
+
+ Gee.HashMap transactions;
+
+ private static GLib.Once instance;
+ public static unowned UbuntuInstaller get_default () {
+ return instance.once (() => {
+ return new UbuntuInstaller ();
+ });
+ }
+
+ private UbuntuInstaller () {}
+
+ construct {
+ transactions = new Gee.HashMap ();
+ aptd = new AptdProxy ();
+
+ try {
+ aptd.connect_to_aptd ();
+ } catch (Error e) {
+ warning ("Could not connect to APT daemon");
+ }
+ }
+
+ public void install (string engine_name) {
+ transaction_mode = TransactionMode.INSTALL;
+ engine_to_address = engine_name;
+ string[] packages = {};
+ packages += engine_to_address;
+
+ foreach (var packet in packages) {
+ message ("Packet: %s", packet);
+ }
+
+ aptd.install_packages.begin (packages, (obj, res) => {
+ try {
+ var transaction_id = aptd.install_packages.end (res);
+ transactions.@set (transaction_id, "i-" + engine_name);
+ run_transaction (transaction_id);
+ } catch (Error e) {
+ warning ("Could not queue downloads: %s", e.message);
+ }
+ });
+ }
+
+ public void cancel_install () {
+ if (install_cancellable) {
+ warning ("cancel_install");
+ try {
+ proxy.cancel ();
+ } catch (Error e) {
+ warning ("cannot cancel installation:%s", e.message);
+ }
+ }
+ }
+
+ private void run_transaction (string transaction_id) {
+ proxy = new AptdTransactionProxy ();
+ proxy.finished.connect (() => {
+ on_apt_finshed (transaction_id, true);
+ });
+
+ proxy.property_changed.connect ((prop, val) => {
+ if (prop == "Progress") {
+ progress_changed ((int) val.get_int32 ());
+ }
+
+ if (prop == "Cancellable") {
+ install_cancellable = val.get_boolean ();
+ }
+ });
+
+ try {
+ proxy.connect_to_aptd (transaction_id);
+ proxy.simulate ();
+
+ proxy.run ();
+ } catch (Error e) {
+ on_apt_finshed (transaction_id, false);
+ warning ("Could no run transaction: %s", e.message);
+ }
+ }
+
+ private void on_apt_finshed (string id, bool success) {
+ if (!success) {
+ install_failed ();
+ transactions.unset (id);
+ return;
+ }
+
+ if (!transactions.has_key (id)) { //transaction already removed
+ return;
+ }
+
+ var action = transactions.get (id);
+ var lang = action[2:action.length];
+
+ message ("ID %s -> %s", id, success ? "success" : "failed");
+
+ if (action[0:1] == "i") { // install
+ install_finished (lang);
+ } else {
+ remove_finished (lang);
+ }
+
+ transactions.unset (id);
+ }
+}
diff --git a/src/InputMethod/Installer/aptd-client.vala b/src/InputMethod/Installer/aptd-client.vala
new file mode 100644
index 000000000..ee5c3f5fe
--- /dev/null
+++ b/src/InputMethod/Installer/aptd-client.vala
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2012 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Authored by Pawel Stolowski
+ */
+
+namespace Pantheon.Keyboard.InputMethodPage {
+ private const string APTD_DBUS_NAME = "org.debian.apt";
+ private const string APTD_DBUS_PATH = "/org/debian/apt";
+
+ /**
+ * Expose a subset of org.debian.apt interfaces -- only what's needed by applications lens.
+ */
+ [DBus (name = "org.debian.apt")]
+ public interface AptdService : GLib.Object {
+ public abstract async string install_packages (string[] packages) throws GLib.Error;
+ public abstract async string remove_packages (string[] packages) throws GLib.Error;
+ public abstract async void quit () throws GLib.Error;
+ }
+
+ [DBus (name = "org.debian.apt.transaction")]
+ public interface AptdTransactionService : GLib.Object {
+ public abstract void run () throws GLib.Error;
+ public abstract void simulate () throws GLib.Error;
+ public abstract void cancel () throws GLib.Error;
+ public signal void finished (string exit_state);
+ public signal void property_changed (string property, Variant val);
+ }
+
+ public class AptdProxy : GLib.Object {
+ private AptdService _aptd_service;
+
+ public void connect_to_aptd () throws GLib.Error {
+ _aptd_service = Bus.get_proxy_sync (BusType.SYSTEM, APTD_DBUS_NAME, APTD_DBUS_PATH);
+ }
+
+ public async string install_packages (string[] packages) throws GLib.Error {
+ string res = yield _aptd_service.install_packages (packages);
+ return res;
+ }
+
+ public async string remove_packages (string[] packages) throws GLib.Error {
+ string res = yield _aptd_service.remove_packages (packages);
+ return res;
+ }
+
+ public async void quit () throws GLib.Error {
+ yield _aptd_service.quit ();
+ }
+ }
+
+ public class AptdTransactionProxy : GLib.Object {
+ public signal void finished (string transaction_id);
+ public signal void property_changed (string property, Variant variant);
+
+ private AptdTransactionService _aptd_service;
+
+ public void connect_to_aptd (string transaction_id) throws GLib.Error {
+ _aptd_service = Bus.get_proxy_sync (BusType.SYSTEM, APTD_DBUS_NAME, transaction_id);
+ _aptd_service.finished.connect ((exit_state) => {
+ debug ("aptd transaction finished: %s\n", exit_state);
+ finished (transaction_id);
+ });
+ _aptd_service.property_changed.connect ((prop, variant) => {
+ property_changed (prop, variant);
+ });
+ }
+
+ public void simulate () throws GLib.Error {
+ _aptd_service.simulate ();
+ }
+
+ public void run () throws GLib.Error {
+ _aptd_service.run ();
+ }
+
+ public void cancel () throws GLib.Error {
+ _aptd_service.cancel ();
+ }
+ }
+}
diff --git a/src/InputMethod/Utils.vala b/src/InputMethod/Utils.vala
new file mode 100644
index 000000000..20691936f
--- /dev/null
+++ b/src/InputMethod/Utils.vala
@@ -0,0 +1,46 @@
+/*
+* 2019-2020 elementary, Inc. (https://elementary.io)
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see .
+*/
+
+public class Pantheon.Keyboard.InputMethodPage.Utils : Object {
+ private static string[] _active_engines;
+ // Stores currently activated engines
+ public static string[] active_engines {
+ get {
+ _active_engines = Pantheon.Keyboard.Plug.ibus_general_settings.get_strv ("preload-engines");
+ return _active_engines;
+ }
+ set {
+ Pantheon.Keyboard.Plug.ibus_general_settings.set_strv ("preload-engines", value);
+ Pantheon.Keyboard.Plug.ibus_general_settings.set_strv ("engines-order", value);
+ }
+ }
+
+ // From https://github.com/ibus/ibus/blob/master/ui/gtk2/i18n.py#L47-L54
+ public static string gettext_engine_longname (IBus.EngineDesc engine) {
+ string name = engine.name;
+ if (name.has_prefix ("xkb:")) {
+ return dgettext ("xkeyboard-config", engine.longname);
+ }
+
+ string textdomain = engine.textdomain;
+ if (textdomain == "") {
+ return engine.longname;
+ }
+
+ return dgettext (textdomain, engine.longname);
+ }
+}
diff --git a/src/Plug.vala b/src/Plug.vala
index 3f0da4213..a0719a951 100644
--- a/src/Plug.vala
+++ b/src/Plug.vala
@@ -18,6 +18,8 @@
*/
public class Pantheon.Keyboard.Plug : Switchboard.Plug {
+ public static GLib.Settings ibus_general_settings;
+
private Gtk.Grid grid;
private Gtk.Stack stack;
@@ -26,6 +28,7 @@ public class Pantheon.Keyboard.Plug : Switchboard.Plug {
settings.set ("input/keyboard", "Layout");
settings.set ("input/keyboard/layout", "Layout");
settings.set ("input/keyboard/behavior", "Behavior");
+ settings.set ("input/keyboard/inputmethod", "Input Method");
settings.set ("input/keyboard/shortcuts", "Shortcuts");
Object (category: Category.HARDWARE,
code_name: "io.elementary.switchboard.keyboard",
@@ -35,11 +38,16 @@ public class Pantheon.Keyboard.Plug : Switchboard.Plug {
supported_settings: settings);
}
+ static construct {
+ ibus_general_settings = new GLib.Settings ("org.freedesktop.ibus.general");
+ }
+
public override Gtk.Widget get_widget () {
if (grid == null) {
stack = new Gtk.Stack ();
stack.margin = 12;
stack.add_titled (new Keyboard.LayoutPage.Page (), "layout", _("Layout"));
+ stack.add_titled (new Keyboard.InputMethodPage.Page (), "inputmethod", _("Input Method"));
stack.add_titled (new Keyboard.Shortcuts.Page (), "shortcuts", _("Shortcuts"));
stack.add_titled (new Keyboard.Behaviour.Page (), "behavior", _("Behavior"));
@@ -74,6 +82,9 @@ public class Pantheon.Keyboard.Plug : Switchboard.Plug {
case "Behavior":
stack.visible_child_name = "behavior";
break;
+ case "Input Method":
+ stack.visible_child_name = "inputmethod";
+ break;
case "Layout":
stack.visible_child_name = "layout";
break;
@@ -88,6 +99,10 @@ public class Pantheon.Keyboard.Plug : Switchboard.Plug {
search_results.set ("%s → %s → %s".printf (display_name, _("Layout"), _("Compose Key")), "Layout");
search_results.set ("%s → %s → %s".printf (display_name, _("Layout"), _("⌘ key behavior")), "Layout");
search_results.set ("%s → %s → %s".printf (display_name, _("Layout"), _("Caps Lock behavior")), "Layout");
+ search_results.set ("%s → %s".printf (display_name, _("Input Method")), "Input Method");
+ search_results.set ("%s → %s → %s".printf (display_name, _("Input Method"), _("Switch engines")), "Input Method");
+ search_results.set ("%s → %s → %s".printf (display_name, _("Input Method"), _("Show candidate window")), "Input Method");
+ search_results.set ("%s → %s → %s".printf (display_name, _("Input Method"), _("Embed preedit text in application window")), "Input Method");
search_results.set ("%s → %s".printf (display_name, _("Shortcuts")), "Shortcuts");
search_results.set ("%s → %s".printf (display_name, _("Behavior")), "Behavior");
search_results.set ("%s → %s → %s".printf (display_name, _("Behavior"), _("Repeat Keys")), "Behavior");
@@ -98,6 +113,7 @@ public class Pantheon.Keyboard.Plug : Switchboard.Plug {
public Switchboard.Plug get_plug (Module module) {
debug ("Activating Keyboard plug");
+ IBus.init ();
var plug = new Pantheon.Keyboard.Plug ();
return plug;
}
diff --git a/src/Views/InputMethod.vala b/src/Views/InputMethod.vala
new file mode 100644
index 000000000..709081953
--- /dev/null
+++ b/src/Views/InputMethod.vala
@@ -0,0 +1,349 @@
+/*
+* 2019-2020 elementary, Inc. (https://elementary.io)
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see .
+*/
+
+public class Pantheon.Keyboard.InputMethodPage.Page : Pantheon.Keyboard.AbstractPage {
+ private IBus.Bus bus;
+ private GLib.Settings ibus_panel_settings;
+ // Stores all installed engines
+#if IBUS_1_5_19
+ private List engines;
+#else
+ private List engines;
+#endif
+
+ private Granite.Widgets.AlertView spawn_failed_alert;
+ private Gtk.ListBox listbox;
+ private Gtk.MenuButton remove_button;
+ private AddEnginesPopover add_engines_popover;
+ private Gtk.Stack stack;
+
+ construct {
+ bus = new IBus.Bus ();
+ ibus_panel_settings = new GLib.Settings ("org.freedesktop.ibus.panel");
+
+ // no_daemon_runnning view shown if IBus Daemon is not running
+ var no_daemon_runnning_alert = new Granite.Widgets.AlertView (
+ _("IBus Daemon is not running"),
+ _("You need to run IBus Daemon to enable or configure input method engines."),
+ "dialog-information"
+ ) {
+ halign = Gtk.Align.CENTER,
+ valign = Gtk.Align.CENTER
+ };
+ no_daemon_runnning_alert.get_style_context ().remove_class (Gtk.STYLE_CLASS_VIEW);
+ no_daemon_runnning_alert.show_action (_("Start IBus Daemon"));
+ no_daemon_runnning_alert.action_activated.connect (() => {
+ spawn_ibus_daemon ();
+ });
+
+ // spawn_failed view shown if IBus Daemon is not running
+ spawn_failed_alert = new Granite.Widgets.AlertView (
+ _("Failed to start IBus Daemon"),
+ "",
+ "dialog-error"
+ ) {
+ halign = Gtk.Align.CENTER,
+ valign = Gtk.Align.CENTER
+ };
+ spawn_failed_alert.get_style_context ().remove_class (Gtk.STYLE_CLASS_VIEW);
+
+ // normal view shown if IBus Daemon is already running
+ listbox = new Gtk.ListBox ();
+
+ var scroll = new Gtk.ScrolledWindow (null, null) {
+ hscrollbar_policy = Gtk.PolicyType.NEVER,
+ expand = true
+ };
+ scroll.add (listbox);
+
+ add_engines_popover = new AddEnginesPopover ();
+
+ var add_button = new Gtk.MenuButton () {
+ image = new Gtk.Image.from_icon_name ("list-add-symbolic", Gtk.IconSize.BUTTON),
+ popover = add_engines_popover,
+ tooltip_text = _("Add…")
+ };
+
+ remove_button = new Gtk.MenuButton () {
+ image = new Gtk.Image.from_icon_name ("list-remove-symbolic", Gtk.IconSize.BUTTON),
+ tooltip_text = _("Remove")
+ };
+
+ var actionbar = new Gtk.ActionBar ();
+ actionbar.get_style_context ().add_class (Gtk.STYLE_CLASS_INLINE_TOOLBAR);
+ actionbar.add (add_button);
+ actionbar.add (remove_button);
+
+ var left_grid = new Gtk.Grid ();
+ left_grid.attach (scroll, 0, 0);
+ left_grid.attach (actionbar, 0, 1);
+
+ var display = new Gtk.Frame (null);
+ display.add (left_grid);
+
+ var keyboard_shortcut_label = new Gtk.Label (_("Switch engines:")) {
+ halign = Gtk.Align.END
+ };
+
+ var keyboard_shortcut_combobox = new Gtk.ComboBoxText () {
+ halign = Gtk.Align.START
+ };
+ keyboard_shortcut_combobox.append ("alt-space", Granite.accel_to_string ("space"));
+ keyboard_shortcut_combobox.append ("ctl-space", Granite.accel_to_string ("space"));
+ keyboard_shortcut_combobox.append ("shift-space", Granite.accel_to_string ("space"));
+ keyboard_shortcut_combobox.active_id = get_keyboard_shortcut ();
+
+ var show_ibus_panel_label = new Gtk.Label (_("Show candidate window:")) {
+ halign = Gtk.Align.END
+ };
+
+ var show_ibus_panel_combobox = new Gtk.ComboBoxText () {
+ halign = Gtk.Align.START
+ };
+ show_ibus_panel_combobox.append ("none", _("Do not show"));
+ show_ibus_panel_combobox.append ("auto-hide", _("Auto hide"));
+ show_ibus_panel_combobox.append ("always-show", _("Always show"));
+
+ var embed_preedit_text_label = new Gtk.Label (_("Embed preedit text in application window:")) {
+ halign = Gtk.Align.END
+ };
+
+ var embed_preedit_text_switch = new Gtk.Switch () {
+ halign = Gtk.Align.START
+ };
+
+ var entry_test = new Gtk.Entry () {
+ hexpand = true,
+ placeholder_text = (_("Type to test your settings"))
+ };
+
+ var ibus_button = new Gtk.Button.with_label (_("Advanced Settings…"));
+
+ var action_area = new Gtk.Grid () {
+ column_spacing = 12,
+ valign = Gtk.Align.END,
+ vexpand = true
+ };
+ action_area.add (entry_test);
+ action_area.add (ibus_button);
+
+ var right_grid = new Gtk.Grid () {
+ column_spacing = 12,
+ halign = Gtk.Align.CENTER,
+ hexpand = true,
+ margin = 12,
+ row_spacing = 12
+ };
+ right_grid.attach (keyboard_shortcut_label, 0, 0);
+ right_grid.attach (keyboard_shortcut_combobox, 1, 0);
+ right_grid.attach (show_ibus_panel_label, 0, 1);
+ right_grid.attach (show_ibus_panel_combobox, 1, 1);
+ right_grid.attach (embed_preedit_text_label, 0, 2);
+ right_grid.attach (embed_preedit_text_switch, 1, 2);
+
+ var main_grid = new Gtk.Grid () {
+ column_spacing = 12,
+ row_spacing = 12
+ };
+ main_grid.attach (display, 0, 0, 1, 2);
+ main_grid.attach (right_grid, 1, 0);
+ main_grid.attach (action_area, 1, 1);
+
+ stack = new Gtk.Stack ();
+ stack.add_named (no_daemon_runnning_alert, "no_daemon_runnning_view");
+ stack.add_named (spawn_failed_alert, "spawn_failed_view");
+ stack.add_named (main_grid, "main_view");
+ stack.show_all ();
+
+ add (stack);
+
+ set_visible_view ();
+
+ add_button.clicked.connect (() => {
+ add_engines_popover.show_all ();
+ });
+
+ add_engines_popover.add_engine.connect ((engine) => {
+ string[] new_engine_list = Utils.active_engines;
+ new_engine_list += engine;
+ Utils.active_engines = new_engine_list;
+
+ update_engines_list ();
+ add_engines_popover.popdown ();
+ });
+
+ remove_button.clicked.connect (() => {
+ int index = listbox.get_selected_row ().get_index ();
+
+ // Convert to GLib.Array once, because Vala does not support "-=" operator
+ Array removed_lists = new Array ();
+ foreach (var active_engine in Utils.active_engines) {
+ removed_lists.append_val (active_engine);
+ }
+
+ // Remove applicable engine from the list
+ removed_lists.remove_index (index);
+
+ /*
+ * Substitute the contents of removed_lists through another string array,
+ * because array concatenation is not supported for public array variables and parameters
+ */
+ string[] new_engines;
+ for (int i = 0; i < removed_lists.length; i++) {
+ new_engines += removed_lists.index (i);
+ }
+
+ Utils.active_engines = new_engines;
+ update_engines_list ();
+ });
+
+ keyboard_shortcut_combobox.changed.connect (() => {
+ set_keyboard_shortcut (keyboard_shortcut_combobox.active_id);
+ });
+
+ ibus_button.clicked.connect (() => {
+ try {
+ var appinfo = GLib.AppInfo.create_from_commandline ("ibus-setup", null, GLib.AppInfoCreateFlags.NONE);
+ appinfo.launch (null, null);
+ } catch (Error e) {
+ critical ("Could not open ibus setup: %s", e.message);
+ }
+ });
+
+ ibus_panel_settings.bind ("show", show_ibus_panel_combobox, "active", SettingsBindFlags.DEFAULT);
+ Pantheon.Keyboard.Plug.ibus_general_settings.bind ("embed-preedit-text", embed_preedit_text_switch, "active", SettingsBindFlags.DEFAULT);
+ }
+
+ private string get_keyboard_shortcut () {
+ // TODO: Support getting multiple shortcut keys like ibus-setup does
+ string[] keyboard_shortcuts = Pantheon.Keyboard.Plug.ibus_general_settings.get_child ("hotkey").get_strv ("triggers");
+
+ string keyboard_shortcut = "";
+ foreach (var ks in keyboard_shortcuts) {
+ switch (ks) {
+ case "space":
+ keyboard_shortcut = "alt-space";
+ break;
+ case "space":
+ keyboard_shortcut = "shift-space";
+ break;
+ case "space":
+ keyboard_shortcut = "ctl-space";
+ break;
+ default:
+ break;
+ }
+ }
+
+ return keyboard_shortcut;
+ }
+
+ private void set_keyboard_shortcut (string combobox_id) {
+ // TODO: Support setting multiple shortcut keys like ibus-setup does
+ string[] keyboard_shortcuts = {};
+
+ switch (combobox_id) {
+ case "alt-space":
+ keyboard_shortcuts += "space";
+ break;
+ case "shift-space":
+ keyboard_shortcuts += "space";
+ break;
+ default:
+ keyboard_shortcuts += "space";
+ break;
+ }
+
+ Pantheon.Keyboard.Plug.ibus_general_settings.get_child ("hotkey").set_strv ("triggers", keyboard_shortcuts);
+ }
+
+ private void update_engines_list () {
+ engines = bus.list_engines ();
+
+ // Stores names of currently activated engines
+ string[] engine_full_names = {};
+
+ listbox.get_children ().foreach ((listbox_child) => {
+ listbox_child.destroy ();
+ });
+
+ // Add the language and the name of activated engines
+ foreach (var active_engine in Utils.active_engines) {
+ foreach (var engine in engines) {
+ if (engine.name == active_engine) {
+ engine_full_names += "%s - %s".printf (IBus.get_language_name (engine.language),
+ Utils.gettext_engine_longname (engine));
+ }
+ }
+ }
+
+ foreach (var engine_full_name in engine_full_names) {
+ var label = new Gtk.Label (engine_full_name) {
+ halign = Gtk.Align.START,
+ margin = 6
+ };
+
+ var listboxrow = new Gtk.ListBoxRow ();
+ listboxrow.add (label);
+
+ listbox.add (listboxrow);
+ }
+
+ listbox.show_all ();
+ listbox.select_row (listbox.get_row_at_index (0));
+
+ // Update the sensitivity of buttons depends on whether there are active engines
+ remove_button.sensitive = listbox.get_row_at_index (0) != null;
+ }
+
+ private void spawn_ibus_daemon () {
+ bool is_spawn_succeeded = false;
+ try {
+ is_spawn_succeeded = Process.spawn_sync ("/", { "ibus-daemon", "-drx" }, Environ.get (), SpawnFlags.SEARCH_PATH, null);
+ } catch (GLib.SpawnError e) {
+ warning (e.message);
+ set_visible_view (e.message);
+ return;
+ }
+
+ uint timeout_start_daemon = Timeout.add (500, () => {
+ set_visible_view ();
+ return Gdk.EVENT_PROPAGATE;
+ });
+ timeout_start_daemon = 0;
+ }
+
+ private void set_visible_view (string error_message = "") {
+ if (error_message != "") {
+ stack.visible_child_name = "spawn_failed_view";
+ spawn_failed_alert.description = error_message;
+ } else if (bus.is_connected ()) {
+ stack.visible_child_name = "main_view";
+ update_engines_list ();
+ add_engines_popover.update_engines_list ();
+ } else {
+ stack.visible_child_name = "no_daemon_runnning_view";
+ }
+ }
+
+ public override void reset () {
+ set_keyboard_shortcut ("ctrl-space");
+ ibus_panel_settings.reset ("show");
+ ibus_panel_settings.reset ("show-icon-on-systray");
+ Pantheon.Keyboard.Plug.ibus_general_settings.reset ("embed-preedit-text");
+ }
+}
diff --git a/src/Views/Layout.vala b/src/Views/Layout.vala
index 21fd3f15b..579a2bb5f 100644
--- a/src/Views/Layout.vala
+++ b/src/Views/Layout.vala
@@ -123,18 +123,10 @@ namespace Pantheon.Keyboard.LayoutPage {
advanced_settings = new AdvancedSettings (panels);
var entry_test = new Gtk.Entry ();
- entry_test.hexpand = true;
+ entry_test.valign = Gtk.Align.END;
+ entry_test.expand = true;
entry_test.placeholder_text = (_("Type to test your layout"));
- var ibus_button = new Gtk.Button.with_label (_("Input Method Settings…"));
-
- var action_area = new Gtk.Grid ();
- action_area.column_spacing = 12;
- action_area.valign = Gtk.Align.END;
- action_area.vexpand = true;
- action_area.add (entry_test);
- action_area.add (ibus_button);
-
attach (display, 0, 0, 1, 9);
attach (switch_layout_label, 1, 0, 1, 1);
attach (switch_layout_combo, 2, 0, 1, 1);
@@ -176,7 +168,7 @@ namespace Pantheon.Keyboard.LayoutPage {
attach (num_lock_indicator_switch, 2, 7);
}
- attach (action_area, 1, 8, 2);
+ attach (entry_test, 1, 8, 2);
// Cannot be just called from the constructor because the stack switcher
// shows every child after the constructor has been called
@@ -188,15 +180,6 @@ namespace Pantheon.Keyboard.LayoutPage {
show_panel_for_active_layout ();
});
- ibus_button.clicked.connect (() => {
- try {
- var appinfo = GLib.AppInfo.create_from_commandline ("ibus-setup", null, GLib.AppInfoCreateFlags.NONE);
- appinfo.launch (null, null);
- } catch (Error e) {
- critical ("Could not open ibus setup: %s", e.message);
- }
- });
-
var gala_behavior_settings = new GLib.Settings ("org.pantheon.desktop.gala.behavior");
var overlay_string = gala_behavior_settings.get_string ("overlay-action");
diff --git a/src/Widgets/InputMethod/AddEnginesPopover.vala b/src/Widgets/InputMethod/AddEnginesPopover.vala
new file mode 100644
index 000000000..46e005d18
--- /dev/null
+++ b/src/Widgets/InputMethod/AddEnginesPopover.vala
@@ -0,0 +1,155 @@
+/*
+* 2019-2020 elementary, Inc. (https://elementary.io)
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see .
+*/
+
+public class Pantheon.Keyboard.InputMethodPage.AddEnginesPopover : Gtk.Popover {
+ public signal void add_engine (string new_engine);
+
+#if IBUS_1_5_19
+ private List engines;
+#else
+ private List engines;
+#endif
+
+ private Gtk.SearchEntry search_entry;
+ private GLib.ListStore liststore;
+ private Gtk.ListBox listbox;
+
+ construct {
+ search_entry = new Gtk.SearchEntry () {
+ margin = 12
+ };
+
+ ///TRANSLATORS: This text appears in a search entry and tell users to type some search word
+ ///to look for a input method engine they want to add.
+ ///It does not mean search engines in web browsers.
+ search_entry.placeholder_text = _("Search engine");
+
+ liststore = new GLib.ListStore (Type.OBJECT);
+
+ listbox = new Gtk.ListBox ();
+
+ var scrolled = new Gtk.ScrolledWindow (null, null) {
+ expand = true,
+ height_request = 300,
+ width_request = 500
+ };
+ scrolled.add (listbox);
+
+ var install_button = new Gtk.Button.with_label (_("Install Unlisted Engines…"));
+
+ var cancel_button = new Gtk.Button.with_label (_("Cancel"));
+
+ var add_button = new Gtk.Button.with_label (_("Add Engine"));
+ add_button.get_style_context ().add_class (Gtk.STYLE_CLASS_SUGGESTED_ACTION);
+
+ var button_box = new Gtk.ButtonBox (Gtk.Orientation.HORIZONTAL) {
+ layout_style = Gtk.ButtonBoxStyle.END,
+ margin = 12,
+ spacing = 6
+ };
+ button_box.add (install_button);
+ button_box.add (cancel_button);
+ button_box.add (add_button);
+ button_box.set_child_secondary (install_button, true);
+
+ var grid = new Gtk.Grid ();
+ grid.attach (search_entry, 0, 0);
+ grid.attach (scrolled, 0, 1);
+ grid.attach (new Gtk.Separator (Gtk.Orientation.HORIZONTAL), 0, 2);
+ grid.attach (button_box, 0, 3);
+
+ add (grid);
+
+ listbox.button_press_event.connect ((event) => {
+ if (event.type == Gdk.EventType.DOUBLE_BUTTON_PRESS) {
+ trigger_add_engine ();
+ return false;
+ }
+
+ return false;
+ });
+
+ listbox.set_filter_func ((list_box_row) => {
+ var item = (AddEnginesList) liststore.get_item (list_box_row.get_index ());
+ return search_entry.text.down () in item.engine_full_name.down ();
+ });
+
+ search_entry.search_changed.connect (() => {
+ listbox.invalidate_filter ();
+ });
+
+ install_button.clicked.connect (() => {
+ popdown ();
+
+ var install_dialog = new InstallEngineDialog ((Gtk.Window) get_toplevel ());
+ install_dialog.run ();
+ install_dialog.destroy ();
+ });
+
+ cancel_button.clicked.connect (() => {
+ popdown ();
+ });
+
+ add_button.clicked.connect (() => {
+ trigger_add_engine ();
+ });
+ }
+
+ private void trigger_add_engine () {
+ int index = listbox.get_selected_row ().get_index ();
+
+ // If the engine trying to add is already active, do not add it
+ foreach (var active_engine in Utils.active_engines) {
+ if (active_engine == (((AddEnginesList) liststore.get_item (index)).engine_id)) {
+ popdown ();
+ return;
+ }
+ }
+
+ add_engine (((AddEnginesList) liststore.get_item (index)).engine_id);
+ }
+
+ public void update_engines_list () {
+ engines = new IBus.Bus ().list_engines ();
+ liststore.remove_all ();
+
+ foreach (var engine in engines) {
+ liststore.append (new AddEnginesList (engine));
+ }
+
+ liststore.sort ((a, b) => {
+ return ((AddEnginesList) a).engine_full_name.collate (((AddEnginesList) b).engine_full_name);
+ });
+
+ for (int i = 0; i < liststore.get_n_items (); i++) {
+ var label = new Gtk.Label (((AddEnginesList) liststore.get_item (i)).engine_full_name) {
+ halign = Gtk.Align.START,
+ margin = 6,
+ margin_end = 12,
+ margin_start = 12
+ };
+
+ var listboxrow = new Gtk.ListBoxRow ();
+ listboxrow.add (label);
+
+ listbox.add (listboxrow);
+ }
+
+ listbox.select_row (listbox.get_row_at_index (0));
+ search_entry.grab_focus ();
+ }
+}
diff --git a/src/Widgets/InputMethod/EnginesRow.vala b/src/Widgets/InputMethod/EnginesRow.vala
new file mode 100644
index 000000000..e336af4fe
--- /dev/null
+++ b/src/Widgets/InputMethod/EnginesRow.vala
@@ -0,0 +1,54 @@
+/*
+* 2019-2020 elementary, Inc. (https://elementary.io)
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see .
+*/
+
+public class Pantheon.Keyboard.InputMethodPage.EnginesRow : Gtk.ListBoxRow {
+ public bool selected { get; set; }
+ public string engine_name { get; construct; }
+
+ public EnginesRow (string engine_name) {
+ Object (
+ engine_name: engine_name
+ );
+ }
+
+ construct {
+ var label = new Gtk.Label (engine_name) {
+ halign = Gtk.Align.START,
+ hexpand = true
+ };
+
+ var selection_icon = new Gtk.Image.from_icon_name ("object-select-symbolic", Gtk.IconSize.MENU) {
+ no_show_all = true,
+ visible = false
+ };
+
+ var grid = new Gtk.Grid () {
+ column_spacing = 6,
+ margin = 3,
+ margin_start = 6,
+ margin_end = 6
+ };
+ grid.add (label);
+ grid.add (selection_icon);
+
+ add (grid);
+
+ notify["selected"].connect (() => {
+ selection_icon.visible = selected;
+ });
+ }
+}
diff --git a/src/Widgets/InputMethod/LanguagesRow.vala b/src/Widgets/InputMethod/LanguagesRow.vala
new file mode 100644
index 000000000..dc064ae5c
--- /dev/null
+++ b/src/Widgets/InputMethod/LanguagesRow.vala
@@ -0,0 +1,43 @@
+/*
+* 2019-2020 elementary, Inc. (https://elementary.io)
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see .
+*/
+
+public class Pantheon.Keyboard.InputMethodPage.LanguagesRow : Gtk.ListBoxRow {
+ public InstallList language { get; construct; }
+
+ public LanguagesRow (InstallList language) {
+ Object (language: language);
+ }
+
+ construct {
+ var label = new Gtk.Label (language.get_name ()) {
+ halign = Gtk.Align.START,
+ hexpand = true
+ };
+
+ var caret = new Gtk.Image.from_icon_name ("pan-end-symbolic", Gtk.IconSize.MENU);
+
+ var grid = new Gtk.Grid () {
+ margin = 3,
+ margin_start = 6,
+ margin_end = 6
+ };
+ grid.add (label);
+ grid.add (caret);
+
+ add (grid);
+ }
+}
diff --git a/src/meson.build b/src/meson.build
index ccd3538aa..b370153cf 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -16,8 +16,12 @@ plug_files = files(
'Widgets/Shortcuts/CustomTree.vala',
'Widgets/Layout/Display.vala',
'Widgets/Layout/AddLayoutPopover.vala',
+ 'Widgets/InputMethod/LanguagesRow.vala',
+ 'Widgets/InputMethod/EnginesRow.vala',
+ 'Widgets/InputMethod/AddEnginesPopover.vala',
'Views/Shortcuts.vala',
'Views/Layout.vala',
+ 'Views/InputMethod.vala',
'Views/Behavior.vala',
'Views/AbstractPage.vala',
'Shortcuts/Shortcut.vala',
@@ -29,7 +33,14 @@ plug_files = files(
'Layout/Handler.vala',
'Layout/AdvancedSettingsPanel.vala',
'Layout/AdvancedSettingsGrid.vala',
- 'Dialogs/ConflictDialog.vala'
+ 'InputMethod/Utils.vala',
+ 'InputMethod/AddEnginesList.vala',
+ 'InputMethod/Installer/UbuntuInstaller.vala',
+ 'InputMethod/Installer/InstallList.vala',
+ 'InputMethod/Installer/aptd-client.vala',
+ 'Dialogs/ProgressDialog.vala',
+ 'Dialogs/InstallEngineDialog.vala',
+ 'Dialogs/ConflictDialog.vala',
)
switchboard_dep = dependency('switchboard-2.0')
@@ -37,6 +48,11 @@ switchboard_plugsdir = switchboard_dep.get_pkgconfig_variable('plugsdir', define
gnome_keyboard_ui_dep = meson.get_compiler('c').find_library('gnomekbdui')
+ibus_dep = dependency('ibus-1.0')
+if(ibus_dep.version().version_compare('>=1.5.19'))
+ add_project_arguments(['--define', 'IBUS_1_5_19'], language: 'vala')
+endif
+
shared_module(
meson.project_name(),
plug_files,
@@ -50,6 +66,7 @@ shared_module(
dependency('libxml-2.0'),
dependency('libgnomekbd'),
gnome_keyboard_ui_dep,
+ ibus_dep,
switchboard_dep
],
install: true,