]> git.lyx.org Git - lyx.git/blob - src/Trans.cpp
'using namespace std' instead of 'using std::xxx'
[lyx.git] / src / Trans.cpp
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.
5  *
6  * \author Lars Gullik Bjønnes
7  * \author Matthias Ettrich
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 #include <config.h>
13
14 #include "Trans.h"
15
16 #include "Buffer.h"
17 #include "BufferView.h"
18 #include "Cursor.h"
19 #include "CutAndPaste.h"
20 #include "support/debug.h"
21 #include "Lexer.h"
22 #include "LyXRC.h"
23 #include "Text.h"
24
25 #include "support/filetools.h"
26 #include "support/lstrings.h"
27 #include "support/convert.h"
28 #include "support/docstream.h"
29
30 using namespace std;
31
32 namespace lyx {
33
34 using support::split;
35 using support::contains;
36 using support::libFileSearch;
37
38
39 /////////////////////////////////////////////////////////////////////
40 //
41 // TeXAccents
42 //
43 /////////////////////////////////////////////////////////////////////
44
45 /* the names used by TeX and XWindows for deadkeys/accents are not the same
46    so here follows a table to clearify the differences. Please correct this
47    if I got it wrong
48
49    |------------------|------------------|------------------|--------------|
50    |      TeX         |     XWindows     |   \bind/LFUN     | used by intl |
51    |------------------|------------------|------------------|--------------|
52    |    grave         |    grave         |LFUN_ACCENT_GRAVE        | grave
53    |    acute         |    acute         |LFUN_ACCENT_ACUTE        | acute
54    |    circumflex    |    circumflex    |LFUN_ACCENT_CIRCUMFLEX   | circumflex
55    | umlaut/dieresis  |    diaeresis     |LFUN_ACCENT_UMLAUT       | umlaut
56    |    tilde         |    tilde         |LFUN_ACCENT_TILDE        | tilde
57    |    macron        |    maron         |LFUN_ACCENT_MACRON       | macron
58    |    dot           |    abovedot      |LFUN_ACCENT_DOT          | dot
59    |    cedilla       |    cedilla       |LFUN_ACCENT_CEDILLA      | cedilla
60    |    underdot      |                  |LFUN_ACCENT_UNDERDOT     | underdot
61    |    underbar      |                  |LFUN_ACCENT_UNDERBAR     | underbar
62    |    hácek         |    caron         |LFUN_ACCENT_CARON        | caron
63    |    breve         |    breve         |LFUN_ACCENT_BREVE        | breve
64    |    tie           |                  |LFUN_ACCENT_TIE          | tie
65    | Hungarian umlaut |    doubleacute   |LFUN_ACCENT_HUNGARIAN_UMLAUT  | hungarian umlaut
66    |    circle        |    abovering     |LFUN_ACCENT_CIRCLE       | circle
67    |                  |    ogonek        |                  |
68    |                  |    iota          |                  |
69    |                  |    voiced_sound  |                  |
70    |                  | semivoiced_sound |                  |
71    |                  |                  |LFUN_ACCENT_SPECIAL_CARON| special caron
72    */
73 static tex_accent_struct lyx_accent_table[] = {
74         {TEX_NOACCENT,   0,      "",                LFUN_NOACTION},
75         {TEX_ACUTE,      0x0301, "acute",           LFUN_ACCENT_ACUTE},
76         {TEX_GRAVE,      0x0300, "grave",           LFUN_ACCENT_GRAVE},
77         {TEX_MACRON,     0x0304, "macron",          LFUN_ACCENT_MACRON},
78         {TEX_TILDE,      0x0303, "tilde",           LFUN_ACCENT_TILDE},
79         {TEX_UNDERBAR,   0x0320, "underbar",        LFUN_ACCENT_UNDERBAR},
80         {TEX_CEDILLA,    0x0327, "cedilla",         LFUN_ACCENT_CEDILLA},
81         {TEX_UNDERDOT,   0x0323, "underdot",        LFUN_ACCENT_UNDERDOT},
82         {TEX_CIRCUMFLEX, 0x0302, "circumflex",      LFUN_ACCENT_CIRCUMFLEX},
83         {TEX_CIRCLE,     0x030a, "circle",          LFUN_ACCENT_CIRCLE},
84         {TEX_TIE,        0x0361, "tie",             LFUN_ACCENT_TIE},
85         {TEX_BREVE,      0x0306, "breve",           LFUN_ACCENT_BREVE},
86         {TEX_CARON,      0x030c, "caron",           LFUN_ACCENT_CARON},
87 //      {TEX_SPECIAL_CARON, 0x030c, "ooo",          LFUN_ACCENT_SPECIAL_CARON},
88         // Don't fix this typo for compatibility reasons!
89         {TEX_HUNGUML,    0x030b, "hugarian_umlaut", LFUN_ACCENT_HUNGARIAN_UMLAUT},
90         {TEX_UMLAUT,     0x0308, "umlaut",          LFUN_ACCENT_UMLAUT},
91         {TEX_DOT,        0x0307, "dot",             LFUN_ACCENT_DOT},
92         {TEX_OGONEK,     0x0328, "ogonek",          LFUN_ACCENT_OGONEK}
93 };
94
95
96 tex_accent_struct get_accent(kb_action action)
97 {
98         int i = 0;
99         while (i <= TEX_MAX_ACCENT) {
100                 if (lyx_accent_table[i].action == action)
101                         return lyx_accent_table[i];
102                 ++i;
103         }
104         struct tex_accent_struct temp = { static_cast<tex_accent>(0), 0,
105                                           0, static_cast<kb_action>(0)};
106         return temp;
107 }
108
109
110 static docstring const doAccent(docstring const & s, tex_accent accent)
111 {
112         if (s.empty())
113                 return docstring(1, lyx_accent_table[accent].ucs4);
114
115         odocstringstream os;
116         os.put(s[0]);
117         os.put(lyx_accent_table[accent].ucs4);
118         if (s.length() > 1) {
119                 if (accent != TEX_TIE || s.length() > 2)
120                         lyxerr << "Warning: Too many characters given for accent "
121                                << lyx_accent_table[accent].name << '.' << std::endl;
122                 os << s.substr(1);
123         }
124         return normalize_c(os.str());
125 }
126
127
128 static docstring const doAccent(char_type c, tex_accent accent)
129 {
130         return doAccent(docstring(1, c), accent);
131 }
132
133
134
135 /////////////////////////////////////////////////////////////////////
136 //
137 // Trans
138 //
139 /////////////////////////////////////////////////////////////////////
140
141
142 void Trans::insertException(KmodException & exclist, char_type c,
143         docstring const & data, bool flag, tex_accent accent)
144 {
145         Keyexc p;
146         p.c = c;
147         p.data = data;
148         p.combined = flag;
149         p.accent = accent;
150         exclist.insert(exclist.begin(), p);
151         // or just
152         // exclist.push_back(p);
153 }
154
155
156 void Trans::freeException(KmodException & exclist)
157 {
158         exclist.clear();
159 }
160
161
162 void Trans::freeKeymap()
163 {
164         kmod_list_.clear();
165         keymap_.clear();
166 }
167
168
169 bool Trans::isDefined() const
170 {
171         return !name_.empty();
172 }
173
174
175 enum kmaptags_ {
176         KCOMB = 1,
177         KMOD,
178         KMAP,
179         KXMOD,
180         K_LAST
181 };
182
183
184 struct keyword_item kmapTags[K_LAST - 1] = {
185         {"\\kcomb", KCOMB },
186         { "\\kmap", KMAP },
187         { "\\kmod", KMOD },
188         { "\\kxmod", KXMOD }
189 };
190
191
192 tex_accent getkeymod(string const &);
193
194
195 void Trans::addDeadkey(tex_accent accent, docstring const & keys)
196 {
197         KmodInfo tmp;
198         tmp.data = keys;
199         tmp.accent = accent;
200         kmod_list_[accent] = tmp;
201
202         for (docstring::size_type i = 0; i < keys.length(); ++i) {
203                 // FIXME This is a hack.
204                 // tmp is no valid UCS4 string, but misused to store the
205                 // accent.
206                 docstring tmp;
207                 tmp += char_type(0);
208                 tmp += char_type(accent);
209                 keymap_[keys[i]] = tmp;
210         }
211 }
212
213
214 int Trans::load(Lexer & lex)
215 {
216         bool error = false;
217
218         while (lex.isOK() && !error) {
219                 switch (lex.lex()) {
220                 case KMOD:
221                 {
222                         LYXERR(Debug::KBMAP, "KMOD:\t" << lex.getString());
223                         if (!lex.next(true))
224                                 return -1;
225
226                         LYXERR(Debug::KBMAP, "key\t`" << lex.getString() << '\'');
227
228                         docstring const keys = lex.getDocString();
229
230                         if (!lex.next(true))
231                                 return -1;
232
233                         LYXERR(Debug::KBMAP, "accent\t`" << lex.getString() << '\'');
234
235                         tex_accent accent = getkeymod(lex.getString());
236
237                         if (accent == TEX_NOACCENT)
238                                 return -1;
239
240 #if 1
241                         // FIXME: This code should be removed...
242                         // But we need to fix up all the kmap files first
243                         // so that this field is not present anymore.
244                         if (!lex.next(true))
245                                 return -1;
246
247                         LYXERR(Debug::KBMAP, "allowed\t`" << lex.getString() << '\'');
248
249                         /* string const allowed = lex.getString(); */
250                         addDeadkey(accent, keys /*, allowed*/);
251 #else
252                         addDeadkey(accent, keys);
253 #endif
254                         break;
255                 }
256                 case KCOMB: {
257                         string str;
258
259                         LYXERR(Debug::KBMAP, "KCOMB:");
260                         if (!lex.next(true))
261                                 return -1;
262
263                         str = lex.getString();
264                         LYXERR(Debug::KBMAP, str);
265
266                         tex_accent accent_1 = getkeymod(str);
267                         if (accent_1 == TEX_NOACCENT)
268                                 return -1;
269
270                         if (!lex.next(true))
271                                 return -1;
272
273                         str = lex.getString();
274                         LYXERR(Debug::KBMAP, str);
275
276                         tex_accent accent_2 = getkeymod(str);
277                         if (accent_2 == TEX_NOACCENT) return -1;
278
279                         map<tex_accent, KmodInfo>::iterator it1 =
280                                 kmod_list_.find(accent_1);
281                         map<tex_accent, KmodInfo>::iterator it2 =
282                                 kmod_list_.find(accent_2);
283                         if (it1 == kmod_list_.end() || it2 == kmod_list_.end())
284                                 return -1;
285
286                         // Find what key accent_2 is on - should
287                         // check about accent_1 also
288                         map<char_type, docstring>::iterator it = keymap_.begin();
289                         map<char_type, docstring>::iterator end = keymap_.end();
290                         for (; it != end; ++it) {
291                                 if (!it->second.empty()
292                                     && it->second[0] == 0
293                                     && it->second[1] == accent_2)
294                                         break;
295                         }
296                         docstring allowed;
297                         if (!lex.next())
298                                 return -1;
299
300                         allowed = lex.getDocString();
301                         LYXERR(Debug::KBMAP, "allowed: " << to_utf8(allowed));
302
303                         insertException(kmod_list_[accent_1].exception_list,
304                                         it->first, allowed, true, accent_2);
305                 }
306                 break;
307                 case KMAP: {
308                         unsigned char key_from;
309
310                         LYXERR(Debug::KBMAP, "KMAP:\t" << lex.getString());
311
312                         if (!lex.next(true))
313                                 return -1;
314
315                         key_from = lex.getString()[0];
316                         LYXERR(Debug::KBMAP, "\t`" << lex.getString() << '\'');
317
318                         if (!lex.next(true))
319                                 return -1;
320
321                         docstring const string_to = lex.getDocString();
322                         keymap_[key_from] = string_to;
323                         LYXERR(Debug::KBMAP, "\t`" << to_utf8(string_to) << '\'');
324                         break;
325                 }
326                 case KXMOD: {
327                         tex_accent accent;
328                         char_type key;
329                         docstring str;
330
331                         LYXERR(Debug::KBMAP, "KXMOD:\t" << lex.getString());
332
333                         if (!lex.next(true))
334                                 return -1;
335
336                         LYXERR(Debug::KBMAP, "\t`" << lex.getString() << '\'');
337                         accent = getkeymod(lex.getString());
338
339                         if (!lex.next(true))
340                                 return -1;
341
342                         LYXERR(Debug::KBMAP, "\t`" << lex.getString() << '\'');
343                         key = lex.getDocString()[0];
344
345                         if (!lex.next(true))
346                                 return -1;
347
348                         LYXERR(Debug::KBMAP, "\t`" << lex.getString() << '\'');
349                         str = lex.getDocString();
350
351                         insertException(kmod_list_[accent].exception_list,
352                                         key, str);
353                         break;
354                 }
355                 case Lexer::LEX_FEOF:
356                         LYXERR(Debug::PARSER, "End of parsing");
357                         break;
358                 default:
359                         lex.printError("ParseKeymapFile: Unknown tag: `$$Token'");
360                         return -1;
361                 }
362         }
363         return 0;
364 }
365
366
367 bool Trans::isAccentDefined(tex_accent accent, KmodInfo & i) const
368 {
369         map<tex_accent, KmodInfo>::const_iterator cit = kmod_list_.find(accent);
370         if (cit == kmod_list_.end())
371                 return false;
372         i = cit->second;
373         return true;
374 }
375
376
377 docstring const Trans::process(char_type c, TransManager & k)
378 {
379         docstring const t = match(c);
380
381         if (t.empty() && c != 0)
382                 return k.normalkey(c);
383
384         if (!t.empty() && t[0] != 0)
385                 return t; //return k.normalkey(c);
386
387         return k.deadkey(c, kmod_list_[static_cast<tex_accent>(t[1])]);
388 }
389
390
391 int Trans::load(string const & language)
392 {
393         support::FileName const filename = libFileSearch("kbd", language, "kmap");
394         if (filename.empty())
395                 return -1;
396
397         freeKeymap();
398         Lexer lex(kmapTags, K_LAST - 1);
399         lex.setFile(filename);
400
401         int const res = load(lex);
402
403         if (res == 0)
404                 name_ = language;
405         else
406                 name_.erase();
407
408         return res;
409 }
410
411
412 tex_accent getkeymod(string const & p)
413         /* return modifier - decoded from p and update p */
414 {
415         for (int i = 1; i <= TEX_MAX_ACCENT; ++i) {
416                 LYXERR(Debug::KBMAP, "p = " << p
417                        << ", lyx_accent_table[" << i
418                        << "].name = `" << lyx_accent_table[i].name << '\'');
419
420                 if (lyx_accent_table[i].name
421                      && contains(p, lyx_accent_table[i].name)) {
422                         LYXERR(Debug::KBMAP, "Found it!");
423                         return static_cast<tex_accent>(i);
424                 }
425         }
426         return TEX_NOACCENT;
427 }
428
429
430 /////////////////////////////////////////////////////////////////////
431 //
432 // TransState
433 //
434 /////////////////////////////////////////////////////////////////////
435
436
437 // TransFSMData
438 TransFSMData::TransFSMData()
439 {
440         deadkey_ = deadkey2_ = 0;
441         deadkey_info_.accent = deadkey2_info_.accent = TEX_NOACCENT;
442 }
443
444
445 // TransState
446 char_type const TransState::TOKEN_SEP = 4;
447
448
449 // TransInitState
450 TransInitState::TransInitState()
451 {
452         init_state_ = this;
453 }
454
455
456 docstring const TransInitState::normalkey(char_type c)
457 {
458         docstring res;
459         res = c;
460         return res;
461 }
462
463
464 docstring const TransInitState::deadkey(char_type c, KmodInfo d)
465 {
466         deadkey_ = c;
467         deadkey_info_ = d;
468         currentState = deadkey_state_;
469         return docstring();
470 }
471
472
473 // TransDeadkeyState
474 TransDeadkeyState::TransDeadkeyState()
475 {
476         deadkey_state_ = this;
477 }
478
479
480 docstring const TransDeadkeyState::normalkey(char_type c)
481 {
482         docstring res;
483
484         KmodException::iterator it = deadkey_info_.exception_list.begin();
485         KmodException::iterator end = deadkey_info_.exception_list.end();
486
487         for (; it != end; ++it) {
488                 if (it->c == c) {
489                         res = it->data;
490                         break;
491                 }
492         }
493         if (it == end) {
494                 res = doAccent(c, deadkey_info_.accent);
495         }
496         currentState = init_state_;
497         return res;
498 }
499
500
501 docstring const TransDeadkeyState::deadkey(char_type c, KmodInfo d)
502 {
503         docstring res;
504
505         // Check if the same deadkey was typed twice
506         if (deadkey_ == c) {
507                 res = deadkey_;
508                 deadkey_ = 0;
509                 deadkey_info_.accent = TEX_NOACCENT;
510                 currentState = init_state_;
511                 return res;
512         }
513
514         // Check if it is a combination or an exception
515         KmodException::const_iterator cit = deadkey_info_.exception_list.begin();
516         KmodException::const_iterator end = deadkey_info_.exception_list.end();
517         for (; cit != end; ++cit) {
518                 if (cit->combined == true && cit->accent == d.accent) {
519                         deadkey2_ = c;
520                         deadkey2_info_ = d;
521                         comb_info_ = (*cit);
522                         currentState = combined_state_;
523                         return docstring();
524                 }
525                 if (cit->c == c) {
526                         res = cit->data;
527                         deadkey_ = 0;
528                         deadkey_info_.accent = TEX_NOACCENT;
529                         currentState = init_state_;
530                         return res;
531                 }
532         }
533
534         // Not a combination or an exception.
535         // Output deadkey1 and keep deadkey2
536
537         if (deadkey_!= 0)
538                 res = deadkey_;
539         deadkey_ = c;
540         deadkey_info_ = d;
541         currentState = deadkey_state_;
542         return res;
543 }
544
545
546 TransCombinedState::TransCombinedState()
547 {
548         combined_state_ = this;
549 }
550
551
552 docstring const TransCombinedState::normalkey(char_type c)
553 {
554         docstring const temp = doAccent(c, deadkey2_info_.accent);
555         docstring const res = doAccent(temp, deadkey_info_.accent);
556         currentState = init_state_;
557         return res;
558 }
559
560
561 docstring const TransCombinedState::deadkey(char_type c, KmodInfo d)
562 {
563         // Third key in a row. Output the first one and
564         // reenter with shifted deadkeys
565         docstring res;
566         if (deadkey_ != 0)
567                 res = deadkey_;
568         res += TOKEN_SEP;
569         deadkey_ = deadkey2_;
570         deadkey_info_ = deadkey2_info_;
571         res += deadkey_state_->deadkey(c, d);
572         return res;
573 }
574
575
576 // TransFSM
577 TransFSM::TransFSM()
578         : TransFSMData(), TransInitState(), TransDeadkeyState(), TransCombinedState()
579 {
580         currentState = init_state_;
581 }
582
583
584 // TransManager
585
586 // Initialize static member.
587 Trans TransManager::default_;
588
589
590 TransManager::TransManager()
591         : active_(0)
592 {}
593
594
595 int TransManager::setPrimary(string const & language)
596 {
597         if (t1_.getName() == language)
598                 return 0;
599
600         return t1_.load(language);
601 }
602
603
604 int TransManager::setSecondary(string const & language)
605 {
606         if (t2_.getName() == language)
607                 return 0;
608
609         return t2_.load(language);
610 }
611
612
613 void TransManager::enablePrimary()
614 {
615         if (t1_.isDefined())
616                 active_ = &t1_;
617
618         LYXERR(Debug::KBMAP, "Enabling primary keymap");
619 }
620
621
622 void TransManager::enableSecondary()
623 {
624         if (t2_.isDefined())
625                 active_ = &t2_;
626         LYXERR(Debug::KBMAP, "Enabling secondary keymap");
627 }
628
629
630 void TransManager::disableKeymap()
631 {
632         active_ = &default_;
633         LYXERR(Debug::KBMAP, "Disabling keymap");
634 }
635
636
637 void  TransManager::translateAndInsert(char_type c, Text * text, Cursor & cur)
638 {
639         docstring res = active_->process(c, *this);
640
641         // Process with tokens
642         docstring temp;
643
644         while (res.length() > 0) {
645                 res = split(res, temp, TransState::TOKEN_SEP);
646                 insert(temp, text, cur);
647         }
648 }
649
650
651 void TransManager::insert(docstring const & str, Text * text, Cursor & cur)
652 {
653         for (size_t i = 0, n = str.size(); i != n; ++i)
654                 text->insertChar(cur, str[i]);
655 }
656
657
658 void TransManager::deadkey(char_type c, tex_accent accent, Text * t, Cursor & cur)
659 {
660         if (c == 0 && active_ != &default_) {
661                 // A deadkey was pressed that cannot be printed
662                 // or a accent command was typed in the minibuffer
663                 KmodInfo i;
664                 if (active_->isAccentDefined(accent, i) == true) {
665                         docstring const res = trans_fsm_
666                                 .currentState->deadkey(c, i);
667                         insert(res, t, cur);
668                         return;
669                 }
670         }
671
672         if (active_ == &default_ || c == 0) {
673                 KmodInfo i;
674                 i.accent = accent;
675                 i.data.erase();
676                 docstring res = trans_fsm_.currentState->deadkey(c, i);
677                 insert(res, t, cur);
678         } else {
679                 // Go through the translation
680                 translateAndInsert(c, t, cur);
681         }
682 }
683
684
685 } // namespace lyx