3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Alfredo Braunstein
7 * \author Lars Gullik Bjønnes
8 * \author Jean-Marc Lasgouttes
9 * \author Angus Leeming
11 * \author André Pönitz
14 * \author Martin Vermeer
15 * \author Jürgen Vigna
17 * Full author contact details are available in file CREDITS.
24 #include "LayoutFile.h"
25 #include "BranchList.h"
26 #include "buffer_funcs.h"
28 #include "BufferList.h"
29 #include "BufferParams.h"
30 #include "BufferView.h"
33 #include "Converter.h"
35 #include "CutAndPaste.h"
36 #include "DispatchResult.h"
38 #include "ErrorList.h"
40 #include "FuncRequest.h"
41 #include "FuncStatus.h"
42 #include "InsetIterator.h"
46 #include "LyXAction.h"
51 #include "Paragraph.h"
52 #include "ParagraphParameters.h"
53 #include "ParIterator.h"
56 #include "SpellChecker.h"
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"
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"
79 using namespace lyx::support;
83 using frontend::LyXView;
85 namespace Alert = frontend::Alert;
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)
96 LyXView * lv = theApp()->currentWindow();
98 if (!theSession().bookmarks().isValid(idx))
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)) {
106 dispatch(FuncRequest(LFUN_FILE_OPEN, file));
110 // open may fail, so we need to test it again
111 if (!theBufferList().exists(bm.filename))
114 // bm can be changed when saving
115 BookmarksSection::Bookmark tmp = bm;
117 // Special case idx == 0 used for back-from-back jump navigation
119 dispatch(FuncRequest(LFUN_BOOKMARK_SAVE, "0"));
121 // if the current buffer is not that one, switch to it.
122 if (!lv->documentBufferView()
123 || lv->documentBufferView()->buffer().fileName() != tmp.filename) {
126 dispatch(FuncRequest(LFUN_BUFFER_SWITCH, file));
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))
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();
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);
154 FuncStatus LyXFunc::getStatus(FuncRequest const & cmd) const
156 //lyxerr << "LyXFunc::getStatus: cmd: " << cmd << endl;
159 if (cmd.action == LFUN_NOACTION) {
160 flag.message(from_utf8(N_("Nothing to do")));
161 flag.setEnabled(false);
165 switch (cmd.action) {
166 case LFUN_UNKNOWN_ACTION:
168 flag.setEnabled(false);
175 if (flag.unknown()) {
176 flag.message(from_utf8(N_("Unknown action")));
180 if (!flag.enabled()) {
181 if (flag.message().empty())
182 flag.message(from_utf8(N_("Command disabled")));
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')
191 switch (cmd.action) {
193 // This could be used for the no-GUI version. The GUI version is handled in
194 // LyXView::getStatus(). See above.
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());
204 case LFUN_BOOKMARK_GOTO: {
205 const unsigned int num = convert<unsigned int>(to_utf8(cmd.argument()));
206 enable = theSession().bookmarks().isValid(num);
210 case LFUN_BOOKMARK_CLEAR:
211 enable = theSession().bookmarks().hasValid();
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);
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()) {
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
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);
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
260 case LFUN_DROP_LAYOUTS_CHOICE:
261 case LFUN_CURSOR_FOLLOWS_SCROLLBAR_TOGGLE:
263 case LFUN_PREFERENCES_SAVE:
264 case LFUN_INSET_EDIT:
265 case LFUN_BUFFER_SAVE_AS_DEFAULT:
266 // these are handled in our dispatch()
274 if (theApp()->getStatus(cmd, flag))
277 // Does the view know something?
278 LyXView * lv = theApp()->currentWindow();
283 if (lv->getStatus(cmd, flag))
286 BufferView * bv = lv->currentBufferView();
287 BufferView * doc_bv = lv->documentBufferView();
288 // If we do not have a BufferView, then other functions are disabled
293 // try the BufferView
294 bool decided = bv->getStatus(cmd, flag);
297 decided = bv->buffer().getStatus(cmd, flag);
298 if (!decided && doc_bv)
299 // try the Document Buffer
300 decided = doc_bv->buffer().getStatus(cmd, flag);
304 flag.setEnabled(false);
306 // the default error message if we disable the command
307 if (!flag.enabled() && flag.message().empty())
308 flag.message(from_utf8(N_("Command disabled")));
313 /// send a post-dispatch status message
314 static docstring sendDispatchMessage(docstring const & msg, FuncRequest const & cmd)
316 const bool verbose = (cmd.origin == FuncRequest::MENU
317 || cmd.origin == FuncRequest::TOOLBAR
318 || cmd.origin == FuncRequest::COMMANDBUFFER);
320 if (cmd.action == LFUN_SELF_INSERT || !verbose) {
321 LYXERR(Debug::ACTION, "dispatch msg is " << msg);
325 docstring dispatch_msg = msg;
326 if (!dispatch_msg.empty())
329 docstring comname = from_utf8(lyxaction.getActionName(cmd.action));
331 bool argsadded = false;
333 if (!cmd.argument().empty()) {
334 if (cmd.action != LFUN_UNKNOWN_ACTION) {
335 comname += ' ' + cmd.argument();
339 docstring const shortcuts = theTopLevelKeymap().
340 printBindings(cmd, KeySequence::ForGui);
342 if (!shortcuts.empty())
343 comname += ": " + shortcuts;
344 else if (!argsadded && !cmd.argument().empty())
345 comname += ' ' + cmd.argument();
347 if (!comname.empty()) {
348 comname = rtrim(comname);
349 dispatch_msg += '(' + rtrim(comname) + ')';
351 LYXERR(Debug::ACTION, "verbose dispatch msg " << to_utf8(dispatch_msg));
356 void LyXFunc::dispatch(FuncRequest const & cmd)
358 string const argument = to_utf8(cmd.argument());
359 FuncCode const action = cmd.action;
361 LYXERR(Debug::ACTION, "\nLyXFunc::dispatch: cmd: " << cmd);
362 //lyxerr << "LyXFunc::dispatch: cmd: " << cmd << endl;
364 // we have not done anything wrong yet.
366 dispatch_buffer.erase();
368 // redraw the screen at the end (first of the two drawing steps).
369 //This is done unless explicitely requested otherwise
370 Update::flags updateFlags = Update::FitCursor;
372 LyXView * lv = theApp()->currentWindow();
374 FuncStatus const flag = getStatus(cmd);
375 if (!flag.enabled()) {
376 // We cannot use this function here
377 LYXERR(Debug::ACTION, "LyXFunc::dispatch: "
378 << lyxaction.getActionName(action)
379 << " [" << action << "] is disabled at this location");
380 setErrorMessage(flag.message());
386 case LFUN_CURSOR_FOLLOWS_SCROLLBAR_TOGGLE:
387 lyxrc.cursor_follows_scrollbar = !lyxrc.cursor_follows_scrollbar;
393 string rest = split(argument, countstr, ' ');
394 istringstream is(countstr);
397 //lyxerr << "repeat: count: " << count << " cmd: " << rest << endl;
398 for (int i = 0; i < count; ++i)
399 dispatch(lyxaction.lookupFunc(rest));
403 case LFUN_COMMAND_SEQUENCE: {
404 // argument contains ';'-terminated commands
405 string arg = argument;
406 // FIXME: this LFUN should also work without any view.
407 Buffer * buffer = (lv && lv->documentBufferView())
408 ? &(lv->documentBufferView()->buffer()) : 0;
409 buffer = &lv->currentBufferView()->buffer();
410 if (buffer && !theBufferList().isLoaded(buffer))
413 buffer->undo().beginUndoGroup();
414 while (!arg.empty()) {
416 arg = split(arg, first, ';');
417 FuncRequest func(lyxaction.lookupFunc(first));
418 func.origin = cmd.origin;
422 buffer->undo().endUndoGroup();
426 case LFUN_COMMAND_ALTERNATIVES: {
427 // argument contains ';'-terminated commands
428 string arg = argument;
429 while (!arg.empty()) {
431 arg = split(arg, first, ';');
432 FuncRequest func(lyxaction.lookupFunc(first));
433 func.origin = cmd.origin;
434 FuncStatus stat = getStatus(func);
435 if (stat.enabled()) {
445 if (theTopLevelCmdDef().lock(argument, func)) {
446 func.origin = cmd.origin;
448 theTopLevelCmdDef().release(argument);
450 if (func.action == LFUN_UNKNOWN_ACTION) {
451 // unknown command definition
452 lyxerr << "Warning: unknown command definition `"
456 // recursion detected
457 lyxerr << "Warning: Recursion in the command definition `"
458 << argument << "' detected"
465 case LFUN_PREFERENCES_SAVE: {
466 lyxrc.write(makeAbsPath("preferences",
467 package().user_support().absFilename()),
472 case LFUN_BUFFER_SAVE_AS_DEFAULT: {
474 addName(addPath(package().user_support().absFilename(), "templates/"),
476 Buffer defaults(fname);
478 istringstream ss(argument);
481 int const unknown_tokens = defaults.readHeader(lex);
483 if (unknown_tokens != 0) {
484 lyxerr << "Warning in LFUN_BUFFER_SAVE_AS_DEFAULT!\n"
485 << unknown_tokens << " unknown token"
486 << (unknown_tokens == 1 ? "" : "s")
490 if (defaults.writeFile(FileName(defaults.absFileName())))
491 setMessage(bformat(_("Document defaults saved in %1$s"),
492 makeDisplayPath(fname)));
494 setErrorMessage(from_ascii(N_("Unable to save document defaults")));
498 case LFUN_BOOKMARK_GOTO:
499 // go to bookmark, open unopened file and switch to buffer if necessary
500 gotoBookmark(convert<unsigned int>(to_utf8(cmd.argument())), true, true);
501 updateFlags = Update::FitCursor;
504 case LFUN_BOOKMARK_CLEAR:
505 theSession().bookmarks().clear();
511 LASSERT(theApp(), /**/);
512 // Let the frontend dispatch its own actions.
513 theApp()->dispatch(cmd, dr);
515 // Nothing more to do.
518 // Everything below is only for active window
522 // Let the current LyXView dispatch its own actions.
523 if (lv->dispatch(cmd)) {
524 BufferView * bv = lv->currentBufferView();
526 updateFlags = bv->cursor().result().update();
530 BufferView * bv = lv->currentBufferView();
533 // Let the current BufferView dispatch its own actions.
534 if (bv->dispatch(cmd)) {
535 // The BufferView took care of its own updates if needed.
536 updateFlags = Update::None;
540 BufferView * doc_bv = lv->documentBufferView();
541 // Try with the document BufferView dispatch if any.
542 if (doc_bv && doc_bv->dispatch(cmd)) {
543 updateFlags = Update::None;
547 // OK, so try the current Buffer itself...
548 bv->buffer().dispatch(cmd, dr);
549 if (dr.dispatched()) {
550 updateFlags = dr.update();
553 // and with the document Buffer.
555 doc_bv->buffer().dispatch(cmd, dr);
556 if (dr.dispatched()) {
557 updateFlags = dr.update();
562 // Is this a function that acts on inset at point?
563 Inset * inset = bv->cursor().nextInset();
564 if (lyxaction.funcHasFlag(action, LyXAction::AtPoint)
566 bv->cursor().result().dispatched(true);
567 bv->cursor().result().update(Update::FitCursor | Update::Force);
568 FuncRequest tmpcmd = cmd;
569 inset->dispatch(bv->cursor(), tmpcmd);
570 if (bv->cursor().result().dispatched()) {
571 updateFlags = bv->cursor().result().update();
576 // Let the current Cursor dispatch its own actions.
577 Cursor old = bv->cursor();
578 bv->cursor().getPos(cursorPosBeforeDispatchX_,
579 cursorPosBeforeDispatchY_);
580 bv->cursor().dispatch(cmd);
582 // notify insets we just left
583 if (bv->cursor() != old) {
585 bool badcursor = notifyCursorLeavesOrEnters(old, bv->cursor());
587 bv->cursor().fixIfBroken();
590 // update completion. We do it here and not in
591 // processKeySym to avoid another redraw just for a
592 // changed inline completion
593 if (cmd.origin == FuncRequest::KEYBOARD) {
594 if (cmd.action == LFUN_SELF_INSERT
595 || (cmd.action == LFUN_ERT_INSERT && bv->cursor().inMathed()))
596 lv->updateCompletion(bv->cursor(), true, true);
597 else if (cmd.action == LFUN_CHAR_DELETE_BACKWARD)
598 lv->updateCompletion(bv->cursor(), false, true);
600 lv->updateCompletion(bv->cursor(), false, false);
603 updateFlags = bv->cursor().result().update();
606 // if we executed a mutating lfun, mark the buffer as dirty
607 Buffer * doc_buffer = (lv && lv->documentBufferView())
608 ? &(lv->documentBufferView()->buffer()) : 0;
609 if (doc_buffer && theBufferList().isLoaded(doc_buffer)
611 && !lyxaction.funcHasFlag(action, LyXAction::NoBuffer)
612 && !lyxaction.funcHasFlag(action, LyXAction::ReadOnly))
613 doc_buffer->markDirty();
615 if (lv && lv->currentBufferView()) {
616 // BufferView::update() updates the ViewMetricsInfo and
617 // also initializes the position cache for all insets in
618 // (at least partially) visible top-level paragraphs.
619 // We will redraw the screen only if needed.
620 lv->currentBufferView()->processUpdateFlags(updateFlags);
622 // Do we have a selection?
623 theSelection().haveSelection(
624 lv->currentBufferView()->cursor().selection());
631 // Some messages may already be translated, so we cannot use _()
632 lv->message(sendDispatchMessage(
633 translateIfPossible(getMessage()), cmd));
638 // Each LyXView should have it's own message method. lyxview and
639 // the minibuffer would use the minibuffer, but lyxserver would
640 // send an ERROR signal to its client. Alejandro 970603
641 // This function is bit problematic when it comes to NLS, to make the
642 // lyx servers client be language indepenent we must not translate
643 // strings sent to this func.
644 void LyXFunc::setErrorMessage(docstring const & m) const
651 void LyXFunc::setMessage(docstring const & m) const