]> git.lyx.org Git - lyx.git/blob - src/KeyMap.cpp
prepare Qt 5.6 builds
[lyx.git] / src / KeyMap.cpp
1 /**
2  * \file KeyMap.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 Jean-Marc Lasgouttes
8  * \author John Levon
9  * \author André Pönitz
10  *
11  * Full author contact details are available in file CREDITS.
12  */
13
14 #include <config.h>
15
16 #include "KeyMap.h"
17
18 #include "KeySequence.h"
19 #include "LyXAction.h"
20 #include "Lexer.h"
21
22 #include "support/debug.h"
23 #include "support/docstream.h"
24 #include "support/FileName.h"
25 #include "support/filetools.h"
26 #include "support/gettext.h"
27 #include "support/lstrings.h"
28 #include "support/TempFile.h"
29
30 #include "frontends/alert.h"
31
32 #include <fstream>
33 #include <sstream>
34 #include <utility>
35
36 using namespace std;
37 using namespace lyx::support;
38
39 namespace lyx {
40
41 string const KeyMap::printKeySym(KeySymbol const & key, KeyModifier mod)
42 {
43         string buf;
44
45         string const s = key.getSymbolName();
46
47         if (mod & ControlModifier)
48                 buf += "C-";
49 #if defined(USE_MACOSX_PACKAGING) || defined(USE_META_KEYBINDING)
50         if (mod & MetaModifier)
51                 buf += "M-";
52         if (mod & AltModifier)
53                 buf += "A-";
54 #else
55         if (mod & AltModifier)
56                 buf += "M-";
57 #endif
58         if (mod & ShiftModifier)
59                 buf += "S-";
60
61         buf += s;
62         return buf;
63 }
64
65
66 size_t KeyMap::bind(string const & seq, FuncRequest const & func)
67 {
68         LYXERR(Debug::KBMAP, "BIND: Sequence `" << seq << "' Action `"
69                << func.action() << '\'');
70
71         KeySequence k(0, 0);
72
73         string::size_type const res = k.parse(seq);
74         if (res == string::npos) {
75                 bind(&k, func);
76         } else {
77                 LYXERR(Debug::KBMAP, "Parse error at position " << res
78                                      << " in key sequence '" << seq << "'.");
79         }
80
81         return res == string::npos ? 0 : res;
82 }
83
84
85 size_t KeyMap::unbind(string const & seq, FuncRequest const & func)
86 {
87         KeySequence k(0, 0);
88
89         string::size_type const res = k.parse(seq);
90         if (res == string::npos)
91                 unbind(&k, func);
92         else
93                 LYXERR(Debug::KBMAP, "Parse error at position " << res
94                                      << " in key sequence '" << seq << "'.");
95         return res == string::npos ? 0 : res;
96 }
97
98
99 void KeyMap::bind(KeySequence * seq, FuncRequest const & func, unsigned int r)
100 {
101         KeySymbol code = seq->sequence[r];
102         if (!code.isOK())
103                 return;
104
105         KeyModifier const mod1 = seq->modifiers[r].first;
106         KeyModifier const mod2 = seq->modifiers[r].second;
107
108         // check if key is already there
109         Table::iterator end = table.end();
110         for (Table::iterator it = table.begin(); it != end; ++it) {
111                 if (code == it->code
112                     && mod1 == it->mod.first
113                     && mod2 == it->mod.second) {
114                         // overwrite binding
115                         if (r + 1 == seq->length()) {
116                                 LYXERR(Debug::KBMAP, "Warning: New binding for '"
117                                         << to_utf8(seq->print(KeySequence::Portable))
118                                         << "' is overriding old binding...");
119                                 if (it->prefixes)
120                                         it->prefixes.reset();
121                                 it->func = func;
122                                 it->func.setOrigin(FuncRequest::KEYBOARD);
123                                 return;
124                         } else if (!it->prefixes) {
125                                 lyxerr << "Error: New binding for '"
126                                        << to_utf8(seq->print(KeySequence::Portable))
127                                        << "' is overriding old binding..."
128                                        << endl;
129                                 return;
130                         } else {
131                                 it->prefixes->bind(seq, func, r + 1);
132                                 return;
133                         }
134                 }
135         }
136
137         Table::iterator newone = table.insert(table.end(), Key());
138         newone->code = code;
139         newone->mod = seq->modifiers[r];
140         if (r + 1 == seq->length()) {
141                 newone->func = func;
142                 newone->func.setOrigin(FuncRequest::KEYBOARD);
143                 newone->prefixes.reset();
144         } else {
145                 newone->prefixes.reset(new KeyMap);
146                 newone->prefixes->bind(seq, func, r + 1);
147         }
148 }
149
150
151 void KeyMap::unbind(KeySequence * seq, FuncRequest const & func, unsigned int r)
152 {
153         KeySymbol code = seq->sequence[r];
154         if (!code.isOK())
155                 return;
156
157         KeyModifier const mod1 = seq->modifiers[r].first;
158         KeyModifier const mod2 = seq->modifiers[r].second;
159
160         // check if key is already there
161         Table::iterator end = table.end();
162         Table::iterator remove = end;
163         for (Table::iterator it = table.begin(); it != end; ++it) {
164                 if (code == it->code
165                     && mod1 == it->mod.first
166                     && mod2 == it->mod.second) {
167                         // remove
168                         if (r + 1 == seq->length()) {
169                                 if (it->func == func) {
170                                         remove = it;
171                                         if (it->prefixes)
172                                                 it->prefixes.reset();
173                                 }
174                         } else if (it->prefixes) {
175                                 it->prefixes->unbind(seq, func, r + 1);
176                                 if (it->prefixes->empty())
177                                         remove = it;
178                                 return;
179                         }
180                 }
181         }
182         if (remove != end)
183                 table.erase(remove);
184 }
185
186
187 FuncRequest KeyMap::getBinding(KeySequence const & seq, unsigned int r)
188 {
189         KeySymbol code = seq.sequence[r];
190         if (!code.isOK())
191                 return FuncRequest::unknown;
192
193         KeyModifier const mod1 = seq.modifiers[r].first;
194         KeyModifier const mod2 = seq.modifiers[r].second;
195
196         // check if key is already there
197         Table::iterator end = table.end();
198         for (Table::iterator it = table.begin(); it != end; ++it) {
199                 if (code == it->code
200                     && mod1 == it->mod.first
201                     && mod2 == it->mod.second) {
202                         if (r + 1 == seq.length())
203                                 return it->func;
204                         else if (it->prefixes)
205                                 return it->prefixes->getBinding(seq, r + 1);
206                 }
207         }
208         return FuncRequest::unknown;
209 }
210
211
212 void KeyMap::clear()
213 {
214         table.clear();
215 }
216
217
218 bool KeyMap::read(string const & bind_file, KeyMap * unbind_map, BindReadType rt)
219 {
220         FileName bf = i18nLibFileSearch("bind", bind_file, "bind");
221         if (bf.empty()) {
222                 if (rt == MissingOK)
223                         return true;
224
225                 lyxerr << "Could not find bind file: " << bind_file;
226                 if (rt == Default) {
227                         frontend::Alert::warning(_("Could not find bind file"),
228                                 bformat(_("Unable to find the bind file\n%1$s.\n"
229                                                 "Please check your installation."), from_utf8(bind_file)));
230                         return false;
231                 }
232
233                 static string const defaultBindfile = "cua";
234                 if (bind_file == defaultBindfile) {
235                         frontend::Alert::warning(_("Could not find `cua.bind' file"),
236                                 _("Unable to find the default bind file `cua.bind'.\n"
237                                    "Please check your installation."));
238                         return false;
239                 }
240
241                 // Try it with the default file.
242                 frontend::Alert::warning(_("Could not find bind file"),
243                         bformat(_("Unable to find the bind file\n%1$s.\n"
244                                   "Falling back to default."), from_utf8(bind_file)));
245                 return read(defaultBindfile, unbind_map);
246         }
247         return read(bf, unbind_map);
248 }
249
250
251 bool KeyMap::read(FileName const & bind_file, KeyMap * unbind_map)
252 {
253         ReturnValues retval = readWithoutConv(bind_file, unbind_map);
254         if (retval != FormatMismatch)
255                 return retval == ReadOK;
256
257         LYXERR(Debug::FILES, "Converting bind file to " << LFUN_FORMAT);
258         TempFile tmp("convert_bind");
259         FileName const tempfile = tmp.name();
260         bool const success = prefs2prefs(bind_file, tempfile, true);
261         if (!success) {
262                 LYXERR0 ("Unable to convert " << bind_file <<
263                         " to format " << LFUN_FORMAT);
264                 return false;
265         }
266         retval = readWithoutConv(tempfile, unbind_map);
267         return retval == ReadOK;
268 }
269
270
271 KeyMap::ReturnValues KeyMap::readWithoutConv(FileName const & bind_file, KeyMap * unbind_map)
272 {
273         enum {
274                 BN_BIND,
275                 BN_BINDFILE,
276                 BN_FORMAT,
277                 BN_UNBIND
278         };
279
280         LexerKeyword bindTags[] = {
281                 { "\\bind",      BN_BIND },
282                 { "\\bind_file", BN_BINDFILE },
283                 { "\\unbind",    BN_UNBIND },
284                 { "format",      BN_FORMAT }
285         };
286
287         Lexer lexrc(bindTags);
288         if (lyxerr.debugging(Debug::PARSER))
289                 lexrc.printTable(lyxerr);
290
291         lexrc.setFile(bind_file);
292         if (!lexrc.isOK()) {
293                 LYXERR0("KeyMap::read: cannot open bind file:" << bind_file.absFileName());
294                 return FileError;
295         }
296
297         LYXERR(Debug::KBMAP, "Reading bind file:" << bind_file.absFileName());
298
299         // format of pre-2.0 bind files, before this tag was introduced.
300         unsigned int format = 0;
301         bool error = false;
302         while (lexrc.isOK()) {
303                 switch (lexrc.lex()) {
304
305                 case Lexer::LEX_UNDEF:
306                         lexrc.printError("Unknown tag `$$Token'");
307                         error = true;
308                         continue;
309
310                 case Lexer::LEX_FEOF:
311                         continue;
312
313                 case BN_FORMAT:
314                         if (lexrc.next())
315                                 format = lexrc.getInteger();
316                         break;
317
318                 case BN_BIND: {
319                         if (!lexrc.next()) {
320                                 lexrc.printError("BN_BIND: Missing key sequence");
321                                 error = true;
322                                 break;
323                         }
324                         string seq = lexrc.getString();
325
326                         if (!lexrc.next(true)) {
327                                 lexrc.printError("BN_BIND: missing command");
328                                 error = true;
329                                 break;
330                         }
331                         string cmd = lexrc.getString();
332
333                         FuncRequest func = lyxaction.lookupFunc(cmd);
334                         if (func.action() == LFUN_UNKNOWN_ACTION) {
335                                 lexrc.printError("BN_BIND: Unknown LyX function `$$Token'");
336                                 error = true;
337                                 break;
338                         }
339
340                         bind(seq, func);
341                         break;
342                 }
343
344                 case BN_UNBIND: {
345                         if (!lexrc.next()) {
346                                 lexrc.printError("BN_UNBIND: Missing key sequence");
347                                 error = true;
348                                 break;
349                         }
350                         string seq = lexrc.getString();
351
352                         if (!lexrc.next(true)) {
353                                 lexrc.printError("BN_UNBIND: missing command");
354                                 error = true;
355                                 break;
356                         }
357                         string cmd = lexrc.getString();
358
359                         FuncRequest func = lyxaction.lookupFunc(cmd);
360                         if (func.action() == LFUN_UNKNOWN_ACTION) {
361                                 lexrc.printError("BN_UNBIND: Unknown LyX"
362                                                  " function `$$Token'");
363                                 error = true;
364                                 break;
365                         }
366
367                         if (unbind_map)
368                                 unbind_map->bind(seq, func);
369                         else
370                                 unbind(seq, func);
371                         break;
372                 }
373
374                 case BN_BINDFILE:
375                         if (!lexrc.next()) {
376                                 lexrc.printError("BN_BINDFILE: Missing file name");
377                                 error = true;
378                                 break;
379                         }
380                         string const tmp = lexrc.getString();
381                         error |= !read(tmp, unbind_map);
382                         break;
383                 }
384
385                 // This is triggered the first time through the loop unless
386                 // we hit a format tag.
387                 if (format != LFUN_FORMAT)
388                         return FormatMismatch;
389         }
390
391         if (error) {
392                 LYXERR0("KeyMap::read: error while reading bind file:" << bind_file.absFileName());
393                 return ReadError;
394         }
395         return ReadOK;
396 }
397
398
399 void KeyMap::write(string const & bind_file, bool append, bool unbind) const
400 {
401         ofstream os(bind_file.c_str(),
402                 append ? (ios::app | ios::out) : ios::out);
403
404         if (!append)
405                 os << "## This file is automatically generated by lyx\n"
406                    << "## All modifications will be lost\n\n"
407                    << "Format " << LFUN_FORMAT << "\n\n";
408
409         string tag = unbind ? "\\unbind" : "\\bind";
410         BindingList const list = listBindings(false);
411         BindingList::const_iterator it = list.begin();
412         BindingList::const_iterator it_end = list.end();
413         for (; it != it_end; ++it) {
414                 FuncCode action = it->request.action();
415                 string arg = to_utf8(it->request.argument());
416
417                 string const cmd = lyxaction.getActionName(action)
418                         + (arg.empty() ? string() : " " + arg) ;
419                 os << tag << " \""
420                    << to_utf8(it->sequence.print(KeySequence::BindFile))
421                    << "\" " << Lexer::quoteString(cmd)
422                    << "\n";
423         }
424         os << "\n";
425         os.close();
426 }
427
428
429 FuncRequest const & KeyMap::lookup(KeySymbol const &key,
430                   KeyModifier mod, KeySequence * seq) const
431 {
432         if (table.empty()) {
433                 seq->reset();
434                 return FuncRequest::unknown;
435         }
436
437         Table::const_iterator end = table.end();
438         for (Table::const_iterator cit = table.begin(); cit != end; ++cit) {
439                 KeyModifier mask = cit->mod.second;
440                 KeyModifier check = static_cast<KeyModifier>(mod & ~mask);
441
442                 if (cit->code == key && cit->mod.first == check) {
443                         // match found
444                         if (cit->prefixes) {
445                                 // this is a prefix key - set new map
446                                 seq->curmap = cit->prefixes.get();
447                                 static const FuncRequest prefix(LFUN_COMMAND_PREFIX);
448                                 return prefix;
449                         } else {
450                                 // final key - reset map
451                                 seq->reset();
452                                 return cit->func;
453                         }
454                 }
455         }
456
457         // error - key not found:
458         seq->reset();
459
460         return FuncRequest::unknown;
461 }
462
463
464 docstring const KeyMap::print(bool forgui) const
465 {
466         docstring buf;
467         Table::const_iterator end = table.end();
468         for (Table::const_iterator cit = table.begin(); cit != end; ++cit) {
469                 buf += cit->code.print(cit->mod.first, forgui);
470                 buf += ' ';
471         }
472         return buf;
473 }
474
475
476 docstring KeyMap::printBindings(FuncRequest const & func,
477                                 KeySequence::outputFormat format) const
478 {
479         Bindings bindings = findBindings(func);
480         if (bindings.empty())
481                 return docstring();
482         
483         odocstringstream res;
484         Bindings::const_iterator cit = bindings.begin();
485         Bindings::const_iterator cit_end = bindings.end();
486         // print the first item
487         res << cit->print(format);
488         // more than one shortcuts?
489         for (++cit; cit != cit_end; ++cit)
490                 res << ", " << cit->print(format);
491         return res.str();
492 }
493
494
495 KeyMap::Bindings KeyMap::findBindings(FuncRequest const & func) const
496 {
497         return findBindings(func, KeySequence(0, 0));
498 }
499
500
501 KeyMap::Bindings KeyMap::findBindings(FuncRequest const & func,
502                         KeySequence const & prefix) const
503 {
504         Bindings res;
505         if (table.empty())
506                 return res;
507
508         Table::const_iterator end = table.end();
509         for (Table::const_iterator cit = table.begin(); cit != end; ++cit) {
510                 if (cit->prefixes) {
511                         KeySequence seq = prefix;
512                         seq.addkey(cit->code, cit->mod.first);
513                         Bindings res2 = cit->prefixes->findBindings(func, seq);
514                         res.insert(res.end(), res2.begin(), res2.end());
515                 } else if (cit->func == func) {
516                         KeySequence seq = prefix;
517                         seq.addkey(cit->code, cit->mod.first);
518                         res.push_back(seq);
519                 }
520         }
521
522         return res;
523 }
524
525
526 KeyMap::BindingList KeyMap::listBindings(bool unbound, KeyMap::ItemType tag) const
527 {
528         BindingList list;
529         listBindings(list, KeySequence(0, 0), tag);
530         if (unbound) {
531                 LyXAction::const_iterator fit = lyxaction.func_begin();
532                 LyXAction::const_iterator const fen = lyxaction.func_end();
533                 for (; fit != fen; ++fit) {
534                         FuncCode action = fit->second;
535                         bool has_action = false;
536                         BindingList::const_iterator bit = list.begin();
537                         BindingList::const_iterator const ben = list.end();
538                         for (; bit != ben; ++bit)
539                                 if (bit->request.action() == action) {
540                                         has_action = true;
541                                         break;
542                                 }
543                         if (!has_action)
544                                 list.push_back(Binding(FuncRequest(action), KeySequence(0, 0), tag));
545                 }       
546         }
547         return list;
548 }
549
550
551 void KeyMap::listBindings(BindingList & list,
552         KeySequence const & prefix, KeyMap::ItemType tag) const
553 {
554         Table::const_iterator it = table.begin();
555         Table::const_iterator it_end = table.end();
556         for (; it != it_end; ++it) {
557                 // a LFUN_COMMAND_PREFIX
558                 if (it->prefixes) {
559                         KeySequence seq = prefix;
560                         seq.addkey(it->code, it->mod.first);
561                         it->prefixes->listBindings(list, seq, tag);
562                 } else {
563                         KeySequence seq = prefix;
564                         seq.addkey(it->code, it->mod.first);
565                         list.push_back(Binding(it->func, seq, tag));
566                 }
567         }
568 }
569
570
571 } // namespace lyx