* de/Tutorial.lyx: minor corrections reported on lyx-docs.
[lyx.git] / src / Trans.cpp
blobd646156dcf5b9afa8d2609bc9dc47bc84386d795
1 /**
2 * \file Trans.cpp
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.
12 #include <config.h>
14 #include "Trans.h"
16 #include "Buffer.h"
17 #include "BufferView.h"
18 #include "Cursor.h"
19 #include "CutAndPaste.h"
20 #include "Lexer.h"
21 #include "LyXRC.h"
22 #include "Text.h"
24 #include "support/debug.h"
25 #include "support/docstream.h"
26 #include "support/FileName.h"
27 #include "support/filetools.h"
28 #include "support/lstrings.h"
30 using namespace std;
31 using namespace lyx::support;
33 namespace lyx {
35 /////////////////////////////////////////////////////////////////////
37 // TeXAccents
39 /////////////////////////////////////////////////////////////////////
41 /* the names used by TeX and XWindows for deadkeys/accents are not the same
42 so here follows a table to clearify the differences. Please correct this
43 if I got it wrong
45 |------------------|------------------|------------------|--------------|
46 | TeX | XWindows | \bind/LFUN | used by intl |
47 |------------------|------------------|------------------|--------------|
48 | grave | grave |LFUN_ACCENT_GRAVE | grave
49 | acute | acute |LFUN_ACCENT_ACUTE | acute
50 | circumflex | circumflex |LFUN_ACCENT_CIRCUMFLEX | circumflex
51 | umlaut/dieresis | diaeresis |LFUN_ACCENT_UMLAUT | umlaut
52 | tilde | tilde |LFUN_ACCENT_TILDE | tilde
53 | macron | maron |LFUN_ACCENT_MACRON | macron
54 | dot | abovedot |LFUN_ACCENT_DOT | dot
55 | cedilla | cedilla |LFUN_ACCENT_CEDILLA | cedilla
56 | underdot | |LFUN_ACCENT_UNDERDOT | underdot
57 | underbar | |LFUN_ACCENT_UNDERBAR | underbar
58 | hácek | caron |LFUN_ACCENT_CARON | caron
59 | breve | breve |LFUN_ACCENT_BREVE | breve
60 | tie | |LFUN_ACCENT_TIE | tie
61 | Hungarian umlaut | doubleacute |LFUN_ACCENT_HUNGARIAN_UMLAUT | hungarian umlaut
62 | circle | abovering |LFUN_ACCENT_CIRCLE | circle
63 | | ogonek | |
64 | | iota | |
65 | | voiced_sound | |
66 | | semivoiced_sound | |
68 static TeXAccent lyx_accent_table[] = {
69 {TEX_NOACCENT, 0, "", LFUN_NOACTION},
70 {TEX_ACUTE, 0x0301, "acute", LFUN_ACCENT_ACUTE},
71 {TEX_GRAVE, 0x0300, "grave", LFUN_ACCENT_GRAVE},
72 {TEX_MACRON, 0x0304, "macron", LFUN_ACCENT_MACRON},
73 {TEX_TILDE, 0x0303, "tilde", LFUN_ACCENT_TILDE},
74 {TEX_UNDERBAR, 0x0320, "underbar", LFUN_ACCENT_UNDERBAR},
75 {TEX_CEDILLA, 0x0327, "cedilla", LFUN_ACCENT_CEDILLA},
76 {TEX_UNDERDOT, 0x0323, "underdot", LFUN_ACCENT_UNDERDOT},
77 {TEX_CIRCUMFLEX, 0x0302, "circumflex", LFUN_ACCENT_CIRCUMFLEX},
78 {TEX_CIRCLE, 0x030a, "circle", LFUN_ACCENT_CIRCLE},
79 {TEX_TIE, 0x0361, "tie", LFUN_ACCENT_TIE},
80 {TEX_BREVE, 0x0306, "breve", LFUN_ACCENT_BREVE},
81 {TEX_CARON, 0x030c, "caron", LFUN_ACCENT_CARON},
82 // Don't fix this typo for compatibility reasons!
83 {TEX_HUNGUML, 0x030b, "hugarian_umlaut", LFUN_ACCENT_HUNGARIAN_UMLAUT},
84 {TEX_UMLAUT, 0x0308, "umlaut", LFUN_ACCENT_UMLAUT},
85 {TEX_DOT, 0x0307, "dot", LFUN_ACCENT_DOT},
86 {TEX_OGONEK, 0x0328, "ogonek", LFUN_ACCENT_OGONEK}
90 TeXAccent get_accent(FuncCode action)
92 int i = 0;
93 while (i <= TEX_MAX_ACCENT) {
94 if (lyx_accent_table[i].action == action)
95 return lyx_accent_table[i];
96 ++i;
98 struct TeXAccent temp = { static_cast<tex_accent>(0), 0,
99 0, static_cast<FuncCode>(0)};
100 return temp;
104 static docstring const doAccent(docstring const & s, tex_accent accent)
106 if (s.empty())
107 return docstring(1, lyx_accent_table[accent].ucs4);
109 odocstringstream os;
110 os.put(s[0]);
111 os.put(lyx_accent_table[accent].ucs4);
112 if (s.length() > 1) {
113 if (accent != TEX_TIE || s.length() > 2)
114 lyxerr << "Warning: Too many characters given for accent "
115 << lyx_accent_table[accent].name << '.' << endl;
116 os << s.substr(1);
118 return normalize_c(os.str());
122 static docstring const doAccent(char_type c, tex_accent accent)
124 return doAccent(docstring(1, c), accent);
129 /////////////////////////////////////////////////////////////////////
131 // Trans
133 /////////////////////////////////////////////////////////////////////
136 void Trans::insertException(KmodException & exclist, char_type c,
137 docstring const & data, bool flag, tex_accent accent)
139 Keyexc p;
140 p.c = c;
141 p.data = data;
142 p.combined = flag;
143 p.accent = accent;
144 exclist.insert(exclist.begin(), p);
145 // or just
146 // exclist.push_back(p);
150 void Trans::freeException(KmodException & exclist)
152 exclist.clear();
156 void Trans::freeKeymap()
158 kmod_list_.clear();
159 keymap_.clear();
163 bool Trans::isDefined() const
165 return !name_.empty();
169 enum {
170 KCOMB = 1,
171 KMOD,
172 KMAP,
173 KXMOD,
177 tex_accent getkeymod(string const &);
180 void Trans::addDeadkey(tex_accent accent, docstring const & keys)
182 KmodInfo tmp;
183 tmp.data = keys;
184 tmp.accent = accent;
185 kmod_list_[accent] = tmp;
187 for (docstring::size_type i = 0; i < keys.length(); ++i) {
188 // FIXME This is a hack.
189 // tmp is no valid UCS4 string, but misused to store the
190 // accent.
191 docstring tmp;
192 tmp += char_type(0);
193 tmp += char_type(accent);
194 keymap_[keys[i]] = tmp;
199 int Trans::load(Lexer & lex)
201 bool error = false;
203 while (lex.isOK() && !error) {
204 switch (lex.lex()) {
205 case KMOD:
207 LYXERR(Debug::KBMAP, "KMOD:\t" << lex.getString());
208 if (!lex.next(true))
209 return -1;
211 LYXERR(Debug::KBMAP, "key\t`" << lex.getString() << '\'');
213 docstring const keys = lex.getDocString();
215 if (!lex.next(true))
216 return -1;
218 LYXERR(Debug::KBMAP, "accent\t`" << lex.getString() << '\'');
220 tex_accent accent = getkeymod(lex.getString());
222 if (accent == TEX_NOACCENT)
223 return -1;
225 #if 1
226 // FIXME: This code should be removed...
227 // But we need to fix up all the kmap files first
228 // so that this field is not present anymore.
229 if (!lex.next(true))
230 return -1;
232 LYXERR(Debug::KBMAP, "allowed\t`" << lex.getString() << '\'');
234 /* string const allowed = lex.getString(); */
235 addDeadkey(accent, keys /*, allowed*/);
236 #else
237 addDeadkey(accent, keys);
238 #endif
239 break;
241 case KCOMB: {
242 string str;
244 LYXERR(Debug::KBMAP, "KCOMB:");
245 if (!lex.next(true))
246 return -1;
248 str = lex.getString();
249 LYXERR(Debug::KBMAP, str);
251 tex_accent accent_1 = getkeymod(str);
252 if (accent_1 == TEX_NOACCENT)
253 return -1;
255 if (!lex.next(true))
256 return -1;
258 str = lex.getString();
259 LYXERR(Debug::KBMAP, str);
261 tex_accent accent_2 = getkeymod(str);
262 if (accent_2 == TEX_NOACCENT) return -1;
264 map<tex_accent, KmodInfo>::iterator it1 =
265 kmod_list_.find(accent_1);
266 map<tex_accent, KmodInfo>::iterator it2 =
267 kmod_list_.find(accent_2);
268 if (it1 == kmod_list_.end() || it2 == kmod_list_.end())
269 return -1;
271 // Find what key accent_2 is on - should
272 // check about accent_1 also
273 map<char_type, docstring>::iterator it = keymap_.begin();
274 map<char_type, docstring>::iterator end = keymap_.end();
275 for (; it != end; ++it) {
276 if (!it->second.empty()
277 && it->second[0] == 0
278 && it->second[1] == accent_2)
279 break;
281 docstring allowed;
282 if (!lex.next())
283 return -1;
285 allowed = lex.getDocString();
286 LYXERR(Debug::KBMAP, "allowed: " << to_utf8(allowed));
288 insertException(kmod_list_[accent_1].exception_list,
289 it->first, allowed, true, accent_2);
291 break;
292 case KMAP: {
293 unsigned char key_from;
295 LYXERR(Debug::KBMAP, "KMAP:\t" << lex.getString());
297 if (!lex.next(true))
298 return -1;
300 key_from = lex.getString()[0];
301 LYXERR(Debug::KBMAP, "\t`" << lex.getString() << '\'');
303 if (!lex.next(true))
304 return -1;
306 docstring const string_to = lex.getDocString();
307 keymap_[key_from] = string_to;
308 LYXERR(Debug::KBMAP, "\t`" << to_utf8(string_to) << '\'');
309 break;
311 case KXMOD: {
312 tex_accent accent;
313 char_type key;
314 docstring str;
316 LYXERR(Debug::KBMAP, "KXMOD:\t" << lex.getString());
318 if (!lex.next(true))
319 return -1;
321 LYXERR(Debug::KBMAP, "\t`" << lex.getString() << '\'');
322 accent = getkeymod(lex.getString());
324 if (!lex.next(true))
325 return -1;
327 LYXERR(Debug::KBMAP, "\t`" << lex.getString() << '\'');
328 key = lex.getDocString()[0];
330 if (!lex.next(true))
331 return -1;
333 LYXERR(Debug::KBMAP, "\t`" << lex.getString() << '\'');
334 str = lex.getDocString();
336 insertException(kmod_list_[accent].exception_list,
337 key, str);
338 break;
340 case Lexer::LEX_FEOF:
341 LYXERR(Debug::PARSER, "End of parsing");
342 break;
343 default:
344 lex.printError("ParseKeymapFile: Unknown tag: `$$Token'");
345 return -1;
348 return 0;
352 bool Trans::isAccentDefined(tex_accent accent, KmodInfo & i) const
354 map<tex_accent, KmodInfo>::const_iterator cit = kmod_list_.find(accent);
355 if (cit == kmod_list_.end())
356 return false;
357 i = cit->second;
358 return true;
362 docstring const Trans::process(char_type c, TransManager & k)
364 docstring const t = match(c);
366 if (t.empty() && c != 0)
367 return k.normalkey(c);
369 if (!t.empty() && t[0] != 0)
370 return t; //return k.normalkey(c);
372 return k.deadkey(c, kmod_list_[static_cast<tex_accent>(t[1])]);
376 int Trans::load(string const & language)
378 LexerKeyword kmapTags[] = {
379 {"\\kcomb", KCOMB },
380 { "\\kmap", KMAP },
381 { "\\kmod", KMOD },
382 { "\\kxmod", KXMOD }
385 FileName const filename = libFileSearch("kbd", language, "kmap");
386 if (filename.empty())
387 return -1;
389 freeKeymap();
390 Lexer lex(kmapTags);
391 lex.setFile(filename);
393 int const res = load(lex);
395 if (res == 0)
396 name_ = language;
397 else
398 name_.erase();
400 return res;
404 tex_accent getkeymod(string const & p)
405 /* return modifier - decoded from p and update p */
407 for (int i = 1; i <= TEX_MAX_ACCENT; ++i) {
408 LYXERR(Debug::KBMAP, "p = " << p
409 << ", lyx_accent_table[" << i
410 << "].name = `" << lyx_accent_table[i].name << '\'');
412 if (lyx_accent_table[i].name
413 && contains(p, lyx_accent_table[i].name)) {
414 LYXERR(Debug::KBMAP, "Found it!");
415 return static_cast<tex_accent>(i);
418 return TEX_NOACCENT;
422 /////////////////////////////////////////////////////////////////////
424 // TransState
426 /////////////////////////////////////////////////////////////////////
429 // TransFSMData
430 TransFSMData::TransFSMData()
432 deadkey_ = deadkey2_ = 0;
433 deadkey_info_.accent = deadkey2_info_.accent = TEX_NOACCENT;
437 // TransState
438 char_type const TransState::TOKEN_SEP = 4;
441 // TransInitState
442 TransInitState::TransInitState()
444 init_state_ = this;
448 docstring const TransInitState::normalkey(char_type c)
450 docstring res;
451 res = c;
452 return res;
456 docstring const TransInitState::deadkey(char_type c, KmodInfo d)
458 deadkey_ = c;
459 deadkey_info_ = d;
460 currentState = deadkey_state_;
461 return docstring();
465 // TransDeadkeyState
466 TransDeadkeyState::TransDeadkeyState()
468 deadkey_state_ = this;
472 docstring const TransDeadkeyState::normalkey(char_type c)
474 docstring res;
476 KmodException::iterator it = deadkey_info_.exception_list.begin();
477 KmodException::iterator end = deadkey_info_.exception_list.end();
479 for (; it != end; ++it) {
480 if (it->c == c) {
481 res = it->data;
482 break;
485 if (it == end) {
486 res = doAccent(c, deadkey_info_.accent);
488 currentState = init_state_;
489 return res;
493 docstring const TransDeadkeyState::deadkey(char_type c, KmodInfo d)
495 docstring res;
497 // Check if the same deadkey was typed twice
498 if (deadkey_ == c) {
499 res = deadkey_;
500 deadkey_ = 0;
501 deadkey_info_.accent = TEX_NOACCENT;
502 currentState = init_state_;
503 return res;
506 // Check if it is a combination or an exception
507 KmodException::const_iterator cit = deadkey_info_.exception_list.begin();
508 KmodException::const_iterator end = deadkey_info_.exception_list.end();
509 for (; cit != end; ++cit) {
510 if (cit->combined == true && cit->accent == d.accent) {
511 deadkey2_ = c;
512 deadkey2_info_ = d;
513 comb_info_ = (*cit);
514 currentState = combined_state_;
515 return docstring();
517 if (cit->c == c) {
518 res = cit->data;
519 deadkey_ = 0;
520 deadkey_info_.accent = TEX_NOACCENT;
521 currentState = init_state_;
522 return res;
526 // Not a combination or an exception.
527 // Output deadkey1 and keep deadkey2
529 if (deadkey_!= 0)
530 res = deadkey_;
531 deadkey_ = c;
532 deadkey_info_ = d;
533 currentState = deadkey_state_;
534 return res;
538 TransCombinedState::TransCombinedState()
540 combined_state_ = this;
544 docstring const TransCombinedState::normalkey(char_type c)
546 docstring const temp = doAccent(c, deadkey2_info_.accent);
547 docstring const res = doAccent(temp, deadkey_info_.accent);
548 currentState = init_state_;
549 return res;
553 docstring const TransCombinedState::deadkey(char_type c, KmodInfo d)
555 // Third key in a row. Output the first one and
556 // reenter with shifted deadkeys
557 docstring res;
558 if (deadkey_ != 0)
559 res = deadkey_;
560 res += TOKEN_SEP;
561 deadkey_ = deadkey2_;
562 deadkey_info_ = deadkey2_info_;
563 res += deadkey_state_->deadkey(c, d);
564 return res;
568 // TransFSM
569 TransFSM::TransFSM()
570 : TransFSMData(), TransInitState(), TransDeadkeyState(), TransCombinedState()
572 currentState = init_state_;
576 // TransManager
578 // Initialize static member.
579 Trans TransManager::default_;
582 TransManager::TransManager()
583 : active_(0)
587 int TransManager::setPrimary(string const & language)
589 if (t1_.getName() == language)
590 return 0;
592 return t1_.load(language);
596 int TransManager::setSecondary(string const & language)
598 if (t2_.getName() == language)
599 return 0;
601 return t2_.load(language);
605 void TransManager::enablePrimary()
607 if (t1_.isDefined())
608 active_ = &t1_;
610 LYXERR(Debug::KBMAP, "Enabling primary keymap");
614 void TransManager::enableSecondary()
616 if (t2_.isDefined())
617 active_ = &t2_;
618 LYXERR(Debug::KBMAP, "Enabling secondary keymap");
622 void TransManager::disableKeymap()
624 active_ = &default_;
625 LYXERR(Debug::KBMAP, "Disabling keymap");
629 void TransManager::translateAndInsert(char_type c, Text * text, Cursor & cur)
631 docstring res = active_->process(c, *this);
633 // Process with tokens
634 docstring temp;
636 while (res.length() > 0) {
637 res = split(res, temp, TransState::TOKEN_SEP);
638 insert(temp, text, cur);
643 void TransManager::insert(docstring const & str, Text * text, Cursor & cur)
645 for (size_t i = 0, n = str.size(); i != n; ++i)
646 text->insertChar(cur, str[i]);
650 void TransManager::deadkey(char_type c, tex_accent accent, Text * t, Cursor & cur)
652 if (c == 0 && active_ != &default_) {
653 // A deadkey was pressed that cannot be printed
654 // or a accent command was typed in the minibuffer
655 KmodInfo i;
656 if (active_->isAccentDefined(accent, i) == true) {
657 docstring const res = trans_fsm_
658 .currentState->deadkey(c, i);
659 insert(res, t, cur);
660 return;
664 if (active_ == &default_ || c == 0) {
665 KmodInfo i;
666 i.accent = accent;
667 i.data.erase();
668 docstring res = trans_fsm_.currentState->deadkey(c, i);
669 insert(res, t, cur);
670 } else {
671 // Go through the translation
672 translateAndInsert(c, t, cur);
677 } // namespace lyx