]> git.lyx.org Git - lyx.git/blob - src/LyXFunc.cpp
small LFUN status cleanups.
[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://bugzilla.lyx.org/show_bug.cgi?id=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         switch (cmd.action) {
166         case LFUN_UNKNOWN_ACTION:
167                 flag.unknown(true);
168                 flag.setEnabled(false);
169                 break;
170
171         default:
172                 break;
173         }
174
175         if (flag.unknown()) {
176                 flag.message(from_utf8(N_("Unknown action")));
177                 return flag;
178         }
179
180         if (!flag.enabled()) {
181                 if (flag.message().empty())
182                         flag.message(from_utf8(N_("Command disabled")));
183                 return flag;
184         }
185
186         // I would really like to avoid having this switch and rather try to
187         // encode this in the function itself.
188         // -- And I'd rather let an inset decide which LFUNs it is willing
189         // to handle (Andre')
190         bool enable = true;
191         switch (cmd.action) {
192
193         // This could be used for the no-GUI version. The GUI version is handled in
194         // LyXView::getStatus(). See above.
195         /*
196         case LFUN_BUFFER_WRITE:
197         case LFUN_BUFFER_WRITE_AS: {
198                 Buffer * b = theBufferList().getBuffer(FileName(cmd.getArg(0)));
199                 enable = b && (b->isUnnamed() || !b->isClean());
200                 break;
201         }
202         */
203
204         case LFUN_BOOKMARK_GOTO: {
205                 const unsigned int num = convert<unsigned int>(to_utf8(cmd.argument()));
206                 enable = theSession().bookmarks().isValid(num);
207                 break;
208         }
209
210         case LFUN_BOOKMARK_CLEAR:
211                 enable = theSession().bookmarks().hasValid();
212                 break;
213
214         // this one is difficult to get right. As a half-baked
215         // solution, we consider only the first action of the sequence
216         case LFUN_COMMAND_SEQUENCE: {
217                 // argument contains ';'-terminated commands
218                 string const firstcmd = token(to_utf8(cmd.argument()), ';', 0);
219                 FuncRequest func(lyxaction.lookupFunc(firstcmd));
220                 func.origin = cmd.origin;
221                 flag = getStatus(func);
222                 break;
223         }
224
225         // we want to check if at least one of these is enabled
226         case LFUN_COMMAND_ALTERNATIVES: {
227                 // argument contains ';'-terminated commands
228                 string arg = to_utf8(cmd.argument());
229                 while (!arg.empty()) {
230                         string first;
231                         arg = split(arg, first, ';');
232                         FuncRequest func(lyxaction.lookupFunc(first));
233                         func.origin = cmd.origin;
234                         flag = getStatus(func);
235                         // if this one is enabled, the whole thing is
236                         if (flag.enabled())
237                                 break;
238                 }
239                 break;
240         }
241
242         case LFUN_CALL: {
243                 FuncRequest func;
244                 string name = to_utf8(cmd.argument());
245                 if (theTopLevelCmdDef().lock(name, func)) {
246                         func.origin = cmd.origin;
247                         flag = getStatus(func);
248                         theTopLevelCmdDef().release(name);
249                 } else {
250                         // catch recursion or unknown command
251                         // definition. all operations until the
252                         // recursion or unknown command definition
253                         // occurs are performed, so set the state to
254                         // enabled
255                         enable = true;
256                 }
257                 break;
258         }
259
260         case LFUN_CURSOR_FOLLOWS_SCROLLBAR_TOGGLE:
261         case LFUN_REPEAT:
262         case LFUN_PREFERENCES_SAVE:
263         case LFUN_BUFFER_SAVE_AS_DEFAULT:
264                 // these are handled in our dispatch()
265                 break;
266
267         default:
268                 if (!theApp()) {
269                         enable = false;
270                         break;
271                 }
272                 if (theApp()->getStatus(cmd, flag))
273                         break;
274
275                 // Does the view know something?
276                 LyXView * lv = theApp()->currentWindow();
277                 if (!lv) {
278                         enable = false;
279                         break;
280                 }
281                 if (lv->getStatus(cmd, flag))
282                         break;
283
284                 BufferView * bv = lv->currentBufferView();
285                 BufferView * doc_bv = lv->documentBufferView();
286                 // If we do not have a BufferView, then other functions are disabled
287                 if (!bv) {
288                         enable = false;
289                         break;
290                 }
291                 // try the BufferView
292                 bool decided = bv->getStatus(cmd, flag);
293                 if (!decided)
294                         // try the Buffer
295                         decided = bv->buffer().getStatus(cmd, flag);
296                 if (!decided && doc_bv)
297                         // try the Document Buffer
298                         decided = doc_bv->buffer().getStatus(cmd, flag);
299         }
300
301         if (!enable)
302                 flag.setEnabled(false);
303
304         // the default error message if we disable the command
305         if (!flag.enabled() && flag.message().empty())
306                 flag.message(from_utf8(N_("Command disabled")));
307
308         return flag;
309 }
310
311 /// send a post-dispatch status message
312 static docstring sendDispatchMessage(docstring const & msg, FuncRequest const & cmd)
313 {
314         const bool verbose = (cmd.origin == FuncRequest::MENU
315                               || cmd.origin == FuncRequest::TOOLBAR
316                               || cmd.origin == FuncRequest::COMMANDBUFFER);
317
318         if (cmd.action == LFUN_SELF_INSERT || !verbose) {
319                 LYXERR(Debug::ACTION, "dispatch msg is " << msg);
320                 return msg;
321         }
322
323         docstring dispatch_msg = msg;
324         if (!dispatch_msg.empty())
325                 dispatch_msg += ' ';
326
327         docstring comname = from_utf8(lyxaction.getActionName(cmd.action));
328
329         bool argsadded = false;
330
331         if (!cmd.argument().empty()) {
332                 if (cmd.action != LFUN_UNKNOWN_ACTION) {
333                         comname += ' ' + cmd.argument();
334                         argsadded = true;
335                 }
336         }
337         docstring const shortcuts = theTopLevelKeymap().
338                 printBindings(cmd, KeySequence::ForGui);
339
340         if (!shortcuts.empty())
341                 comname += ": " + shortcuts;
342         else if (!argsadded && !cmd.argument().empty())
343                 comname += ' ' + cmd.argument();
344
345         if (!comname.empty()) {
346                 comname = rtrim(comname);
347                 dispatch_msg += '(' + rtrim(comname) + ')';
348         }
349         LYXERR(Debug::ACTION, "verbose dispatch msg " << to_utf8(dispatch_msg));
350         return dispatch_msg;
351 }
352
353
354 void LyXFunc::dispatch(FuncRequest const & cmd)
355 {
356         string const argument = to_utf8(cmd.argument());
357         FuncCode const action = cmd.action;
358
359         LYXERR(Debug::ACTION, "\nLyXFunc::dispatch: cmd: " << cmd);
360         //lyxerr << "LyXFunc::dispatch: cmd: " << cmd << endl;
361
362         // we have not done anything wrong yet.
363         errorstat = false;
364         dispatch_buffer.erase();
365
366         // redraw the screen at the end (first of the two drawing steps).
367         //This is done unless explicitely requested otherwise
368         Update::flags updateFlags = Update::FitCursor;
369
370         LyXView * lv = theApp()->currentWindow();
371
372         FuncStatus const flag = getStatus(cmd);
373         if (!flag.enabled()) {
374                 // We cannot use this function here
375                 LYXERR(Debug::ACTION, "LyXFunc::dispatch: "
376                        << lyxaction.getActionName(action)
377                        << " [" << action << "] is disabled at this location");
378                 setErrorMessage(flag.message());
379                 if (lv)
380                         lv->restartCursor();
381         } else {
382                 switch (action) {
383
384                 case LFUN_CURSOR_FOLLOWS_SCROLLBAR_TOGGLE:
385                         lyxrc.cursor_follows_scrollbar = !lyxrc.cursor_follows_scrollbar;
386                         break;
387
388                 case LFUN_REPEAT: {
389                         // repeat command
390                         string countstr;
391                         string rest = split(argument, countstr, ' ');
392                         istringstream is(countstr);
393                         int count = 0;
394                         is >> count;
395                         //lyxerr << "repeat: count: " << count << " cmd: " << rest << endl;
396                         for (int i = 0; i < count; ++i)
397                                 dispatch(lyxaction.lookupFunc(rest));
398                         break;
399                 }
400
401                 case LFUN_COMMAND_SEQUENCE: {
402                         // argument contains ';'-terminated commands
403                         string arg = argument;
404                         // FIXME: this LFUN should also work without any view.
405                         Buffer * buffer = (lv && lv->documentBufferView())
406                                 ? &(lv->documentBufferView()->buffer()) : 0;
407                         buffer = &lv->currentBufferView()->buffer();
408                         if (buffer && !theBufferList().isLoaded(buffer))
409                                 buffer = 0;
410                         if (buffer)
411                                 buffer->undo().beginUndoGroup();
412                         while (!arg.empty()) {
413                                 string first;
414                                 arg = split(arg, first, ';');
415                                 FuncRequest func(lyxaction.lookupFunc(first));
416                                 func.origin = cmd.origin;
417                                 dispatch(func);
418                         }
419                         if (buffer)
420                                 buffer->undo().endUndoGroup();
421                         break;
422                 }
423
424                 case LFUN_COMMAND_ALTERNATIVES: {
425                         // argument contains ';'-terminated commands
426                         string arg = argument;
427                         while (!arg.empty()) {
428                                 string first;
429                                 arg = split(arg, first, ';');
430                                 FuncRequest func(lyxaction.lookupFunc(first));
431                                 func.origin = cmd.origin;
432                                 FuncStatus stat = getStatus(func);
433                                 if (stat.enabled()) {
434                                         dispatch(func);
435                                         break;
436                                 }
437                         }
438                         break;
439                 }
440
441                 case LFUN_CALL: {
442                         FuncRequest func;
443                         if (theTopLevelCmdDef().lock(argument, func)) {
444                                 func.origin = cmd.origin;
445                                 dispatch(func);
446                                 theTopLevelCmdDef().release(argument);
447                         } else {
448                                 if (func.action == LFUN_UNKNOWN_ACTION) {
449                                         // unknown command definition
450                                         lyxerr << "Warning: unknown command definition `"
451                                                    << argument << "'"
452                                                    << endl;
453                                 } else {
454                                         // recursion detected
455                                         lyxerr << "Warning: Recursion in the command definition `"
456                                                    << argument << "' detected"
457                                                    << endl;
458                                 }
459                         }
460                         break;
461                 }
462
463                 case LFUN_PREFERENCES_SAVE: {
464                         lyxrc.write(makeAbsPath("preferences",
465                                                 package().user_support().absFilename()),
466                                     false);
467                         break;
468                 }
469
470                 case LFUN_BUFFER_SAVE_AS_DEFAULT: {
471                         string const fname =
472                                 addName(addPath(package().user_support().absFilename(), "templates/"),
473                                         "defaults.lyx");
474                         Buffer defaults(fname);
475
476                         istringstream ss(argument);
477                         Lexer lex;
478                         lex.setStream(ss);
479                         int const unknown_tokens = defaults.readHeader(lex);
480
481                         if (unknown_tokens != 0) {
482                                 lyxerr << "Warning in LFUN_BUFFER_SAVE_AS_DEFAULT!\n"
483                                        << unknown_tokens << " unknown token"
484                                        << (unknown_tokens == 1 ? "" : "s")
485                                        << endl;
486                         }
487
488                         if (defaults.writeFile(FileName(defaults.absFileName())))
489                                 setMessage(bformat(_("Document defaults saved in %1$s"),
490                                                    makeDisplayPath(fname)));
491                         else
492                                 setErrorMessage(from_ascii(N_("Unable to save document defaults")));
493                         break;
494                 }
495
496                 case LFUN_BOOKMARK_GOTO:
497                         // go to bookmark, open unopened file and switch to buffer if necessary
498                         gotoBookmark(convert<unsigned int>(to_utf8(cmd.argument())), true, true);
499                         updateFlags = Update::FitCursor;
500                         break;
501
502                 case LFUN_BOOKMARK_CLEAR:
503                         theSession().bookmarks().clear();
504                         break;
505
506                 default:
507                         DispatchResult dr;
508
509                         LASSERT(theApp(), /**/);
510                         // Let the frontend dispatch its own actions.
511                         theApp()->dispatch(cmd, dr);
512                         if (dr.dispatched())
513                                 // Nothing more to do.
514                                 break;
515
516                         // Everything below is only for active window
517                         if (lv == 0)
518                                 break;
519
520                         // Let the current LyXView dispatch its own actions.
521                         if (lv->dispatch(cmd)) {
522                                 BufferView * bv = lv->currentBufferView();
523                                 if (bv)
524                                         updateFlags = bv->cursor().result().update();
525                                 break;
526                         }
527
528                         BufferView * bv = lv->currentBufferView();
529                         LASSERT(bv, /**/);
530
531                         // Let the current BufferView dispatch its own actions.
532                         if (bv->dispatch(cmd)) {
533                                 // The BufferView took care of its own updates if needed.
534                                 updateFlags = Update::None;
535                                 break;
536                         }
537
538                         BufferView * doc_bv = lv->documentBufferView();
539                         // Try with the document BufferView dispatch if any.
540                         if (doc_bv && doc_bv->dispatch(cmd)) {
541                                 updateFlags = Update::None;
542                                 break;
543                         }
544
545                         // OK, so try the current Buffer itself...
546                         bv->buffer().dispatch(cmd, dr);
547                         if (dr.dispatched()) {
548                                 updateFlags = dr.update();
549                                 break;
550                         }
551                         // and with the document Buffer.
552                         if (doc_bv) {
553                                 doc_bv->buffer().dispatch(cmd, dr);
554                                 if (dr.dispatched()) {
555                                         updateFlags = dr.update();
556                                         break;
557                                 }
558                         }
559
560                         // Is this a function that acts on inset at point?
561                         Inset * inset = bv->cursor().nextInset();
562                         if (lyxaction.funcHasFlag(action, LyXAction::AtPoint)
563                             && inset) {
564                                 bv->cursor().result().dispatched(true);
565                                 bv->cursor().result().update(Update::FitCursor | Update::Force);
566                                 FuncRequest tmpcmd = cmd;
567                                 inset->dispatch(bv->cursor(), tmpcmd);
568                                 if (bv->cursor().result().dispatched()) {
569                                         updateFlags = bv->cursor().result().update();
570                                         break;
571                                 }
572                         }
573
574                         // Let the current Cursor dispatch its own actions.
575                         Cursor old = bv->cursor();
576                         bv->cursor().getPos(cursorPosBeforeDispatchX_,
577                                                 cursorPosBeforeDispatchY_);
578                         bv->cursor().dispatch(cmd);
579
580                         // notify insets we just left
581                         if (bv->cursor() != old) {
582                                 old.fixIfBroken();
583                                 bool badcursor = notifyCursorLeavesOrEnters(old, bv->cursor());
584                                 if (badcursor)
585                                         bv->cursor().fixIfBroken();
586                         }
587
588                         // update completion. We do it here and not in
589                         // processKeySym to avoid another redraw just for a
590                         // changed inline completion
591                         if (cmd.origin == FuncRequest::KEYBOARD) {
592                                 if (cmd.action == LFUN_SELF_INSERT
593                                     || (cmd.action == LFUN_ERT_INSERT && bv->cursor().inMathed()))
594                                         lv->updateCompletion(bv->cursor(), true, true);
595                                 else if (cmd.action == LFUN_CHAR_DELETE_BACKWARD)
596                                         lv->updateCompletion(bv->cursor(), false, true);
597                                 else
598                                         lv->updateCompletion(bv->cursor(), false, false);
599                         }
600
601                         updateFlags = bv->cursor().result().update();
602                 }
603
604                 // if we executed a mutating lfun, mark the buffer as dirty
605                 Buffer * doc_buffer = (lv && lv->documentBufferView())
606                         ? &(lv->documentBufferView()->buffer()) : 0;
607                 if (doc_buffer && theBufferList().isLoaded(doc_buffer)
608                         && flag.enabled()
609                     && !lyxaction.funcHasFlag(action, LyXAction::NoBuffer)
610                     && !lyxaction.funcHasFlag(action, LyXAction::ReadOnly))
611                         doc_buffer->markDirty();                        
612
613                 if (lv && lv->currentBufferView()) {
614                         // BufferView::update() updates the ViewMetricsInfo and
615                         // also initializes the position cache for all insets in
616                         // (at least partially) visible top-level paragraphs.
617                         // We will redraw the screen only if needed.
618                         lv->currentBufferView()->processUpdateFlags(updateFlags);
619
620                         // Do we have a selection?
621                         theSelection().haveSelection(
622                                 lv->currentBufferView()->cursor().selection());
623                         
624                         // update gui
625                         lv->restartCursor();
626                 }
627         }
628         if (lv) {
629                 // Some messages may already be translated, so we cannot use _()
630                 lv->message(sendDispatchMessage(
631                         translateIfPossible(getMessage()), cmd));
632         }
633 }
634
635
636 // Each LyXView should have it's own message method. lyxview and
637 // the minibuffer would use the minibuffer, but lyxserver would
638 // send an ERROR signal to its client.  Alejandro 970603
639 // This function is bit problematic when it comes to NLS, to make the
640 // lyx servers client be language indepenent we must not translate
641 // strings sent to this func.
642 void LyXFunc::setErrorMessage(docstring const & m) const
643 {
644         dispatch_buffer = m;
645         errorstat = true;
646 }
647
648
649 void LyXFunc::setMessage(docstring const & m) const
650 {
651         dispatch_buffer = m;
652 }
653
654
655 } // namespace lyx