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