]> git.lyx.org Git - lyx.git/blob - src/LyXFunc.cpp
Fix bug #3325: Labels with special characters in equations do not work
[lyx.git] / src / LyXFunc.cpp
1 /**
2  * \file LyXFunc.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Alfredo Braunstein
7  * \author Lars Gullik Bjønnes
8  * \author Jean-Marc Lasgouttes
9  * \author Angus Leeming
10  * \author John Levon
11  * \author André Pönitz
12  * \author Allan Rae
13  * \author Dekel Tsur
14  * \author Martin Vermeer
15  * \author Jürgen Vigna
16  *
17  * Full author contact details are available in file CREDITS.
18  */
19
20 #include <config.h>
21
22 #include "LyXFunc.h"
23
24 #include "LayoutFile.h"
25 #include "BranchList.h"
26 #include "buffer_funcs.h"
27 #include "Buffer.h"
28 #include "BufferList.h"
29 #include "BufferParams.h"
30 #include "BufferView.h"
31 #include "CmdDef.h"
32 #include "Color.h"
33 #include "Converter.h"
34 #include "Cursor.h"
35 #include "CutAndPaste.h"
36 #include "DispatchResult.h"
37 #include "Encoding.h"
38 #include "ErrorList.h"
39 #include "Format.h"
40 #include "FuncRequest.h"
41 #include "FuncStatus.h"
42 #include "InsetIterator.h"
43 #include "KeyMap.h"
44 #include "Language.h"
45 #include "Lexer.h"
46 #include "LyXAction.h"
47 #include "lyxfind.h"
48 #include "LyX.h"
49 #include "LyXRC.h"
50 #include "LyXVC.h"
51 #include "Paragraph.h"
52 #include "ParagraphParameters.h"
53 #include "ParIterator.h"
54 #include "Row.h"
55 #include "Session.h"
56 #include "SpellChecker.h"
57
58 #include "frontends/alert.h"
59 #include "frontends/Application.h"
60 #include "frontends/KeySymbol.h"
61 #include "frontends/LyXView.h"
62 #include "frontends/Selection.h"
63
64 #include "support/debug.h"
65 #include "support/environment.h"
66 #include "support/FileName.h"
67 #include "support/filetools.h"
68 #include "support/gettext.h"
69 #include "support/lassert.h"
70 #include "support/lstrings.h"
71 #include "support/Package.h"
72 #include "support/convert.h"
73 #include "support/os.h"
74
75 #include <sstream>
76 #include <vector>
77
78 using namespace std;
79 using namespace lyx::support;
80
81 namespace lyx {
82
83 using frontend::LyXView;
84
85 namespace Alert = frontend::Alert;
86
87 LyXFunc::LyXFunc()
88 {
89 }
90
91
92 //FIXME: bookmark handling is a frontend issue. This code should be transferred
93 // to GuiView and be GuiView and be window dependent.
94 void LyXFunc::gotoBookmark(unsigned int idx, bool openFile, bool switchToBuffer)
95 {
96         LyXView * lv = theApp()->currentWindow();
97         LASSERT(lv, /**/);
98         if (!theSession().bookmarks().isValid(idx))
99                 return;
100         BookmarksSection::Bookmark const & bm = theSession().bookmarks().bookmark(idx);
101         LASSERT(!bm.filename.empty(), /**/);
102         string const file = bm.filename.absFilename();
103         // if the file is not opened, open it.
104         if (!theBufferList().exists(bm.filename)) {
105                 if (openFile)
106                         dispatch(FuncRequest(LFUN_FILE_OPEN, file));
107                 else
108                         return;
109         }
110         // open may fail, so we need to test it again
111         if (!theBufferList().exists(bm.filename))
112                 return;
113
114         // bm can be changed when saving
115         BookmarksSection::Bookmark tmp = bm;
116
117         // Special case idx == 0 used for back-from-back jump navigation
118         if (idx == 0)
119                 dispatch(FuncRequest(LFUN_BOOKMARK_SAVE, "0"));
120
121         // if the current buffer is not that one, switch to it.
122         if (!lv->documentBufferView()
123                 || lv->documentBufferView()->buffer().fileName() != tmp.filename) {
124                 if (!switchToBuffer)
125                         return;
126                 dispatch(FuncRequest(LFUN_BUFFER_SWITCH, file));
127         }
128
129         // moveToPosition try paragraph id first and then paragraph (pit, pos).
130         if (!lv->documentBufferView()->moveToPosition(
131                 tmp.bottom_pit, tmp.bottom_pos, tmp.top_id, tmp.top_pos))
132                 return;
133
134         // bm changed
135         if (idx == 0)
136                 return;
137
138         // Cursor jump succeeded!
139         Cursor const & cur = lv->documentBufferView()->cursor();
140         pit_type new_pit = cur.pit();
141         pos_type new_pos = cur.pos();
142         int new_id = cur.paragraph().id();
143
144         // if bottom_pit, bottom_pos or top_id has been changed, update bookmark
145         // see http://www.lyx.org/trac/ticket/3092
146         if (bm.bottom_pit != new_pit || bm.bottom_pos != new_pos 
147                 || bm.top_id != new_id) {
148                 const_cast<BookmarksSection::Bookmark &>(bm).updatePos(
149                         new_pit, new_pos, new_id);
150         }
151 }
152
153
154 FuncStatus LyXFunc::getStatus(FuncRequest const & cmd) const
155 {
156         //lyxerr << "LyXFunc::getStatus: cmd: " << cmd << endl;
157         FuncStatus flag;
158
159         if (cmd.action == LFUN_NOACTION) {
160                 flag.message(from_utf8(N_("Nothing to do")));
161                 flag.setEnabled(false);
162                 return flag;
163         }
164
165         if (cmd.action == LFUN_UNKNOWN_ACTION) {
166                 flag.unknown(true);
167                 flag.setEnabled(false);
168                 flag.message(from_utf8(N_("Unknown action")));
169                 return flag;
170         }
171
172         // I would really like to avoid having this switch and rather try to
173         // encode this in the function itself.
174         // -- And I'd rather let an inset decide which LFUNs it is willing
175         // to handle (Andre')
176         bool enable = true;
177         switch (cmd.action) {
178
179         // This could be used for the no-GUI version. The GUI version is handled in
180         // LyXView::getStatus(). See above.
181         /*
182         case LFUN_BUFFER_WRITE:
183         case LFUN_BUFFER_WRITE_AS: {
184                 Buffer * b = theBufferList().getBuffer(FileName(cmd.getArg(0)));
185                 enable = b && (b->isUnnamed() || !b->isClean());
186                 break;
187         }
188         */
189
190         case LFUN_BOOKMARK_GOTO: {
191                 const unsigned int num = convert<unsigned int>(to_utf8(cmd.argument()));
192                 enable = theSession().bookmarks().isValid(num);
193                 break;
194         }
195
196         case LFUN_BOOKMARK_CLEAR:
197                 enable = theSession().bookmarks().hasValid();
198                 break;
199
200         // this one is difficult to get right. As a half-baked
201         // solution, we consider only the first action of the sequence
202         case LFUN_COMMAND_SEQUENCE: {
203                 // argument contains ';'-terminated commands
204                 string const firstcmd = token(to_utf8(cmd.argument()), ';', 0);
205                 FuncRequest func(lyxaction.lookupFunc(firstcmd));
206                 func.origin = cmd.origin;
207                 flag = getStatus(func);
208                 break;
209         }
210
211         // we want to check if at least one of these is enabled
212         case LFUN_COMMAND_ALTERNATIVES: {
213                 // argument contains ';'-terminated commands
214                 string arg = to_utf8(cmd.argument());
215                 while (!arg.empty()) {
216                         string first;
217                         arg = split(arg, first, ';');
218                         FuncRequest func(lyxaction.lookupFunc(first));
219                         func.origin = cmd.origin;
220                         flag = getStatus(func);
221                         // if this one is enabled, the whole thing is
222                         if (flag.enabled())
223                                 break;
224                 }
225                 break;
226         }
227
228         case LFUN_CALL: {
229                 FuncRequest func;
230                 string name = to_utf8(cmd.argument());
231                 if (theTopLevelCmdDef().lock(name, func)) {
232                         func.origin = cmd.origin;
233                         flag = getStatus(func);
234                         theTopLevelCmdDef().release(name);
235                 } else {
236                         // catch recursion or unknown command
237                         // definition. all operations until the
238                         // recursion or unknown command definition
239                         // occurs are performed, so set the state to
240                         // enabled
241                         enable = true;
242                 }
243                 break;
244         }
245
246         case LFUN_CURSOR_FOLLOWS_SCROLLBAR_TOGGLE:
247         case LFUN_REPEAT:
248         case LFUN_PREFERENCES_SAVE:
249         case LFUN_BUFFER_SAVE_AS_DEFAULT:
250         case LFUN_DEBUG_LEVEL_SET:
251                 // these are handled in our dispatch()
252                 break;
253
254         default:
255                 if (!theApp()) {
256                         enable = false;
257                         break;
258                 }
259                 if (theApp()->getStatus(cmd, flag))
260                         break;
261
262                 // Does the view know something?
263                 LyXView * lv = theApp()->currentWindow();
264                 if (!lv) {
265                         enable = false;
266                         break;
267                 }
268                 if (lv->getStatus(cmd, flag))
269                         break;
270
271                 BufferView * bv = lv->currentBufferView();
272                 BufferView * doc_bv = lv->documentBufferView();
273                 // If we do not have a BufferView, then other functions are disabled
274                 if (!bv) {
275                         enable = false;
276                         break;
277                 }
278                 // try the BufferView
279                 bool decided = bv->getStatus(cmd, flag);
280                 if (!decided)
281                         // try the Buffer
282                         decided = bv->buffer().getStatus(cmd, flag);
283                 if (!decided && doc_bv)
284                         // try the Document Buffer
285                         decided = doc_bv->buffer().getStatus(cmd, flag);
286         }
287
288         if (!enable)
289                 flag.setEnabled(false);
290
291         // the default error message if we disable the command
292         if (!flag.enabled() && flag.message().empty())
293                 flag.message(from_utf8(N_("Command disabled")));
294
295         return flag;
296 }
297
298 /// make a post-dispatch status message
299 static docstring makeDispatchMessage(docstring const & msg, 
300                                      FuncRequest const & cmd)
301 {
302         const bool verbose = (cmd.origin == FuncRequest::MENU
303                               || cmd.origin == FuncRequest::TOOLBAR
304                               || cmd.origin == FuncRequest::COMMANDBUFFER);
305
306         if (cmd.action == LFUN_SELF_INSERT || !verbose) {
307                 LYXERR(Debug::ACTION, "dispatch msg is " << msg);
308                 return msg;
309         }
310
311         docstring dispatch_msg = msg;
312         if (!dispatch_msg.empty())
313                 dispatch_msg += ' ';
314
315         docstring comname = from_utf8(lyxaction.getActionName(cmd.action));
316
317         bool argsadded = false;
318
319         if (!cmd.argument().empty()) {
320                 if (cmd.action != LFUN_UNKNOWN_ACTION) {
321                         comname += ' ' + cmd.argument();
322                         argsadded = true;
323                 }
324         }
325         docstring const shortcuts = theTopLevelKeymap().
326                 printBindings(cmd, KeySequence::ForGui);
327
328         if (!shortcuts.empty())
329                 comname += ": " + shortcuts;
330         else if (!argsadded && !cmd.argument().empty())
331                 comname += ' ' + cmd.argument();
332
333         if (!comname.empty()) {
334                 comname = rtrim(comname);
335                 dispatch_msg += '(' + rtrim(comname) + ')';
336         }
337         LYXERR(Debug::ACTION, "verbose dispatch msg " << to_utf8(dispatch_msg));
338         return dispatch_msg;
339 }
340
341
342 void LyXFunc::dispatch(FuncRequest const & cmd)
343 {
344         DispatchResult dr;
345         // redraw the screen at the end (first of the two drawing steps).
346         //This is done unless explicitly requested otherwise
347         dr.update(Update::FitCursor);
348         dispatch(cmd, dr);
349
350         LyXView * lv = theApp()->currentWindow();
351         if (lv && lv->currentBufferView()) {
352                 // BufferView::update() updates the ViewMetricsInfo and
353                 // also initializes the position cache for all insets in
354                 // (at least partially) visible top-level paragraphs.
355                 // We will redraw the screen only if needed.
356                 lv->currentBufferView()->processUpdateFlags(dr.update());
357                 
358                 // Do we have a selection?
359                 theSelection().haveSelection(
360                         lv->currentBufferView()->cursor().selection());
361                 
362                 // update gui
363                         lv->restartCursor();
364         }
365 }
366
367
368 void LyXFunc::dispatch(FuncRequest const & cmd, DispatchResult & dr)
369 {
370         string const argument = to_utf8(cmd.argument());
371         FuncCode const action = cmd.action;
372
373         LYXERR(Debug::ACTION, "\nLyXFunc::dispatch: cmd: " << cmd);
374
375         // we have not done anything wrong yet.
376         dr.setError(false);
377
378         LyXView * lv = theApp()->currentWindow();
379
380         FuncStatus const flag = getStatus(cmd);
381         if (!flag.enabled()) {
382                 // We cannot use this function here
383                 LYXERR(Debug::ACTION, "LyXFunc::dispatch: "
384                        << lyxaction.getActionName(action)
385                        << " [" << action << "] is disabled at this location");
386                 if (lv)
387                         lv->restartCursor();
388                 dr.setMessage(flag.message());
389                 dr.setError(true);
390                 dr.dispatched(false);
391                 dr.update(Update::None);
392         } else {
393                 switch (action) {
394
395                 case LFUN_CURSOR_FOLLOWS_SCROLLBAR_TOGGLE:
396                         lyxrc.cursor_follows_scrollbar = !lyxrc.cursor_follows_scrollbar;
397                         break;
398
399                 case LFUN_REPEAT: {
400                         // repeat command
401                         string countstr;
402                         string rest = split(argument, countstr, ' ');
403                         istringstream is(countstr);
404                         int count = 0;
405                         is >> count;
406                         //lyxerr << "repeat: count: " << count << " cmd: " << rest << endl;
407                         for (int i = 0; i < count; ++i)
408                                 dispatch(lyxaction.lookupFunc(rest));
409                         break;
410                 }
411
412                 case LFUN_COMMAND_SEQUENCE: {
413                         // argument contains ';'-terminated commands
414                         string arg = argument;
415                         // FIXME: this LFUN should also work without any view.
416                         Buffer * buffer = (lv && lv->documentBufferView())
417                                 ? &(lv->documentBufferView()->buffer()) : 0;
418                         if (buffer)
419                                 buffer->undo().beginUndoGroup();
420                         while (!arg.empty()) {
421                                 string first;
422                                 arg = split(arg, first, ';');
423                                 FuncRequest func(lyxaction.lookupFunc(first));
424                                 func.origin = cmd.origin;
425                                 dispatch(func);
426                         }
427                         // the buffer may have been closed by one action
428                         if (theBufferList().isLoaded(buffer))
429                                 buffer->undo().endUndoGroup();
430                         break;
431                 }
432
433                 case LFUN_COMMAND_ALTERNATIVES: {
434                         // argument contains ';'-terminated commands
435                         string arg = argument;
436                         while (!arg.empty()) {
437                                 string first;
438                                 arg = split(arg, first, ';');
439                                 FuncRequest func(lyxaction.lookupFunc(first));
440                                 func.origin = cmd.origin;
441                                 FuncStatus stat = getStatus(func);
442                                 if (stat.enabled()) {
443                                         dispatch(func);
444                                         break;
445                                 }
446                         }
447                         break;
448                 }
449
450                 case LFUN_CALL: {
451                         FuncRequest func;
452                         if (theTopLevelCmdDef().lock(argument, func)) {
453                                 func.origin = cmd.origin;
454                                 dispatch(func);
455                                 theTopLevelCmdDef().release(argument);
456                         } else {
457                                 if (func.action == LFUN_UNKNOWN_ACTION) {
458                                         // unknown command definition
459                                         lyxerr << "Warning: unknown command definition `"
460                                                    << argument << "'"
461                                                    << endl;
462                                 } else {
463                                         // recursion detected
464                                         lyxerr << "Warning: Recursion in the command definition `"
465                                                    << argument << "' detected"
466                                                    << endl;
467                                 }
468                         }
469                         break;
470                 }
471
472                 case LFUN_PREFERENCES_SAVE: {
473                         lyxrc.write(makeAbsPath("preferences",
474                                                 package().user_support().absFilename()),
475                                     false);
476                         break;
477                 }
478
479                 case LFUN_BUFFER_SAVE_AS_DEFAULT: {
480                         string const fname =
481                                 addName(addPath(package().user_support().absFilename(), "templates/"),
482                                         "defaults.lyx");
483                         Buffer defaults(fname);
484
485                         istringstream ss(argument);
486                         Lexer lex;
487                         lex.setStream(ss);
488                         int const unknown_tokens = defaults.readHeader(lex);
489
490                         if (unknown_tokens != 0) {
491                                 lyxerr << "Warning in LFUN_BUFFER_SAVE_AS_DEFAULT!\n"
492                                        << unknown_tokens << " unknown token"
493                                        << (unknown_tokens == 1 ? "" : "s")
494                                        << endl;
495                         }
496
497                         if (defaults.writeFile(FileName(defaults.absFileName())))
498                                 dr.setMessage(bformat(_("Document defaults saved in %1$s"),
499                                                    makeDisplayPath(fname)));
500                         else {
501                                 dr.setError(true);
502                                 dr.setMessage(from_ascii(N_("Unable to save document defaults")));
503                         }
504                         break;
505                 }
506
507                 case LFUN_BOOKMARK_GOTO:
508                         // go to bookmark, open unopened file and switch to buffer if necessary
509                         gotoBookmark(convert<unsigned int>(to_utf8(cmd.argument())), true, true);
510                         dr.update(Update::FitCursor);
511                         break;
512
513                 case LFUN_BOOKMARK_CLEAR:
514                         theSession().bookmarks().clear();
515                         break;
516
517                 case LFUN_DEBUG_LEVEL_SET:
518                         lyxerr.setLevel(Debug::value(to_utf8(cmd.argument())));
519                         break;
520
521                 default:
522                         LASSERT(theApp(), /**/);
523                         // Let the frontend dispatch its own actions.
524                         theApp()->dispatch(cmd, dr);
525                         if (dr.dispatched())
526                                 // Nothing more to do.
527                                 break;
528
529                         // Everything below is only for active window
530                         if (lv == 0)
531                                 break;
532
533                         // Let the current LyXView dispatch its own actions.
534                         lv->dispatch(cmd, dr);
535                         if (dr.dispatched())
536                                 break;
537
538                         BufferView * bv = lv->currentBufferView();
539                         LASSERT(bv, /**/);
540
541                         // Let the current BufferView dispatch its own actions.
542                         bv->dispatch(cmd, dr);
543                         if (dr.dispatched())
544                                 break;
545
546                         BufferView * doc_bv = lv->documentBufferView();
547                         // Try with the document BufferView dispatch if any.
548                         if (doc_bv) {
549                                 doc_bv->dispatch(cmd, dr);
550                                 if (dr.dispatched())
551                                         break;
552                         }
553
554                         // OK, so try the current Buffer itself...
555                         bv->buffer().dispatch(cmd, dr);
556                         if (dr.dispatched())
557                                 break;
558
559                         // and with the document Buffer.
560                         if (doc_bv) {
561                                 doc_bv->buffer().dispatch(cmd, dr);
562                                 if (dr.dispatched())
563                                         break;
564                         }
565
566                         // Let the current Cursor dispatch its own actions.
567                         Cursor old = bv->cursor();
568                         bv->cursor().getPos(cursorPosBeforeDispatchX_,
569                                                 cursorPosBeforeDispatchY_);
570                         bv->cursor().dispatch(cmd);
571
572                         // notify insets we just left
573                         if (bv->cursor() != old) {
574                                 old.fixIfBroken();
575                                 bool badcursor = notifyCursorLeavesOrEnters(old, bv->cursor());
576                                 if (badcursor)
577                                         bv->cursor().fixIfBroken();
578                         }
579
580                         // update completion. We do it here and not in
581                         // processKeySym to avoid another redraw just for a
582                         // changed inline completion
583                         if (cmd.origin == FuncRequest::KEYBOARD) {
584                                 if (cmd.action == LFUN_SELF_INSERT
585                                     || (cmd.action == LFUN_ERT_INSERT && bv->cursor().inMathed()))
586                                         lv->updateCompletion(bv->cursor(), true, true);
587                                 else if (cmd.action == LFUN_CHAR_DELETE_BACKWARD)
588                                         lv->updateCompletion(bv->cursor(), false, true);
589                                 else
590                                         lv->updateCompletion(bv->cursor(), false, false);
591                         }
592
593                         dr = bv->cursor().result();
594                 }
595
596                 // if we executed a mutating lfun, mark the buffer as dirty
597                 Buffer * doc_buffer = (lv && lv->documentBufferView())
598                         ? &(lv->documentBufferView()->buffer()) : 0;
599                 if (doc_buffer && theBufferList().isLoaded(doc_buffer)
600                         && flag.enabled()
601                     && !lyxaction.funcHasFlag(action, LyXAction::NoBuffer)
602                     && !lyxaction.funcHasFlag(action, LyXAction::ReadOnly))
603                         lv->currentBufferView()->buffer().markDirty();                  
604         }
605         if (lv) {
606                 // Some messages may already be translated, so we cannot use _()
607                 lv->message(makeDispatchMessage(translateIfPossible(dr.message()), cmd));
608         }
609 }
610
611 } // namespace lyx