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.
25 #include "BranchList.h"
27 #include "buffer_funcs.h"
28 #include "BufferList.h"
29 #include "BufferParams.h"
30 #include "BufferView.h"
31 #include "bufferview_funcs.h"
33 #include "CutAndPaste.h"
35 #include "DispatchResult.h"
37 #include "ErrorList.h"
40 #include "FuncRequest.h"
41 #include "FuncStatus.h"
44 #include "InsetIterator.h"
52 #include "LyXAction.h"
58 #include "TextClassList.h"
60 #include "Paragraph.h"
61 #include "ParIterator.h"
62 #include "ParagraphParameters.h"
65 #include "insets/InsetBox.h"
66 #include "insets/InsetBranch.h"
67 #include "insets/InsetCommand.h"
68 #include "insets/InsetERT.h"
69 #include "insets/InsetExternal.h"
70 #include "insets/InsetFloat.h"
71 #include "insets/InsetListings.h"
72 #include "insets/InsetGraphics.h"
73 #include "insets/InsetInclude.h"
74 #include "insets/InsetNote.h"
75 #include "insets/InsetTabular.h"
76 #include "insets/InsetVSpace.h"
77 #include "insets/InsetWrap.h"
79 #include "frontends/Application.h"
80 #include "frontends/alert.h"
81 #include "frontends/Dialogs.h"
82 #include "frontends/FileDialog.h"
83 #include "frontends/FontLoader.h"
84 #include "frontends/Gui.h"
85 #include "frontends/KeySymbol.h"
86 #include "frontends/LyXView.h"
87 #include "frontends/Menubar.h"
88 #include "frontends/Toolbars.h"
90 #include "support/environment.h"
91 #include "support/FileFilterList.h"
92 #include "support/filetools.h"
93 #include "support/ForkedcallsController.h"
94 #include "support/fs_extras.h"
95 #include "support/lstrings.h"
96 #include "support/Path.h"
97 #include "support/Package.h"
98 #include "support/Systemcall.h"
99 #include "support/convert.h"
100 #include "support/os.h"
102 #include <boost/current_function.hpp>
103 #include <boost/filesystem/operations.hpp>
110 using bv_funcs::freefont2string;
112 using support::absolutePath;
113 using support::addName;
114 using support::addPath;
115 using support::bformat;
116 using support::changeExtension;
117 using support::contains;
118 using support::FileFilterList;
119 using support::FileName;
120 using support::fileSearch;
121 using support::ForkedcallsController;
122 using support::i18nLibFileSearch;
123 using support::isDirWriteable;
124 using support::isFileReadable;
125 using support::isStrInt;
126 using support::makeAbsPath;
127 using support::makeDisplayPath;
128 using support::package;
129 using support::quoteName;
130 using support::rtrim;
131 using support::split;
132 using support::subst;
133 using support::Systemcall;
134 using support::token;
136 using support::prefixIs;
139 using std::make_pair;
142 using std::istringstream;
143 using std::ostringstream;
145 namespace Alert = frontend::Alert;
146 namespace fs = boost::filesystem;
151 bool getLocalStatus(Cursor cursor,
152 FuncRequest const & cmd, FuncStatus & status)
154 // Try to fix cursor in case it is broken.
155 cursor.fixIfBroken();
157 // This is, of course, a mess. Better create a new doc iterator and use
158 // this in Inset::getStatus. This might require an additional
159 // BufferView * arg, though (which should be avoided)
160 //Cursor safe = *this;
162 for ( ; cursor.depth(); cursor.pop()) {
163 //lyxerr << "\nCursor::getStatus: cmd: " << cmd << endl << *this << endl;
164 BOOST_ASSERT(cursor.idx() <= cursor.lastidx());
165 BOOST_ASSERT(cursor.pit() <= cursor.lastpit());
166 BOOST_ASSERT(cursor.pos() <= cursor.lastpos());
168 // The inset's getStatus() will return 'true' if it made
169 // a definitive decision on whether it want to handle the
170 // request or not. The result of this decision is put into
171 // the 'status' parameter.
172 if (cursor.inset().getStatus(cursor, cmd, status)) {
181 /** Return the change status at cursor position, taking in account the
182 * status at each level of the document iterator (a table in a deleted
183 * footnote is deleted).
184 * When \param outer is true, the top slice is not looked at.
186 Change::Type lookupChangeType(DocIterator const & dit, bool outer = false)
188 size_t const depth = dit.depth() - (outer ? 1 : 0);
190 for (size_t i = 0 ; i < depth ; ++i) {
191 CursorSlice const & slice = dit[i];
192 if (!slice.inset().inMathed()
193 && slice.pos() < slice.paragraph().size()) {
194 Change::Type const ch = slice.paragraph().lookupChange(slice.pos()).type;
195 if (ch != Change::UNCHANGED)
199 return Change::UNCHANGED;
207 meta_fake_bit(key_modifier::none)
212 void LyXFunc::initKeySequences(KeyMap * kb)
214 keyseq.reset(new KeySequence(kb, kb));
215 cancel_meta_seq.reset(new KeySequence(kb, kb));
219 void LyXFunc::setLyXView(LyXView * lv)
225 void LyXFunc::handleKeyFunc(kb_action action)
227 char_type c = encoded_last_key;
229 if (keyseq->length())
232 lyx_view_->view()->getIntl().getTransManager().deadkey(
233 c, get_accent(action).accent, view()->cursor().innerText(), view()->cursor());
234 // Need to clear, in case the minibuffer calls these
237 // copied verbatim from do_accent_char
238 view()->cursor().resetAnchor();
243 void LyXFunc::gotoBookmark(unsigned int idx, bool openFile, bool switchToBuffer)
245 BOOST_ASSERT(lyx_view_);
246 if (!LyX::ref().session().bookmarks().isValid(idx))
248 BookmarksSection::Bookmark const & bm = LyX::ref().session().bookmarks().bookmark(idx);
249 BOOST_ASSERT(!bm.filename.empty());
250 string const file = bm.filename.absFilename();
251 // if the file is not opened, open it.
252 if (!theBufferList().exists(file)) {
254 dispatch(FuncRequest(LFUN_FILE_OPEN, file));
258 // open may fail, so we need to test it again
259 if (theBufferList().exists(file)) {
260 // if the current buffer is not that one, switch to it.
261 if (lyx_view_->buffer()->fileName() != file) {
263 dispatch(FuncRequest(LFUN_BUFFER_SWITCH, file));
267 // moveToPosition use par_id, and par_pit and return new par_id.
271 boost::tie(new_pit, new_pos, new_id) = view()->moveToPosition(bm.bottom_pit, bm.bottom_pos, bm.top_id, bm.top_pos);
272 // if bottom_pit, bottom_pos or top_id has been changed, update bookmark
273 // see http://bugzilla.lyx.org/show_bug.cgi?id=3092
274 if (bm.bottom_pit != new_pit || bm.bottom_pos != new_pos || bm.top_id != new_id )
275 const_cast<BookmarksSection::Bookmark &>(bm).updatePos(new_pit, new_pos, new_id);
280 void LyXFunc::processKeySym(KeySymbolPtr keysym, key_modifier::state state)
282 LYXERR(Debug::KEY) << "KeySym is " << keysym->getSymbolName() << endl;
284 // Do nothing if we have nothing (JMarc)
285 if (!keysym->isOK()) {
286 LYXERR(Debug::KEY) << "Empty kbd action (probably composing)"
291 if (keysym->isModifier()) {
292 LYXERR(Debug::KEY) << "isModifier true" << endl;
296 //Encoding const * encoding = view()->cursor().getEncoding();
297 //encoded_last_key = keysym->getISOEncoded(encoding ? encoding->name() : "");
298 // FIXME: encoded_last_key shadows the member variable of the same
299 // name. Is that intended?
300 char_type encoded_last_key = keysym->getUCSEncoded();
302 // Do a one-deep top-level lookup for
303 // cancel and meta-fake keys. RVDK_PATCH_5
304 cancel_meta_seq->reset();
306 FuncRequest func = cancel_meta_seq->addkey(keysym, state);
307 LYXERR(Debug::KEY) << BOOST_CURRENT_FUNCTION
308 << " action first set to [" << func.action << ']'
311 // When not cancel or meta-fake, do the normal lookup.
312 // Note how the meta_fake Mod1 bit is OR-ed in and reset afterwards.
313 // Mostly, meta_fake_bit = key_modifier::none. RVDK_PATCH_5.
314 if ((func.action != LFUN_CANCEL) && (func.action != LFUN_META_PREFIX)) {
315 // remove Caps Lock and Mod2 as a modifiers
316 func = keyseq->addkey(keysym, (state | meta_fake_bit));
317 LYXERR(Debug::KEY) << BOOST_CURRENT_FUNCTION
318 << "action now set to ["
319 << func.action << ']' << endl;
322 // Dont remove this unless you know what you are doing.
323 meta_fake_bit = key_modifier::none;
325 // Can this happen now ?
326 if (func.action == LFUN_NOACTION) {
327 func = FuncRequest(LFUN_COMMAND_PREFIX);
330 LYXERR(Debug::KEY) << BOOST_CURRENT_FUNCTION
332 << func.action << "]["
333 << to_utf8(keyseq->print(false)) << ']'
336 // already here we know if it any point in going further
337 // why not return already here if action == -1 and
338 // num_bytes == 0? (Lgb)
340 if (keyseq->length() > 1) {
341 lyx_view_->message(keyseq->print(true));
345 // Maybe user can only reach the key via holding down shift.
346 // Let's see. But only if shift is the only modifier
347 if (func.action == LFUN_UNKNOWN_ACTION &&
348 state == key_modifier::shift) {
349 LYXERR(Debug::KEY) << "Trying without shift" << endl;
350 func = keyseq->addkey(keysym, key_modifier::none);
351 LYXERR(Debug::KEY) << "Action now " << func.action << endl;
354 if (func.action == LFUN_UNKNOWN_ACTION) {
355 // Hmm, we didn't match any of the keysequences. See
356 // if it's normal insertable text not already covered
358 if (keysym->isText() && keyseq->length() == 1) {
359 LYXERR(Debug::KEY) << "isText() is true, inserting." << endl;
360 func = FuncRequest(LFUN_SELF_INSERT,
361 FuncRequest::KEYBOARD);
363 LYXERR(Debug::KEY) << "Unknown, !isText() - giving up" << endl;
364 lyx_view_->message(_("Unknown function."));
369 if (func.action == LFUN_SELF_INSERT) {
370 if (encoded_last_key != 0) {
371 docstring const arg(1, encoded_last_key);
372 dispatch(FuncRequest(LFUN_SELF_INSERT, arg,
373 FuncRequest::KEYBOARD));
375 << "SelfInsert arg[`" << to_utf8(arg) << "']" << endl;
383 FuncStatus LyXFunc::getStatus(FuncRequest const & cmd) const
385 //lyxerr << "LyXFunc::getStatus: cmd: " << cmd << endl;
388 Cursor & cur = view()->cursor();
390 /* In LyX/Mac, when a dialog is open, the menus of the
391 application can still be accessed without giving focus to
392 the main window. In this case, we want to disable the menu
393 entries that are buffer-related.
395 Note that this code is not perfect, as bug 1941 attests:
396 http://bugzilla.lyx.org/show_bug.cgi?id=1941#c4
398 Buffer * buf = lyx_view_? lyx_view_->buffer() : 0;
399 if (lyx_view_ && cmd.origin == FuncRequest::MENU && !lyx_view_->hasFocus())
402 if (cmd.action == LFUN_NOACTION) {
403 flag.message(from_utf8(N_("Nothing to do")));
408 switch (cmd.action) {
409 case LFUN_UNKNOWN_ACTION:
410 #ifndef HAVE_LIBAIKSAURUS
411 case LFUN_THESAURUS_ENTRY:
421 if (flag.unknown()) {
422 flag.message(from_utf8(N_("Unknown action")));
426 if (!flag.enabled()) {
427 if (flag.message().empty())
428 flag.message(from_utf8(N_("Command disabled")));
432 // Check whether we need a buffer
433 if (!lyxaction.funcHasFlag(cmd.action, LyXAction::NoBuffer) && !buf) {
435 flag.message(from_utf8(N_("Command not allowed with"
436 "out any document open")));
441 // I would really like to avoid having this switch and rather try to
442 // encode this in the function itself.
443 // -- And I'd rather let an inset decide which LFUNs it is willing
444 // to handle (Andre')
446 switch (cmd.action) {
447 case LFUN_BUFFER_TOGGLE_READ_ONLY:
448 flag.setOnOff(buf->isReadonly());
451 case LFUN_BUFFER_SWITCH:
452 // toggle on the current buffer, but do not toggle off
453 // the other ones (is that a good idea?)
454 if (to_utf8(cmd.argument()) == buf->fileName())
458 case LFUN_BUFFER_EXPORT:
459 enable = cmd.argument() == "custom"
460 || Exporter::isExportable(*buf, to_utf8(cmd.argument()));
463 case LFUN_BUFFER_CHKTEX:
464 enable = buf->isLatex() && !lyxrc.chktex_command.empty();
467 case LFUN_BUILD_PROGRAM:
468 enable = Exporter::isExportable(*buf, "program");
471 case LFUN_LAYOUT_TABULAR:
472 enable = cur.innerInsetOfType(Inset::TABULAR_CODE);
476 case LFUN_LAYOUT_PARAGRAPH:
477 enable = !cur.inset().forceDefaultParagraphs(cur.idx());
480 case LFUN_VC_REGISTER:
481 enable = !buf->lyxvc().inUse();
483 case LFUN_VC_CHECK_IN:
484 enable = buf->lyxvc().inUse() && !buf->isReadonly();
486 case LFUN_VC_CHECK_OUT:
487 enable = buf->lyxvc().inUse() && buf->isReadonly();
490 case LFUN_VC_UNDO_LAST:
491 enable = buf->lyxvc().inUse();
493 case LFUN_BUFFER_RELOAD:
494 enable = !buf->isUnnamed() && !buf->isClean();
497 case LFUN_INSET_SETTINGS: {
501 Inset::Code code = cur.inset().lyxCode();
503 case Inset::TABULAR_CODE:
504 enable = cmd.argument() == "tabular";
506 case Inset::ERT_CODE:
507 enable = cmd.argument() == "ert";
509 case Inset::FLOAT_CODE:
510 enable = cmd.argument() == "float";
512 case Inset::WRAP_CODE:
513 enable = cmd.argument() == "wrap";
515 case Inset::NOTE_CODE:
516 enable = cmd.argument() == "note";
518 case Inset::BRANCH_CODE:
519 enable = cmd.argument() == "branch";
521 case Inset::BOX_CODE:
522 enable = cmd.argument() == "box";
524 case Inset::LISTINGS_CODE:
525 enable = cmd.argument() == "listings";
533 case LFUN_INSET_APPLY: {
534 string const name = cmd.getArg(0);
535 Inset * inset = lyx_view_->getDialogs().getOpenInset(name);
537 FuncRequest fr(LFUN_INSET_MODIFY, cmd.argument());
539 if (!inset->getStatus(cur, fr, fs)) {
540 // Every inset is supposed to handle this
545 FuncRequest fr(LFUN_INSET_INSERT, cmd.argument());
546 flag |= getStatus(fr);
548 enable = flag.enabled();
552 case LFUN_DIALOG_TOGGLE:
553 flag.setOnOff(lyx_view_->getDialogs().visible(cmd.getArg(0)));
554 // fall through to set "enable"
555 case LFUN_DIALOG_SHOW: {
556 string const name = cmd.getArg(0);
558 enable = name == "aboutlyx"
559 || name == "file" //FIXME: should be removed.
561 || name == "texinfo";
562 else if (name == "print")
563 enable = Exporter::isExportable(*buf, "dvi")
564 && lyxrc.print_command != "none";
565 else if (name == "character")
566 enable = cur.inset().lyxCode() != Inset::ERT_CODE &&
567 cur.inset().lyxCode() != Inset::LISTINGS_CODE;
568 else if (name == "latexlog")
569 enable = isFileReadable(FileName(buf->getLogName().second));
570 else if (name == "spellchecker")
571 #if defined (USE_ASPELL) || defined (USE_ISPELL) || defined (USE_PSPELL)
572 enable = !buf->isReadonly();
576 else if (name == "vclog")
577 enable = buf->lyxvc().inUse();
581 case LFUN_DIALOG_SHOW_NEW_INSET:
582 enable = cur.inset().lyxCode() != Inset::ERT_CODE &&
583 cur.inset().lyxCode() != Inset::LISTINGS_CODE;
584 if (cur.inset().lyxCode() == Inset::CAPTION_CODE) {
586 if (cur.inset().getStatus(cur, cmd, flag))
591 case LFUN_DIALOG_UPDATE: {
592 string const name = cmd.getArg(0);
594 enable = name == "prefs";
598 case LFUN_CITATION_INSERT: {
599 FuncRequest fr(LFUN_INSET_INSERT, "citation");
600 enable = getStatus(fr).enabled();
604 case LFUN_BUFFER_WRITE: {
605 enable = view()->buffer()->isUnnamed()
606 || !view()->buffer()->isClean();
610 case LFUN_BOOKMARK_GOTO: {
611 const unsigned int num = convert<unsigned int>(to_utf8(cmd.argument()));
612 enable = LyX::ref().session().bookmarks().isValid(num);
616 case LFUN_BOOKMARK_CLEAR:
617 enable = LyX::ref().session().bookmarks().size() > 0;
620 case LFUN_TOOLBAR_TOGGLE_STATE: {
621 ToolbarInfo::Flags flags = lyx_view_->getToolbarState(to_utf8(cmd.argument()));
622 if (!(flags & ToolbarInfo::AUTO))
623 flag.setOnOff(flags & ToolbarInfo::ON);
627 case LFUN_TOOLBAR_TOGGLE: {
628 bool const current = lyx_view_->getToolbars().visible(cmd.getArg(0));
629 flag.setOnOff(current);
633 case LFUN_WINDOW_CLOSE: {
634 enable = (theApp()->gui().viewIds().size() > 1);
638 // this one is difficult to get right. As a half-baked
639 // solution, we consider only the first action of the sequence
640 case LFUN_COMMAND_SEQUENCE: {
641 // argument contains ';'-terminated commands
642 string const firstcmd = token(to_utf8(cmd.argument()), ';', 0);
643 FuncRequest func(lyxaction.lookupFunc(firstcmd));
644 func.origin = cmd.origin;
645 flag = getStatus(func);
648 case LFUN_BUFFER_NEW:
649 case LFUN_BUFFER_NEW_TEMPLATE:
650 case LFUN_WORD_FIND_FORWARD:
651 case LFUN_WORD_FIND_BACKWARD:
652 case LFUN_COMMAND_PREFIX:
653 case LFUN_COMMAND_EXECUTE:
655 case LFUN_META_PREFIX:
656 case LFUN_BUFFER_CLOSE:
657 case LFUN_BUFFER_WRITE_AS:
658 case LFUN_BUFFER_UPDATE:
659 case LFUN_BUFFER_VIEW:
660 case LFUN_BUFFER_IMPORT:
661 case LFUN_BUFFER_AUTO_SAVE:
662 case LFUN_RECONFIGURE:
666 case LFUN_DROP_LAYOUTS_CHOICE:
668 case LFUN_SERVER_GET_NAME:
669 case LFUN_SERVER_NOTIFY:
670 case LFUN_SERVER_GOTO_FILE_ROW:
671 case LFUN_DIALOG_HIDE:
672 case LFUN_DIALOG_DISCONNECT_INSET:
673 case LFUN_BUFFER_CHILD_OPEN:
674 case LFUN_TOGGLE_CURSOR_FOLLOWS_SCROLLBAR:
675 case LFUN_KEYMAP_OFF:
676 case LFUN_KEYMAP_PRIMARY:
677 case LFUN_KEYMAP_SECONDARY:
678 case LFUN_KEYMAP_TOGGLE:
680 case LFUN_BUFFER_EXPORT_CUSTOM:
681 case LFUN_BUFFER_PRINT:
682 case LFUN_PREFERENCES_SAVE:
683 case LFUN_SCREEN_FONT_UPDATE:
686 case LFUN_EXTERNAL_EDIT:
687 case LFUN_GRAPHICS_EDIT:
688 case LFUN_ALL_INSETS_TOGGLE:
689 case LFUN_BUFFER_LANGUAGE:
690 case LFUN_TEXTCLASS_APPLY:
691 case LFUN_TEXTCLASS_LOAD:
692 case LFUN_BUFFER_SAVE_AS_DEFAULT:
693 case LFUN_BUFFER_PARAMS_APPLY:
694 case LFUN_LYXRC_APPLY:
695 case LFUN_BUFFER_NEXT:
696 case LFUN_BUFFER_PREVIOUS:
697 case LFUN_WINDOW_NEW:
699 // these are handled in our dispatch()
703 if (!getLocalStatus(cur, cmd, flag))
704 flag = view()->getStatus(cmd);
710 // Can we use a readonly buffer?
711 if (buf && buf->isReadonly()
712 && !lyxaction.funcHasFlag(cmd.action, LyXAction::ReadOnly)
713 && !lyxaction.funcHasFlag(cmd.action, LyXAction::NoBuffer)) {
714 flag.message(from_utf8(N_("Document is read-only")));
718 // Are we in a DELETED change-tracking region?
719 if (buf && lookupChangeType(cur, true) == Change::DELETED
720 && !lyxaction.funcHasFlag(cmd.action, LyXAction::ReadOnly)
721 && !lyxaction.funcHasFlag(cmd.action, LyXAction::NoBuffer)) {
722 flag.message(from_utf8(N_("This portion of the document is deleted.")));
726 // the default error message if we disable the command
727 if (!flag.enabled() && flag.message().empty())
728 flag.message(from_utf8(N_("Command disabled")));
734 bool LyXFunc::ensureBufferClean(BufferView * bv)
736 Buffer & buf = *bv->buffer();
740 docstring const file = makeDisplayPath(buf.fileName(), 30);
741 docstring text = bformat(_("The document %1$s has unsaved "
742 "changes.\n\nDo you want to save "
743 "the document?"), file);
744 int const ret = Alert::prompt(_("Save changed document?"),
745 text, 0, 1, _("&Save"),
749 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
751 return buf.isClean();
757 void showPrintError(string const & name)
759 docstring str = bformat(_("Could not print the document %1$s.\n"
760 "Check that your printer is set up correctly."),
761 makeDisplayPath(name, 50));
762 Alert::error(_("Print document failed"), str);
766 void loadTextclass(string const & name)
768 std::pair<bool, textclass_type> const tc_pair =
769 textclasslist.numberOfClass(name);
771 if (!tc_pair.first) {
772 lyxerr << "Document class \"" << name
773 << "\" does not exist."
778 textclass_type const tc = tc_pair.second;
780 if (!textclasslist[tc].load()) {
781 docstring s = bformat(_("The document could not be converted\n"
782 "into the document class %1$s."),
783 from_utf8(textclasslist[tc].name()));
784 Alert::error(_("Could not change class"), s);
789 void actOnUpdatedPrefs(LyXRC const & lyxrc_orig, LyXRC const & lyxrc_new);
794 void LyXFunc::dispatch(FuncRequest const & cmd)
796 string const argument = to_utf8(cmd.argument());
797 kb_action const action = cmd.action;
799 LYXERR(Debug::ACTION) << endl << "LyXFunc::dispatch: cmd: " << cmd << endl;
800 //lyxerr << "LyXFunc::dispatch: cmd: " << cmd << endl;
802 // we have not done anything wrong yet.
804 dispatch_buffer.erase();
806 // redraw the screen at the end (first of the two drawing steps).
807 //This is done unless explicitely requested otherwise
808 Update::flags updateFlags = Update::FitCursor;
810 FuncStatus const flag = getStatus(cmd);
811 if (!flag.enabled()) {
812 // We cannot use this function here
813 LYXERR(Debug::ACTION) << "LyXFunc::dispatch: "
814 << lyxaction.getActionName(action)
815 << " [" << action << "] is disabled at this location"
817 setErrorMessage(flag.message());
821 case LFUN_WORD_FIND_FORWARD:
822 case LFUN_WORD_FIND_BACKWARD: {
823 BOOST_ASSERT(lyx_view_ && lyx_view_->view());
824 static docstring last_search;
825 docstring searched_string;
827 if (!cmd.argument().empty()) {
828 last_search = cmd.argument();
829 searched_string = cmd.argument();
831 searched_string = last_search;
834 if (searched_string.empty())
837 bool const fw = action == LFUN_WORD_FIND_FORWARD;
838 docstring const data =
839 find2string(searched_string, true, false, fw);
840 find(view(), FuncRequest(LFUN_WORD_FIND, data));
844 case LFUN_COMMAND_PREFIX:
845 BOOST_ASSERT(lyx_view_);
846 lyx_view_->message(keyseq->printOptions(true));
849 case LFUN_COMMAND_EXECUTE:
850 BOOST_ASSERT(lyx_view_);
851 lyx_view_->getToolbars().display("minibuffer", true);
852 lyx_view_->focus_command_buffer();
856 BOOST_ASSERT(lyx_view_ && lyx_view_->view());
858 meta_fake_bit = key_modifier::none;
859 if (view()->buffer())
860 // cancel any selection
861 dispatch(FuncRequest(LFUN_MARK_OFF));
862 setMessage(from_ascii(N_("Cancel")));
865 case LFUN_META_PREFIX:
866 meta_fake_bit = key_modifier::alt;
867 setMessage(keyseq->print(true));
870 case LFUN_BUFFER_TOGGLE_READ_ONLY:
871 BOOST_ASSERT(lyx_view_ && lyx_view_->view() && lyx_view_->buffer());
872 if (lyx_view_->buffer()->lyxvc().inUse())
873 lyx_view_->buffer()->lyxvc().toggleReadOnly();
875 lyx_view_->buffer()->setReadonly(
876 !lyx_view_->buffer()->isReadonly());
879 // --- Menus -----------------------------------------------
880 case LFUN_BUFFER_NEW:
881 menuNew(argument, false);
884 case LFUN_BUFFER_NEW_TEMPLATE:
885 menuNew(argument, true);
888 case LFUN_BUFFER_CLOSE:
893 case LFUN_BUFFER_WRITE:
894 BOOST_ASSERT(lyx_view_ && lyx_view_->buffer());
895 if (!lyx_view_->buffer()->isUnnamed()) {
896 docstring const str = bformat(_("Saving document %1$s..."),
897 makeDisplayPath(lyx_view_->buffer()->fileName()));
898 lyx_view_->message(str);
899 menuWrite(lyx_view_->buffer());
900 lyx_view_->message(str + _(" done."));
902 writeAs(lyx_view_->buffer());
904 updateFlags = Update::None;
907 case LFUN_BUFFER_WRITE_AS:
908 BOOST_ASSERT(lyx_view_ && lyx_view_->buffer());
909 writeAs(lyx_view_->buffer(), argument);
910 updateFlags = Update::None;
913 case LFUN_BUFFER_RELOAD: {
914 BOOST_ASSERT(lyx_view_ && lyx_view_->buffer());
915 docstring const file = makeDisplayPath(view()->buffer()->fileName(), 20);
916 docstring text = bformat(_("Any changes will be lost. Are you sure "
917 "you want to revert to the saved version of the document %1$s?"), file);
918 int const ret = Alert::prompt(_("Revert to saved document?"),
919 text, 1, 1, _("&Revert"), _("&Cancel"));
926 case LFUN_BUFFER_UPDATE:
927 BOOST_ASSERT(lyx_view_ && lyx_view_->buffer());
928 Exporter::Export(lyx_view_->buffer(), argument, true);
931 case LFUN_BUFFER_VIEW:
932 BOOST_ASSERT(lyx_view_ && lyx_view_->buffer());
933 Exporter::preview(lyx_view_->buffer(), argument);
936 case LFUN_BUILD_PROGRAM:
937 BOOST_ASSERT(lyx_view_ && lyx_view_->buffer());
938 Exporter::Export(lyx_view_->buffer(), "program", true);
941 case LFUN_BUFFER_CHKTEX:
942 BOOST_ASSERT(lyx_view_ && lyx_view_->buffer());
943 lyx_view_->buffer()->runChktex();
946 case LFUN_BUFFER_EXPORT:
947 BOOST_ASSERT(lyx_view_ && lyx_view_->buffer());
948 if (argument == "custom")
949 lyx_view_->getDialogs().show("sendto");
951 Exporter::Export(lyx_view_->buffer(), argument, false);
955 case LFUN_BUFFER_EXPORT_CUSTOM: {
956 BOOST_ASSERT(lyx_view_ && lyx_view_->buffer());
958 string command = split(argument, format_name, ' ');
959 Format const * format = formats.getFormat(format_name);
961 lyxerr << "Format \"" << format_name
962 << "\" not recognized!"
967 Buffer * buffer = lyx_view_->buffer();
969 // The name of the file created by the conversion process
972 // Output to filename
973 if (format->name() == "lyx") {
974 string const latexname =
975 buffer->getLatexName(false);
976 filename = changeExtension(latexname,
977 format->extension());
978 filename = addName(buffer->temppath(), filename);
980 if (!buffer->writeFile(FileName(filename)))
984 Exporter::Export(buffer, format_name, true, filename);
987 // Substitute $$FName for filename
988 if (!contains(command, "$$FName"))
989 command = "( " + command + " ) < $$FName";
990 command = subst(command, "$$FName", filename);
992 // Execute the command in the background
994 call.startscript(Systemcall::DontWait, command);
998 case LFUN_BUFFER_PRINT: {
999 BOOST_ASSERT(lyx_view_ && lyx_view_->buffer());
1002 string command = split(split(argument, target, ' '),
1006 || target_name.empty()
1007 || command.empty()) {
1008 lyxerr << "Unable to parse \""
1009 << argument << '"' << std::endl;
1012 if (target != "printer" && target != "file") {
1013 lyxerr << "Unrecognized target \""
1014 << target << '"' << std::endl;
1018 Buffer * buffer = lyx_view_->buffer();
1020 if (!Exporter::Export(buffer, "dvi", true)) {
1021 showPrintError(buffer->fileName());
1025 // Push directory path.
1026 string const path(buffer->temppath());
1027 // Prevent the compiler from optimizing away p
1029 support::Path p(pp);
1031 // there are three cases here:
1032 // 1. we print to a file
1033 // 2. we print directly to a printer
1034 // 3. we print using a spool command (print to file first)
1037 string const dviname =
1038 changeExtension(buffer->getLatexName(true),
1041 if (target == "printer") {
1042 if (!lyxrc.print_spool_command.empty()) {
1043 // case 3: print using a spool
1044 string const psname =
1045 changeExtension(dviname,".ps");
1046 command += lyxrc.print_to_file
1049 + quoteName(dviname);
1052 lyxrc.print_spool_command +' ';
1053 if (target_name != "default") {
1054 command2 += lyxrc.print_spool_printerprefix
1058 command2 += quoteName(psname);
1060 // If successful, then spool command
1061 res = one.startscript(
1066 res = one.startscript(
1067 Systemcall::DontWait,
1070 // case 2: print directly to a printer
1071 if (target_name != "default")
1072 command += lyxrc.print_to_printer + target_name + ' ';
1073 res = one.startscript(
1074 Systemcall::DontWait,
1075 command + quoteName(dviname));
1079 // case 1: print to a file
1080 FileName const filename(makeAbsPath(target_name, path));
1081 if (fs::exists(filename.toFilesystemEncoding())) {
1082 docstring text = bformat(
1083 _("The file %1$s already exists.\n\n"
1084 "Do you want to over-write that file?"),
1085 makeDisplayPath(filename.absFilename()));
1086 if (Alert::prompt(_("Over-write file?"),
1087 text, 0, 1, _("&Over-write"), _("&Cancel")) != 0)
1090 command += lyxrc.print_to_file
1091 + quoteName(filename.toFilesystemEncoding())
1093 + quoteName(dviname);
1094 res = one.startscript(Systemcall::DontWait,
1099 showPrintError(buffer->fileName());
1103 case LFUN_BUFFER_IMPORT:
1108 // quitting is triggered by the gui code
1109 // (leaving the event loop).
1110 lyx_view_->message(from_utf8(N_("Exiting.")));
1111 if (theBufferList().quitWriteAll())
1112 theApp()->gui().closeAllViews();
1115 case LFUN_BUFFER_AUTO_SAVE:
1119 case LFUN_RECONFIGURE:
1120 BOOST_ASSERT(lyx_view_);
1121 reconfigure(*lyx_view_);
1124 case LFUN_HELP_OPEN: {
1125 BOOST_ASSERT(lyx_view_);
1126 string const arg = argument;
1128 setErrorMessage(from_ascii(N_("Missing argument")));
1131 FileName const fname = i18nLibFileSearch("doc", arg, "lyx");
1132 if (fname.empty()) {
1133 lyxerr << "LyX: unable to find documentation file `"
1134 << arg << "'. Bad installation?" << endl;
1137 lyx_view_->message(bformat(_("Opening help file %1$s..."),
1138 makeDisplayPath(fname.absFilename())));
1139 lyx_view_->loadLyXFile(fname, false);
1143 // --- version control -------------------------------
1144 case LFUN_VC_REGISTER:
1145 BOOST_ASSERT(lyx_view_ && lyx_view_->buffer());
1146 if (!ensureBufferClean(view()))
1148 if (!lyx_view_->buffer()->lyxvc().inUse()) {
1149 lyx_view_->buffer()->lyxvc().registrer();
1152 updateFlags = Update::Force;
1155 case LFUN_VC_CHECK_IN:
1156 BOOST_ASSERT(lyx_view_ && lyx_view_->buffer());
1157 if (!ensureBufferClean(view()))
1159 if (lyx_view_->buffer()->lyxvc().inUse()
1160 && !lyx_view_->buffer()->isReadonly()) {
1161 lyx_view_->buffer()->lyxvc().checkIn();
1166 case LFUN_VC_CHECK_OUT:
1167 BOOST_ASSERT(lyx_view_ && lyx_view_->buffer());
1168 if (!ensureBufferClean(view()))
1170 if (lyx_view_->buffer()->lyxvc().inUse()
1171 && lyx_view_->buffer()->isReadonly()) {
1172 lyx_view_->buffer()->lyxvc().checkOut();
1177 case LFUN_VC_REVERT:
1178 BOOST_ASSERT(lyx_view_ && lyx_view_->buffer());
1179 lyx_view_->buffer()->lyxvc().revert();
1183 case LFUN_VC_UNDO_LAST:
1184 BOOST_ASSERT(lyx_view_ && lyx_view_->buffer());
1185 lyx_view_->buffer()->lyxvc().undoLast();
1189 // --- buffers ----------------------------------------
1190 case LFUN_BUFFER_SWITCH:
1191 BOOST_ASSERT(lyx_view_);
1192 lyx_view_->setBuffer(theBufferList().getBuffer(argument));
1195 case LFUN_BUFFER_NEXT:
1196 BOOST_ASSERT(lyx_view_);
1197 lyx_view_->setBuffer(theBufferList().next(view()->buffer()));
1200 case LFUN_BUFFER_PREVIOUS:
1201 BOOST_ASSERT(lyx_view_);
1202 lyx_view_->setBuffer(theBufferList().previous(view()->buffer()));
1206 BOOST_ASSERT(lyx_view_);
1207 newFile(view(), argument);
1210 case LFUN_FILE_OPEN:
1211 BOOST_ASSERT(lyx_view_);
1215 case LFUN_DROP_LAYOUTS_CHOICE:
1216 BOOST_ASSERT(lyx_view_);
1217 lyx_view_->getToolbars().openLayoutList();
1220 case LFUN_MENU_OPEN:
1221 BOOST_ASSERT(lyx_view_);
1222 lyx_view_->getMenubar().openByName(from_utf8(argument));
1225 // --- lyxserver commands ----------------------------
1226 case LFUN_SERVER_GET_NAME:
1227 BOOST_ASSERT(lyx_view_ && lyx_view_->buffer());
1228 setMessage(from_utf8(lyx_view_->buffer()->fileName()));
1229 LYXERR(Debug::INFO) << "FNAME["
1230 << lyx_view_->buffer()->fileName()
1234 case LFUN_SERVER_NOTIFY:
1235 dispatch_buffer = keyseq->print(false);
1236 theServer().notifyClient(to_utf8(dispatch_buffer));
1239 case LFUN_SERVER_GOTO_FILE_ROW: {
1240 BOOST_ASSERT(lyx_view_);
1243 istringstream is(argument);
1244 is >> file_name >> row;
1245 if (prefixIs(file_name, package().temp_dir().absFilename())) {
1246 // Needed by inverse dvi search. If it is a file
1247 // in tmpdir, call the apropriated function
1248 lyx_view_->setBuffer(theBufferList().getBufferFromTmp(file_name));
1250 // Must replace extension of the file to be .lyx
1251 // and get full path
1252 FileName const s = fileSearch(string(), changeExtension(file_name, ".lyx"), "lyx");
1253 // Either change buffer or load the file
1254 if (theBufferList().exists(s.absFilename())) {
1255 lyx_view_->setBuffer(theBufferList().getBuffer(s.absFilename()));
1257 lyx_view_->loadLyXFile(s);
1261 view()->setCursorFromRow(row);
1263 updateFlags = Update::FitCursor;
1267 case LFUN_DIALOG_SHOW: {
1268 BOOST_ASSERT(lyx_view_);
1269 string const name = cmd.getArg(0);
1270 string data = trim(to_utf8(cmd.argument()).substr(name.size()));
1272 if (name == "character") {
1273 data = freefont2string();
1275 lyx_view_->getDialogs().show("character", data);
1276 } else if (name == "latexlog") {
1277 pair<Buffer::LogType, string> const logfile =
1278 lyx_view_->buffer()->getLogName();
1279 switch (logfile.first) {
1280 case Buffer::latexlog:
1283 case Buffer::buildlog:
1287 data += Lexer::quoteString(logfile.second);
1288 lyx_view_->getDialogs().show("log", data);
1289 } else if (name == "vclog") {
1290 string const data = "vc " +
1291 Lexer::quoteString(lyx_view_->buffer()->lyxvc().getLogFile());
1292 lyx_view_->getDialogs().show("log", data);
1294 lyx_view_->getDialogs().show(name, data);
1298 case LFUN_DIALOG_SHOW_NEW_INSET: {
1299 BOOST_ASSERT(lyx_view_);
1300 string const name = cmd.getArg(0);
1301 string data = trim(to_utf8(cmd.argument()).substr(name.size()));
1302 if (name == "bibitem" ||
1306 name == "nomenclature" ||
1310 InsetCommandParams p(name);
1311 data = InsetCommandMailer::params2string(name, p);
1312 } else if (name == "include") {
1313 // data is the include type: one of "include",
1314 // "input", "verbatiminput" or "verbatiminput*"
1316 // default type is requested
1318 InsetCommandParams p(data);
1319 data = InsetIncludeMailer::params2string(p);
1320 } else if (name == "box") {
1321 // \c data == "Boxed" || "Frameless" etc
1322 InsetBoxParams p(data);
1323 data = InsetBoxMailer::params2string(p);
1324 } else if (name == "branch") {
1325 InsetBranchParams p;
1326 data = InsetBranchMailer::params2string(p);
1327 } else if (name == "citation") {
1328 InsetCommandParams p("cite");
1329 data = InsetCommandMailer::params2string(name, p);
1330 } else if (name == "ert") {
1331 data = InsetERTMailer::params2string(InsetCollapsable::Open);
1332 } else if (name == "external") {
1333 InsetExternalParams p;
1334 Buffer const & buffer = *lyx_view_->buffer();
1335 data = InsetExternalMailer::params2string(p, buffer);
1336 } else if (name == "float") {
1338 data = InsetFloatMailer::params2string(p);
1339 } else if (name == "listings") {
1340 InsetListingsParams p;
1341 data = InsetListingsMailer::params2string(p);
1342 } else if (name == "graphics") {
1343 InsetGraphicsParams p;
1344 Buffer const & buffer = *lyx_view_->buffer();
1345 data = InsetGraphicsMailer::params2string(p, buffer);
1346 } else if (name == "note") {
1348 data = InsetNoteMailer::params2string(p);
1349 } else if (name == "vspace") {
1351 data = InsetVSpaceMailer::params2string(space);
1352 } else if (name == "wrap") {
1354 data = InsetWrapMailer::params2string(p);
1356 lyx_view_->getDialogs().show(name, data, 0);
1360 case LFUN_DIALOG_UPDATE: {
1361 BOOST_ASSERT(lyx_view_);
1362 string const & name = argument;
1363 // Can only update a dialog connected to an existing inset
1364 Inset * inset = lyx_view_->getDialogs().getOpenInset(name);
1366 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
1367 inset->dispatch(view()->cursor(), fr);
1368 } else if (name == "paragraph") {
1369 dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
1370 } else if (name == "prefs") {
1371 lyx_view_->getDialogs().update(name, string());
1376 case LFUN_DIALOG_HIDE:
1377 Dialogs::hide(argument, 0);
1380 case LFUN_DIALOG_TOGGLE: {
1381 BOOST_ASSERT(lyx_view_);
1382 if (lyx_view_->getDialogs().visible(cmd.getArg(0)))
1383 dispatch(FuncRequest(LFUN_DIALOG_HIDE, argument));
1385 dispatch(FuncRequest(LFUN_DIALOG_SHOW, argument));
1389 case LFUN_DIALOG_DISCONNECT_INSET:
1390 BOOST_ASSERT(lyx_view_);
1391 lyx_view_->getDialogs().disconnect(argument);
1395 case LFUN_CITATION_INSERT: {
1396 BOOST_ASSERT(lyx_view_);
1397 if (!argument.empty()) {
1398 // we can have one optional argument, delimited by '|'
1399 // citation-insert <key>|<text_before>
1400 // this should be enhanced to also support text_after
1401 // and citation style
1402 string arg = argument;
1404 if (contains(argument, "|")) {
1405 arg = token(argument, '|', 0);
1406 opt1 = token(argument, '|', 1);
1408 InsetCommandParams icp("cite");
1409 icp["key"] = from_utf8(arg);
1411 icp["before"] = from_utf8(opt1);
1412 string icstr = InsetCommandMailer::params2string("citation", icp);
1413 FuncRequest fr(LFUN_INSET_INSERT, icstr);
1416 dispatch(FuncRequest(LFUN_DIALOG_SHOW_NEW_INSET, "citation"));
1420 case LFUN_BUFFER_CHILD_OPEN: {
1421 BOOST_ASSERT(lyx_view_);
1422 FileName const filename =
1423 makeAbsPath(argument, lyx_view_->buffer()->filePath());
1424 view()->saveBookmark(false);
1425 string const parentfilename = lyx_view_->buffer()->fileName();
1426 if (theBufferList().exists(filename.absFilename()))
1427 lyx_view_->setBuffer(theBufferList().getBuffer(filename.absFilename()));
1429 if (lyx_view_->loadLyXFile(filename)) {
1430 // Set the parent name of the child document.
1431 // This makes insertion of citations and references in the child work,
1432 // when the target is in the parent or another child document.
1433 lyx_view_->buffer()->setParentName(parentfilename);
1434 setMessage(bformat(_("Opening child document %1$s..."),
1435 makeDisplayPath(filename.absFilename())));
1437 setMessage(_("Document not loaded."));
1441 case LFUN_TOGGLE_CURSOR_FOLLOWS_SCROLLBAR:
1442 BOOST_ASSERT(lyx_view_);
1443 lyxrc.cursor_follows_scrollbar = !lyxrc.cursor_follows_scrollbar;
1446 case LFUN_KEYMAP_OFF:
1447 BOOST_ASSERT(lyx_view_);
1448 lyx_view_->view()->getIntl().keyMapOn(false);
1451 case LFUN_KEYMAP_PRIMARY:
1452 BOOST_ASSERT(lyx_view_);
1453 lyx_view_->view()->getIntl().keyMapPrim();
1456 case LFUN_KEYMAP_SECONDARY:
1457 BOOST_ASSERT(lyx_view_);
1458 lyx_view_->view()->getIntl().keyMapSec();
1461 case LFUN_KEYMAP_TOGGLE:
1462 BOOST_ASSERT(lyx_view_);
1463 lyx_view_->view()->getIntl().toggleKeyMap();
1469 string rest = split(argument, countstr, ' ');
1470 istringstream is(countstr);
1473 lyxerr << "repeat: count: " << count << " cmd: " << rest << endl;
1474 for (int i = 0; i < count; ++i)
1475 dispatch(lyxaction.lookupFunc(rest));
1479 case LFUN_COMMAND_SEQUENCE: {
1480 // argument contains ';'-terminated commands
1481 string arg = argument;
1482 while (!arg.empty()) {
1484 arg = split(arg, first, ';');
1485 FuncRequest func(lyxaction.lookupFunc(first));
1486 func.origin = cmd.origin;
1492 case LFUN_PREFERENCES_SAVE: {
1493 lyxrc.write(makeAbsPath("preferences",
1494 package().user_support().absFilename()),
1499 case LFUN_SCREEN_FONT_UPDATE:
1500 BOOST_ASSERT(lyx_view_);
1501 // handle the screen font changes.
1502 theFontLoader().update();
1503 /// FIXME: only the current view will be updated. the Gui
1504 /// class is able to furnish the list of views.
1505 updateFlags = Update::Force;
1508 case LFUN_SET_COLOR: {
1510 string const x11_name = split(argument, lyx_name, ' ');
1511 if (lyx_name.empty() || x11_name.empty()) {
1512 setErrorMessage(from_ascii(N_(
1513 "Syntax: set-color <lyx_name>"
1518 bool const graphicsbg_changed =
1519 (lyx_name == lcolor.getLyXName(Color::graphicsbg) &&
1520 x11_name != lcolor.getX11Name(Color::graphicsbg));
1522 if (!lcolor.setColor(lyx_name, x11_name)) {
1524 bformat(_("Set-color \"%1$s\" failed "
1525 "- color is undefined or "
1526 "may not be redefined"),
1527 from_utf8(lyx_name)));
1531 theApp()->updateColor(lcolor.getFromLyXName(lyx_name));
1533 if (graphicsbg_changed) {
1534 #ifdef WITH_WARNINGS
1535 #warning FIXME!! The graphics cache no longer has a changeDisplay method.
1538 graphics::GCache::get().changeDisplay(true);
1545 BOOST_ASSERT(lyx_view_);
1546 lyx_view_->message(from_utf8(argument));
1549 case LFUN_EXTERNAL_EDIT: {
1550 BOOST_ASSERT(lyx_view_);
1551 FuncRequest fr(action, argument);
1552 InsetExternal().dispatch(view()->cursor(), fr);
1556 case LFUN_GRAPHICS_EDIT: {
1557 FuncRequest fr(action, argument);
1558 InsetGraphics().dispatch(view()->cursor(), fr);
1562 case LFUN_INSET_APPLY: {
1563 BOOST_ASSERT(lyx_view_);
1564 string const name = cmd.getArg(0);
1565 Inset * inset = lyx_view_->getDialogs().getOpenInset(name);
1567 FuncRequest fr(LFUN_INSET_MODIFY, argument);
1568 inset->dispatch(view()->cursor(), fr);
1570 FuncRequest fr(LFUN_INSET_INSERT, argument);
1573 // ideally, the update flag should be set by the insets,
1574 // but this is not possible currently
1575 updateFlags = Update::Force | Update::FitCursor;
1579 case LFUN_ALL_INSETS_TOGGLE: {
1580 BOOST_ASSERT(lyx_view_);
1582 string const name = split(argument, action, ' ');
1583 Inset::Code const inset_code =
1584 Inset::translate(name);
1586 Cursor & cur = view()->cursor();
1587 FuncRequest fr(LFUN_INSET_TOGGLE, action);
1589 Inset & inset = lyx_view_->buffer()->inset();
1590 InsetIterator it = inset_iterator_begin(inset);
1591 InsetIterator const end = inset_iterator_end(inset);
1592 for (; it != end; ++it) {
1593 if (!it->asInsetMath()
1594 && (inset_code == Inset::NO_CODE
1595 || inset_code == it->lyxCode())) {
1596 Cursor tmpcur = cur;
1597 tmpcur.pushLeft(*it);
1598 it->dispatch(tmpcur, fr);
1601 updateFlags = Update::Force | Update::FitCursor;
1605 case LFUN_BUFFER_LANGUAGE: {
1606 BOOST_ASSERT(lyx_view_);
1607 Buffer & buffer = *lyx_view_->buffer();
1608 Language const * oldL = buffer.params().language;
1609 Language const * newL = languages.getLanguage(argument);
1610 if (!newL || oldL == newL)
1613 if (oldL->rightToLeft() == newL->rightToLeft()
1614 && !buffer.isMultiLingual())
1615 buffer.changeLanguage(oldL, newL);
1619 case LFUN_BUFFER_SAVE_AS_DEFAULT: {
1620 string const fname =
1621 addName(addPath(package().user_support().absFilename(), "templates/"),
1623 Buffer defaults(fname);
1625 istringstream ss(argument);
1628 int const unknown_tokens = defaults.readHeader(lex);
1630 if (unknown_tokens != 0) {
1631 lyxerr << "Warning in LFUN_BUFFER_SAVE_AS_DEFAULT!\n"
1632 << unknown_tokens << " unknown token"
1633 << (unknown_tokens == 1 ? "" : "s")
1637 if (defaults.writeFile(FileName(defaults.fileName())))
1638 setMessage(bformat(_("Document defaults saved in %1$s"),
1639 makeDisplayPath(fname)));
1641 setErrorMessage(from_ascii(N_("Unable to save document defaults")));
1645 case LFUN_BUFFER_PARAMS_APPLY: {
1646 BOOST_ASSERT(lyx_view_);
1647 biblio::CiteEngine const engine =
1648 lyx_view_->buffer()->params().getEngine();
1650 istringstream ss(argument);
1653 int const unknown_tokens =
1654 lyx_view_->buffer()->readHeader(lex);
1656 if (unknown_tokens != 0) {
1657 lyxerr << "Warning in LFUN_BUFFER_PARAMS_APPLY!\n"
1658 << unknown_tokens << " unknown token"
1659 << (unknown_tokens == 1 ? "" : "s")
1662 if (engine == lyx_view_->buffer()->params().getEngine())
1665 Cursor & cur = view()->cursor();
1666 FuncRequest fr(LFUN_INSET_REFRESH);
1668 Inset & inset = lyx_view_->buffer()->inset();
1669 InsetIterator it = inset_iterator_begin(inset);
1670 InsetIterator const end = inset_iterator_end(inset);
1671 for (; it != end; ++it)
1672 if (it->lyxCode() == Inset::CITE_CODE)
1673 it->dispatch(cur, fr);
1677 case LFUN_TEXTCLASS_APPLY: {
1678 BOOST_ASSERT(lyx_view_);
1679 Buffer * buffer = lyx_view_->buffer();
1681 textclass_type const old_class =
1682 buffer->params().textclass;
1684 loadTextclass(argument);
1686 std::pair<bool, textclass_type> const tc_pair =
1687 textclasslist.numberOfClass(argument);
1692 textclass_type const new_class = tc_pair.second;
1693 if (old_class == new_class)
1697 lyx_view_->message(_("Converting document to new document class..."));
1698 recordUndoFullDocument(view());
1699 buffer->params().textclass = new_class;
1700 StableDocIterator backcur(view()->cursor());
1701 ErrorList & el = buffer->errorList("Class Switch");
1702 cap::switchBetweenClasses(
1703 old_class, new_class,
1704 static_cast<InsetText &>(buffer->inset()), el);
1706 view()->setCursor(backcur.asDocIterator(&(buffer->inset())));
1708 buffer->errors("Class Switch");
1709 updateLabels(*buffer);
1710 updateFlags = Update::Force | Update::FitCursor;
1714 case LFUN_TEXTCLASS_LOAD:
1715 loadTextclass(argument);
1718 case LFUN_LYXRC_APPLY: {
1719 LyXRC const lyxrc_orig = lyxrc;
1721 istringstream ss(argument);
1722 bool const success = lyxrc.read(ss) == 0;
1725 lyxerr << "Warning in LFUN_LYXRC_APPLY!\n"
1726 << "Unable to read lyxrc data"
1731 actOnUpdatedPrefs(lyxrc_orig, lyxrc);
1733 /// We force the redraw in any case because there might be
1734 /// some screen font changes.
1735 /// FIXME: only the current view will be updated. the Gui
1736 /// class is able to furnish the list of views.
1737 updateFlags = Update::Force;
1741 case LFUN_WINDOW_NEW:
1742 LyX::ref().newLyXView();
1745 case LFUN_WINDOW_CLOSE:
1746 BOOST_ASSERT(lyx_view_);
1747 BOOST_ASSERT(theApp());
1748 // update bookmark pit of the current buffer before window close
1749 for (size_t i = 0; i < LyX::ref().session().bookmarks().size(); ++i)
1750 gotoBookmark(i+1, false, false);
1751 // ask the user for saving changes or cancel quit
1752 if (!theBufferList().quitWriteAll())
1757 case LFUN_BOOKMARK_GOTO:
1758 // go to bookmark, open unopened file and switch to buffer if necessary
1759 gotoBookmark(convert<unsigned int>(to_utf8(cmd.argument())), true, true);
1762 case LFUN_BOOKMARK_CLEAR:
1763 LyX::ref().session().bookmarks().clear();
1766 case LFUN_TOOLBAR_TOGGLE_STATE:
1767 lyx_view_->toggleToolbarState(argument);
1770 case LFUN_TOOLBAR_TOGGLE: {
1771 BOOST_ASSERT(lyx_view_);
1772 string const name = to_utf8(cmd.argument());
1773 bool const current = lyx_view_->getToolbars().visible(name);
1774 lyx_view_->getToolbars().display(name, !current);
1779 BOOST_ASSERT(lyx_view_);
1780 view()->cursor().dispatch(cmd);
1781 updateFlags = view()->cursor().result().update();
1782 if (!view()->cursor().result().dispatched())
1783 updateFlags = view()->dispatch(cmd);
1788 if (lyx_view_ && view()->buffer()) {
1789 // BufferView::update() updates the ViewMetricsInfo and
1790 // also initializes the position cache for all insets in
1791 // (at least partially) visible top-level paragraphs.
1792 // We will redraw the screen only if needed.
1793 if (view()->update(updateFlags)) {
1794 // Buffer::changed() signals that a repaint is needed.
1795 // The frontend (WorkArea) knows which area to repaint
1796 // thanks to the ViewMetricsInfo updated above.
1797 view()->buffer()->changed();
1800 lyx_view_->updateStatusBar();
1802 // if we executed a mutating lfun, mark the buffer as dirty
1804 && !lyxaction.funcHasFlag(action, LyXAction::NoBuffer)
1805 && !lyxaction.funcHasFlag(action, LyXAction::ReadOnly))
1806 view()->buffer()->markDirty();
1808 if (view()->cursor().inTexted()) {
1809 lyx_view_->updateLayoutChoice();
1814 lyx_view_->updateMenubar();
1815 lyx_view_->updateToolbars();
1816 // Some messages may already be translated, so we cannot use _()
1817 sendDispatchMessage(translateIfPossible(getMessage()), cmd);
1822 void LyXFunc::sendDispatchMessage(docstring const & msg, FuncRequest const & cmd)
1824 const bool verbose = (cmd.origin == FuncRequest::MENU
1825 || cmd.origin == FuncRequest::TOOLBAR
1826 || cmd.origin == FuncRequest::COMMANDBUFFER);
1828 if (cmd.action == LFUN_SELF_INSERT || !verbose) {
1829 LYXERR(Debug::ACTION) << "dispatch msg is " << to_utf8(msg) << endl;
1831 lyx_view_->message(msg);
1835 docstring dispatch_msg = msg;
1836 if (!dispatch_msg.empty())
1837 dispatch_msg += ' ';
1839 docstring comname = from_utf8(lyxaction.getActionName(cmd.action));
1841 bool argsadded = false;
1843 if (!cmd.argument().empty()) {
1844 if (cmd.action != LFUN_UNKNOWN_ACTION) {
1845 comname += ' ' + cmd.argument();
1850 docstring const shortcuts = theTopLevelKeymap().printbindings(cmd);
1852 if (!shortcuts.empty())
1853 comname += ": " + shortcuts;
1854 else if (!argsadded && !cmd.argument().empty())
1855 comname += ' ' + cmd.argument();
1857 if (!comname.empty()) {
1858 comname = rtrim(comname);
1859 dispatch_msg += '(' + rtrim(comname) + ')';
1862 LYXERR(Debug::ACTION) << "verbose dispatch msg "
1863 << to_utf8(dispatch_msg) << endl;
1864 if (!dispatch_msg.empty())
1865 lyx_view_->message(dispatch_msg);
1869 void LyXFunc::menuNew(string const & name, bool fromTemplate)
1871 // FIXME: initpath is not used. What to do?
1872 string initpath = lyxrc.document_path;
1873 string filename(name);
1875 if (view()->buffer()) {
1876 string const trypath = lyx_view_->buffer()->filePath();
1877 // If directory is writeable, use this as default.
1878 if (isDirWriteable(FileName(trypath)))
1882 static int newfile_number;
1884 if (filename.empty()) {
1885 filename = addName(lyxrc.document_path,
1886 "newfile" + convert<string>(++newfile_number) + ".lyx");
1887 while (theBufferList().exists(filename) ||
1888 fs::is_readable(FileName(filename).toFilesystemEncoding())) {
1890 filename = addName(lyxrc.document_path,
1891 "newfile" + convert<string>(newfile_number) +
1896 // The template stuff
1899 FileDialog fileDlg(_("Select template file"),
1900 LFUN_SELECT_FILE_SYNC,
1901 make_pair(_("Documents|#o#O"), from_utf8(lyxrc.document_path)),
1902 make_pair(_("Templates|#T#t"), from_utf8(lyxrc.template_path)));
1904 FileDialog::Result result =
1905 fileDlg.open(from_utf8(lyxrc.template_path),
1906 FileFilterList(_("LyX Documents (*.lyx)")),
1909 if (result.first == FileDialog::Later)
1911 if (result.second.empty())
1913 templname = to_utf8(result.second);
1916 Buffer * const b = newFile(filename, templname, !name.empty());
1919 lyx_view_->setBuffer(b);
1924 void LyXFunc::open(string const & fname)
1926 string initpath = lyxrc.document_path;
1928 if (view()->buffer()) {
1929 string const trypath = lyx_view_->buffer()->filePath();
1930 // If directory is writeable, use this as default.
1931 if (isDirWriteable(FileName(trypath)))
1937 if (fname.empty()) {
1938 FileDialog fileDlg(_("Select document to open"),
1940 make_pair(_("Documents|#o#O"), from_utf8(lyxrc.document_path)),
1941 make_pair(_("Examples|#E#e"), from_utf8(addPath(package().system_support().absFilename(), "examples"))));
1943 FileDialog::Result result =
1944 fileDlg.open(from_utf8(initpath),
1945 FileFilterList(_("LyX Documents (*.lyx)")),
1948 if (result.first == FileDialog::Later)
1951 filename = to_utf8(result.second);
1953 // check selected filename
1954 if (filename.empty()) {
1955 lyx_view_->message(_("Canceled."));
1961 // get absolute path of file and add ".lyx" to the filename if
1963 FileName const fullname = fileSearch(string(), filename, "lyx");
1964 if (!fullname.empty())
1965 filename = fullname.absFilename();
1967 // if the file doesn't exist, let the user create one
1968 if (!fs::exists(fullname.toFilesystemEncoding())) {
1969 // the user specifically chose this name. Believe him.
1970 Buffer * const b = newFile(filename, string(), true);
1972 lyx_view_->setBuffer(b);
1976 docstring const disp_fn = makeDisplayPath(filename);
1977 lyx_view_->message(bformat(_("Opening document %1$s..."), disp_fn));
1980 if (lyx_view_->loadLyXFile(fullname)) {
1981 str2 = bformat(_("Document %1$s opened."), disp_fn);
1983 str2 = bformat(_("Could not open document %1$s"), disp_fn);
1985 lyx_view_->message(str2);
1989 void LyXFunc::doImport(string const & argument)
1992 string filename = split(argument, format, ' ');
1994 LYXERR(Debug::INFO) << "LyXFunc::doImport: " << format
1995 << " file: " << filename << endl;
1997 // need user interaction
1998 if (filename.empty()) {
1999 string initpath = lyxrc.document_path;
2001 if (view()->buffer()) {
2002 string const trypath = lyx_view_->buffer()->filePath();
2003 // If directory is writeable, use this as default.
2004 if (isDirWriteable(FileName(trypath)))
2008 docstring const text = bformat(_("Select %1$s file to import"),
2009 formats.prettyName(format));
2011 FileDialog fileDlg(text,
2013 make_pair(_("Documents|#o#O"), from_utf8(lyxrc.document_path)),
2014 make_pair(_("Examples|#E#e"),
2015 from_utf8(addPath(package().system_support().absFilename(), "examples"))));
2017 docstring filter = formats.prettyName(format);
2020 filter += from_utf8(formats.extension(format));
2023 FileDialog::Result result =
2024 fileDlg.open(from_utf8(initpath),
2025 FileFilterList(filter),
2028 if (result.first == FileDialog::Later)
2031 filename = to_utf8(result.second);
2033 // check selected filename
2034 if (filename.empty())
2035 lyx_view_->message(_("Canceled."));
2038 if (filename.empty())
2041 // get absolute path of file
2042 FileName const fullname(makeAbsPath(filename));
2044 FileName const lyxfile(changeExtension(fullname.absFilename(), ".lyx"));
2046 // Check if the document already is open
2047 if (use_gui && theBufferList().exists(lyxfile.absFilename())) {
2048 if (!theBufferList().close(theBufferList().getBuffer(lyxfile.absFilename()), true)) {
2049 lyx_view_->message(_("Canceled."));
2054 // if the file exists already, and we didn't do
2055 // -i lyx thefile.lyx, warn
2056 if (fs::exists(lyxfile.toFilesystemEncoding()) && fullname != lyxfile) {
2057 docstring const file = makeDisplayPath(lyxfile.absFilename(), 30);
2059 docstring text = bformat(_("The document %1$s already exists.\n\n"
2060 "Do you want to over-write that document?"), file);
2061 int const ret = Alert::prompt(_("Over-write document?"),
2062 text, 0, 1, _("&Over-write"), _("&Cancel"));
2065 lyx_view_->message(_("Canceled."));
2070 ErrorList errorList;
2071 Importer::Import(lyx_view_, fullname, format, errorList);
2072 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2076 void LyXFunc::closeBuffer()
2078 // goto bookmark to update bookmark pit.
2079 for (size_t i = 0; i < LyX::ref().session().bookmarks().size(); ++i)
2080 gotoBookmark(i+1, false, false);
2081 if (theBufferList().close(lyx_view_->buffer(), true) && !quitting) {
2082 if (theBufferList().empty()) {
2083 // need this otherwise SEGV may occur while
2084 // trying to set variables that don't exist
2085 // since there's no current buffer
2086 lyx_view_->getDialogs().hideBufferDependent();
2088 lyx_view_->setBuffer(theBufferList().first());
2094 void LyXFunc::reloadBuffer()
2096 FileName filename(lyx_view_->buffer()->fileName());
2098 lyx_view_->loadLyXFile(filename);
2101 // Each "lyx_view_" should have it's own message method. lyxview and
2102 // the minibuffer would use the minibuffer, but lyxserver would
2103 // send an ERROR signal to its client. Alejandro 970603
2104 // This function is bit problematic when it comes to NLS, to make the
2105 // lyx servers client be language indepenent we must not translate
2106 // strings sent to this func.
2107 void LyXFunc::setErrorMessage(docstring const & m) const
2109 dispatch_buffer = m;
2114 void LyXFunc::setMessage(docstring const & m) const
2116 dispatch_buffer = m;
2120 docstring const LyXFunc::viewStatusMessage()
2122 // When meta-fake key is pressed, show the key sequence so far + "M-".
2124 return keyseq->print(true) + "M-";
2126 // Else, when a non-complete key sequence is pressed,
2127 // show the available options.
2128 if (keyseq->length() > 0 && !keyseq->deleted())
2129 return keyseq->printOptions(true);
2131 if (!view()->buffer())
2132 return _("Welcome to LyX!");
2134 return view()->cursor().currentState();
2138 BufferView * LyXFunc::view() const
2140 BOOST_ASSERT(lyx_view_);
2141 return lyx_view_->view();
2145 bool LyXFunc::wasMetaKey() const
2147 return (meta_fake_bit != key_modifier::none);
2153 void actOnUpdatedPrefs(LyXRC const & lyxrc_orig, LyXRC const & lyxrc_new)
2155 // Why the switch you might ask. It is a trick to ensure that all
2156 // the elements in the LyXRCTags enum is handled. As you can see
2157 // there are no breaks at all. So it is just a huge fall-through.
2158 // The nice thing is that we will get a warning from the compiler
2159 // if we forget an element.
2160 LyXRC::LyXRCTags tag = LyXRC::RC_LAST;
2162 case LyXRC::RC_ACCEPT_COMPOUND:
2163 case LyXRC::RC_ALT_LANG:
2164 case LyXRC::RC_PLAINTEXT_ROFF_COMMAND:
2165 case LyXRC::RC_PLAINTEXT_LINELEN:
2166 case LyXRC::RC_AUTOREGIONDELETE:
2167 case LyXRC::RC_AUTORESET_OPTIONS:
2168 case LyXRC::RC_AUTOSAVE:
2169 case LyXRC::RC_AUTO_NUMBER:
2170 case LyXRC::RC_BACKUPDIR_PATH:
2171 case LyXRC::RC_BIBTEX_COMMAND:
2172 case LyXRC::RC_BINDFILE:
2173 case LyXRC::RC_CHECKLASTFILES:
2174 case LyXRC::RC_USELASTFILEPOS:
2175 case LyXRC::RC_LOADSESSION:
2176 case LyXRC::RC_CHKTEX_COMMAND:
2177 case LyXRC::RC_CONVERTER:
2178 case LyXRC::RC_CONVERTER_CACHE_MAXAGE:
2179 case LyXRC::RC_COPIER:
2180 case LyXRC::RC_CURSOR_FOLLOWS_SCROLLBAR:
2181 case LyXRC::RC_CUSTOM_EXPORT_COMMAND:
2182 case LyXRC::RC_CUSTOM_EXPORT_FORMAT:
2183 case LyXRC::RC_DATE_INSERT_FORMAT:
2184 case LyXRC::RC_DEFAULT_LANGUAGE:
2185 case LyXRC::RC_DEFAULT_PAPERSIZE:
2186 case LyXRC::RC_DIALOGS_ICONIFY_WITH_MAIN:
2187 case LyXRC::RC_DISPLAY_GRAPHICS:
2188 case LyXRC::RC_DOCUMENTPATH:
2189 if (lyxrc_orig.document_path != lyxrc_new.document_path) {
2190 string const encoded = FileName(
2191 lyxrc_new.document_path).toFilesystemEncoding();
2192 if (fs::exists(encoded) && fs::is_directory(encoded))
2193 support::package().document_dir() = FileName(lyxrc.document_path);
2195 case LyXRC::RC_ESC_CHARS:
2196 case LyXRC::RC_FONT_ENCODING:
2197 case LyXRC::RC_FORMAT:
2198 case LyXRC::RC_INDEX_COMMAND:
2199 case LyXRC::RC_INPUT:
2200 case LyXRC::RC_KBMAP:
2201 case LyXRC::RC_KBMAP_PRIMARY:
2202 case LyXRC::RC_KBMAP_SECONDARY:
2203 case LyXRC::RC_LABEL_INIT_LENGTH:
2204 case LyXRC::RC_LANGUAGE_AUTO_BEGIN:
2205 case LyXRC::RC_LANGUAGE_AUTO_END:
2206 case LyXRC::RC_LANGUAGE_COMMAND_BEGIN:
2207 case LyXRC::RC_LANGUAGE_COMMAND_END:
2208 case LyXRC::RC_LANGUAGE_COMMAND_LOCAL:
2209 case LyXRC::RC_LANGUAGE_GLOBAL_OPTIONS:
2210 case LyXRC::RC_LANGUAGE_PACKAGE:
2211 case LyXRC::RC_LANGUAGE_USE_BABEL:
2212 case LyXRC::RC_MAKE_BACKUP:
2213 case LyXRC::RC_MARK_FOREIGN_LANGUAGE:
2214 case LyXRC::RC_NUMLASTFILES:
2215 case LyXRC::RC_PATH_PREFIX:
2216 if (lyxrc_orig.path_prefix != lyxrc_new.path_prefix) {
2217 support::prependEnvPath("PATH", lyxrc.path_prefix);
2219 case LyXRC::RC_PERS_DICT:
2220 case LyXRC::RC_PREVIEW:
2221 case LyXRC::RC_PREVIEW_HASHED_LABELS:
2222 case LyXRC::RC_PREVIEW_SCALE_FACTOR:
2223 case LyXRC::RC_PRINTCOLLCOPIESFLAG:
2224 case LyXRC::RC_PRINTCOPIESFLAG:
2225 case LyXRC::RC_PRINTER:
2226 case LyXRC::RC_PRINTEVENPAGEFLAG:
2227 case LyXRC::RC_PRINTEXSTRAOPTIONS:
2228 case LyXRC::RC_PRINTFILEEXTENSION:
2229 case LyXRC::RC_PRINTLANDSCAPEFLAG:
2230 case LyXRC::RC_PRINTODDPAGEFLAG:
2231 case LyXRC::RC_PRINTPAGERANGEFLAG:
2232 case LyXRC::RC_PRINTPAPERDIMENSIONFLAG:
2233 case LyXRC::RC_PRINTPAPERFLAG:
2234 case LyXRC::RC_PRINTREVERSEFLAG:
2235 case LyXRC::RC_PRINTSPOOL_COMMAND:
2236 case LyXRC::RC_PRINTSPOOL_PRINTERPREFIX:
2237 case LyXRC::RC_PRINTTOFILE:
2238 case LyXRC::RC_PRINTTOPRINTER:
2239 case LyXRC::RC_PRINT_ADAPTOUTPUT:
2240 case LyXRC::RC_PRINT_COMMAND:
2241 case LyXRC::RC_RTL_SUPPORT:
2242 case LyXRC::RC_SCREEN_DPI:
2243 case LyXRC::RC_SCREEN_FONT_ROMAN:
2244 case LyXRC::RC_SCREEN_FONT_ROMAN_FOUNDRY:
2245 case LyXRC::RC_SCREEN_FONT_SANS:
2246 case LyXRC::RC_SCREEN_FONT_SANS_FOUNDRY:
2247 case LyXRC::RC_SCREEN_FONT_SCALABLE:
2248 case LyXRC::RC_SCREEN_FONT_SIZES:
2249 case LyXRC::RC_SCREEN_FONT_TYPEWRITER:
2250 case LyXRC::RC_SCREEN_FONT_TYPEWRITER_FOUNDRY:
2251 case LyXRC::RC_SCREEN_GEOMETRY_HEIGHT:
2252 case LyXRC::RC_SCREEN_GEOMETRY_WIDTH:
2253 case LyXRC::RC_SCREEN_GEOMETRY_XYSAVED:
2254 case LyXRC::RC_SCREEN_ZOOM:
2255 case LyXRC::RC_SERVERPIPE:
2256 case LyXRC::RC_SET_COLOR:
2257 case LyXRC::RC_SHOW_BANNER:
2258 case LyXRC::RC_SPELL_COMMAND:
2259 case LyXRC::RC_TEMPDIRPATH:
2260 case LyXRC::RC_TEMPLATEPATH:
2261 case LyXRC::RC_TEX_ALLOWS_SPACES:
2262 case LyXRC::RC_TEX_EXPECTS_WINDOWS_PATHS:
2263 if (lyxrc_orig.windows_style_tex_paths != lyxrc_new.windows_style_tex_paths) {
2264 support::os::windows_style_tex_paths(lyxrc_new.windows_style_tex_paths);
2266 case LyXRC::RC_UIFILE:
2267 case LyXRC::RC_USER_EMAIL:
2268 case LyXRC::RC_USER_NAME:
2269 case LyXRC::RC_USETEMPDIR:
2270 case LyXRC::RC_USE_ALT_LANG:
2271 case LyXRC::RC_USE_CONVERTER_CACHE:
2272 case LyXRC::RC_USE_ESC_CHARS:
2273 case LyXRC::RC_USE_INP_ENC:
2274 case LyXRC::RC_USE_PERS_DICT:
2275 case LyXRC::RC_USE_SPELL_LIB:
2276 case LyXRC::RC_VIEWDVI_PAPEROPTION:
2277 case LyXRC::RC_VIEWER:
2278 case LyXRC::RC_LAST: