3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Lars Gullik Bjønnes
7 * \author Matthias Ettrich
9 * Full author contact details are available in file CREDITS.
19 #include "support/debug.h"
20 #include "support/docstream.h"
21 #include "support/FileName.h"
22 #include "support/filetools.h"
23 #include "support/lstrings.h"
26 using namespace lyx::support;
30 /////////////////////////////////////////////////////////////////////
34 /////////////////////////////////////////////////////////////////////
36 /* the names used by TeX and XWindows for deadkeys/accents are not the same
37 so here follows a table to clearify the differences. Please correct this
40 |------------------|------------------|------------------|--------------|
41 | TeX | XWindows | \bind/LFUN | used by intl |
42 |------------------|------------------|------------------|--------------|
43 | grave | grave |LFUN_ACCENT_GRAVE | grave
44 | acute | acute |LFUN_ACCENT_ACUTE | acute
45 | circumflex | circumflex |LFUN_ACCENT_CIRCUMFLEX | circumflex
46 | umlaut/dieresis | diaeresis |LFUN_ACCENT_UMLAUT | umlaut
47 | tilde | tilde |LFUN_ACCENT_TILDE | tilde
48 | macron | maron |LFUN_ACCENT_MACRON | macron
49 | dot | abovedot |LFUN_ACCENT_DOT | dot
50 | cedilla | cedilla |LFUN_ACCENT_CEDILLA | cedilla
51 | underdot | |LFUN_ACCENT_UNDERDOT | underdot
52 | underbar | |LFUN_ACCENT_UNDERBAR | underbar
53 | hácek | caron |LFUN_ACCENT_CARON | caron
54 | breve | breve |LFUN_ACCENT_BREVE | breve
55 | tie | |LFUN_ACCENT_TIE | tie
56 | Hungarian umlaut | doubleacute |LFUN_ACCENT_HUNGARIAN_UMLAUT | hungarian umlaut
57 | circle | abovering |LFUN_ACCENT_CIRCLE | circle
61 | | semivoiced_sound | |
63 static TeXAccent lyx_accent_table[] = {
64 {TEX_NOACCENT, 0, "", LFUN_NOACTION},
65 {TEX_ACUTE, 0x0301, "acute", LFUN_ACCENT_ACUTE},
66 {TEX_GRAVE, 0x0300, "grave", LFUN_ACCENT_GRAVE},
67 {TEX_MACRON, 0x0304, "macron", LFUN_ACCENT_MACRON},
68 {TEX_TILDE, 0x0303, "tilde", LFUN_ACCENT_TILDE},
69 {TEX_PERISPOMENI, 0x0342, "perispomeni", LFUN_ACCENT_PERISPOMENI},
70 {TEX_UNDERBAR, 0x0320, "underbar", LFUN_ACCENT_UNDERBAR}, // COMBINING MINUS SIGN BELOW or 0x0331 COMBINING MACRON BELOW ?
72 {TEX_CEDILLA, 0x0327, "cedilla", LFUN_ACCENT_CEDILLA},
73 {TEX_UNDERDOT, 0x0323, "underdot", LFUN_ACCENT_UNDERDOT},
74 {TEX_CIRCUMFLEX, 0x0302, "circumflex", LFUN_ACCENT_CIRCUMFLEX},
75 {TEX_CIRCLE, 0x030a, "circle", LFUN_ACCENT_CIRCLE},
76 {TEX_TIE, 0x0361, "tie", LFUN_ACCENT_TIE},
77 {TEX_BREVE, 0x0306, "breve", LFUN_ACCENT_BREVE},
78 {TEX_CARON, 0x030c, "caron", LFUN_ACCENT_CARON},
79 // Don't fix this typo for compatibility reasons!
80 {TEX_HUNGUML, 0x030b, "hugarian_umlaut", LFUN_ACCENT_HUNGARIAN_UMLAUT},
81 {TEX_UMLAUT, 0x0308, "umlaut", LFUN_ACCENT_UMLAUT},
82 {TEX_DOT, 0x0307, "dot", LFUN_ACCENT_DOT},
83 {TEX_OGONEK, 0x0328, "ogonek", LFUN_ACCENT_OGONEK}
87 TeXAccent get_accent(FuncCode action)
90 while (i <= TEX_MAX_ACCENT) {
91 if (lyx_accent_table[i].action == action)
92 return lyx_accent_table[i];
95 struct TeXAccent temp = { static_cast<tex_accent>(0), 0,
96 nullptr, static_cast<FuncCode>(0)};
101 static docstring const doAccent(docstring const & s, tex_accent accent)
104 return docstring(1, lyx_accent_table[accent].ucs4);
108 os.put(lyx_accent_table[accent].ucs4);
109 if (s.length() > 1) {
110 if (accent != TEX_TIE || s.length() > 2)
111 lyxerr << "Warning: Too many characters given for accent "
112 << lyx_accent_table[accent].name << '.' << endl;
115 return normalize_c(os.str());
119 static docstring const doAccent(char_type c, tex_accent accent)
121 return doAccent(docstring(1, c), accent);
126 /////////////////////////////////////////////////////////////////////
130 /////////////////////////////////////////////////////////////////////
133 void Trans::insertException(KmodException & exclist, char_type c,
134 docstring const & data, bool flag, tex_accent accent)
141 exclist.insert(exclist.begin(), p);
143 // exclist.push_back(p);
147 void Trans::freeException(KmodException & exclist)
153 void Trans::freeKeymap()
160 bool Trans::isDefined() const
162 return !name_.empty();
174 tex_accent getkeymod(string const &);
177 void Trans::addDeadkey(tex_accent accent, docstring const & keys)
182 kmod_list_[accent] = tmp;
184 for (char_type key : keys) {
185 // FIXME This is a hack.
186 // tmp is no valid UCS4 string, but misused to store the
189 tmpd += char_type(0);
190 tmpd += char_type(accent);
196 int Trans::load(Lexer & lex)
202 LYXERR(Debug::KBMAP, "KMOD:\t" << lex.getString());
206 LYXERR(Debug::KBMAP, "key\t`" << lex.getString() << '\'');
208 docstring const keys = lex.getDocString();
213 LYXERR(Debug::KBMAP, "accent\t`" << lex.getString() << '\'');
215 tex_accent accent = getkeymod(lex.getString());
217 if (accent == TEX_NOACCENT)
221 // FIXME: This code should be removed...
222 // But we need to fix up all the kmap files first
223 // so that this field is not present anymore.
227 LYXERR(Debug::KBMAP, "allowed\t`" << lex.getString() << '\'');
229 /* string const allowed = lex.getString(); */
230 addDeadkey(accent, keys /*, allowed*/);
232 addDeadkey(accent, keys);
239 LYXERR(Debug::KBMAP, "KCOMB:");
243 str = lex.getString();
244 LYXERR(Debug::KBMAP, str);
246 tex_accent accent_1 = getkeymod(str);
247 if (accent_1 == TEX_NOACCENT)
253 str = lex.getString();
254 LYXERR(Debug::KBMAP, str);
256 tex_accent accent_2 = getkeymod(str);
257 if (accent_2 == TEX_NOACCENT) return -1;
259 map<tex_accent, KmodInfo>::iterator it1 =
260 kmod_list_.find(accent_1);
261 map<tex_accent, KmodInfo>::iterator it2 =
262 kmod_list_.find(accent_2);
263 if (it1 == kmod_list_.end() || it2 == kmod_list_.end())
266 // Find what key accent_2 is on - should
267 // check about accent_1 also
268 map<char_type, docstring>::iterator it = keymap_.begin();
269 map<char_type, docstring>::iterator end = keymap_.end();
270 for (; it != end; ++it) {
271 if (!it->second.empty()
272 && it->second[0] == 0
273 && it->second[1] == accent_2)
277 // could not find accent2 on a key -- this should not happen.
284 allowed = lex.getDocString();
285 LYXERR(Debug::KBMAP, "allowed: " << to_utf8(allowed));
287 insertException(kmod_list_[accent_1].exception_list,
288 it->first, allowed, true, accent_2);
292 unsigned char key_from;
294 LYXERR(Debug::KBMAP, "KMAP:\t" << lex.getString());
299 key_from = static_cast<unsigned char>(lex.getString()[0]);
300 LYXERR(Debug::KBMAP, "\t`" << lex.getString() << '\'');
305 docstring const string_to = lex.getDocString();
306 keymap_[key_from] = string_to;
307 LYXERR(Debug::KBMAP, "\t`" << to_utf8(string_to) << '\'');
315 LYXERR(Debug::KBMAP, "KXMOD:\t" << lex.getString());
320 LYXERR(Debug::KBMAP, "\t`" << lex.getString() << '\'');
321 accent = getkeymod(lex.getString());
326 LYXERR(Debug::KBMAP, "\t`" << lex.getString() << '\'');
327 key = lex.getDocString()[0];
332 LYXERR(Debug::KBMAP, "\t`" << lex.getString() << '\'');
333 str = lex.getDocString();
335 insertException(kmod_list_[accent].exception_list,
339 case Lexer::LEX_FEOF:
340 LYXERR(Debug::PARSER, "End of parsing");
343 lex.printError("ParseKeymapFile: Unknown tag: `$$Token'");
351 bool Trans::isAccentDefined(tex_accent accent, KmodInfo & i) const
353 map<tex_accent, KmodInfo>::const_iterator cit = kmod_list_.find(accent);
354 if (cit == kmod_list_.end())
361 docstring const Trans::process(char_type c, TransManager & k)
363 docstring const t = match(c);
365 if (t.empty() && c != 0)
366 return k.normalkey(c);
368 if (!t.empty() && t[0] != 0)
369 return t; //return k.normalkey(c);
371 return k.deadkey(c, kmod_list_[static_cast<tex_accent>(t[1])]);
375 int Trans::load(string const & language)
377 LexerKeyword kmapTags[] = {
384 FileName const filename = libFileSearch("kbd", language, "kmap");
385 if (filename.empty())
390 lex.setFile(filename);
392 int const res = load(lex);
403 tex_accent getkeymod(string const & p)
404 /* return modifier - decoded from p and update p */
406 for (int i = 1; i <= TEX_MAX_ACCENT; ++i) {
407 LYXERR(Debug::KBMAP, "p = " << p
408 << ", lyx_accent_table[" << i
409 << "].name = `" << lyx_accent_table[i].name << '\'');
411 if (lyx_accent_table[i].name
412 && contains(p, lyx_accent_table[i].name)) {
413 LYXERR(Debug::KBMAP, "Found it!");
414 return static_cast<tex_accent>(i);
421 /////////////////////////////////////////////////////////////////////
425 /////////////////////////////////////////////////////////////////////
429 TransFSMData::TransFSMData() : deadkey_(0), deadkey2_(0), init_state_(nullptr),
430 deadkey_state_(nullptr), combined_state_(nullptr), currentState(nullptr)
436 char_type const TransState::TOKEN_SEP = 4;
440 TransInitState::TransInitState()
446 docstring const TransInitState::normalkey(char_type c)
454 docstring const TransInitState::deadkey(char_type c, KmodInfo d)
458 currentState = deadkey_state_;
464 TransDeadkeyState::TransDeadkeyState()
466 deadkey_state_ = this;
470 docstring const TransDeadkeyState::normalkey(char_type c)
474 KmodException::iterator it = deadkey_info_.exception_list.begin();
475 KmodException::iterator end = deadkey_info_.exception_list.end();
477 for (; it != end; ++it) {
484 res = doAccent(c, deadkey_info_.accent);
486 currentState = init_state_;
491 docstring const TransDeadkeyState::deadkey(char_type c, KmodInfo d)
495 // Check if the same deadkey was typed twice
499 deadkey_info_.accent = TEX_NOACCENT;
500 currentState = init_state_;
504 // Check if it is a combination or an exception
505 KmodException::const_iterator cit = deadkey_info_.exception_list.begin();
506 KmodException::const_iterator end = deadkey_info_.exception_list.end();
507 for (; cit != end; ++cit) {
508 if (cit->combined && cit->accent == d.accent) {
512 currentState = combined_state_;
518 deadkey_info_.accent = TEX_NOACCENT;
519 currentState = init_state_;
524 // Not a combination or an exception.
525 // Output deadkey1 and keep deadkey2
531 currentState = deadkey_state_;
536 TransCombinedState::TransCombinedState()
538 combined_state_ = this;
542 docstring const TransCombinedState::normalkey(char_type c)
544 docstring const temp = doAccent(c, deadkey2_info_.accent);
545 docstring const res = doAccent(temp, deadkey_info_.accent);
546 currentState = init_state_;
551 docstring const TransCombinedState::deadkey(char_type c, KmodInfo d)
553 // Third key in a row. Output the first one and
554 // reenter with shifted deadkeys
559 deadkey_ = deadkey2_;
560 deadkey_info_ = deadkey2_info_;
561 res += deadkey_state_->deadkey(c, d);
568 : TransFSMData(), TransInitState(), TransDeadkeyState(), TransCombinedState()
570 currentState = init_state_;
576 // Initialize static member.
577 Trans TransManager::default_;
580 TransManager::TransManager()
585 int TransManager::setPrimary(string const & language)
587 if (t1_.getName() == language)
590 return t1_.load(language);
594 int TransManager::setSecondary(string const & language)
596 if (t2_.getName() == language)
599 return t2_.load(language);
603 void TransManager::enablePrimary()
608 LYXERR(Debug::KBMAP, "Enabling primary keymap");
612 void TransManager::enableSecondary()
616 LYXERR(Debug::KBMAP, "Enabling secondary keymap");
620 void TransManager::disableKeymap()
623 LYXERR(Debug::KBMAP, "Disabling keymap");
627 void TransManager::translateAndInsert(char_type c, Text * text, Cursor & cur)
629 docstring res = active_->process(c, *this);
631 // Process with tokens
634 while (res.length() > 0) {
635 res = split(res, temp, TransState::TOKEN_SEP);
636 insert(temp, text, cur);
641 void TransManager::insert(docstring const & str, Text * text, Cursor & cur)
643 for (size_t i = 0, n = str.size(); i != n; ++i)
644 text->insertChar(cur, str[i]);
648 void TransManager::deadkey(char_type c, tex_accent accent, Text * t, Cursor & cur)
650 if (c == 0 && active_ != &default_) {
651 // A deadkey was pressed that cannot be printed
652 // or a accent command was typed in the minibuffer
654 if (active_->isAccentDefined(accent, i)) {
655 docstring const res = trans_fsm_
656 .currentState->deadkey(c, i);
662 if (active_ == &default_ || c == 0) {
666 docstring res = trans_fsm_.currentState->deadkey(c, i);
669 // Go through the translation
670 translateAndInsert(c, t, cur);