whitespace.
[lyx.git] / src / Trans.cpp
blob0b642bcb731998c08b09a88776073cb4f703ae60
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/convert.h"
25 #include "support/debug.h"
26 #include "support/docstream.h"
27 #include "support/FileName.h"
28 #include "support/filetools.h"
29 #include "support/lstrings.h"
31 using namespace std;
32 using namespace lyx::support;
34 namespace lyx {
36 /////////////////////////////////////////////////////////////////////
38 // TeXAccents
40 /////////////////////////////////////////////////////////////////////
42 /* the names used by TeX and XWindows for deadkeys/accents are not the same
43 so here follows a table to clearify the differences. Please correct this
44 if I got it wrong
46 |------------------|------------------|------------------|--------------|
47 | TeX | XWindows | \bind/LFUN | used by intl |
48 |------------------|------------------|------------------|--------------|
49 | grave | grave |LFUN_ACCENT_GRAVE | grave
50 | acute | acute |LFUN_ACCENT_ACUTE | acute
51 | circumflex | circumflex |LFUN_ACCENT_CIRCUMFLEX | circumflex
52 | umlaut/dieresis | diaeresis |LFUN_ACCENT_UMLAUT | umlaut
53 | tilde | tilde |LFUN_ACCENT_TILDE | tilde
54 | macron | maron |LFUN_ACCENT_MACRON | macron
55 | dot | abovedot |LFUN_ACCENT_DOT | dot
56 | cedilla | cedilla |LFUN_ACCENT_CEDILLA | cedilla
57 | underdot | |LFUN_ACCENT_UNDERDOT | underdot
58 | underbar | |LFUN_ACCENT_UNDERBAR | underbar
59 | hácek | caron |LFUN_ACCENT_CARON | caron
60 | breve | breve |LFUN_ACCENT_BREVE | breve
61 | tie | |LFUN_ACCENT_TIE | tie
62 | Hungarian umlaut | doubleacute |LFUN_ACCENT_HUNGARIAN_UMLAUT | hungarian umlaut
63 | circle | abovering |LFUN_ACCENT_CIRCLE | circle
64 | | ogonek | |
65 | | iota | |
66 | | voiced_sound | |
67 | | semivoiced_sound | |
69 static TeXAccent lyx_accent_table[] = {
70 {TEX_NOACCENT, 0, "", LFUN_NOACTION},
71 {TEX_ACUTE, 0x0301, "acute", LFUN_ACCENT_ACUTE},
72 {TEX_GRAVE, 0x0300, "grave", LFUN_ACCENT_GRAVE},
73 {TEX_MACRON, 0x0304, "macron", LFUN_ACCENT_MACRON},
74 {TEX_TILDE, 0x0303, "tilde", LFUN_ACCENT_TILDE},
75 {TEX_UNDERBAR, 0x0320, "underbar", LFUN_ACCENT_UNDERBAR},
76 {TEX_CEDILLA, 0x0327, "cedilla", LFUN_ACCENT_CEDILLA},
77 {TEX_UNDERDOT, 0x0323, "underdot", LFUN_ACCENT_UNDERDOT},
78 {TEX_CIRCUMFLEX, 0x0302, "circumflex", LFUN_ACCENT_CIRCUMFLEX},
79 {TEX_CIRCLE, 0x030a, "circle", LFUN_ACCENT_CIRCLE},
80 {TEX_TIE, 0x0361, "tie", LFUN_ACCENT_TIE},
81 {TEX_BREVE, 0x0306, "breve", LFUN_ACCENT_BREVE},
82 {TEX_CARON, 0x030c, "caron", LFUN_ACCENT_CARON},
83 // Don't fix this typo for compatibility reasons!
84 {TEX_HUNGUML, 0x030b, "hugarian_umlaut", LFUN_ACCENT_HUNGARIAN_UMLAUT},
85 {TEX_UMLAUT, 0x0308, "umlaut", LFUN_ACCENT_UMLAUT},
86 {TEX_DOT, 0x0307, "dot", LFUN_ACCENT_DOT},
87 {TEX_OGONEK, 0x0328, "ogonek", LFUN_ACCENT_OGONEK}
91 TeXAccent get_accent(FuncCode action)
93 int i = 0;
94 while (i <= TEX_MAX_ACCENT) {
95 if (lyx_accent_table[i].action == action)
96 return lyx_accent_table[i];
97 ++i;
99 struct TeXAccent temp = { static_cast<tex_accent>(0), 0,
100 0, static_cast<FuncCode>(0)};
101 return temp;
105 static docstring const doAccent(docstring const & s, tex_accent accent)
107 if (s.empty())
108 return docstring(1, lyx_accent_table[accent].ucs4);
110 odocstringstream os;
111 os.put(s[0]);
112 os.put(lyx_accent_table[accent].ucs4);
113 if (s.length() > 1) {
114 if (accent != TEX_TIE || s.length() > 2)
115 lyxerr << "Warning: Too many characters given for accent "
116 << lyx_accent_table[accent].name << '.' << endl;
117 os << s.substr(1);
119 return normalize_c(os.str());
123 static docstring const doAccent(char_type c, tex_accent accent)
125 return doAccent(docstring(1, c), accent);
130 /////////////////////////////////////////////////////////////////////
132 // Trans
134 /////////////////////////////////////////////////////////////////////
137 void Trans::insertException(KmodException & exclist, char_type c,
138 docstring const & data, bool flag, tex_accent accent)
140 Keyexc p;
141 p.c = c;
142 p.data = data;
143 p.combined = flag;
144 p.accent = accent;
145 exclist.insert(exclist.begin(), p);
146 // or just
147 // exclist.push_back(p);
151 void Trans::freeException(KmodException & exclist)
153 exclist.clear();
157 void Trans::freeKeymap()
159 kmod_list_.clear();
160 keymap_.clear();
164 bool Trans::isDefined() const
166 return !name_.empty();
170 enum {
171 KCOMB = 1,
172 KMOD,
173 KMAP,
174 KXMOD,
178 tex_accent getkeymod(string const &);
181 void Trans::addDeadkey(tex_accent accent, docstring const & keys)
183 KmodInfo tmp;
184 tmp.data = keys;
185 tmp.accent = accent;
186 kmod_list_[accent] = tmp;
188 for (docstring::size_type i = 0; i < keys.length(); ++i) {
189 // FIXME This is a hack.
190 // tmp is no valid UCS4 string, but misused to store the
191 // accent.
192 docstring tmp;
193 tmp += char_type(0);
194 tmp += char_type(accent);
195 keymap_[keys[i]] = tmp;
200 int Trans::load(Lexer & lex)
202 bool error = false;
204 while (lex.isOK() && !error) {
205 switch (lex.lex()) {
206 case KMOD:
208 LYXERR(Debug::KBMAP, "KMOD:\t" << lex.getString());
209 if (!lex.next(true))
210 return -1;
212 LYXERR(Debug::KBMAP, "key\t`" << lex.getString() << '\'');
214 docstring const keys = lex.getDocString();
216 if (!lex.next(true))
217 return -1;
219 LYXERR(Debug::KBMAP, "accent\t`" << lex.getString() << '\'');
221 tex_accent accent = getkeymod(lex.getString());
223 if (accent == TEX_NOACCENT)
224 return -1;
226 #if 1
227 // FIXME: This code should be removed...
228 // But we need to fix up all the kmap files first
229 // so that this field is not present anymore.
230 if (!lex.next(true))
231 return -1;
233 LYXERR(Debug::KBMAP, "allowed\t`" << lex.getString() << '\'');
235 /* string const allowed = lex.getString(); */
236 addDeadkey(accent, keys /*, allowed*/);
237 #else
238 addDeadkey(accent, keys);
239 #endif
240 break;
242 case KCOMB: {
243 string str;
245 LYXERR(Debug::KBMAP, "KCOMB:");
246 if (!lex.next(true))
247 return -1;
249 str = lex.getString();
250 LYXERR(Debug::KBMAP, str);
252 tex_accent accent_1 = getkeymod(str);
253 if (accent_1 == TEX_NOACCENT)
254 return -1;
256 if (!lex.next(true))
257 return -1;
259 str = lex.getString();
260 LYXERR(Debug::KBMAP, str);
262 tex_accent accent_2 = getkeymod(str);
263 if (accent_2 == TEX_NOACCENT) return -1;
265 map<tex_accent, KmodInfo>::iterator it1 =
266 kmod_list_.find(accent_1);
267 map<tex_accent, KmodInfo>::iterator it2 =
268 kmod_list_.find(accent_2);
269 if (it1 == kmod_list_.end() || it2 == kmod_list_.end())
270 return -1;
272 // Find what key accent_2 is on - should
273 // check about accent_1 also
274 map<char_type, docstring>::iterator it = keymap_.begin();
275 map<char_type, docstring>::iterator end = keymap_.end();
276 for (; it != end; ++it) {
277 if (!it->second.empty()
278 && it->second[0] == 0
279 && it->second[1] == accent_2)
280 break;
282 docstring allowed;
283 if (!lex.next())
284 return -1;
286 allowed = lex.getDocString();
287 LYXERR(Debug::KBMAP, "allowed: " << to_utf8(allowed));
289 insertException(kmod_list_[accent_1].exception_list,
290 it->first, allowed, true, accent_2);
292 break;
293 case KMAP: {
294 unsigned char key_from;
296 LYXERR(Debug::KBMAP, "KMAP:\t" << lex.getString());
298 if (!lex.next(true))
299 return -1;
301 key_from = lex.getString()[0];
302 LYXERR(Debug::KBMAP, "\t`" << lex.getString() << '\'');
304 if (!lex.next(true))
305 return -1;
307 docstring const string_to = lex.getDocString();
308 keymap_[key_from] = string_to;
309 LYXERR(Debug::KBMAP, "\t`" << to_utf8(string_to) << '\'');
310 break;
312 case KXMOD: {
313 tex_accent accent;
314 char_type key;
315 docstring str;
317 LYXERR(Debug::KBMAP, "KXMOD:\t" << lex.getString());
319 if (!lex.next(true))
320 return -1;
322 LYXERR(Debug::KBMAP, "\t`" << lex.getString() << '\'');
323 accent = getkeymod(lex.getString());
325 if (!lex.next(true))
326 return -1;
328 LYXERR(Debug::KBMAP, "\t`" << lex.getString() << '\'');
329 key = lex.getDocString()[0];
331 if (!lex.next(true))
332 return -1;
334 LYXERR(Debug::KBMAP, "\t`" << lex.getString() << '\'');
335 str = lex.getDocString();
337 insertException(kmod_list_[accent].exception_list,
338 key, str);
339 break;
341 case Lexer::LEX_FEOF:
342 LYXERR(Debug::PARSER, "End of parsing");
343 break;
344 default:
345 lex.printError("ParseKeymapFile: Unknown tag: `$$Token'");
346 return -1;
349 return 0;
353 bool Trans::isAccentDefined(tex_accent accent, KmodInfo & i) const
355 map<tex_accent, KmodInfo>::const_iterator cit = kmod_list_.find(accent);
356 if (cit == kmod_list_.end())
357 return false;
358 i = cit->second;
359 return true;
363 docstring const Trans::process(char_type c, TransManager & k)
365 docstring const t = match(c);
367 if (t.empty() && c != 0)
368 return k.normalkey(c);
370 if (!t.empty() && t[0] != 0)
371 return t; //return k.normalkey(c);
373 return k.deadkey(c, kmod_list_[static_cast<tex_accent>(t[1])]);
377 int Trans::load(string const & language)
379 LexerKeyword kmapTags[] = {
380 {"\\kcomb", KCOMB },
381 { "\\kmap", KMAP },
382 { "\\kmod", KMOD },
383 { "\\kxmod", KXMOD }
386 FileName const filename = libFileSearch("kbd", language, "kmap");
387 if (filename.empty())
388 return -1;
390 freeKeymap();
391 Lexer lex(kmapTags);
392 lex.setFile(filename);
394 int const res = load(lex);
396 if (res == 0)
397 name_ = language;
398 else
399 name_.erase();
401 return res;
405 tex_accent getkeymod(string const & p)
406 /* return modifier - decoded from p and update p */
408 for (int i = 1; i <= TEX_MAX_ACCENT; ++i) {
409 LYXERR(Debug::KBMAP, "p = " << p
410 << ", lyx_accent_table[" << i
411 << "].name = `" << lyx_accent_table[i].name << '\'');
413 if (lyx_accent_table[i].name
414 && contains(p, lyx_accent_table[i].name)) {
415 LYXERR(Debug::KBMAP, "Found it!");
416 return static_cast<tex_accent>(i);
419 return TEX_NOACCENT;
423 /////////////////////////////////////////////////////////////////////
425 // TransState
427 /////////////////////////////////////////////////////////////////////
430 // TransFSMData
431 TransFSMData::TransFSMData()
433 deadkey_ = deadkey2_ = 0;
434 deadkey_info_.accent = deadkey2_info_.accent = TEX_NOACCENT;
438 // TransState
439 char_type const TransState::TOKEN_SEP = 4;
442 // TransInitState
443 TransInitState::TransInitState()
445 init_state_ = this;
449 docstring const TransInitState::normalkey(char_type c)
451 docstring res;
452 res = c;
453 return res;
457 docstring const TransInitState::deadkey(char_type c, KmodInfo d)
459 deadkey_ = c;
460 deadkey_info_ = d;
461 currentState = deadkey_state_;
462 return docstring();
466 // TransDeadkeyState
467 TransDeadkeyState::TransDeadkeyState()
469 deadkey_state_ = this;
473 docstring const TransDeadkeyState::normalkey(char_type c)
475 docstring res;
477 KmodException::iterator it = deadkey_info_.exception_list.begin();
478 KmodException::iterator end = deadkey_info_.exception_list.end();
480 for (; it != end; ++it) {
481 if (it->c == c) {
482 res = it->data;
483 break;
486 if (it == end) {
487 res = doAccent(c, deadkey_info_.accent);
489 currentState = init_state_;
490 return res;
494 docstring const TransDeadkeyState::deadkey(char_type c, KmodInfo d)
496 docstring res;
498 // Check if the same deadkey was typed twice
499 if (deadkey_ == c) {
500 res = deadkey_;
501 deadkey_ = 0;
502 deadkey_info_.accent = TEX_NOACCENT;
503 currentState = init_state_;
504 return res;
507 // Check if it is a combination or an exception
508 KmodException::const_iterator cit = deadkey_info_.exception_list.begin();
509 KmodException::const_iterator end = deadkey_info_.exception_list.end();
510 for (; cit != end; ++cit) {
511 if (cit->combined == true && cit->accent == d.accent) {
512 deadkey2_ = c;
513 deadkey2_info_ = d;
514 comb_info_ = (*cit);
515 currentState = combined_state_;
516 return docstring();
518 if (cit->c == c) {
519 res = cit->data;
520 deadkey_ = 0;
521 deadkey_info_.accent = TEX_NOACCENT;
522 currentState = init_state_;
523 return res;
527 // Not a combination or an exception.
528 // Output deadkey1 and keep deadkey2
530 if (deadkey_!= 0)
531 res = deadkey_;
532 deadkey_ = c;
533 deadkey_info_ = d;
534 currentState = deadkey_state_;
535 return res;
539 TransCombinedState::TransCombinedState()
541 combined_state_ = this;
545 docstring const TransCombinedState::normalkey(char_type c)
547 docstring const temp = doAccent(c, deadkey2_info_.accent);
548 docstring const res = doAccent(temp, deadkey_info_.accent);
549 currentState = init_state_;
550 return res;
554 docstring const TransCombinedState::deadkey(char_type c, KmodInfo d)
556 // Third key in a row. Output the first one and
557 // reenter with shifted deadkeys
558 docstring res;
559 if (deadkey_ != 0)
560 res = deadkey_;
561 res += TOKEN_SEP;
562 deadkey_ = deadkey2_;
563 deadkey_info_ = deadkey2_info_;
564 res += deadkey_state_->deadkey(c, d);
565 return res;
569 // TransFSM
570 TransFSM::TransFSM()
571 : TransFSMData(), TransInitState(), TransDeadkeyState(), TransCombinedState()
573 currentState = init_state_;
577 // TransManager
579 // Initialize static member.
580 Trans TransManager::default_;
583 TransManager::TransManager()
584 : active_(0)
588 int TransManager::setPrimary(string const & language)
590 if (t1_.getName() == language)
591 return 0;
593 return t1_.load(language);
597 int TransManager::setSecondary(string const & language)
599 if (t2_.getName() == language)
600 return 0;
602 return t2_.load(language);
606 void TransManager::enablePrimary()
608 if (t1_.isDefined())
609 active_ = &t1_;
611 LYXERR(Debug::KBMAP, "Enabling primary keymap");
615 void TransManager::enableSecondary()
617 if (t2_.isDefined())
618 active_ = &t2_;
619 LYXERR(Debug::KBMAP, "Enabling secondary keymap");
623 void TransManager::disableKeymap()
625 active_ = &default_;
626 LYXERR(Debug::KBMAP, "Disabling keymap");
630 void TransManager::translateAndInsert(char_type c, Text * text, Cursor & cur)
632 docstring res = active_->process(c, *this);
634 // Process with tokens
635 docstring temp;
637 while (res.length() > 0) {
638 res = split(res, temp, TransState::TOKEN_SEP);
639 insert(temp, text, cur);
644 void TransManager::insert(docstring const & str, Text * text, Cursor & cur)
646 for (size_t i = 0, n = str.size(); i != n; ++i)
647 text->insertChar(cur, str[i]);
651 void TransManager::deadkey(char_type c, tex_accent accent, Text * t, Cursor & cur)
653 if (c == 0 && active_ != &default_) {
654 // A deadkey was pressed that cannot be printed
655 // or a accent command was typed in the minibuffer
656 KmodInfo i;
657 if (active_->isAccentDefined(accent, i) == true) {
658 docstring const res = trans_fsm_
659 .currentState->deadkey(c, i);
660 insert(res, t, cur);
661 return;
665 if (active_ == &default_ || c == 0) {
666 KmodInfo i;
667 i.accent = accent;
668 i.data.erase();
669 docstring res = trans_fsm_.currentState->deadkey(c, i);
670 insert(res, t, cur);
671 } else {
672 // Go through the translation
673 translateAndInsert(c, t, cur);
678 } // namespace lyx