]> git.lyx.org Git - lyx.git/blob - src/KeyMap.cpp
Update RELEASE-NOTES
[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 string const KeyMap::printKeySym(KeySymbol const & key, KeyModifier mod)
41 {
42         string buf;
43
44         string const s = key.getSymbolName();
45
46         if (mod & ControlModifier)
47                 buf += "C-";
48 #if defined(USE_MACOSX_PACKAGING) || defined(USE_META_KEYBINDING)
49         if (mod & MetaModifier)
50                 buf += "M-";
51         if (mod & AltModifier)
52                 buf += "A-";
53 #else
54         if (mod & AltModifier)
55                 buf += "M-";
56 #endif
57         if (mod & ShiftModifier)
58                 buf += "S-";
59
60         buf += s;
61         return buf;
62 }
63
64
65 size_t KeyMap::bind(string const & seq, FuncRequest const & func)
66 {
67         LYXERR(Debug::KBMAP, "BIND: Sequence `" << seq << "' Action `"
68                << func.action() << '\'');
69
70         KeySequence k(0, 0);
71
72         string::size_type const res = k.parse(seq);
73         if (res == string::npos) {
74                 bind(&k, func);
75         } else {
76                 LYXERR(Debug::KBMAP, "Parse error at position " << res
77                                      << " in key sequence '" << seq << "'.");
78         }
79
80         return res == string::npos ? 0 : res;
81 }
82
83
84 size_t KeyMap::unbind(string const & seq, FuncRequest const & func)
85 {
86         KeySequence k(0, 0);
87
88         string::size_type const res = k.parse(seq);
89         if (res == string::npos)
90                 unbind(&k, func);
91         else
92                 LYXERR(Debug::KBMAP, "Parse error at position " << res
93                                      << " in key sequence '" << seq << "'.");
94         return res == string::npos ? 0 : res;
95 }
96
97
98 void KeyMap::bind(KeySequence * seq, FuncRequest const & func, unsigned int r)
99 {
100         KeySymbol code = seq->sequence[r];
101         if (!code.isOK())
102                 return;
103
104         KeyModifier const mod1 = seq->modifiers[r].first;
105         KeyModifier const mod2 = seq->modifiers[r].second;
106
107         // check if key is already there
108         Table::iterator end = table.end();
109         for (Table::iterator it = table.begin(); it != end; ++it) {
110                 if (code == it->code
111                     && mod1 == it->mod.first
112                     && mod2 == it->mod.second) {
113                         // overwrite binding
114                         if (r + 1 == seq->length()) {
115                                 LYXERR(Debug::KBMAP, "Warning: New binding for '"
116                                         << to_utf8(seq->print(KeySequence::Portable))
117                                         << "' is overriding old binding...");
118                                 if (it->prefixes)
119                                         it->prefixes.reset();
120                                 it->func = func;
121                                 it->func.setOrigin(FuncRequest::KEYBOARD);
122                                 return;
123                         } else if (!it->prefixes) {
124                                 lyxerr << "Error: New binding for '"
125                                        << to_utf8(seq->print(KeySequence::Portable))
126                                        << "' is overriding old binding..."
127                                        << endl;
128                                 return;
129                         } else {
130                                 it->prefixes->bind(seq, func, r + 1);
131                                 return;
132                         }
133                 }
134         }
135
136         Table::iterator newone = table.insert(table.end(), Key());
137         newone->code = code;
138         newone->mod = seq->modifiers[r];
139         if (r + 1 == seq->length()) {
140                 newone->func = func;
141                 newone->func.setOrigin(FuncRequest::KEYBOARD);
142                 newone->prefixes.reset();
143         } else {
144                 newone->prefixes.reset(new KeyMap);
145                 newone->prefixes->bind(seq, func, r + 1);
146         }
147 }
148
149
150 void KeyMap::unbind(KeySequence * seq, FuncRequest const & func, unsigned int r)
151 {
152         KeySymbol code = seq->sequence[r];
153         if (!code.isOK())
154                 return;
155
156         KeyModifier const mod1 = seq->modifiers[r].first;
157         KeyModifier const mod2 = seq->modifiers[r].second;
158
159         // check if key is already there
160         Table::iterator end = table.end();
161         Table::iterator remove = end;
162         for (Table::iterator it = table.begin(); it != end; ++it) {
163                 if (code == it->code
164                     && mod1 == it->mod.first
165                     && mod2 == it->mod.second) {
166                         // remove
167                         if (r + 1 == seq->length()) {
168                                 if (it->func == func) {
169                                         remove = it;
170                                         if (it->prefixes)
171                                                 it->prefixes.reset();
172                                 }
173                         } else if (it->prefixes) {
174                                 it->prefixes->unbind(seq, func, r + 1);
175                                 if (it->prefixes->empty())
176                                         remove = it;
177                                 return;
178                         }
179                 }
180         }
181         if (remove != end)
182                 table.erase(remove);
183 }
184
185
186 FuncRequest KeyMap::getBinding(KeySequence const & seq, unsigned int r)
187 {
188         KeySymbol code = seq.sequence[r];
189         if (!code.isOK())
190                 return FuncRequest::unknown;
191
192         KeyModifier const mod1 = seq.modifiers[r].first;
193         KeyModifier const mod2 = seq.modifiers[r].second;
194
195         // check if key is already there
196         Table::iterator end = table.end();
197         for (Table::iterator it = table.begin(); it != end; ++it) {
198                 if (code == it->code
199                     && mod1 == it->mod.first
200                     && mod2 == it->mod.second) {
201                         if (r + 1 == seq.length())
202                                 return it->func;
203                         else if (it->prefixes)
204                                 return it->prefixes->getBinding(seq, r + 1);
205                 }
206         }
207         return FuncRequest::unknown;
208 }
209
210
211 void KeyMap::clear()
212 {
213         table.clear();
214 }
215
216
217 bool KeyMap::read(string const & bind_file, KeyMap * unbind_map, BindReadType rt)
218 {
219         FileName bf = i18nLibFileSearch("bind", bind_file, "bind");
220         if (bf.empty()) {
221                 if (rt == MissingOK)
222                         return true;
223
224                 lyxerr << "Could not find bind file: " << bind_file;
225                 if (rt == Default) {
226                         frontend::Alert::warning(_("Could not find bind file"),
227                                 bformat(_("Unable to find the bind file\n%1$s.\n"
228                                                 "Please check your installation."), from_utf8(bind_file)));
229                         return false;
230                 }
231
232                 static string const defaultBindfile = "cua";
233                 if (bind_file == defaultBindfile) {
234                         frontend::Alert::warning(_("Could not find `cua.bind' file"),
235                                 _("Unable to find the default bind file `cua.bind'.\n"
236                                    "Please check your installation."));
237                         return false;
238                 }
239
240                 // Try it with the default file.
241                 frontend::Alert::warning(_("Could not find bind file"),
242                         bformat(_("Unable to find the bind file\n%1$s.\n"
243                                   "Falling back to default."), from_utf8(bind_file)));
244                 return read(defaultBindfile, unbind_map);
245         }
246         return read(bf, unbind_map);
247 }
248
249
250 bool KeyMap::read(FileName const & bind_file, KeyMap * unbind_map)
251 {
252         ReturnValues retval = readWithoutConv(bind_file, unbind_map);
253         if (retval != FormatMismatch)
254                 return retval == ReadOK;
255
256         LYXERR(Debug::FILES, "Converting bind file to " << LFUN_FORMAT);
257         FileName const tempfile = FileName::tempName("convert_bind");
258         bool const success = prefs2prefs(bind_file, tempfile, true);
259         if (!success) {
260                 LYXERR0 ("Unable to convert " << bind_file <<
261                         " to format " << LFUN_FORMAT);
262                 return false;
263         }
264         retval = readWithoutConv(tempfile, unbind_map);
265         tempfile.removeFile();
266         return retval == ReadOK;
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 != LFUN_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 " << LFUN_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) {
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) {
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) {
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