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 "BranchList.h"
26 #include "buffer_funcs.h"
27 #include "bufferlist.h"
28 #include "bufferparams.h"
29 #include "BufferView.h"
31 #include "CutAndPaste.h"
33 #include "dispatchresult.h"
35 #include "errorlist.h"
38 #include "funcrequest.h"
41 #include "insetiterator.h"
47 #include "LyXAction.h"
52 #include "lyxserver.h"
53 #include "lyxtextclasslist.h"
55 #include "paragraph.h"
56 #include "pariterator.h"
57 #include "ParagraphParameters.h"
60 #include "insets/insetbox.h"
61 #include "insets/insetbranch.h"
62 #include "insets/insetcommand.h"
63 #include "insets/insetert.h"
64 #include "insets/insetexternal.h"
65 #include "insets/insetfloat.h"
66 #include "insets/insetgraphics.h"
67 #include "insets/insetnote.h"
68 #include "insets/insettabular.h"
69 #include "insets/insetvspace.h"
70 #include "insets/insetwrap.h"
72 #include "frontends/Alert.h"
73 #include "frontends/Dialogs.h"
74 #include "frontends/FileDialog.h"
75 #include "frontends/lyx_gui.h"
76 #include "frontends/LyXKeySym.h"
77 #include "frontends/LyXView.h"
78 #include "frontends/Menubar.h"
79 #include "frontends/Toolbars.h"
81 #include "support/FileInfo.h"
82 #include "support/filetools.h"
83 #include "support/forkedcontr.h"
84 #include "support/globbing.h"
85 #include "support/lstrings.h"
86 #include "support/path.h"
87 #include "support/path_defines.h"
88 #include "support/systemcall.h"
89 #include "support/tostr.h"
90 #include "support/os.h"
94 using bv_funcs::freefont2string;
96 using lyx::support::AddName;
97 using lyx::support::AddPath;
98 using lyx::support::bformat;
99 using lyx::support::ChangeExtension;
100 using lyx::support::contains;
101 using lyx::support::FileFilterList;
102 using lyx::support::FileInfo;
103 using lyx::support::FileSearch;
104 using lyx::support::ForkedcallsController;
105 using lyx::support::i18nLibFileSearch;
106 using lyx::support::IsDirWriteable;
107 using lyx::support::IsFileReadable;
108 using lyx::support::isStrInt;
109 using lyx::support::MakeAbsPath;
110 using lyx::support::MakeDisplayPath;
111 using lyx::support::Path;
112 using lyx::support::QuoteName;
113 using lyx::support::rtrim;
114 using lyx::support::split;
115 using lyx::support::strToInt;
116 using lyx::support::strToUnsignedInt;
117 using lyx::support::subst;
118 using lyx::support::system_lyxdir;
119 using lyx::support::Systemcall;
120 using lyx::support::token;
121 using lyx::support::trim;
122 using lyx::support::user_lyxdir;
123 using lyx::support::prefixIs;
124 using lyx::support::os::getTmpDir;
127 using std::make_pair;
130 using std::istringstream;
132 namespace biblio = lyx::biblio;
135 extern BufferList bufferlist;
136 extern LyXServer * lyxserver;
137 extern bool selection_possible;
139 extern boost::scoped_ptr<kb_keymap> toplevel_keymap;
142 extern tex_accent_struct get_accent(kb_action action);
145 LyXFunc::LyXFunc(LyXView * lv)
148 keyseq(toplevel_keymap.get(), toplevel_keymap.get()),
149 cancel_meta_seq(toplevel_keymap.get(), toplevel_keymap.get()),
150 meta_fake_bit(key_modifier::none)
155 void LyXFunc::handleKeyFunc(kb_action action)
157 char c = encoded_last_key;
159 if (keyseq.length()) {
163 owner->getIntl().getTransManager()
164 .deadkey(c, get_accent(action).accent, view()->getLyXText());
165 // Need to clear, in case the minibuffer calls these
168 // copied verbatim from do_accent_char
169 view()->cursor().resetAnchor();
174 void LyXFunc::processKeySym(LyXKeySymPtr keysym, key_modifier::state state)
176 lyxerr[Debug::KEY] << "KeySym is " << keysym->getSymbolName() << endl;
178 // Do nothing if we have nothing (JMarc)
179 if (!keysym->isOK()) {
180 lyxerr[Debug::KEY] << "Empty kbd action (probably composing)"
185 if (keysym->isModifier()) {
186 lyxerr[Debug::KEY] << "isModifier true" << endl;
190 Encoding const * encoding = view()->cursor().getEncoding();
192 encoded_last_key = keysym->getISOEncoded(encoding ? encoding->Name() : "");
194 // Do a one-deep top-level lookup for
195 // cancel and meta-fake keys. RVDK_PATCH_5
196 cancel_meta_seq.reset();
198 FuncRequest func = cancel_meta_seq.addkey(keysym, state);
199 lyxerr[Debug::KEY] << "action first set to [" << func.action << ']' << endl;
201 // When not cancel or meta-fake, do the normal lookup.
202 // Note how the meta_fake Mod1 bit is OR-ed in and reset afterwards.
203 // Mostly, meta_fake_bit = key_modifier::none. RVDK_PATCH_5.
204 if ((func.action != LFUN_CANCEL) && (func.action != LFUN_META_FAKE)) {
205 // remove Caps Lock and Mod2 as a modifiers
206 func = keyseq.addkey(keysym, (state | meta_fake_bit));
207 lyxerr[Debug::KEY] << "action now set to ["
208 << func.action << ']' << endl;
211 // Dont remove this unless you know what you are doing.
212 meta_fake_bit = key_modifier::none;
214 // can this happen now ?
215 if (func.action == LFUN_NOACTION) {
216 func = FuncRequest(LFUN_PREFIX);
219 if (lyxerr.debugging(Debug::KEY)) {
220 lyxerr << "Key [action="
221 << func.action << "]["
222 << keyseq.print() << ']'
226 // already here we know if it any point in going further
227 // why not return already here if action == -1 and
228 // num_bytes == 0? (Lgb)
230 if (keyseq.length() > 1) {
231 owner->message(keyseq.print());
235 // Maybe user can only reach the key via holding down shift.
236 // Let's see. But only if shift is the only modifier
237 if (func.action == LFUN_UNKNOWN_ACTION &&
238 state == key_modifier::shift) {
239 lyxerr[Debug::KEY] << "Trying without shift" << endl;
240 func = keyseq.addkey(keysym, key_modifier::none);
241 lyxerr[Debug::KEY] << "Action now " << func.action << endl;
244 if (func.action == LFUN_UNKNOWN_ACTION) {
245 // Hmm, we didn't match any of the keysequences. See
246 // if it's normal insertable text not already covered
248 if (keysym->isText() && keyseq.length() == 1) {
249 lyxerr[Debug::KEY] << "isText() is true, inserting." << endl;
250 func = FuncRequest(LFUN_SELFINSERT);
252 lyxerr[Debug::KEY] << "Unknown, !isText() - giving up" << endl;
253 owner->message(_("Unknown function."));
258 if (func.action == LFUN_SELFINSERT) {
259 if (encoded_last_key != 0) {
260 string arg(1, encoded_last_key);
261 dispatch(FuncRequest(LFUN_SELFINSERT, arg));
263 << "SelfInsert arg[`" << arg << "']" << endl;
271 FuncStatus LyXFunc::getStatus(FuncRequest const & cmd) const
273 //lyxerr << "LyXFunc::getStatus: cmd: " << cmd << endl;
275 LCursor & cur = view()->cursor();
277 /* In LyX/Mac, when a dialog is open, the menus of the
278 application can still be accessed without giving focus to
279 the main window. In this case, we want to disable the menu
280 entries that are buffer-related.
283 if (cmd.origin == FuncRequest::UI && !owner->hasFocus())
286 buf = owner->buffer();
288 if (cmd.action == LFUN_NOACTION) {
289 flag.message(N_("Nothing to do"));
294 switch (cmd.action) {
295 case LFUN_UNKNOWN_ACTION:
296 #ifndef HAVE_LIBAIKSAURUS
297 case LFUN_THESAURUS_ENTRY:
303 flag |= lyx_gui::getStatus(cmd);
306 if (flag.unknown()) {
307 flag.message(N_("Unknown action"));
311 // the default error message if we disable the command
312 flag.message(N_("Command disabled"));
316 // Check whether we need a buffer
317 if (!lyxaction.funcHasFlag(cmd.action, LyXAction::NoBuffer) && !buf) {
319 flag.message(N_("Command not allowed with"
320 "out any document open"));
325 // I would really like to avoid having this switch and rather try to
326 // encode this in the function itself.
327 // -- And I'd rather let an inset decide which LFUNs it is willing
328 // to handle (Andre')
330 switch (cmd.action) {
331 case LFUN_TOOLTIPS_TOGGLE:
332 flag.setOnOff(owner->getDialogs().tooltipsEnabled());
335 case LFUN_READ_ONLY_TOGGLE:
336 flag.setOnOff(buf->isReadonly());
339 case LFUN_SWITCHBUFFER:
340 // toggle on the current buffer, but do not toggle off
341 // the other ones (is that a good idea?)
342 if (cmd.argument == buf->fileName())
347 enable = cmd.argument == "custom"
348 || Exporter::IsExportable(*buf, cmd.argument);
352 enable = cur.selection();
356 enable = buf->isLatex() && lyxrc.chktex_command != "none";
360 enable = Exporter::IsExportable(*buf, "program");
363 case LFUN_LAYOUT_TABULAR:
364 enable = cur.innerInsetOfType(InsetBase::TABULAR_CODE);
368 case LFUN_LAYOUT_PARAGRAPH:
369 enable = !cur.inset().forceDefaultParagraphs(&cur.inset());
372 case LFUN_VC_REGISTER:
373 enable = !buf->lyxvc().inUse();
375 case LFUN_VC_CHECKIN:
376 enable = buf->lyxvc().inUse() && !buf->isReadonly();
378 case LFUN_VC_CHECKOUT:
379 enable = buf->lyxvc().inUse() && buf->isReadonly();
383 enable = buf->lyxvc().inUse();
385 case LFUN_MENURELOAD:
386 enable = !buf->isUnnamed() && !buf->isClean();
390 case LFUN_INSET_SETTINGS: {
394 UpdatableInset * inset = cur.inset().asUpdatableInset();
395 lyxerr << "inset: " << inset << endl;
399 InsetOld::Code code = inset->lyxCode();
401 case InsetOld::TABULAR_CODE:
402 enable = cmd.argument == "tabular";
404 case InsetOld::ERT_CODE:
405 enable = cmd.argument == "ert";
407 case InsetOld::FLOAT_CODE:
408 enable = cmd.argument == "float";
410 case InsetOld::WRAP_CODE:
411 enable = cmd.argument == "wrap";
413 case InsetOld::NOTE_CODE:
414 enable = cmd.argument == "note";
416 case InsetOld::BRANCH_CODE:
417 enable = cmd.argument == "branch";
419 case InsetOld::BOX_CODE:
420 enable = cmd.argument == "box";
428 case LFUN_DIALOG_SHOW: {
429 string const name = cmd.getArg(0);
431 enable = name == "aboutlyx"
435 || name == "texinfo";
436 else if (name == "print")
437 enable = Exporter::IsExportable(*buf, "dvi")
438 && lyxrc.print_command != "none";
439 else if (name == "character")
440 enable = cur.inset().lyxCode() != InsetOld::ERT_CODE;
441 else if (name == "vclog")
442 enable = buf->lyxvc().inUse();
443 else if (name == "latexlog")
444 enable = IsFileReadable(buf->getLogName().second);
448 case LFUN_DIALOG_UPDATE: {
449 string const name = cmd.getArg(0);
451 enable = name == "prefs";
455 // this one is difficult to get right. As a half-baked
456 // solution, we consider only the first action of the sequence
457 case LFUN_SEQUENCE: {
458 // argument contains ';'-terminated commands
459 string const firstcmd = token(cmd.argument, ';', 0);
460 FuncRequest func(lyxaction.lookupFunc(firstcmd));
461 func.origin = cmd.origin;
462 flag = getStatus(func);
466 case LFUN_MENUNEWTMPLT:
467 case LFUN_WORDFINDFORWARD:
468 case LFUN_WORDFINDBACKWARD:
470 case LFUN_EXEC_COMMAND:
473 case LFUN_CLOSEBUFFER:
482 case LFUN_RECONFIGURE:
486 case LFUN_DROP_LAYOUTS_CHOICE:
487 case LFUN_MENU_OPEN_BY_NAME:
490 case LFUN_GOTOFILEROW:
491 case LFUN_GOTO_PARAGRAPH:
492 case LFUN_DIALOG_SHOW_NEW_INSET:
493 case LFUN_DIALOG_SHOW_NEXT_INSET:
494 case LFUN_DIALOG_HIDE:
495 case LFUN_DIALOG_DISCONNECT_INSET:
497 case LFUN_TOGGLECURSORFOLLOW:
501 case LFUN_KMAP_TOGGLE:
503 case LFUN_EXPORT_CUSTOM:
505 case LFUN_SAVEPREFERENCES:
506 case LFUN_SCREEN_FONT_UPDATE:
509 case LFUN_EXTERNAL_EDIT:
510 case LFUN_GRAPHICS_EDIT:
511 case LFUN_ALL_INSETS_TOGGLE:
512 case LFUN_LANGUAGE_BUFFER:
513 case LFUN_TEXTCLASS_APPLY:
514 case LFUN_TEXTCLASS_LOAD:
515 case LFUN_SAVE_AS_DEFAULT:
516 case LFUN_BUFFERPARAMS_APPLY:
517 case LFUN_LYXRC_APPLY:
518 case LFUN_NEXTBUFFER:
519 case LFUN_PREVIOUSBUFFER:
520 // these are handled in our dispatch()
525 if (!cur.getStatus(cmd, flag))
526 flag = view()->getStatus(cmd);
532 // Can we use a readonly buffer?
533 if (buf && buf->isReadonly()
534 && !lyxaction.funcHasFlag(cmd.action, LyXAction::ReadOnly)
535 && !lyxaction.funcHasFlag(cmd.action, LyXAction::NoBuffer)) {
536 flag.message(N_("Document is read-only"));
540 //lyxerr << "LyXFunc::getStatus: got: " << flag.enabled() << endl;
547 bool ensureBufferClean(BufferView * bv)
549 Buffer & buf = *bv->buffer();
553 string const file = MakeDisplayPath(buf.fileName(), 30);
554 string text = bformat(_("The document %1$s has unsaved "
555 "changes.\n\nDo you want to save "
556 "the document?"), file);
557 int const ret = Alert::prompt(_("Save changed document?"),
558 text, 0, 1, _("&Save"),
562 bv->owner()->dispatch(FuncRequest(LFUN_MENUWRITE));
564 return buf.isClean();
568 void showPrintError(string const & name)
570 string str = bformat(_("Could not print the document %1$s.\n"
571 "Check that your printer is set up correctly."),
572 MakeDisplayPath(name, 50));
573 Alert::error(_("Print document failed"), str);
577 void loadTextclass(string const & name)
579 std::pair<bool, lyx::textclass_type> const tc_pair =
580 textclasslist.NumberOfClass(name);
582 if (!tc_pair.first) {
583 lyxerr << "Document class \"" << name
584 << "\" does not exist."
589 lyx::textclass_type const tc = tc_pair.second;
591 if (!textclasslist[tc].load()) {
592 string s = bformat(_("The document could not be converted\n"
593 "into the document class %1$s."),
594 textclasslist[tc].name());
595 Alert::error(_("Could not change class"), s);
602 void LyXFunc::dispatch(FuncRequest const & cmd)
604 string const argument = cmd.argument;
605 kb_action const action = cmd.action;
607 lyxerr[Debug::ACTION] << "LyXFunc::dispatch: cmd: " << cmd << endl;
608 //lyxerr << "LyXFunc::dispatch: cmd: " << cmd << endl;
610 // we have not done anything wrong yet.
612 dispatch_buffer.erase();
613 selection_possible = false;
617 FuncStatus const flag = getStatus(cmd);
618 if (!flag.enabled()) {
619 // We cannot use this function here
620 lyxerr[Debug::ACTION] << "LyXFunc::dispatch: "
621 << lyxaction.getActionName(action)
622 << " [" << action << "] is disabled at this location"
624 setErrorMessage(flag.message());
627 if (view()->available())
628 view()->hideCursor();
632 case LFUN_WORDFINDFORWARD:
633 case LFUN_WORDFINDBACKWARD: {
634 static string last_search;
635 string searched_string;
637 if (!argument.empty()) {
638 last_search = argument;
639 searched_string = argument;
641 searched_string = last_search;
644 if (searched_string.empty())
647 bool const fw = action == LFUN_WORDFINDFORWARD;
649 lyx::find::find2string(searched_string, true, false, fw);
650 lyx::find::find(view(), FuncRequest(LFUN_WORD_FIND, data));
655 owner->message(keyseq.printOptions());
658 case LFUN_EXEC_COMMAND:
659 owner->getToolbars().display("minibuffer", true);
660 owner->focus_command_buffer();
665 meta_fake_bit = key_modifier::none;
666 if (view()->available())
667 // cancel any selection
668 dispatch(FuncRequest(LFUN_MARK_OFF));
669 setMessage(N_("Cancel"));
673 meta_fake_bit = key_modifier::alt;
674 setMessage(keyseq.print());
677 case LFUN_READ_ONLY_TOGGLE:
678 if (owner->buffer()->lyxvc().inUse())
679 owner->buffer()->lyxvc().toggleReadOnly();
681 owner->buffer()->setReadonly(
682 !owner->buffer()->isReadonly());
685 // --- Menus -----------------------------------------------
687 menuNew(argument, false);
690 case LFUN_MENUNEWTMPLT:
691 menuNew(argument, true);
694 case LFUN_CLOSEBUFFER:
699 if (!owner->buffer()->isUnnamed()) {
700 string const str = bformat(_("Saving document %1$s..."),
701 MakeDisplayPath(owner->buffer()->fileName()));
703 MenuWrite(owner->buffer());
704 owner->message(str + _(" done."));
706 WriteAs(owner->buffer());
710 WriteAs(owner->buffer(), argument);
713 case LFUN_MENURELOAD: {
714 string const file = MakeDisplayPath(view()->buffer()->fileName(), 20);
715 string text = bformat(_("Any changes will be lost. Are you sure "
716 "you want to revert to the saved version of the document %1$s?"), file);
717 int const ret = Alert::prompt(_("Revert to saved document?"),
718 text, 0, 1, _("&Revert"), _("&Cancel"));
726 Exporter::Export(owner->buffer(), argument, true);
727 view()->showErrorList(BufferFormat(*owner->buffer()));
731 Exporter::Preview(owner->buffer(), argument);
732 view()->showErrorList(BufferFormat(*owner->buffer()));
736 Exporter::Export(owner->buffer(), "program", true);
737 view()->showErrorList(_("Build"));
741 owner->buffer()->runChktex();
742 view()->showErrorList(_("ChkTeX"));
746 if (argument == "custom")
747 owner->getDialogs().show("sendto");
749 Exporter::Export(owner->buffer(), argument, false);
750 view()->showErrorList(BufferFormat(*owner->buffer()));
754 case LFUN_EXPORT_CUSTOM: {
756 string command = split(argument, format_name, ' ');
757 Format const * format = formats.getFormat(format_name);
759 lyxerr << "Format \"" << format_name
760 << "\" not recognized!"
765 Buffer * buffer = owner->buffer();
767 // The name of the file created by the conversion process
770 // Output to filename
771 if (format->name() == "lyx") {
772 string const latexname =
773 buffer->getLatexName(false);
774 filename = ChangeExtension(latexname,
775 format->extension());
776 filename = AddName(buffer->temppath(), filename);
778 if (!buffer->writeFile(filename))
782 Exporter::Export(buffer, format_name, true,
786 // Substitute $$FName for filename
787 if (!contains(command, "$$FName"))
788 command = "( " + command + " ) < $$FName";
789 command = subst(command, "$$FName", filename);
791 // Execute the command in the background
793 call.startscript(Systemcall::DontWait, command);
800 string command = split(split(argument, target, ' '),
804 || target_name.empty()
805 || command.empty()) {
806 lyxerr << "Unable to parse \""
807 << argument << '"' << std::endl;
810 if (target != "printer" && target != "file") {
811 lyxerr << "Unrecognized target \""
812 << target << '"' << std::endl;
816 Buffer * buffer = owner->buffer();
818 if (!Exporter::Export(buffer, "dvi", true)) {
819 showPrintError(buffer->fileName());
823 // Push directory path.
824 string const path = buffer->temppath();
827 // there are three cases here:
828 // 1. we print to a file
829 // 2. we print directly to a printer
830 // 3. we print using a spool command (print to file first)
833 string const dviname =
834 ChangeExtension(buffer->getLatexName(true),
837 if (target == "printer") {
838 if (!lyxrc.print_spool_command.empty()) {
839 // case 3: print using a spool
840 string const psname =
841 ChangeExtension(dviname,".ps");
842 command += lyxrc.print_to_file
845 + QuoteName(dviname);
848 lyxrc.print_spool_command +' ';
849 if (target_name != "default") {
850 command2 += lyxrc.print_spool_printerprefix
854 command2 += QuoteName(psname);
856 // If successful, then spool command
857 res = one.startscript(
862 res = one.startscript(
863 Systemcall::DontWait,
866 // case 2: print directly to a printer
867 res = one.startscript(
868 Systemcall::DontWait,
869 command + QuoteName(dviname));
873 // case 1: print to a file
874 command += lyxrc.print_to_file
875 + QuoteName(MakeAbsPath(target_name,
878 + QuoteName(dviname);
879 res = one.startscript(Systemcall::DontWait,
884 showPrintError(buffer->fileName());
897 InsetCommandParams p("tableofcontents");
898 string const data = InsetCommandMailer::params2string("toc", p);
899 owner->getDialogs().show("toc", data, 0);
907 case LFUN_RECONFIGURE:
911 case LFUN_HELP_OPEN: {
912 string const arg = argument;
914 setErrorMessage(N_("Missing argument"));
917 string const fname = i18nLibFileSearch("doc", arg, "lyx");
919 lyxerr << "LyX: unable to find documentation file `"
920 << arg << "'. Bad installation?" << endl;
923 owner->message(bformat(_("Opening help file %1$s..."),
924 MakeDisplayPath(fname)));
925 view()->loadLyXFile(fname, false);
929 // --- version control -------------------------------
930 case LFUN_VC_REGISTER:
931 if (!ensureBufferClean(view()))
933 if (!owner->buffer()->lyxvc().inUse()) {
934 owner->buffer()->lyxvc().registrer();
939 case LFUN_VC_CHECKIN:
940 if (!ensureBufferClean(view()))
942 if (owner->buffer()->lyxvc().inUse()
943 && !owner->buffer()->isReadonly()) {
944 owner->buffer()->lyxvc().checkIn();
949 case LFUN_VC_CHECKOUT:
950 if (!ensureBufferClean(view()))
952 if (owner->buffer()->lyxvc().inUse()
953 && owner->buffer()->isReadonly()) {
954 owner->buffer()->lyxvc().checkOut();
960 owner->buffer()->lyxvc().revert();
965 owner->buffer()->lyxvc().undoLast();
969 // --- buffers ----------------------------------------
970 case LFUN_SWITCHBUFFER:
971 view()->setBuffer(bufferlist.getBuffer(argument));
974 case LFUN_NEXTBUFFER:
975 view()->setBuffer(bufferlist.next(view()->buffer()));
978 case LFUN_PREVIOUSBUFFER:
979 view()->setBuffer(bufferlist.previous(view()->buffer()));
983 NewFile(view(), argument);
990 case LFUN_DROP_LAYOUTS_CHOICE:
991 owner->getToolbars().openLayoutList();
994 case LFUN_MENU_OPEN_BY_NAME:
995 owner->getMenubar().openByName(argument);
998 // --- lyxserver commands ----------------------------
1000 setMessage(owner->buffer()->fileName());
1001 lyxerr[Debug::INFO] << "FNAME["
1002 << owner->buffer()->fileName()
1007 dispatch_buffer = keyseq.print();
1008 lyxserver->notifyClient(dispatch_buffer);
1011 case LFUN_GOTOFILEROW: {
1014 istringstream is(argument);
1015 is >> file_name >> row;
1016 if (prefixIs(file_name, getTmpDir())) {
1017 // Needed by inverse dvi search. If it is a file
1018 // in tmpdir, call the apropriated function
1019 view()->setBuffer(bufferlist.getBufferFromTmp(file_name));
1021 // Must replace extension of the file to be .lyx
1022 // and get full path
1023 string const s = ChangeExtension(file_name, ".lyx");
1024 // Either change buffer or load the file
1025 if (bufferlist.exists(s)) {
1026 view()->setBuffer(bufferlist.getBuffer(s));
1028 view()->loadLyXFile(s);
1032 view()->setCursorFromRow(row);
1035 // see BufferView_pimpl::center()
1036 view()->updateScrollbar();
1040 case LFUN_GOTO_PARAGRAPH: {
1041 istringstream is(argument);
1044 ParIterator par = owner->buffer()->getParFromID(id);
1045 if (par == owner->buffer()->par_iterator_end()) {
1046 lyxerr[Debug::INFO] << "No matching paragraph found! ["
1047 << id << ']' << endl;
1050 lyxerr[Debug::INFO] << "Paragraph " << par->id()
1051 << " found." << endl;
1055 view()->setCursor(par, 0);
1057 view()->switchKeyMap();
1058 owner->view_state_changed();
1061 // see BufferView_pimpl::center()
1062 view()->updateScrollbar();
1066 case LFUN_DIALOG_SHOW: {
1067 string const name = cmd.getArg(0);
1068 string data = trim(cmd.argument.substr(name.size()));
1070 if (name == "character") {
1071 data = freefont2string();
1073 owner->getDialogs().show("character", data);
1076 else if (name == "latexlog") {
1077 pair<Buffer::LogType, string> const logfile =
1078 owner->buffer()->getLogName();
1079 switch (logfile.first) {
1080 case Buffer::latexlog:
1083 case Buffer::buildlog:
1087 data += logfile.second;
1088 owner->getDialogs().show("log", data);
1090 else if (name == "vclog") {
1091 string const data = "vc " +
1092 owner->buffer()->lyxvc().getLogFile();
1093 owner->getDialogs().show("log", data);
1096 owner->getDialogs().show(name, data);
1100 case LFUN_DIALOG_SHOW_NEW_INSET: {
1101 string const name = cmd.getArg(0);
1102 string data = trim(cmd.argument.substr(name.size()));
1103 if (name == "bibitem" ||
1105 name == "include" ||
1111 InsetCommandParams p(name);
1112 data = InsetCommandMailer::params2string(name, p);
1113 } else if (name == "box") {
1114 // \c data == "Boxed" || "Frameless" etc
1115 InsetBoxParams p(data);
1116 data = InsetBoxMailer::params2string(p);
1117 } else if (name == "branch") {
1118 InsetBranchParams p;
1119 data = InsetBranchMailer::params2string(p);
1120 } else if (name == "citation") {
1121 InsetCommandParams p("cite");
1122 data = InsetCommandMailer::params2string(name, p);
1123 } else if (name == "ert") {
1124 data = InsetERTMailer::params2string(InsetCollapsable::Open);
1125 } else if (name == "external") {
1126 InsetExternalParams p;
1127 Buffer const & buffer = *owner->buffer();
1128 data = InsetExternalMailer::params2string(p, buffer);
1129 } else if (name == "float") {
1131 data = InsetFloatMailer::params2string(p);
1132 } else if (name == "graphics") {
1133 InsetGraphicsParams p;
1134 Buffer const & buffer = *owner->buffer();
1135 data = InsetGraphicsMailer::params2string(p, buffer);
1136 } else if (name == "note") {
1138 data = InsetNoteMailer::params2string(p);
1139 } else if (name == "vspace") {
1141 data = InsetVSpaceMailer::params2string(space);
1142 } else if (name == "wrap") {
1144 data = InsetWrapMailer::params2string(p);
1146 owner->getDialogs().show(name, data, 0);
1150 case LFUN_DIALOG_SHOW_NEXT_INSET:
1153 case LFUN_DIALOG_UPDATE: {
1154 string const & name = argument;
1155 // Can only update a dialog connected to an existing inset
1156 InsetBase * inset = owner->getDialogs().getOpenInset(name);
1158 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument);
1159 inset->dispatch(view()->cursor(), fr);
1160 } else if (name == "paragraph") {
1161 dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
1162 } else if (name == "prefs") {
1163 owner->getDialogs().update(name, string());
1168 case LFUN_DIALOG_HIDE:
1169 Dialogs::hide(argument, 0);
1172 case LFUN_DIALOG_DISCONNECT_INSET:
1173 owner->getDialogs().disconnect(argument);
1176 case LFUN_CHILDOPEN: {
1177 string const filename =
1178 MakeAbsPath(argument, owner->buffer()->filePath());
1179 setMessage(N_("Opening child document ") +
1180 MakeDisplayPath(filename) + "...");
1181 view()->savePosition(0);
1182 string const parentfilename = owner->buffer()->fileName();
1183 if (bufferlist.exists(filename))
1184 view()->setBuffer(bufferlist.getBuffer(filename));
1186 view()->loadLyXFile(filename);
1187 // Set the parent name of the child document.
1188 // This makes insertion of citations and references in the child work,
1189 // when the target is in the parent or another child document.
1190 owner->buffer()->setParentName(parentfilename);
1194 case LFUN_TOGGLECURSORFOLLOW:
1195 lyxrc.cursor_follows_scrollbar = !lyxrc.cursor_follows_scrollbar;
1199 owner->getIntl().KeyMapOn(false);
1202 case LFUN_KMAP_PRIM:
1203 owner->getIntl().KeyMapPrim();
1207 owner->getIntl().KeyMapSec();
1210 case LFUN_KMAP_TOGGLE:
1211 owner->getIntl().ToggleKeyMap();
1217 string rest = split(argument, countstr, ' ');
1218 istringstream is(countstr);
1221 lyxerr << "repeat: count: " << count << " cmd: " << rest << endl;
1222 for (int i = 0; i < count; ++i)
1223 dispatch(lyxaction.lookupFunc(rest));
1227 case LFUN_SEQUENCE: {
1228 // argument contains ';'-terminated commands
1229 string arg = argument;
1230 while (!arg.empty()) {
1232 arg = split(arg, first, ';');
1233 FuncRequest func(lyxaction.lookupFunc(first));
1234 func.origin = cmd.origin;
1240 case LFUN_SAVEPREFERENCES: {
1241 Path p(user_lyxdir());
1242 lyxrc.write("preferences", false);
1246 case LFUN_SCREEN_FONT_UPDATE:
1247 // handle the screen font changes.
1248 lyxrc.set_font_norm_type();
1249 lyx_gui::update_fonts();
1250 // All visible buffers will need resize
1254 case LFUN_SET_COLOR: {
1256 string const x11_name = split(argument, lyx_name, ' ');
1257 if (lyx_name.empty() || x11_name.empty()) {
1258 setErrorMessage(N_("Syntax: set-color <lyx_name>"
1263 bool const graphicsbg_changed =
1264 (lyx_name == lcolor.getLyXName(LColor::graphicsbg) &&
1265 x11_name != lcolor.getX11Name(LColor::graphicsbg));
1267 if (!lcolor.setColor(lyx_name, x11_name)) {
1269 bformat(_("Set-color \"%1$s\" failed "
1270 "- color is undefined or "
1271 "may not be redefined"), lyx_name));
1275 lyx_gui::update_color(lcolor.getFromLyXName(lyx_name));
1277 if (graphicsbg_changed) {
1278 #ifdef WITH_WARNINGS
1279 #warning FIXME!! The graphics cache no longer has a changeDisplay method.
1282 lyx::graphics::GCache::get().changeDisplay(true);
1289 owner->message(argument);
1292 case LFUN_TOOLTIPS_TOGGLE:
1293 owner->getDialogs().toggleTooltips();
1296 case LFUN_EXTERNAL_EDIT: {
1297 FuncRequest fr(action, argument);
1298 InsetExternal().dispatch(view()->cursor(), fr);
1302 case LFUN_GRAPHICS_EDIT: {
1303 FuncRequest fr(action, argument);
1304 InsetGraphics().dispatch(view()->cursor(), fr);
1308 case LFUN_ALL_INSETS_TOGGLE: {
1310 string const name = split(argument, action, ' ');
1311 InsetBase::Code const inset_code =
1312 InsetBase::translate(name);
1314 LCursor & cur = view()->cursor();
1315 FuncRequest fr(LFUN_INSET_TOGGLE, action);
1317 InsetBase & inset = owner->buffer()->inset();
1318 InsetIterator it = inset_iterator_begin(inset);
1319 InsetIterator const end = inset_iterator_end(inset);
1320 for (; it != end; ++it) {
1321 if (inset_code == InsetBase::NO_CODE
1322 || inset_code == it->lyxCode())
1323 it->dispatch(cur, fr);
1328 case LFUN_LANGUAGE_BUFFER: {
1329 Buffer & buffer = *owner->buffer();
1330 Language const * oldL = buffer.params().language;
1331 Language const * newL = languages.getLanguage(argument);
1332 if (!newL || oldL == newL)
1335 if (oldL->RightToLeft() == newL->RightToLeft()
1336 && !buffer.isMultiLingual())
1337 buffer.changeLanguage(oldL, newL);
1339 buffer.updateDocLang(newL);
1343 case LFUN_SAVE_AS_DEFAULT: {
1344 string const fname =
1345 AddName(AddPath(user_lyxdir(), "templates/"),
1347 Buffer defaults(fname);
1349 istringstream ss(argument);
1352 int const unknown_tokens = defaults.readHeader(lex);
1354 if (unknown_tokens != 0) {
1355 lyxerr << "Warning in LFUN_SAVE_AS_DEFAULT!\n"
1356 << unknown_tokens << " unknown token"
1357 << (unknown_tokens == 1 ? "" : "s")
1361 if (defaults.writeFile(defaults.fileName()))
1362 setMessage(_("Document defaults saved in ")
1363 + MakeDisplayPath(fname));
1365 setErrorMessage(_("Unable to save document defaults"));
1369 case LFUN_BUFFERPARAMS_APPLY: {
1370 biblio::CiteEngine const engine =
1371 owner->buffer()->params().cite_engine;
1373 istringstream ss(argument);
1376 int const unknown_tokens =
1377 owner->buffer()->readHeader(lex);
1379 if (unknown_tokens != 0) {
1380 lyxerr << "Warning in LFUN_BUFFERPARAMS_APPLY!\n"
1381 << unknown_tokens << " unknown token"
1382 << (unknown_tokens == 1 ? "" : "s")
1385 if (engine == owner->buffer()->params().cite_engine)
1388 LCursor & cur = view()->cursor();
1389 FuncRequest fr(LFUN_INSET_REFRESH);
1391 InsetBase & inset = owner->buffer()->inset();
1392 InsetIterator it = inset_iterator_begin(inset);
1393 InsetIterator const end = inset_iterator_end(inset);
1394 for (; it != end; ++it)
1395 if (it->lyxCode() == InsetBase::CITE_CODE)
1396 it->dispatch(cur, fr);
1400 case LFUN_TEXTCLASS_APPLY: {
1401 Buffer * buffer = owner->buffer();
1403 lyx::textclass_type const old_class =
1404 buffer->params().textclass;
1406 loadTextclass(argument);
1408 std::pair<bool, lyx::textclass_type> const tc_pair =
1409 textclasslist.NumberOfClass(argument);
1414 lyx::textclass_type const new_class = tc_pair.second;
1415 if (old_class == new_class)
1419 owner->message(_("Converting document to new document class..."));
1421 lyx::cap::SwitchLayoutsBetweenClasses(
1422 old_class, new_class,
1423 buffer->paragraphs(), el);
1425 bufferErrors(*buffer, el);
1426 view()->showErrorList(_("Class switch"));
1430 case LFUN_TEXTCLASS_LOAD:
1431 loadTextclass(argument);
1434 case LFUN_LYXRC_APPLY: {
1435 istringstream ss(argument);
1436 bool const success = lyxrc.read(ss) == 0;
1439 lyxerr << "Warning in LFUN_LYXRC_APPLY!\n"
1440 << "Unable to read lyxrc data"
1448 view()->cursor().dispatch(cmd);
1449 if (view()->cursor().result().dispatched())
1450 update |= view()->cursor().result().update();
1452 update |= view()->dispatch(cmd);
1458 if (view()->available()) {
1459 // Redraw screen unless explicitly told otherwise.
1460 // This also initializes the position cache for all insets
1461 // in (at least partially) visible top-level paragraphs.
1465 // fitCursor() needs valid inset position. The previous call to
1466 // update() makes sure we have such even for freshly created
1468 if (view()->fitCursor())
1470 // if we executed a mutating lfun, mark the buffer as dirty
1471 if (getStatus(cmd).enabled()
1472 && !lyxaction.funcHasFlag(cmd.action, LyXAction::NoBuffer)
1473 && !lyxaction.funcHasFlag(cmd.action, LyXAction::ReadOnly))
1474 view()->buffer()->markDirty();
1477 if (view()->cursor().inTexted()) {
1478 view()->owner()->updateLayoutChoice();
1481 sendDispatchMessage(getMessage(), cmd);
1485 void LyXFunc::sendDispatchMessage(string const & msg, FuncRequest const & cmd)
1487 owner->updateMenubar();
1488 owner->updateToolbars();
1490 const bool verbose = (cmd.origin == FuncRequest::UI
1491 || cmd.origin == FuncRequest::COMMANDBUFFER);
1493 if (cmd.action == LFUN_SELFINSERT || !verbose) {
1494 lyxerr[Debug::ACTION] << "dispatch msg is " << msg << endl;
1496 owner->message(msg);
1500 string dispatch_msg = msg;
1501 if (!dispatch_msg.empty())
1502 dispatch_msg += ' ';
1504 string comname = lyxaction.getActionName(cmd.action);
1506 bool argsadded = false;
1508 if (!cmd.argument.empty()) {
1509 if (cmd.action != LFUN_UNKNOWN_ACTION) {
1510 comname += ' ' + cmd.argument;
1515 string const shortcuts = toplevel_keymap->printbindings(cmd);
1517 if (!shortcuts.empty()) {
1518 comname += ": " + shortcuts;
1519 } else if (!argsadded && !cmd.argument.empty()) {
1520 comname += ' ' + cmd.argument;
1523 if (!comname.empty()) {
1524 comname = rtrim(comname);
1525 dispatch_msg += '(' + comname + ')';
1528 lyxerr[Debug::ACTION] << "verbose dispatch msg " << dispatch_msg << endl;
1529 if (!dispatch_msg.empty())
1530 owner->message(dispatch_msg);
1534 void LyXFunc::setupLocalKeymap()
1536 keyseq.stdmap = toplevel_keymap.get();
1537 keyseq.curmap = toplevel_keymap.get();
1538 cancel_meta_seq.stdmap = toplevel_keymap.get();
1539 cancel_meta_seq.curmap = toplevel_keymap.get();
1543 void LyXFunc::menuNew(string const & name, bool fromTemplate)
1545 string initpath = lyxrc.document_path;
1546 string filename(name);
1548 if (view()->available()) {
1549 string const trypath = owner->buffer()->filePath();
1550 // If directory is writeable, use this as default.
1551 if (IsDirWriteable(trypath))
1555 static int newfile_number;
1557 if (filename.empty()) {
1558 filename = AddName(lyxrc.document_path,
1559 "newfile" + tostr(++newfile_number) + ".lyx");
1560 FileInfo fi(filename);
1561 while (bufferlist.exists(filename) || fi.readable()) {
1563 filename = AddName(lyxrc.document_path,
1564 "newfile" + tostr(newfile_number) +
1566 fi.newFile(filename);
1570 // The template stuff
1573 FileDialog fileDlg(_("Select template file"),
1574 LFUN_SELECT_FILE_SYNC,
1575 make_pair(string(_("Documents|#o#O")),
1576 string(lyxrc.document_path)),
1577 make_pair(string(_("Templates|#T#t")),
1578 string(lyxrc.template_path)));
1580 FileDialog::Result result =
1581 fileDlg.open(lyxrc.template_path,
1582 FileFilterList(_("LyX Documents (*.lyx)")),
1585 if (result.first == FileDialog::Later)
1587 if (result.second.empty())
1589 templname = result.second;
1592 view()->newFile(filename, templname, !name.empty());
1596 void LyXFunc::open(string const & fname)
1598 string initpath = lyxrc.document_path;
1600 if (view()->available()) {
1601 string const trypath = owner->buffer()->filePath();
1602 // If directory is writeable, use this as default.
1603 if (IsDirWriteable(trypath))
1609 if (fname.empty()) {
1610 FileDialog fileDlg(_("Select document to open"),
1612 make_pair(string(_("Documents|#o#O")),
1613 string(lyxrc.document_path)),
1614 make_pair(string(_("Examples|#E#e")),
1615 string(AddPath(system_lyxdir(), "examples"))));
1617 FileDialog::Result result =
1618 fileDlg.open(initpath,
1619 FileFilterList(_("LyX Documents (*.lyx)")),
1622 if (result.first == FileDialog::Later)
1625 filename = result.second;
1627 // check selected filename
1628 if (filename.empty()) {
1629 owner->message(_("Canceled."));
1635 // get absolute path of file and add ".lyx" to the filename if
1637 string const fullpath = FileSearch(string(), filename, "lyx");
1638 if (!fullpath.empty()) {
1639 filename = fullpath;
1642 string const disp_fn(MakeDisplayPath(filename));
1644 // if the file doesn't exist, let the user create one
1645 FileInfo const f(filename, true);
1647 // the user specifically chose this name. Believe them.
1648 view()->newFile(filename, "", true);
1652 owner->message(bformat(_("Opening document %1$s..."), disp_fn));
1655 if (view()->loadLyXFile(filename)) {
1656 str2 = bformat(_("Document %1$s opened."), disp_fn);
1658 str2 = bformat(_("Could not open document %1$s"), disp_fn);
1660 owner->message(str2);
1664 void LyXFunc::doImport(string const & argument)
1667 string filename = split(argument, format, ' ');
1669 lyxerr[Debug::INFO] << "LyXFunc::doImport: " << format
1670 << " file: " << filename << endl;
1672 // need user interaction
1673 if (filename.empty()) {
1674 string initpath = lyxrc.document_path;
1676 if (view()->available()) {
1677 string const trypath = owner->buffer()->filePath();
1678 // If directory is writeable, use this as default.
1679 if (IsDirWriteable(trypath))
1683 string const text = bformat(_("Select %1$s file to import"),
1684 formats.prettyName(format));
1686 FileDialog fileDlg(text,
1688 make_pair(string(_("Documents|#o#O")),
1689 string(lyxrc.document_path)),
1690 make_pair(string(_("Examples|#E#e")),
1691 string(AddPath(system_lyxdir(), "examples"))));
1693 string const filter = formats.prettyName(format)
1694 + " (*." + formats.extension(format) + ')';
1696 FileDialog::Result result =
1697 fileDlg.open(initpath,
1698 FileFilterList(filter),
1701 if (result.first == FileDialog::Later)
1704 filename = result.second;
1706 // check selected filename
1707 if (filename.empty())
1708 owner->message(_("Canceled."));
1711 if (filename.empty())
1714 // get absolute path of file
1715 filename = MakeAbsPath(filename);
1717 string const lyxfile = ChangeExtension(filename, ".lyx");
1719 // Check if the document already is open
1720 if (lyx_gui::use_gui && bufferlist.exists(lyxfile)) {
1721 if (!bufferlist.close(bufferlist.getBuffer(lyxfile), true)) {
1722 owner->message(_("Canceled."));
1727 // if the file exists already, and we didn't do
1728 // -i lyx thefile.lyx, warn
1729 if (FileInfo(lyxfile, true).exist() && filename != lyxfile) {
1730 string const file = MakeDisplayPath(lyxfile, 30);
1732 string text = bformat(_("The document %1$s already exists.\n\n"
1733 "Do you want to over-write that document?"), file);
1734 int const ret = Alert::prompt(_("Over-write document?"),
1735 text, 0, 1, _("&Over-write"), _("&Cancel"));
1738 owner->message(_("Canceled."));
1743 Importer::Import(owner, filename, format);
1747 void LyXFunc::closeBuffer()
1749 if (bufferlist.close(owner->buffer(), true) && !quitting) {
1750 if (bufferlist.empty()) {
1751 // need this otherwise SEGV may occur while
1752 // trying to set variables that don't exist
1753 // since there's no current buffer
1754 owner->getDialogs().hideBufferDependent();
1756 view()->setBuffer(bufferlist.first());
1762 // Each "owner" should have it's own message method. lyxview and
1763 // the minibuffer would use the minibuffer, but lyxserver would
1764 // send an ERROR signal to its client. Alejandro 970603
1765 // This function is bit problematic when it comes to NLS, to make the
1766 // lyx servers client be language indepenent we must not translate
1767 // strings sent to this func.
1768 void LyXFunc::setErrorMessage(string const & m) const
1770 dispatch_buffer = m;
1775 void LyXFunc::setMessage(string const & m) const
1777 dispatch_buffer = m;
1781 string const LyXFunc::viewStatusMessage()
1783 // When meta-fake key is pressed, show the key sequence so far + "M-".
1785 return keyseq.print() + "M-";
1787 // Else, when a non-complete key sequence is pressed,
1788 // show the available options.
1789 if (keyseq.length() > 0 && !keyseq.deleted())
1790 return keyseq.printOptions();
1792 if (!view()->available())
1793 return _("Welcome to LyX!");
1795 return view()->cursor().currentState();
1799 BufferView * LyXFunc::view() const
1801 BOOST_ASSERT(owner);
1802 return owner->view().get();
1806 bool LyXFunc::wasMetaKey() const
1808 return (meta_fake_bit != key_modifier::none);