[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