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 if (cmd
.action
== LFUN_UNKNOWN_ACTION
) {
167 flag
.setEnabled(false);
168 flag
.message(from_utf8(N_("Unknown action")));
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')
177 switch (cmd
.action
) {
179 // This could be used for the no-GUI version. The GUI version is handled in
180 // LyXView::getStatus(). See above.
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());
190 case LFUN_BOOKMARK_GOTO
: {
191 const unsigned int num
= convert
<unsigned int>(to_utf8(cmd
.argument()));
192 enable
= theSession().bookmarks().isValid(num
);
196 case LFUN_BOOKMARK_CLEAR
:
197 enable
= theSession().bookmarks().hasValid();
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
);
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()) {
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
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
);
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
246 case LFUN_CURSOR_FOLLOWS_SCROLLBAR_TOGGLE
:
248 case LFUN_PREFERENCES_SAVE
:
249 case LFUN_BUFFER_SAVE_AS_DEFAULT
:
250 // these are handled in our dispatch()
258 if (theApp()->getStatus(cmd
, flag
))
261 // Does the view know something?
262 LyXView
* lv
= theApp()->currentWindow();
267 if (lv
->getStatus(cmd
, flag
))
270 BufferView
* bv
= lv
->currentBufferView();
271 BufferView
* doc_bv
= lv
->documentBufferView();
272 // If we do not have a BufferView, then other functions are disabled
277 // try the BufferView
278 bool decided
= bv
->getStatus(cmd
, flag
);
281 decided
= bv
->buffer().getStatus(cmd
, flag
);
282 if (!decided
&& doc_bv
)
283 // try the Document Buffer
284 decided
= doc_bv
->buffer().getStatus(cmd
, flag
);
288 flag
.setEnabled(false);
290 // the default error message if we disable the command
291 if (!flag
.enabled() && flag
.message().empty())
292 flag
.message(from_utf8(N_("Command disabled")));
297 /// send a post-dispatch status message
298 static docstring
sendDispatchMessage(docstring
const & msg
, FuncRequest
const & cmd
)
300 const bool verbose
= (cmd
.origin
== FuncRequest::MENU
301 || cmd
.origin
== FuncRequest::TOOLBAR
302 || cmd
.origin
== FuncRequest::COMMANDBUFFER
);
304 if (cmd
.action
== LFUN_SELF_INSERT
|| !verbose
) {
305 LYXERR(Debug::ACTION
, "dispatch msg is " << msg
);
309 docstring dispatch_msg
= msg
;
310 if (!dispatch_msg
.empty())
313 docstring comname
= from_utf8(lyxaction
.getActionName(cmd
.action
));
315 bool argsadded
= false;
317 if (!cmd
.argument().empty()) {
318 if (cmd
.action
!= LFUN_UNKNOWN_ACTION
) {
319 comname
+= ' ' + cmd
.argument();
323 docstring
const shortcuts
= theTopLevelKeymap().
324 printBindings(cmd
, KeySequence::ForGui
);
326 if (!shortcuts
.empty())
327 comname
+= ": " + shortcuts
;
328 else if (!argsadded
&& !cmd
.argument().empty())
329 comname
+= ' ' + cmd
.argument();
331 if (!comname
.empty()) {
332 comname
= rtrim(comname
);
333 dispatch_msg
+= '(' + rtrim(comname
) + ')';
335 LYXERR(Debug::ACTION
, "verbose dispatch msg " << to_utf8(dispatch_msg
));
340 void LyXFunc::dispatch(FuncRequest
const & cmd
)
342 string
const argument
= to_utf8(cmd
.argument());
343 FuncCode
const action
= cmd
.action
;
345 LYXERR(Debug::ACTION
, "\nLyXFunc::dispatch: cmd: " << cmd
);
346 //lyxerr << "LyXFunc::dispatch: cmd: " << cmd << endl;
348 // we have not done anything wrong yet.
350 dispatch_buffer
.erase();
352 // redraw the screen at the end (first of the two drawing steps).
353 //This is done unless explicitely requested otherwise
354 Update::flags updateFlags
= Update::FitCursor
;
356 LyXView
* lv
= theApp()->currentWindow();
358 FuncStatus
const flag
= getStatus(cmd
);
359 if (!flag
.enabled()) {
360 // We cannot use this function here
361 LYXERR(Debug::ACTION
, "LyXFunc::dispatch: "
362 << lyxaction
.getActionName(action
)
363 << " [" << action
<< "] is disabled at this location");
364 setErrorMessage(flag
.message());
370 case LFUN_CURSOR_FOLLOWS_SCROLLBAR_TOGGLE
:
371 lyxrc
.cursor_follows_scrollbar
= !lyxrc
.cursor_follows_scrollbar
;
377 string rest
= split(argument
, countstr
, ' ');
378 istringstream
is(countstr
);
381 //lyxerr << "repeat: count: " << count << " cmd: " << rest << endl;
382 for (int i
= 0; i
< count
; ++i
)
383 dispatch(lyxaction
.lookupFunc(rest
));
387 case LFUN_COMMAND_SEQUENCE
: {
388 // argument contains ';'-terminated commands
389 string arg
= argument
;
390 // FIXME: this LFUN should also work without any view.
391 Buffer
* buffer
= (lv
&& lv
->documentBufferView())
392 ? &(lv
->documentBufferView()->buffer()) : 0;
394 buffer
->undo().beginUndoGroup();
395 while (!arg
.empty()) {
397 arg
= split(arg
, first
, ';');
398 FuncRequest
func(lyxaction
.lookupFunc(first
));
399 func
.origin
= cmd
.origin
;
402 // the buffer may have been closed by one action
403 if (theBufferList().isLoaded(buffer
))
404 buffer
->undo().endUndoGroup();
408 case LFUN_COMMAND_ALTERNATIVES
: {
409 // argument contains ';'-terminated commands
410 string arg
= argument
;
411 while (!arg
.empty()) {
413 arg
= split(arg
, first
, ';');
414 FuncRequest
func(lyxaction
.lookupFunc(first
));
415 func
.origin
= cmd
.origin
;
416 FuncStatus stat
= getStatus(func
);
417 if (stat
.enabled()) {
427 if (theTopLevelCmdDef().lock(argument
, func
)) {
428 func
.origin
= cmd
.origin
;
430 theTopLevelCmdDef().release(argument
);
432 if (func
.action
== LFUN_UNKNOWN_ACTION
) {
433 // unknown command definition
434 lyxerr
<< "Warning: unknown command definition `"
438 // recursion detected
439 lyxerr
<< "Warning: Recursion in the command definition `"
440 << argument
<< "' detected"
447 case LFUN_PREFERENCES_SAVE
: {
448 lyxrc
.write(makeAbsPath("preferences",
449 package().user_support().absFilename()),
454 case LFUN_BUFFER_SAVE_AS_DEFAULT
: {
456 addName(addPath(package().user_support().absFilename(), "templates/"),
458 Buffer
defaults(fname
);
460 istringstream
ss(argument
);
463 int const unknown_tokens
= defaults
.readHeader(lex
);
465 if (unknown_tokens
!= 0) {
466 lyxerr
<< "Warning in LFUN_BUFFER_SAVE_AS_DEFAULT!\n"
467 << unknown_tokens
<< " unknown token"
468 << (unknown_tokens
== 1 ? "" : "s")
472 if (defaults
.writeFile(FileName(defaults
.absFileName())))
473 setMessage(bformat(_("Document defaults saved in %1$s"),
474 makeDisplayPath(fname
)));
476 setErrorMessage(from_ascii(N_("Unable to save document defaults")));
480 case LFUN_BOOKMARK_GOTO
:
481 // go to bookmark, open unopened file and switch to buffer if necessary
482 gotoBookmark(convert
<unsigned int>(to_utf8(cmd
.argument())), true, true);
483 updateFlags
= Update::FitCursor
;
486 case LFUN_BOOKMARK_CLEAR
:
487 theSession().bookmarks().clear();
493 LASSERT(theApp(), /**/);
494 // Let the frontend dispatch its own actions.
495 theApp()->dispatch(cmd
, dr
);
497 // Nothing more to do.
500 // Everything below is only for active window
504 // Let the current LyXView dispatch its own actions.
505 if (lv
->dispatch(cmd
)) {
506 BufferView
* bv
= lv
->currentBufferView();
508 updateFlags
= bv
->cursor().result().update();
512 BufferView
* bv
= lv
->currentBufferView();
515 // Let the current BufferView dispatch its own actions.
516 if (bv
->dispatch(cmd
)) {
517 // The BufferView took care of its own updates if needed.
518 updateFlags
= Update::None
;
522 BufferView
* doc_bv
= lv
->documentBufferView();
523 // Try with the document BufferView dispatch if any.
524 if (doc_bv
&& doc_bv
->dispatch(cmd
)) {
525 updateFlags
= Update::None
;
529 // OK, so try the current Buffer itself...
530 bv
->buffer().dispatch(cmd
, dr
);
531 if (dr
.dispatched()) {
532 updateFlags
= dr
.update();
535 // and with the document Buffer.
537 doc_bv
->buffer().dispatch(cmd
, dr
);
538 if (dr
.dispatched()) {
539 updateFlags
= dr
.update();
544 // Is this a function that acts on inset at point?
545 Inset
* inset
= bv
->cursor().nextInset();
546 if (lyxaction
.funcHasFlag(action
, LyXAction::AtPoint
)
548 bv
->cursor().result().dispatched(true);
549 bv
->cursor().result().update(Update::FitCursor
| Update::Force
);
550 FuncRequest tmpcmd
= cmd
;
551 inset
->dispatch(bv
->cursor(), tmpcmd
);
552 if (bv
->cursor().result().dispatched()) {
553 updateFlags
= bv
->cursor().result().update();
558 // Let the current Cursor dispatch its own actions.
559 Cursor old
= bv
->cursor();
560 bv
->cursor().getPos(cursorPosBeforeDispatchX_
,
561 cursorPosBeforeDispatchY_
);
562 bv
->cursor().dispatch(cmd
);
564 // notify insets we just left
565 if (bv
->cursor() != old
) {
567 bool badcursor
= notifyCursorLeavesOrEnters(old
, bv
->cursor());
569 bv
->cursor().fixIfBroken();
572 // update completion. We do it here and not in
573 // processKeySym to avoid another redraw just for a
574 // changed inline completion
575 if (cmd
.origin
== FuncRequest::KEYBOARD
) {
576 if (cmd
.action
== LFUN_SELF_INSERT
577 || (cmd
.action
== LFUN_ERT_INSERT
&& bv
->cursor().inMathed()))
578 lv
->updateCompletion(bv
->cursor(), true, true);
579 else if (cmd
.action
== LFUN_CHAR_DELETE_BACKWARD
)
580 lv
->updateCompletion(bv
->cursor(), false, true);
582 lv
->updateCompletion(bv
->cursor(), false, false);
585 updateFlags
= bv
->cursor().result().update();
588 // if we executed a mutating lfun, mark the buffer as dirty
589 Buffer
* doc_buffer
= (lv
&& lv
->documentBufferView())
590 ? &(lv
->documentBufferView()->buffer()) : 0;
591 if (doc_buffer
&& theBufferList().isLoaded(doc_buffer
)
593 && !lyxaction
.funcHasFlag(action
, LyXAction::NoBuffer
)
594 && !lyxaction
.funcHasFlag(action
, LyXAction::ReadOnly
))
595 doc_buffer
->markDirty();
597 if (lv
&& lv
->currentBufferView()) {
598 // BufferView::update() updates the ViewMetricsInfo and
599 // also initializes the position cache for all insets in
600 // (at least partially) visible top-level paragraphs.
601 // We will redraw the screen only if needed.
602 lv
->currentBufferView()->processUpdateFlags(updateFlags
);
604 // Do we have a selection?
605 theSelection().haveSelection(
606 lv
->currentBufferView()->cursor().selection());
613 // Some messages may already be translated, so we cannot use _()
614 lv
->message(sendDispatchMessage(
615 translateIfPossible(getMessage()), cmd
));
620 // Each LyXView should have it's own message method. lyxview and
621 // the minibuffer would use the minibuffer, but lyxserver would
622 // send an ERROR signal to its client. Alejandro 970603
623 // This function is bit problematic when it comes to NLS, to make the
624 // lyx servers client be language indepenent we must not translate
625 // strings sent to this func.
626 void LyXFunc::setErrorMessage(docstring
const & m
) const
633 void LyXFunc::setMessage(docstring
const & m
) const