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