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