[LyX/master] keyboard maps: replace primary/secondary with per-language selection

Udi Fogiel ufogiel at lyx.org
Sun Aug 2 11:22:19 UTC 2026


commit 054374cf4c3b8a984e5fb64aa0a9b61bdd9be314
Author: Udi Fogiel <ufogiel at lyx.org>
Date:   Sun Aug 2 14:20:58 2026 +0300

    keyboard maps: replace primary/secondary with per-language selection
    
    The old code auto-switched between exactly two maps based on the
    text direction unconditionally on every keystroke,
    any manual switch was silently reverted on the very next character typed.
    
    keymap-select "<name>" is a new LFUN to switch to any configured map
    by name, or back to the configured default with no argument.
    
    Fixes #4051.
---
 lib/RELEASE-NOTES                  |  28 +++++++-
 lib/bind/de/menus.bind             |   3 -
 lib/bind/menus.bind                |   3 -
 lib/bind/ru/menus.bind             |   6 --
 lib/configure.py                   |   2 +-
 lib/doc/UserGuide.lyx              |  61 +++++++++++++++-
 lib/scripts/prefs2prefs_prefs.py   |  33 ++++++++-
 src/BufferView.cpp                 |  32 +++------
 src/FuncCode.h                     |   4 +-
 src/Intl.cpp                       | 109 +++++++++++++++++-----------
 src/Intl.h                         |  61 +++++++++-------
 src/LyXAction.cpp                  |  41 +++++------
 src/LyXRC.cpp                      |  70 ++++++++++++------
 src/LyXRC.h                        |  13 ++--
 src/Trans.cpp                      |  45 +++++-------
 src/Trans.h                        |  19 +++--
 src/frontends/qt/GuiPrefs.cpp      | 139 +++++++++++++++++++++++++++++++-----
 src/frontends/qt/GuiPrefs.h        |   7 +-
 src/frontends/qt/ui/PrefInputUi.ui | 141 +++++++++++++++++++++++++++++--------
 19 files changed, 569 insertions(+), 248 deletions(-)

diff --git a/lib/RELEASE-NOTES b/lib/RELEASE-NOTES
index 672c03e531..e4b2583e45 100644
--- a/lib/RELEASE-NOTES
+++ b/lib/RELEASE-NOTES
@@ -10,8 +10,22 @@
   argument of the buffer-load-graphics lfun.
 
 
+!!Keyboard maps
+
+- The old primary/secondary keyboard map scheme has been replaced with
+  explicit per-language keyboard maps. Instead of toggling between two
+  fixed maps, a keyboard map can now be selected by name (keymap-select
+  lfun) and, optionally, chosen automatically according to the language
+  at the cursor (Preferences > Language > Keyboard).
+
+
 !!!The following pref variables were added in 2.:
 
+- \kbmap_follow_language: select the keyboard map automatically according
+  to the language at the cursor.
+
+- \kbmap_lang: assigns a keyboard map to a given language, used when
+  \kbmap_follow_language is set.
 
 
 !!!The following pref variables were changed in 2.6:
@@ -24,12 +38,21 @@
 
 !!!The following pref variables are obsoleted in 2.6:
 
+- \kbmap_secondary has been replaced by \kbmap_follow_language and
+  \kbmap_lang (see "Keyboard maps" above).
+
 !!!The following new LyX functions have been introduced in 2.6:
 
 - buffer-load-graphics: triggers preview conversion for every image in
   the current buffer at once, instead of waiting for each image to be
   scrolled into view.
 
+- keymap-select <NAME>: turns on and selects a keyboard map by name
+  (the base name of a .kmap file). If NAME is omitted, any manual
+  selection is dropped and the configured default (or, if
+  \kbmap_follow_language is set, the map for the language at the
+  cursor) is applied again.
+
 !!!The following LyX functions have been changed in 2.6:
 
 - The function newpage-insert has been renamed to textbreak-insert
@@ -37,9 +60,12 @@
 
 - The function language now accepts an additional "local" argument
   which ensures the language switch is an inline one (e.g. \foreignlanguage).
-  
+
 !!!The following LyX functions have been removed in 2.6:
 
+- keymap-primary, keymap-secondary and keymap-toggle have been removed
+  in favor of keymap-select (see above).
+
 !!!The following LyX function has been added and then removed in 2.6 development cycle:
 
 !!!The following layout-related changes has been done in 2.6 development cycle:
diff --git a/lib/bind/de/menus.bind b/lib/bind/de/menus.bind
index a194b64f24..fff7133e7e 100644
--- a/lib/bind/de/menus.bind
+++ b/lib/bind/de/menus.bind
@@ -162,8 +162,5 @@ Format 5
 #
 # Menü Tastatur
 #
-\bind "M-t 1"			"keymap-primary"
-\bind "M-t 2"			"keymap-secondary"
 \bind "M-t o"			"keymap-off"
-\bind "M-t t"			"keymap-toggle"
 \bind "M-t x"			"keymap-off"
diff --git a/lib/bind/menus.bind b/lib/bind/menus.bind
index ced3cd9df0..697b56d5ff 100644
--- a/lib/bind/menus.bind
+++ b/lib/bind/menus.bind
@@ -165,8 +165,5 @@ Format 5
 # Keyboard menu
 #
 
-\bind "M-k 1"			"keymap-primary"
-\bind "M-k 2"			"keymap-secondary"
 \bind "M-k o"			"keymap-off"
-\bind "M-k t"			"keymap-toggle"
 \bind "M-k x"			"keymap-off"
diff --git a/lib/bind/ru/menus.bind b/lib/bind/ru/menus.bind
index c535abe8b5..50b1568168 100644
--- a/lib/bind/ru/menus.bind
+++ b/lib/bind/ru/menus.bind
@@ -258,14 +258,8 @@ Format 5
 # Keyboard menu
 #
 
-\bind "M-k 1"			"keymap-primary"
-\bind "M-k 2"			"keymap-secondary"
 \bind "M-k o"			"keymap-off"
-\bind "M-k t"			"keymap-toggle"
 \bind "M-k x"			"keymap-off"
 
-\bind "M-л 1"			"keymap-primary"
-\bind "M-л 2"			"keymap-secondary"
 \bind "M-л щ"			"keymap-off"
-\bind "M-л е"			"keymap-toggle"
 \bind "M-л ч"			"keymap-off"
diff --git a/lib/configure.py b/lib/configure.py
index c168d01ed4..5f53d135c7 100644
--- a/lib/configure.py
+++ b/lib/configure.py
@@ -2111,7 +2111,7 @@ if __name__ == '__main__':
     lyx_check_config = True
     lyx_kpsewhich = True
     outfile = 'lyxrc.defaults'
-    lyxrc_fileformat = 42
+    lyxrc_fileformat = 43
     rc_entries = ''
     lyx_keep_temps = False
     version_suffix = ''
diff --git a/lib/doc/UserGuide.lyx b/lib/doc/UserGuide.lyx
index 862210ddca..ecfd4ec5d5 100644
--- a/lib/doc/UserGuide.lyx
+++ b/lib/doc/UserGuide.lyx
@@ -186,6 +186,7 @@ logicalmkup
 \docbook_mathml_prefix 1
 \docbook_mathml_version 0
 \author -712698321 "Jürgen Spitzmüller"
+\author 673085648 "ufogiel"
 \author 1064312605 "Udi Fogiel"
 \end_header
 
@@ -43793,7 +43794,14 @@ Preferences\SpecialChar menuseparator
 Editing\SpecialChar menuseparator
 Keyboard/Mouse
 \family default
- dialog allows you to choose up to two keyboard mappings,
+ dialog 
+\change_deleted 673085648 1785137856
+allows you to choose up to two keyboard mappings
+\change_inserted 673085648 1785141903
+lets you set a default keyboard map,
+ or have LyX select one automatically according to the language of the text under the cursor
+\change_unchanged
+,
  see section
 \begin_inset space ~
 \end_inset
@@ -43807,7 +43815,14 @@ nolink "false"
 \end_inset
 
 .
+
+\change_deleted 673085648 1785137891
  You can choose primary and secondary keyboard languages and then select which one you want to use.
+
+\change_inserted 673085648 1785141829
+ 
+\change_unchanged
+
 \end_layout
 
 \begin_layout Standard
@@ -55876,14 +55891,30 @@ keyboard
 
 map
 \family default
- and select the keyboard map file named 
+ and 
+\change_deleted 673085648 1785141037
+select the keyboard map file named
+\change_inserted 673085648 1785141079
+set the 
+\family sans
+Default
+\family default
+ field to the keyboard map named
+\change_unchanged
+ 
 \shape italic
-romanian.kmap
+romanian
+\change_deleted 673085648 1785141089
+.kmap
+\change_unchanged
+
 \shape default
 .
 \end_layout
 
 \begin_layout Standard
+
+\change_deleted 673085648 1785141098
 You can specify a 
 \family sans
 First
@@ -55917,6 +55948,30 @@ arg   "keymap-toggle"
 \end_inset
 
 .
+\change_inserted 673085648 1785141671
+If you regularly work with more than one language,
+ you can instead let \SpecialChar LyX
+ select the keyboard map automatically according to the language of the text under the cursor:
+ enable Select keyboard map automatically by language and assign a keyboard map to each language you use,
+ in addition to the 
+\family sans
+Default
+\family default
+ map used for languages that don't have their own assignment.
+ You can also switch to any configured keyboard map manually at any time,
+ or turn key mapping off,
+ by binding a key of your choice to the 
+\family sans
+keymap-select
+\family default
+ and 
+\family sans
+keymap-off
+\family default
+ \SpecialChar LyX
+ functions respectively.
+\change_unchanged
+
 \end_layout
 
 \begin_layout Standard
diff --git a/lib/scripts/prefs2prefs_prefs.py b/lib/scripts/prefs2prefs_prefs.py
index 6f0ba8002d..c78fd34b90 100644
--- a/lib/scripts/prefs2prefs_prefs.py
+++ b/lib/scripts/prefs2prefs_prefs.py
@@ -186,6 +186,15 @@
 #   default is true, so this keeps behavior the same for existing
 #   users)
 
+# Incremented to format 43, by ufogiel
+#   Replace \kbmap_secondary (used for the old automatic switch between
+#   a primary and a secondary keyboard map depending on the direction
+#   of the text at the cursor) with \kbmap_follow_language and
+#   \kbmap_lang (explicit per-language keyboard-map assignment).
+#   Existing \kbmap_secondary settings are migrated to a \kbmap_lang
+#   assignment for every right-to-left language in lib/languages, with
+#   \kbmap_follow_language turned on, to approximate prior behavior.
+
 # NOTE: The format should also be updated in LYXRC.cpp and
 # in configure.py (search for lyxrc_fileformat).
 
@@ -558,6 +567,27 @@ def add_visual_cursor_default(lines):
 	lines.append("\\visual_cursor false")
 
 
+# right-to-left languages defined in lib/languages (RTL true)
+rtl_languages = ("arabic_arabtex", "arabic_arabi", "farsi", "hebrew",
+		  "nko", "syriac", "urdu", "uyghur")
+
+def migrate_kbmap_secondary(lines):
+	secondary = None
+	newlines = []
+	for l in lines:
+		if l.strip().lower().startswith("\\kbmap_secondary"):
+			entries = get_format(l)
+			if len(entries) > 1 and entries[1]:
+				secondary = entries[1]
+			continue
+		newlines.append(l)
+	if secondary:
+		newlines.append("\\kbmap_follow_language true")
+		for lang in rtl_languages:
+			newlines.append('\\kbmap_lang "%s" "%s"' % (lang, secondary))
+	lines[:] = newlines
+
+
 # End conversions for LyX 2.5 to 2.6
 ####################################
 
@@ -613,5 +643,6 @@ conversions = [
 	[ 39, []],
     [ 40, [bookmarks_visibility]],
     [ 41, [rename_langpack]],
-	[ 42, [add_visual_cursor_default]]
+	[ 42, [add_visual_cursor_default]],
+	[ 43, [migrate_kbmap_secondary]]
 ]
diff --git a/src/BufferView.cpp b/src/BufferView.cpp
index 0cdf5d24bb..304a939c5b 100644
--- a/src/BufferView.cpp
+++ b/src/BufferView.cpp
@@ -1006,14 +1006,7 @@ bool BufferView::moveToPosition(pit_type bottom_pit, pos_type bottom_pos,
 
 void BufferView::translateAndInsert(char_type c, Text * t, Cursor & cur)
 {
-	if (d->cursor_.real_current_font.isRightToLeft()) {
-		if (d->intl_.keymap == Intl::PRIMARY)
-			d->intl_.keyMapSec();
-	} else {
-		if (d->intl_.keymap == Intl::SECONDARY)
-			d->intl_.keyMapPrim();
-	}
-
+	d->intl_.syncActiveMap(d->cursor_.real_current_font.language()->lang());
 	d->intl_.getTransManager().translateAndInsert(c, t, cur);
 }
 
@@ -1308,9 +1301,7 @@ bool BufferView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
 	case LFUN_BIBTEX_DATABASE_LIST:
 	case LFUN_STATISTICS:
 	case LFUN_KEYMAP_OFF:
-	case LFUN_KEYMAP_PRIMARY:
-	case LFUN_KEYMAP_SECONDARY:
-	case LFUN_KEYMAP_TOGGLE:
+	case LFUN_KEYMAP_SELECT:
 	case LFUN_INSET_SELECT_ALL:
 	case LFUN_SERVER_GET_XY:
 		flag.setEnabled(true);
@@ -2539,20 +2530,17 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
 	}
 
 	case LFUN_KEYMAP_OFF:
-		getIntl().keyMapOn(false);
-		break;
-
-	case LFUN_KEYMAP_PRIMARY:
-		getIntl().keyMapPrim();
+		getIntl().deselectMap();
 		break;
 
-	case LFUN_KEYMAP_SECONDARY:
-		getIntl().keyMapSec();
-		break;
-
-	case LFUN_KEYMAP_TOGGLE:
-		getIntl().toggleKeyMap();
+	case LFUN_KEYMAP_SELECT: {
+		string const name = cmd.getArg(0);
+		if (name.empty())
+			getIntl().selectDefault(d->cursor_.real_current_font.language()->lang());
+		else if (!getIntl().selectMap(name))
+			message(bformat(_("Keyboard map not found: %1$s"), from_utf8(name)));
 		break;
+	}
 
 	case LFUN_DIALOG_SHOW_NEW_INSET: {
 		string const name = cmd.getArg(0);
diff --git a/src/FuncCode.h b/src/FuncCode.h
index 5c3259dd68..4f78596b9c 100644
--- a/src/FuncCode.h
+++ b/src/FuncCode.h
@@ -147,10 +147,8 @@ enum FuncCode
 	LFUN_NOTE_INSERT,
 	LFUN_UI_TOGGLE,
 	LFUN_KEYMAP_OFF,
-	LFUN_KEYMAP_PRIMARY,
-	LFUN_KEYMAP_SECONDARY,
+	LFUN_KEYMAP_SELECT,
 	// 100
-	LFUN_KEYMAP_TOGGLE,
 	LFUN_MATH_INSERT,
 	LFUN_MATH_MATRIX,
 	LFUN_MATH_LIMITS,
diff --git a/src/Intl.cpp b/src/Intl.cpp
index fdae0b1aba..85f13f879c 100644
--- a/src/Intl.cpp
+++ b/src/Intl.cpp
@@ -22,76 +22,99 @@ namespace lyx {
 
 
 Intl::Intl()
-	: keymap(Intl::PRIMARY), keymapon(lyxrc.use_kbmap),
-	prim_lang(lyxrc.primary_kbmap), sec_lang(lyxrc.secondary_kbmap)
+	: use_kbmap_(lyxrc.use_kbmap),
+	  default_lang_(lyxrc.default_kbmap),
+	  follow_language_(lyxrc.keymap_follow_language),
+	  lang_keymaps_(lyxrc.keymap_by_language)
 {
 }
 
 
-void Intl::keyMapOn(bool on)
+bool Intl::selectMap(std::string const & name)
 {
-	keymapon = on;
-
-	if (on) {
-		if (keymap == PRIMARY)
-			keyMapPrim();
-		else
-			keyMapSec();
-	} else {
-		trans.disableKeymap();
+	if (name.empty()) {
+		deselectMap();
+		return true;
 	}
+
+	if (trans.setActive(name) != 0)
+		return false;
+
+	active_map_ = name;
+	return true;
 }
 
 
-void Intl::toggleKeyMap()
+void Intl::deselectMap()
 {
-	if (keymapon && (keymap == PRIMARY))
-		keyMapSec();
-	else if (keymapon)
-		keyMapOn(false);
-	else
-		keyMapPrim();
+	trans.disableKeymap();
+	active_map_.erase();
 }
 
 
-void Intl::keyMapPrim()
+std::string Intl::mapForLanguage(std::string const & langcode) const
 {
-	if (!trans.setPrimary(prim_lang))
-		trans.enablePrimary();
-
-	keymapon = true;
-	keymap = PRIMARY;
+	std::map<std::string, std::string>::const_iterator it =
+		lang_keymaps_.find(langcode);
+	return it == lang_keymaps_.end() ? default_lang_ : it->second;
 }
 
 
-void Intl::keyMapSec()
+void Intl::syncActiveMap(std::string const & langcode)
 {
-	if (!trans.setSecondary(sec_lang))
-		trans.enableSecondary();
+	if (!use_kbmap_) {
+		deselectMap();
+		last_synced_.erase();
+		last_langcode_.erase();
+		return;
+	}
 
-	keymapon = true;
-	keymap = SECONDARY;
+	// a language changemust focre a resync even if it resolves to the
+	// same map name as before (e.g. two languages sharing the default)
+	bool const lang_changed = follow_language_ && langcode != last_langcode_;
+	last_langcode_ = langcode;
+
+	std::string const wanted =
+		follow_language_ ? mapForLanguage(langcode) : default_lang_;
+	if (!lang_changed && wanted == last_synced_)
+		return;
+
+	last_synced_ = wanted;
+	if (wanted.empty())
+		deselectMap();
+	else
+		selectMap(wanted);
 }
 
 
-void Intl::initKeyMapper(bool on)
+void Intl::selectDefault(std::string const & langcode)
 {
-	LYXERR(Debug::INIT, "Initializing key mappings...");
-
-	if (trans.setPrimary(prim_lang) == -1)
-		prim_lang.erase();
-	if (trans.setSecondary(sec_lang) == -1)
-		sec_lang.erase();
+	if (!use_kbmap_) {
+		deselectMap();
+		last_synced_.erase();
+		last_langcode_.erase();
+		return;
+	}
 
-	if (prim_lang.empty() && sec_lang.empty())
-		keymapon = false;
+	std::string const wanted =
+		follow_language_ ? mapForLanguage(langcode) : default_lang_;
+	last_synced_ = wanted;
+	last_langcode_ = langcode;
+	if (wanted.empty())
+		deselectMap();
 	else
-		keymapon = on;
+		selectMap(wanted);
+}
 
-	keyMapOn(keymapon);
 
-	if (keymapon)
-		keyMapPrim();
+void Intl::initKeyMapper(bool on)
+{
+	LYXERR(Debug::INIT, "Initializing key mappings...");
+
+	if (!on || default_lang_.empty() || !selectMap(default_lang_))
+		deselectMap();
+	last_synced_ = active_map_;
+	last_langcode_.erase();
 }
 
 
diff --git a/src/Intl.h b/src/Intl.h
index 228ea0d48f..2d5487a120 100644
--- a/src/Intl.h
+++ b/src/Intl.h
@@ -17,6 +17,9 @@
 
 #include "Trans.h"
 
+#include <map>
+#include <string>
+
 
 namespace lyx {
 
@@ -26,42 +29,52 @@ namespace lyx {
  */
 class Intl {
 public:
-	/// which keymap is currently used ?
-	enum Keymap {
-		PRIMARY,
-		SECONDARY
-	};
-
 	Intl();
 
-	/// {en/dis}able the keymap
-	void keyMapOn(bool on);
-
-	/// set the primary language keymap
-	void keyMapPrim();
+	/// select a keymap by name (base name of a .kmap file); false if not found
+	bool selectMap(std::string const & name);
 
-	/// set the secondary language keymap
-	void keyMapSec();
+	/// Turn off key mapping.
+	void deselectMap();
 
-	/// turn on/off key mappings, status in keymapon
-	void toggleKeyMap();
+	/// force-apply like syncActiveMap(); used by keymap-select with no argument
+	void selectDefault(std::string const & langcode);
 
 	/// initialize key mapper
 	void initKeyMapper(bool on);
 
+	/// is keyboard mapping enabled at all (the "Use keyboard map" preference)?
+	bool enabled() const { return use_kbmap_; }
+
+	/// choose the active map automatically from the language at the cursor?
+	bool followLanguage() const { return use_kbmap_ && follow_language_; }
+
+	/// configured keymap for the language, or the default; empty if neither is set
+	std::string mapForLanguage(std::string const & langcode) const;
+
+	/// reapply the target map only if it changed, so manual overrides stick
+	void syncActiveMap(std::string const & langcode);
+
+	/// name of the currently selected map, empty if none
+	std::string const & activeMap() const { return active_map_; }
+
 	// Get the Translation Manager
 	inline TransManager & getTransManager() { return trans; }
 
-	/// using primary or secondary keymap ?
-	Keymap keymap;
-
 private:
-	/// is key mapping enabled ?
-	bool keymapon;
-	/// the primary language keymap
-	std::string & prim_lang;
-	/// the secondary language keymap
-	std::string & sec_lang;
+	/// currently selected map, empty if none
+	std::string active_map_;
+	/// last synced (map, langcode); catches real changes even if the map repeats
+	std::string last_synced_;
+	std::string last_langcode_;
+	/// the "Use keyboard map" preference: master on/off switch
+	bool & use_kbmap_;
+	/// the default keymap, loaded at start-up if enabled
+	std::string & default_lang_;
+	/// select the active map automatically from the language at the cursor?
+	bool & follow_language_;
+	/// per-language map assignments, used when follow_language_ is set
+	std::map<std::string, std::string> & lang_keymaps_;
 	/// the translation manager
 	TransManager trans;
 };
diff --git a/src/LyXAction.cpp b/src/LyXAction.cpp
index 5cea911475..bd9c9df054 100644
--- a/src/LyXAction.cpp
+++ b/src/LyXAction.cpp
@@ -2422,33 +2422,26 @@ void LyXAction::init()
 		{ LFUN_KEYMAP_OFF, "keymap-off", ReadOnly, Edit },
 
 /*!
- * \var lyx::FuncCode lyx::LFUN_KEYMAP_PRIMARY
- * \li Action: Turn on the primary keyboard map.
+ * \var lyx::FuncCode lyx::LFUN_KEYMAP_SELECT
+ * \li Action: Turn on and select a keyboard map by name.
  * \li Notion: Maps were widely used in past, when X-windows didn't have nowadays
                keyboard support. They can be still used to maintain uniform keyboard
                layout across the various platforms.\n
-               The language is to be set in the Preferences dialog.
- * \li Syntax: keymap-primary
- * \endvar
- */
-		{ LFUN_KEYMAP_PRIMARY, "keymap-primary", ReadOnly, Edit },
-
-/*!
- * \var lyx::FuncCode lyx::LFUN_KEYMAP_SECONDARY
- * \li Action: Turn on the secondary keyboard map.
- * \li Syntax: keymap-secondary
- * \endvar
- */
-		{ LFUN_KEYMAP_SECONDARY, "keymap-secondary", ReadOnly, Edit },
-
-/*!
- * \var lyx::FuncCode lyx::LFUN_KEYMAP_TOGGLE
- * \li Action: Toggles keyboard maps (first/second/off).
- * \li Syntax: keymap-toggle
- * \li Origin: leeming, 30 Mar 2004
- * \endvar
- */
-		{ LFUN_KEYMAP_TOGGLE, "keymap-toggle", ReadOnly, Edit },
+               The name is the base name of a .kmap file, as configured in the
+               Preferences dialog (e.g. "hebrew" for hebrew.kmap). Keyboard maps
+               can also be selected automatically according to the language at
+               the cursor; see the Preferences dialog. If no name is given,
+               any manual selection is dropped and the map configured in the
+               Preferences dialog is (re-)applied: the default map, or, if
+               automatic selection by language is enabled, the map configured
+               for the language at the cursor.
+ * \li Syntax: keymap-select <NAME>
+ * \li Params: <NAME>: the name of the keyboard map to select; if omitted,
+               return to the configured default.
+ * \li Origin: ufogiel, 27 July 2026
+ * \endvar
+ */
+		{ LFUN_KEYMAP_SELECT, "keymap-select", ReadOnly, Edit },
 
 
 /*!
diff --git a/src/LyXRC.cpp b/src/LyXRC.cpp
index 98ec1a40ea..eafac0a34f 100644
--- a/src/LyXRC.cpp
+++ b/src/LyXRC.cpp
@@ -60,7 +60,7 @@ namespace {
 
 // The format should also be updated in configure.py, and conversion code
 // should be added to prefs2prefs_prefs.py.
-static unsigned int const LYXRC_FILEFORMAT = 42; // Change default of \visual_cursor to true
+static unsigned int const LYXRC_FILEFORMAT = 43; // Migrate \kbmap_secondary to \kbmap_follow_language + \kbmap_lang
 // when adding something to this array keep it sorted!
 LexerKeyword lyxrcTags[] = {
 	{ "\\accept_compound", LyXRC::RC_ACCEPT_COMPOUND },
@@ -132,8 +132,9 @@ LexerKeyword lyxrcTags[] = {
 	{ "\\jbibtex_command", LyXRC::RC_JBIBTEX_COMMAND },
 	{ "\\jindex_command", LyXRC::RC_JINDEX_COMMAND },
 	{ "\\kbmap", LyXRC::RC_KBMAP },
+	{ "\\kbmap_follow_language", LyXRC::RC_KBMAP_FOLLOW_LANGUAGE },
+	{ "\\kbmap_lang", LyXRC::RC_KBMAP_LANG },
 	{ "\\kbmap_primary", LyXRC::RC_KBMAP_PRIMARY },
-	{ "\\kbmap_secondary", LyXRC::RC_KBMAP_SECONDARY },
 	{ "\\language_auto_begin", LyXRC::RC_LANGUAGE_AUTO_BEGIN },
 	{ "\\language_auto_end", LyXRC::RC_LANGUAGE_AUTO_END },
 	{ "\\language_command_begin", LyXRC::RC_LANGUAGE_COMMAND_BEGIN },
@@ -369,24 +370,33 @@ LyXRC::ReturnValues LyXRC::read(Lexer & lexrc, bool check_format)
 				string const kmap(os::internal_path(lexrc.getString()));
 				if (!libFileSearch("kbd", kmap, "kmap").empty()
 				    || kmap.empty()) {
-					primary_kbmap = kmap;
+					default_kbmap = kmap;
 				} else {
 					lexrc.printError("LyX: Keymap `$$Token' not found");
 				}
 			}
 			break;
 
-		case RC_KBMAP_SECONDARY:
-			if (lexrc.next()) {
-				string const kmap(os::internal_path(lexrc.getString()));
-				if (!libFileSearch("kbd", kmap, "kmap").empty()
-					  || kmap.empty()) {
-					secondary_kbmap = kmap;
-				} else {
-					lexrc.printError("LyX: Keymap `$$Token' not found");
-				}
+		case RC_KBMAP_FOLLOW_LANGUAGE:
+			lexrc >> keymap_follow_language;
+			break;
+
+		case RC_KBMAP_LANG: {
+			string lang;
+			if (lexrc.next())
+				lang = lexrc.getString();
+			string kmap;
+			if (lexrc.next())
+				kmap = os::internal_path(lexrc.getString());
+			if (kmap.empty()) {
+				keymap_by_language.erase(lang);
+			} else if (!libFileSearch("kbd", kmap, "kmap").empty()) {
+				keymap_by_language[lang] = kmap;
+			} else {
+				lexrc.printError("LyX: Keymap `$$Token' not found");
 			}
 			break;
+		}
 
 		case RC_PRINTLANDSCAPEFLAG:
 			lexrc >> print_landscape_flag;
@@ -1492,18 +1502,30 @@ void LyXRC::write(ostream & os, bool ignore_system_lyxrc, string const & name) c
 		// fall through
 	case RC_KBMAP_PRIMARY:
 		if (ignore_system_lyxrc ||
-		    primary_kbmap != system_lyxrc.primary_kbmap) {
-			string const path = os::external_path(primary_kbmap);
+		    default_kbmap != system_lyxrc.default_kbmap) {
+			string const path = os::external_path(default_kbmap);
 			os << "\\kbmap_primary \"" << path << "\"\n";
 		}
 		if (tag != RC_LAST)
 			break;
 		// fall through
-	case RC_KBMAP_SECONDARY:
+	case RC_KBMAP_FOLLOW_LANGUAGE:
 		if (ignore_system_lyxrc ||
-		    secondary_kbmap != system_lyxrc.secondary_kbmap) {
-			string const path = os::external_path(secondary_kbmap);
-			os << "\\kbmap_secondary \"" << path << "\"\n";
+		    keymap_follow_language != system_lyxrc.keymap_follow_language) {
+			os << "\\kbmap_follow_language "
+			   << convert<string>(keymap_follow_language) << '\n';
+		}
+		if (tag != RC_LAST)
+			break;
+		// fall through
+	case RC_KBMAP_LANG:
+		if (ignore_system_lyxrc ||
+		    keymap_by_language != system_lyxrc.keymap_by_language) {
+			for (auto const & lang_map : keymap_by_language) {
+				string const path = os::external_path(lang_map.second);
+				os << "\\kbmap_lang \"" << lang_map.first
+				   << "\" \"" << path << "\"\n";
+			}
 		}
 		if (tag != RC_LAST)
 			break;
@@ -2984,7 +3006,8 @@ void actOnUpdatedPrefs(LyXRC const & lyxrc_orig, LyXRC const & lyxrc_new)
 	case LyXRC::RC_INPUT:
 	case LyXRC::RC_KBMAP:
 	case LyXRC::RC_KBMAP_PRIMARY:
-	case LyXRC::RC_KBMAP_SECONDARY:
+	case LyXRC::RC_KBMAP_FOLLOW_LANGUAGE:
+	case LyXRC::RC_KBMAP_LANG:
 	case LyXRC::RC_LANGUAGE_AUTO_BEGIN:
 	case LyXRC::RC_LANGUAGE_AUTO_END:
 	case LyXRC::RC_LANGUAGE_COMMAND_BEGIN:
@@ -3278,10 +3301,17 @@ string const LyXRC::getDescription(LyXRCTags tag)
 
 	case RC_KBMAP:
 	case RC_KBMAP_PRIMARY:
-	case RC_KBMAP_SECONDARY:
 		str = _("Use this to set the correct mapping file for your keyboard. You'll need this if you for instance want to type German documents on an American keyboard.");
 		break;
 
+	case RC_KBMAP_FOLLOW_LANGUAGE:
+		str = _("Select the keyboard map automatically according to the language at the cursor, falling back to the default keyboard map for languages with no assignment of their own.");
+		break;
+
+	case RC_KBMAP_LANG:
+		str = _("Keyboard map to use for a given language when the keyboard map is selected automatically according to language.");
+		break;
+
 	case RC_LANGUAGE_AUTO_BEGIN:
 		str = _("Select if a language switching command is needed at the beginning of the document.");
 		break;
diff --git a/src/LyXRC.h b/src/LyXRC.h
index 82df958e44..4172303531 100644
--- a/src/LyXRC.h
+++ b/src/LyXRC.h
@@ -111,7 +111,8 @@ public:
 		RC_JINDEX_COMMAND,
 		RC_KBMAP,
 		RC_KBMAP_PRIMARY,
-		RC_KBMAP_SECONDARY,
+		RC_KBMAP_FOLLOW_LANGUAGE,
+		RC_KBMAP_LANG,
 		RC_LANGUAGE_AUTO_BEGIN,
 		RC_LANGUAGE_AUTO_END,
 		RC_LANGUAGE_COMMAND_BEGIN,
@@ -384,10 +385,12 @@ public:
 	bool spellcheck_notes = true;
 	///
 	bool use_kbmap = false;
-	///
-	std::string primary_kbmap;
-	///
-	std::string secondary_kbmap;
+	/// the keyboard map to load at start-up, if use_kbmap is set
+	std::string default_kbmap;
+	/// select the keyboard map by language instead of default_kbmap
+	bool keymap_follow_language = false;
+	/// keyboard map per language code, used when keymap_follow_language is set
+	std::map<std::string, std::string> keymap_by_language;
 	///
 	std::string lyxpipes;
 	///
diff --git a/src/Trans.cpp b/src/Trans.cpp
index 12086658b1..470186b10d 100644
--- a/src/Trans.cpp
+++ b/src/Trans.cpp
@@ -23,6 +23,8 @@
 #include "support/Lexer.h"
 #include "support/lstrings.h"
 
+#include <utility>
+
 using namespace std;
 using namespace lyx::support;
 
@@ -579,38 +581,25 @@ TransManager::TransManager()
 {}
 
 
-int TransManager::setPrimary(string const & language)
-{
-	if (t1_.getName() == language)
-		return 0;
-
-	return t1_.load(language);
-}
-
-
-int TransManager::setSecondary(string const & language)
+int TransManager::setActive(string const & name)
 {
-	if (t2_.getName() == language)
+	if (name.empty()) {
+		disableKeymap();
 		return 0;
+	}
 
-	return t2_.load(language);
-}
-
-
-void TransManager::enablePrimary()
-{
-	if (t1_.isDefined())
-		active_ = &t1_;
-
-	LYXERR(Debug::KBMAP, "Enabling primary keymap");
-}
-
+	map<string, Trans>::iterator it = maps_.find(name);
+	if (it == maps_.end()) {
+		Trans t;
+		int const res = t.load(name);
+		if (res != 0)
+			return res;
+		it = maps_.emplace(name, std::move(t)).first;
+	}
 
-void TransManager::enableSecondary()
-{
-	if (t2_.isDefined())
-		active_ = &t2_;
-	LYXERR(Debug::KBMAP, "Enabling secondary keymap");
+	active_ = &it->second;
+	LYXERR(Debug::KBMAP, "Enabling keymap `" << name << '\'');
+	return 0;
 }
 
 
diff --git a/src/Trans.h b/src/Trans.h
index c8d828e785..7f6bda43fe 100644
--- a/src/Trans.h
+++ b/src/Trans.h
@@ -135,6 +135,11 @@ public:
 	Trans() {}
 	///
 	~Trans() { freeKeymap(); }
+	/// defaulted explicitly: the destructor above suppresses these
+	/// implicitly, which would silently turn moves into copies
+	Trans(Trans &&) = default;
+	///
+	Trans & operator=(Trans &&) = default;
 
 	///
 	int load(std::string const & language);
@@ -313,9 +318,7 @@ private:
 	///
 	Trans * active_;
 	///
-	Trans t1_;
-	///
-	Trans t2_;
+	std::map<std::string, Trans> maps_;
 	///
 	static Trans default_;
 	///
@@ -323,14 +326,8 @@ private:
 public:
 	///
 	TransManager();
-	///
-	int setPrimary(std::string const &);
-	///
-	int setSecondary(std::string const &);
-	///
-	void enablePrimary();
-	///
-	void enableSecondary();
+	/// load (if needed) and activate the named keymap; 0 on success, -1 if not found
+	int setActive(std::string const &);
 	///
 	void disableKeymap();
 	///
diff --git a/src/frontends/qt/GuiPrefs.cpp b/src/frontends/qt/GuiPrefs.cpp
index 6998430780..df50b086c0 100644
--- a/src/frontends/qt/GuiPrefs.cpp
+++ b/src/frontends/qt/GuiPrefs.cpp
@@ -318,8 +318,6 @@ PrefInput::PrefInput(GuiPreferences * form)
 		this, SIGNAL(changed()));
 	connect(firstKeymapED, SIGNAL(textChanged(QString)),
 		this, SIGNAL(changed()));
-	connect(secondKeymapED, SIGNAL(textChanged(QString)),
-		this, SIGNAL(changed()));
 	connect(mouseWheelSpeedSB, SIGNAL(valueChanged(double)),
 		this, SIGNAL(changed()));
 	connect(scrollzoomEnableCB, SIGNAL(clicked()),
@@ -330,6 +328,21 @@ PrefInput::PrefInput(GuiPreferences * form)
 		this, SIGNAL(changed()));
 	connect(mmPasteCB, SIGNAL(toggled(bool)),
 		this, SIGNAL(changed()));
+	connect(keymapFollowLangCB, SIGNAL(clicked()),
+		this, SIGNAL(changed()));
+	connect(keymapLangAddPB, SIGNAL(clicked()),
+		this, SLOT(addKeymapLang()));
+	connect(keymapLangAddPB, SIGNAL(clicked()),
+		this, SIGNAL(changed()));
+	connect(keymapLangRemovePB, SIGNAL(clicked()),
+		this, SLOT(removeKeymapLang()));
+	connect(keymapLangRemovePB, SIGNAL(clicked()),
+		this, SIGNAL(changed()));
+
+	keymapLangED->setValidator(new NoNewLineValidator(keymapLangED));
+
+	keymapLangCO->setModel(guiApp->languageModel());
+	keymapLangCO->setModelColumn(0);
 
 	// reveal checkbox for switching Ctrl and Meta on Mac:
 #ifdef Q_OS_MAC
@@ -342,10 +355,9 @@ PrefInput::PrefInput(GuiPreferences * form)
 
 void PrefInput::applyRC(LyXRC & rc) const
 {
-	// FIXME: can derive CB from the two EDs
+	// FIXME: can derive CB from the ED
 	rc.use_kbmap = keymapCB->isChecked();
-	rc.primary_kbmap = internal_path(fromqstr(firstKeymapED->text()));
-	rc.secondary_kbmap = internal_path(fromqstr(secondKeymapED->text()));
+	rc.default_kbmap = internal_path(fromqstr(firstKeymapED->text()));
 	rc.mouse_wheel_speed = mouseWheelSpeedSB->value();
 	if (scrollzoomEnableCB->isChecked()) {
 		switch (scrollzoomValueCO->currentIndex()) {
@@ -364,15 +376,23 @@ void PrefInput::applyRC(LyXRC & rc) const
 	}
 	rc.mac_dontswap_ctrl_meta  = dontswapCB->isChecked();
 	rc.mouse_middlebutton_paste = mmPasteCB->isChecked();
+
+	rc.keymap_follow_language = keymapFollowLangCB->isChecked();
+	rc.keymap_by_language.clear();
+	for (int i = 0; i < keymapLangLW->count(); ++i) {
+		QListWidgetItem const * item = keymapLangLW->item(i);
+		string const langcode = fromqstr(item->data(Qt::UserRole).toString());
+		string const kmap = internal_path(fromqstr(item->data(Qt::UserRole + 1).toString()));
+		rc.keymap_by_language[langcode] = kmap;
+	}
 }
 
 
 void PrefInput::updateRC(LyXRC const & rc)
 {
-	// FIXME: can derive CB from the two EDs
+	// FIXME: can derive CB from the ED
 	keymapCB->setChecked(rc.use_kbmap);
-	firstKeymapED->setText(toqstr(external_path(rc.primary_kbmap)));
-	secondKeymapED->setText(toqstr(external_path(rc.secondary_kbmap)));
+	firstKeymapED->setText(toqstr(external_path(rc.default_kbmap)));
 	mouseWheelSpeedSB->setValue(rc.mouse_wheel_speed);
 	switch (rc.scroll_wheel_zoom) {
 	case LyXRC::SCROLL_WHEEL_ZOOM_OFF:
@@ -393,6 +413,20 @@ void PrefInput::updateRC(LyXRC const & rc)
 	}
 	dontswapCB->setChecked(rc.mac_dontswap_ctrl_meta);
 	mmPasteCB->setChecked(rc.mouse_middlebutton_paste);
+
+	keymapFollowLangCB->setChecked(rc.keymap_follow_language);
+	keymapLangLW->clear();
+	for (auto const & lang_map : rc.keymap_by_language) {
+		Language const * lang = languages.getLanguage(lang_map.first);
+		QString const langname = lang ? qt_(lang->display())
+			: toqstr(lang_map.first);
+		QString const kmap = toqstr(external_path(lang_map.second));
+		QListWidgetItem * item =
+			new QListWidgetItem(langname + ": " + kmap, keymapLangLW);
+		item->setData(Qt::UserRole, toqstr(lang_map.first));
+		item->setData(Qt::UserRole + 1, kmap);
+	}
+	keymapLangLW->sortItems();
 }
 
 
@@ -410,22 +444,13 @@ void PrefInput::on_firstKeymapPB_clicked(bool)
 }
 
 
-void PrefInput::on_secondKeymapPB_clicked(bool)
-{
-	QString const file = testKeymap(secondKeymapED->text());
-	if (!file.isEmpty())
-		secondKeymapED->setText(file);
-}
-
-
 void PrefInput::on_keymapCB_toggled(bool keymap)
 {
 	firstKeymapLA->setEnabled(keymap);
-	secondKeymapLA->setEnabled(keymap);
 	firstKeymapED->setEnabled(keymap);
-	secondKeymapED->setEnabled(keymap);
 	firstKeymapPB->setEnabled(keymap);
-	secondKeymapPB->setEnabled(keymap);
+	keymapFollowLangCB->setEnabled(keymap);
+	updateKeymapLangEnabled();
 }
 
 
@@ -435,6 +460,80 @@ void PrefInput::on_scrollzoomEnableCB_toggled(bool enabled)
 }
 
 
+void PrefInput::on_keymapFollowLangCB_toggled(bool)
+{
+	updateKeymapLangEnabled();
+}
+
+
+void PrefInput::updateKeymapLangEnabled()
+{
+	bool const enabled = keymapCB->isChecked() && keymapFollowLangCB->isChecked();
+	keymapLangLW->setEnabled(enabled);
+	keymapLangCO->setEnabled(enabled);
+	keymapLangED->setEnabled(enabled);
+	keymapLangPB->setEnabled(enabled);
+	keymapLangAddPB->setEnabled(enabled);
+	keymapLangRemovePB->setEnabled(enabled && keymapLangLW->currentRow() >= 0);
+}
+
+
+void PrefInput::on_keymapLangLW_currentRowChanged(int row)
+{
+	keymapLangRemovePB->setEnabled(row >= 0
+		&& keymapCB->isChecked() && keymapFollowLangCB->isChecked());
+	if (row < 0)
+		return;
+	QListWidgetItem const * item = keymapLangLW->item(row);
+	int const pos = keymapLangCO->findData(item->data(Qt::UserRole));
+	if (pos >= 0)
+		keymapLangCO->setCurrentIndex(pos);
+	keymapLangED->setText(item->data(Qt::UserRole + 1).toString());
+}
+
+
+void PrefInput::on_keymapLangPB_clicked(bool)
+{
+	QString const file = testKeymap(keymapLangED->text());
+	if (!file.isEmpty())
+		keymapLangED->setText(file);
+}
+
+
+void PrefInput::addKeymapLang()
+{
+	int const idx = keymapLangCO->currentIndex();
+	QString const kmap = keymapLangED->text();
+	if (idx < 0 || kmap.isEmpty())
+		return;
+
+	QString const langcode = keymapLangCO->itemData(idx).toString();
+	QString const text = keymapLangCO->currentText() + ": " + kmap;
+
+	// replace any existing entry for this language
+	for (int i = 0; i < keymapLangLW->count(); ++i) {
+		if (keymapLangLW->item(i)->data(Qt::UserRole).toString() == langcode) {
+			delete keymapLangLW->takeItem(i);
+			break;
+		}
+	}
+
+	QListWidgetItem * item = new QListWidgetItem(text, keymapLangLW);
+	item->setData(Qt::UserRole, langcode);
+	item->setData(Qt::UserRole + 1, kmap);
+	keymapLangLW->sortItems();
+	keymapLangLW->setCurrentItem(item);
+}
+
+
+void PrefInput::removeKeymapLang()
+{
+	int const row = keymapLangLW->currentRow();
+	if (row >= 0)
+		delete keymapLangLW->takeItem(row);
+}
+
+
 /////////////////////////////////////////////////////////////////////
 //
 // PrefCompletion
@@ -4574,6 +4673,8 @@ void GuiPreferences::dispatchParams()
 
 	theMovers() = movers_;
 
+	lyxrc.keymap_by_language = rc_.keymap_by_language;
+
 	for (string const & color : colors_)
 		dispatch(FuncRequest(LFUN_SET_COLOR, color));
 	colors_.clear();
diff --git a/src/frontends/qt/GuiPrefs.h b/src/frontends/qt/GuiPrefs.h
index cae84feae4..ba74b14fc3 100644
--- a/src/frontends/qt/GuiPrefs.h
+++ b/src/frontends/qt/GuiPrefs.h
@@ -183,12 +183,17 @@ public:
 
 private Q_SLOTS:
 	void on_firstKeymapPB_clicked(bool);
-	void on_secondKeymapPB_clicked(bool);
 	void on_keymapCB_toggled(bool);
 	void on_scrollzoomEnableCB_toggled(bool);
+	void on_keymapFollowLangCB_toggled(bool);
+	void on_keymapLangLW_currentRowChanged(int);
+	void on_keymapLangPB_clicked(bool);
+	void addKeymapLang();
+	void removeKeymapLang();
 
 private:
 	QString testKeymap(QString const & keymap);
+	void updateKeymapLangEnabled();
 };
 
 
diff --git a/src/frontends/qt/ui/PrefInputUi.ui b/src/frontends/qt/ui/PrefInputUi.ui
index a9960b2a2a..cc78677f26 100644
--- a/src/frontends/qt/ui/PrefInputUi.ui
+++ b/src/frontends/qt/ui/PrefInputUi.ui
@@ -43,59 +43,142 @@
         </property>
        </widget>
       </item>
-      <item row="2" column="2">
-       <widget class="QPushButton" name="secondKeymapPB">
+      <item row="1" column="2">
+       <widget class="QPushButton" name="firstKeymapPB">
         <property name="enabled">
          <bool>false</bool>
         </property>
         <property name="text">
-         <string>&Browse...</string>
+         <string>Br&owse...</string>
         </property>
         <property name="autoDefault">
          <bool>false</bool>
         </property>
        </widget>
       </item>
-      <item row="1" column="2">
-       <widget class="QPushButton" name="firstKeymapPB">
+      <item row="1" column="0">
+       <widget class="QLabel" name="firstKeymapLA">
         <property name="enabled">
          <bool>false</bool>
         </property>
         <property name="text">
-         <string>Br&owse...</string>
+         <string>&Default:</string>
         </property>
-        <property name="autoDefault">
-         <bool>false</bool>
+        <property name="buddy">
+         <cstring>firstKeymapED</cstring>
         </property>
        </widget>
       </item>
-      <item row="2" column="0">
-       <widget class="QLabel" name="secondKeymapLA">
+      <item row="2" column="0" colspan="3">
+       <widget class="QCheckBox" name="keymapFollowLangCB">
         <property name="enabled">
          <bool>false</bool>
         </property>
-        <property name="text">
-         <string>S&econdary:</string>
+        <property name="toolTip">
+         <string>If checked, the keyboard map is selected automatically according to the language at the cursor, using the assignments below, falling back to the Default map above for languages without an assignment</string>
         </property>
-        <property name="buddy">
-         <cstring>secondKeymapED</cstring>
+        <property name="text">
+         <string>&Select keyboard map automatically by language</string>
         </property>
        </widget>
       </item>
-      <item row="1" column="0">
-       <widget class="QLabel" name="firstKeymapLA">
+      <item row="3" column="0" colspan="2">
+       <widget class="QListWidget" name="keymapLangLW">
         <property name="enabled">
          <bool>false</bool>
         </property>
-        <property name="text">
-         <string>&Primary:</string>
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+          <horstretch>0</horstretch>
+          <verstretch>0</verstretch>
+         </sizepolicy>
         </property>
-        <property name="buddy">
-         <cstring>firstKeymapED</cstring>
+        <property name="toolTip">
+         <string>Keyboard map assigned to each language</string>
         </property>
        </widget>
       </item>
-      <item row="3" column="0" colspan="3">
+      <item row="3" column="2">
+       <layout class="QVBoxLayout" name="verticalLayout_kbmaplang">
+        <item>
+         <widget class="QComboBox" name="keymapLangCO">
+          <property name="enabled">
+           <bool>false</bool>
+          </property>
+          <property name="toolTip">
+           <string>Language to assign a keyboard map to</string>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <layout class="QHBoxLayout" name="horizontalLayout_kbmaplang">
+          <item>
+           <widget class="QLineEdit" name="keymapLangED">
+            <property name="enabled">
+             <bool>false</bool>
+            </property>
+            <property name="toolTip">
+             <string>Keyboard map to use for the selected language</string>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QPushButton" name="keymapLangPB">
+            <property name="enabled">
+             <bool>false</bool>
+            </property>
+            <property name="text">
+             <string>&Browse...</string>
+            </property>
+            <property name="autoDefault">
+             <bool>false</bool>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </item>
+        <item>
+         <widget class="QPushButton" name="keymapLangAddPB">
+          <property name="enabled">
+           <bool>false</bool>
+          </property>
+          <property name="text">
+           <string>A&dd</string>
+          </property>
+          <property name="autoDefault">
+           <bool>false</bool>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QPushButton" name="keymapLangRemovePB">
+          <property name="enabled">
+           <bool>false</bool>
+          </property>
+          <property name="text">
+           <string>Remo&ve</string>
+          </property>
+          <property name="autoDefault">
+           <bool>false</bool>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <spacer name="verticalSpacer_kbmaplang">
+          <property name="orientation">
+           <enum>Qt::Vertical</enum>
+          </property>
+          <property name="sizeHint" stdset="0">
+           <size>
+            <width>20</width>
+            <height>40</height>
+           </size>
+          </property>
+         </spacer>
+        </item>
+       </layout>
+      </item>
+      <item row="4" column="0" colspan="3">
        <widget class="QCheckBox" name="dontswapCB">
         <property name="toolTip">
          <string>macOS specific setting for use with emacs bindings. Takes effect next time LyX is launched.</string>
@@ -108,13 +191,6 @@
         </property>
        </widget>
       </item>
-      <item row="2" column="1">
-       <widget class="QLineEdit" name="secondKeymapED">
-        <property name="enabled">
-         <bool>false</bool>
-        </property>
-       </widget>
-      </item>
      </layout>
     </widget>
    </item>
@@ -299,8 +375,13 @@
   <tabstop>keymapCB</tabstop>
   <tabstop>firstKeymapED</tabstop>
   <tabstop>firstKeymapPB</tabstop>
-  <tabstop>secondKeymapED</tabstop>
-  <tabstop>secondKeymapPB</tabstop>
+  <tabstop>keymapFollowLangCB</tabstop>
+  <tabstop>keymapLangLW</tabstop>
+  <tabstop>keymapLangCO</tabstop>
+  <tabstop>keymapLangED</tabstop>
+  <tabstop>keymapLangPB</tabstop>
+  <tabstop>keymapLangAddPB</tabstop>
+  <tabstop>keymapLangRemovePB</tabstop>
   <tabstop>mouseWheelSpeedSB</tabstop>
  </tabstops>
  <includes>


More information about the lyx-cvs mailing list