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 setStatusMessage(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 setStatusMessage(N_("Unknown action"));
311 // the default error message if we disable the command
312 setStatusMessage(N_("Command disabled"));
316 // Check whether we need a buffer
317 if (!lyxaction.funcHasFlag(cmd.action, LyXAction::NoBuffer) && !buf) {
319 setStatusMessage(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 cur.getStatus(cmd, flag);
527 flag = view()->getStatus(cmd);
533 // Can we use a readonly buffer?
534 if (buf && buf->isReadonly()
535 && !lyxaction.funcHasFlag(cmd.action, LyXAction::ReadOnly)
536 && !lyxaction.funcHasFlag(cmd.action, LyXAction::NoBuffer)) {
537 setStatusMessage(N_("Document is read-only"));
541 //lyxerr << "LyXFunc::getStatus: got: " << flag.enabled() << endl;
548 bool ensureBufferClean(BufferView * bv)
550 Buffer & buf = *bv->buffer();
554 string const file = MakeDisplayPath(buf.fileName(), 30);
555 string text = bformat(_("The document %1$s has unsaved "
556 "changes.\n\nDo you want to save "
557 "the document?"), file);
558 int const ret = Alert::prompt(_("Save changed document?"),
559 text, 0, 1, _("&Save"),
563 bv->owner()->dispatch(FuncRequest(LFUN_MENUWRITE));
565 return buf.isClean();
569 void showPrintError(string const & name)
571 string str = bformat(_("Could not print the document %1$s.\n"
572 "Check that your printer is set up correctly."),
573 MakeDisplayPath(name, 50));
574 Alert::error(_("Print document failed"), str);
578 void loadTextclass(string const & name)
580 std::pair<bool, lyx::textclass_type> const tc_pair =
581 textclasslist.NumberOfClass(name);
583 if (!tc_pair.first) {
584 lyxerr << "Document class \"" << name
585 << "\" does not exist."
590 lyx::textclass_type const tc = tc_pair.second;
592 if (!textclasslist[tc].load()) {
593 string s = bformat(_("The document could not be converted\n"
594 "into the document class %1$s."),
595 textclasslist[tc].name());
596 Alert::error(_("Could not change class"), s);
603 void LyXFunc::dispatch(FuncRequest const & cmd)
605 string const argument = cmd.argument;
606 kb_action const action = cmd.action;
608 lyxerr[Debug::ACTION] << "LyXFunc::dispatch: cmd: " << cmd << endl;
609 //lyxerr << "LyXFunc::dispatch: cmd: " << cmd << endl;
611 // we have not done anything wrong yet.
613 dispatch_buffer.erase();
614 selection_possible = false;
618 // We cannot use this function here
619 if (!getStatus(cmd).enabled()) {
620 lyxerr[Debug::ACTION] << "LyXFunc::dispatch: "
621 << lyxaction.getActionName(action)
622 << " [" << action << "] is disabled at this location"
624 setErrorMessage(getStatusMessage());
628 if (view()->available())
629 view()->hideCursor();
633 case LFUN_WORDFINDFORWARD:
634 case LFUN_WORDFINDBACKWARD: {
635 static string last_search;
636 string searched_string;
638 if (!argument.empty()) {
639 last_search = argument;
640 searched_string = argument;
642 searched_string = last_search;
645 if (searched_string.empty())
648 bool const fw = action == LFUN_WORDFINDFORWARD;
650 lyx::find::find2string(searched_string, true, false, fw);
651 lyx::find::find(view(), FuncRequest(LFUN_WORD_FIND, data));
656 owner->message(keyseq.printOptions());
659 case LFUN_EXEC_COMMAND:
660 owner->getToolbars().display("minibuffer", true);
661 owner->focus_command_buffer();
666 meta_fake_bit = key_modifier::none;
667 if (view()->available())
668 // cancel any selection
669 dispatch(FuncRequest(LFUN_MARK_OFF));
670 setMessage(N_("Cancel"));
674 meta_fake_bit = key_modifier::alt;
675 setMessage(keyseq.print());
678 case LFUN_READ_ONLY_TOGGLE:
679 if (owner->buffer()->lyxvc().inUse())
680 owner->buffer()->lyxvc().toggleReadOnly();
682 owner->buffer()->setReadonly(
683 !owner->buffer()->isReadonly());
686 // --- Menus -----------------------------------------------
688 menuNew(argument, false);
691 case LFUN_MENUNEWTMPLT:
692 menuNew(argument, true);
695 case LFUN_CLOSEBUFFER:
700 if (!owner->buffer()->isUnnamed()) {
701 string const str = bformat(_("Saving document %1$s..."),
702 MakeDisplayPath(owner->buffer()->fileName()));
704 MenuWrite(owner->buffer());
705 owner->message(str + _(" done."));
707 WriteAs(owner->buffer());
711 WriteAs(owner->buffer(), argument);
714 case LFUN_MENURELOAD: {
715 string const file = MakeDisplayPath(view()->buffer()->fileName(), 20);
716 string text = bformat(_("Any changes will be lost. Are you sure "
717 "you want to revert to the saved version of the document %1$s?"), file);
718 int const ret = Alert::prompt(_("Revert to saved document?"),
719 text, 0, 1, _("&Revert"), _("&Cancel"));
727 Exporter::Export(owner->buffer(), argument, true);
728 view()->showErrorList(BufferFormat(*owner->buffer()));
732 Exporter::Preview(owner->buffer(), argument);
733 view()->showErrorList(BufferFormat(*owner->buffer()));
737 Exporter::Export(owner->buffer(), "program", true);
738 view()->showErrorList(_("Build"));
742 owner->buffer()->runChktex();
743 view()->showErrorList(_("ChkTeX"));
747 if (argument == "custom")
748 owner->getDialogs().show("sendto");
750 Exporter::Export(owner->buffer(), argument, false);
751 view()->showErrorList(BufferFormat(*owner->buffer()));
755 case LFUN_EXPORT_CUSTOM: {
757 string command = split(argument, format_name, ' ');
758 Format const * format = formats.getFormat(format_name);
760 lyxerr << "Format \"" << format_name
761 << "\" not recognized!"
766 Buffer * buffer = owner->buffer();
768 // The name of the file created by the conversion process
771 // Output to filename
772 if (format->name() == "lyx") {
773 string const latexname =
774 buffer->getLatexName(false);
775 filename = ChangeExtension(latexname,
776 format->extension());
777 filename = AddName(buffer->temppath(), filename);
779 if (!buffer->writeFile(filename))
783 Exporter::Export(buffer, format_name, true,
787 // Substitute $$FName for filename
788 if (!contains(command, "$$FName"))
789 command = "( " + command + " ) < $$FName";
790 command = subst(command, "$$FName", filename);
792 // Execute the command in the background
794 call.startscript(Systemcall::DontWait, command);
801 string command = split(split(argument, target, ' '),
805 || target_name.empty()
806 || command.empty()) {
807 lyxerr << "Unable to parse \""
808 << argument << '"' << std::endl;
811 if (target != "printer" && target != "file") {
812 lyxerr << "Unrecognized target \""
813 << target << '"' << std::endl;
817 Buffer * buffer = owner->buffer();
819 if (!Exporter::Export(buffer, "dvi", true)) {
820 showPrintError(buffer->fileName());
824 // Push directory path.
825 string const path = buffer->temppath();
828 // there are three cases here:
829 // 1. we print to a file
830 // 2. we print directly to a printer
831 // 3. we print using a spool command (print to file first)
834 string const dviname =
835 ChangeExtension(buffer->getLatexName(true),
838 if (target == "printer") {
839 if (!lyxrc.print_spool_command.empty()) {
840 // case 3: print using a spool
841 string const psname =
842 ChangeExtension(dviname,".ps");
843 command += lyxrc.print_to_file
846 + QuoteName(dviname);
849 lyxrc.print_spool_command +' ';
850 if (target_name != "default") {
851 command2 += lyxrc.print_spool_printerprefix
855 command2 += QuoteName(psname);
857 // If successful, then spool command
858 res = one.startscript(
863 res = one.startscript(
864 Systemcall::DontWait,
867 // case 2: print directly to a printer
868 res = one.startscript(
869 Systemcall::DontWait,
870 command + QuoteName(dviname));
874 // case 1: print to a file
875 command += lyxrc.print_to_file
876 + QuoteName(MakeAbsPath(target_name,
879 + QuoteName(dviname);
880 res = one.startscript(Systemcall::DontWait,
885 showPrintError(buffer->fileName());
898 InsetCommandParams p("tableofcontents");
899 string const data = InsetCommandMailer::params2string("toc", p);
900 owner->getDialogs().show("toc", data, 0);
908 case LFUN_RECONFIGURE:
912 case LFUN_HELP_OPEN: {
913 string const arg = argument;
915 setErrorMessage(N_("Missing argument"));
918 string const fname = i18nLibFileSearch("doc", arg, "lyx");
920 lyxerr << "LyX: unable to find documentation file `"
921 << arg << "'. Bad installation?" << endl;
924 owner->message(bformat(_("Opening help file %1$s..."),
925 MakeDisplayPath(fname)));
926 view()->loadLyXFile(fname, false);
930 // --- version control -------------------------------
931 case LFUN_VC_REGISTER:
932 if (!ensureBufferClean(view()))
934 if (!owner->buffer()->lyxvc().inUse()) {
935 owner->buffer()->lyxvc().registrer();
940 case LFUN_VC_CHECKIN:
941 if (!ensureBufferClean(view()))
943 if (owner->buffer()->lyxvc().inUse()
944 && !owner->buffer()->isReadonly()) {
945 owner->buffer()->lyxvc().checkIn();
950 case LFUN_VC_CHECKOUT:
951 if (!ensureBufferClean(view()))
953 if (owner->buffer()->lyxvc().inUse()
954 && owner->buffer()->isReadonly()) {
955 owner->buffer()->lyxvc().checkOut();
961 owner->buffer()->lyxvc().revert();
966 owner->buffer()->lyxvc().undoLast();
970 // --- buffers ----------------------------------------
971 case LFUN_SWITCHBUFFER:
972 view()->setBuffer(bufferlist.getBuffer(argument));
975 case LFUN_NEXTBUFFER:
976 view()->setBuffer(bufferlist.next(view()->buffer()));
979 case LFUN_PREVIOUSBUFFER:
980 view()->setBuffer(bufferlist.previous(view()->buffer()));
984 NewFile(view(), argument);
991 case LFUN_DROP_LAYOUTS_CHOICE:
992 owner->getToolbars().openLayoutList();
995 case LFUN_MENU_OPEN_BY_NAME:
996 owner->getMenubar().openByName(argument);
999 // --- lyxserver commands ----------------------------
1001 setMessage(owner->buffer()->fileName());
1002 lyxerr[Debug::INFO] << "FNAME["
1003 << owner->buffer()->fileName()
1008 dispatch_buffer = keyseq.print();
1009 lyxserver->notifyClient(dispatch_buffer);
1012 case LFUN_GOTOFILEROW: {
1015 istringstream is(argument);
1016 is >> file_name >> row;
1017 if (prefixIs(file_name, getTmpDir())) {
1018 // Needed by inverse dvi search. If it is a file
1019 // in tmpdir, call the apropriated function
1020 view()->setBuffer(bufferlist.getBufferFromTmp(file_name));
1022 // Must replace extension of the file to be .lyx
1023 // and get full path
1024 string const s = ChangeExtension(file_name, ".lyx");
1025 // Either change buffer or load the file
1026 if (bufferlist.exists(s)) {
1027 view()->setBuffer(bufferlist.getBuffer(s));
1029 view()->loadLyXFile(s);
1033 view()->setCursorFromRow(row);
1036 // see BufferView_pimpl::center()
1037 view()->updateScrollbar();
1041 case LFUN_GOTO_PARAGRAPH: {
1042 istringstream is(argument);
1045 ParIterator par = owner->buffer()->getParFromID(id);
1046 if (par == owner->buffer()->par_iterator_end()) {
1047 lyxerr[Debug::INFO] << "No matching paragraph found! ["
1048 << id << ']' << endl;
1051 lyxerr[Debug::INFO] << "Paragraph " << par->id()
1052 << " found." << endl;
1056 view()->setCursor(par, 0);
1058 view()->switchKeyMap();
1059 owner->view_state_changed();
1062 // see BufferView_pimpl::center()
1063 view()->updateScrollbar();
1067 case LFUN_DIALOG_SHOW: {
1068 string const name = cmd.getArg(0);
1069 string data = trim(cmd.argument.substr(name.size()));
1071 if (name == "character") {
1072 data = freefont2string();
1074 owner->getDialogs().show("character", data);
1077 else if (name == "latexlog") {
1078 pair<Buffer::LogType, string> const logfile =
1079 owner->buffer()->getLogName();
1080 switch (logfile.first) {
1081 case Buffer::latexlog:
1084 case Buffer::buildlog:
1088 data += logfile.second;
1089 owner->getDialogs().show("log", data);
1091 else if (name == "vclog") {
1092 string const data = "vc " +
1093 owner->buffer()->lyxvc().getLogFile();
1094 owner->getDialogs().show("log", data);
1097 owner->getDialogs().show(name, data);
1101 case LFUN_DIALOG_SHOW_NEW_INSET: {
1102 string const name = cmd.getArg(0);
1103 string data = trim(cmd.argument.substr(name.size()));
1104 if (name == "bibitem" ||
1106 name == "include" ||
1112 InsetCommandParams p(name);
1113 data = InsetCommandMailer::params2string(name, p);
1114 } else if (name == "box") {
1115 // \c data == "Boxed" || "Frameless" etc
1116 InsetBoxParams p(data);
1117 data = InsetBoxMailer::params2string(p);
1118 } else if (name == "branch") {
1119 InsetBranchParams p;
1120 data = InsetBranchMailer::params2string(p);
1121 } else if (name == "citation") {
1122 InsetCommandParams p("cite");
1123 data = InsetCommandMailer::params2string(name, p);
1124 } else if (name == "ert") {
1125 data = InsetERTMailer::params2string(InsetCollapsable::Open);
1126 } else if (name == "external") {
1127 InsetExternalParams p;
1128 Buffer const & buffer = *owner->buffer();
1129 data = InsetExternalMailer::params2string(p, buffer);
1130 } else if (name == "float") {
1132 data = InsetFloatMailer::params2string(p);
1133 } else if (name == "graphics") {
1134 InsetGraphicsParams p;
1135 Buffer const & buffer = *owner->buffer();
1136 data = InsetGraphicsMailer::params2string(p, buffer);
1137 } else if (name == "note") {
1139 data = InsetNoteMailer::params2string(p);
1140 } else if (name == "vspace") {
1142 data = InsetVSpaceMailer::params2string(space);
1143 } else if (name == "wrap") {
1145 data = InsetWrapMailer::params2string(p);
1147 owner->getDialogs().show(name, data, 0);
1151 case LFUN_DIALOG_SHOW_NEXT_INSET:
1154 case LFUN_DIALOG_UPDATE: {
1155 string const & name = argument;
1156 // Can only update a dialog connected to an existing inset
1157 InsetBase * inset = owner->getDialogs().getOpenInset(name);
1159 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument);
1160 inset->dispatch(view()->cursor(), fr);
1161 } else if (name == "paragraph") {
1162 dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
1163 } else if (name == "prefs") {
1164 owner->getDialogs().update(name, string());
1169 case LFUN_DIALOG_HIDE:
1170 Dialogs::hide(argument, 0);
1173 case LFUN_DIALOG_DISCONNECT_INSET:
1174 owner->getDialogs().disconnect(argument);
1177 case LFUN_CHILDOPEN: {
1178 string const filename =
1179 MakeAbsPath(argument, owner->buffer()->filePath());
1180 setMessage(N_("Opening child document ") +
1181 MakeDisplayPath(filename) + "...");
1182 view()->savePosition(0);
1183 string const parentfilename = owner->buffer()->fileName();
1184 if (bufferlist.exists(filename))
1185 view()->setBuffer(bufferlist.getBuffer(filename));
1187 view()->loadLyXFile(filename);
1188 // Set the parent name of the child document.
1189 // This makes insertion of citations and references in the child work,
1190 // when the target is in the parent or another child document.
1191 owner->buffer()->setParentName(parentfilename);
1195 case LFUN_TOGGLECURSORFOLLOW:
1196 lyxrc.cursor_follows_scrollbar = !lyxrc.cursor_follows_scrollbar;
1200 owner->getIntl().KeyMapOn(false);
1203 case LFUN_KMAP_PRIM:
1204 owner->getIntl().KeyMapPrim();
1208 owner->getIntl().KeyMapSec();
1211 case LFUN_KMAP_TOGGLE:
1212 owner->getIntl().ToggleKeyMap();
1218 string rest = split(argument, countstr, ' ');
1219 istringstream is(countstr);
1222 lyxerr << "repeat: count: " << count << " cmd: " << rest << endl;
1223 for (int i = 0; i < count; ++i)
1224 dispatch(lyxaction.lookupFunc(rest));
1228 case LFUN_SEQUENCE: {
1229 // argument contains ';'-terminated commands
1230 string arg = argument;
1231 while (!arg.empty()) {
1233 arg = split(arg, first, ';');
1234 FuncRequest func(lyxaction.lookupFunc(first));
1235 func.origin = cmd.origin;
1241 case LFUN_SAVEPREFERENCES: {
1242 Path p(user_lyxdir());
1243 lyxrc.write("preferences", false);
1247 case LFUN_SCREEN_FONT_UPDATE:
1248 // handle the screen font changes.
1249 lyxrc.set_font_norm_type();
1250 lyx_gui::update_fonts();
1251 // All visible buffers will need resize
1255 case LFUN_SET_COLOR: {
1257 string const x11_name = split(argument, lyx_name, ' ');
1258 if (lyx_name.empty() || x11_name.empty()) {
1259 setErrorMessage(N_("Syntax: set-color <lyx_name>"
1264 bool const graphicsbg_changed =
1265 (lyx_name == lcolor.getLyXName(LColor::graphicsbg) &&
1266 x11_name != lcolor.getX11Name(LColor::graphicsbg));
1268 if (!lcolor.setColor(lyx_name, x11_name)) {
1270 bformat(_("Set-color \"%1$s\" failed "
1271 "- color is undefined or "
1272 "may not be redefined"), lyx_name));
1276 lyx_gui::update_color(lcolor.getFromLyXName(lyx_name));
1278 if (graphicsbg_changed) {
1279 #ifdef WITH_WARNINGS
1280 #warning FIXME!! The graphics cache no longer has a changeDisplay method.
1283 lyx::graphics::GCache::get().changeDisplay(true);
1290 owner->message(argument);
1293 case LFUN_TOOLTIPS_TOGGLE:
1294 owner->getDialogs().toggleTooltips();
1297 case LFUN_EXTERNAL_EDIT: {
1298 FuncRequest fr(action, argument);
1299 InsetExternal().dispatch(view()->cursor(), fr);
1303 case LFUN_GRAPHICS_EDIT: {
1304 FuncRequest fr(action, argument);
1305 InsetGraphics().dispatch(view()->cursor(), fr);
1309 case LFUN_ALL_INSETS_TOGGLE: {
1311 string const name = split(argument, action, ' ');
1312 InsetBase::Code const inset_code =
1313 InsetBase::translate(name);
1315 LCursor & cur = view()->cursor();
1316 FuncRequest fr(LFUN_INSET_TOGGLE, action);
1318 InsetBase & inset = owner->buffer()->inset();
1319 InsetIterator it = inset_iterator_begin(inset);
1320 InsetIterator const end = inset_iterator_end(inset);
1321 for (; it != end; ++it) {
1322 if (inset_code == InsetBase::NO_CODE
1323 || inset_code == it->lyxCode())
1324 it->dispatch(cur, fr);
1329 case LFUN_LANGUAGE_BUFFER: {
1330 Buffer & buffer = *owner->buffer();
1331 Language const * oldL = buffer.params().language;
1332 Language const * newL = languages.getLanguage(argument);
1333 if (!newL || oldL == newL)
1336 if (oldL->RightToLeft() == newL->RightToLeft()
1337 && !buffer.isMultiLingual())
1338 buffer.changeLanguage(oldL, newL);
1340 buffer.updateDocLang(newL);
1344 case LFUN_SAVE_AS_DEFAULT: {
1345 string const fname =
1346 AddName(AddPath(user_lyxdir(), "templates/"),
1348 Buffer defaults(fname);
1350 istringstream ss(argument);
1353 int const unknown_tokens = defaults.readHeader(lex);
1355 if (unknown_tokens != 0) {
1356 lyxerr << "Warning in LFUN_SAVE_AS_DEFAULT!\n"
1357 << unknown_tokens << " unknown token"
1358 << (unknown_tokens == 1 ? "" : "s")
1362 if (defaults.writeFile(defaults.fileName()))
1363 setMessage(_("Document defaults saved in ")
1364 + MakeDisplayPath(fname));
1366 setErrorMessage(_("Unable to save document defaults"));
1370 case LFUN_BUFFERPARAMS_APPLY: {
1371 biblio::CiteEngine const engine =
1372 owner->buffer()->params().cite_engine;
1374 istringstream ss(argument);
1377 int const unknown_tokens =
1378 owner->buffer()->readHeader(lex);
1380 if (unknown_tokens != 0) {
1381 lyxerr << "Warning in LFUN_BUFFERPARAMS_APPLY!\n"
1382 << unknown_tokens << " unknown token"
1383 << (unknown_tokens == 1 ? "" : "s")
1386 if (engine == owner->buffer()->params().cite_engine)
1389 LCursor & cur = view()->cursor();
1390 FuncRequest fr(LFUN_INSET_REFRESH);
1392 InsetBase & inset = owner->buffer()->inset();
1393 InsetIterator it = inset_iterator_begin(inset);
1394 InsetIterator const end = inset_iterator_end(inset);
1395 for (; it != end; ++it)
1396 if (it->lyxCode() == InsetBase::CITE_CODE)
1397 it->dispatch(cur, fr);
1401 case LFUN_TEXTCLASS_APPLY: {
1402 Buffer * buffer = owner->buffer();
1404 lyx::textclass_type const old_class =
1405 buffer->params().textclass;
1407 loadTextclass(argument);
1409 std::pair<bool, lyx::textclass_type> const tc_pair =
1410 textclasslist.NumberOfClass(argument);
1415 lyx::textclass_type const new_class = tc_pair.second;
1416 if (old_class == new_class)
1420 owner->message(_("Converting document to new document class..."));
1422 lyx::cap::SwitchLayoutsBetweenClasses(
1423 old_class, new_class,
1424 buffer->paragraphs(), el);
1426 bufferErrors(*buffer, el);
1427 view()->showErrorList(_("Class switch"));
1431 case LFUN_TEXTCLASS_LOAD:
1432 loadTextclass(argument);
1435 case LFUN_LYXRC_APPLY: {
1436 istringstream ss(argument);
1437 bool const success = lyxrc.read(ss) == 0;
1440 lyxerr << "Warning in LFUN_LYXRC_APPLY!\n"
1441 << "Unable to read lyxrc data"
1449 view()->cursor().dispatch(cmd);
1450 if (view()->cursor().result().dispatched())
1451 update |= view()->cursor().result().update();
1453 update |= view()->dispatch(cmd);
1459 if (view()->available()) {
1460 // Redraw screen unless explicitly told otherwise.
1461 // This also initializes the position cache for all insets
1462 // in (at least partially) visible top-level paragraphs.
1466 // fitCursor() needs valid inset position. The previous call to
1467 // update() makes sure we have such even for freshly created
1469 if (view()->fitCursor())
1471 // if we executed a mutating lfun, mark the buffer as dirty
1472 if (getStatus(cmd).enabled()
1473 && !lyxaction.funcHasFlag(cmd.action, LyXAction::NoBuffer)
1474 && !lyxaction.funcHasFlag(cmd.action, LyXAction::ReadOnly))
1475 view()->buffer()->markDirty();
1478 if (view()->cursor().inTexted()) {
1479 view()->owner()->updateLayoutChoice();
1480 sendDispatchMessage(getMessage(), cmd);
1486 void LyXFunc::sendDispatchMessage(string const & msg, FuncRequest const & cmd)
1488 owner->updateMenubar();
1489 owner->updateToolbars();
1491 const bool verbose = (cmd.origin == FuncRequest::UI
1492 || cmd.origin == FuncRequest::COMMANDBUFFER);
1494 if (cmd.action == LFUN_SELFINSERT || !verbose) {
1495 lyxerr[Debug::ACTION] << "dispatch msg is " << msg << endl;
1497 owner->message(msg);
1501 string dispatch_msg = msg;
1502 if (!dispatch_msg.empty())
1503 dispatch_msg += ' ';
1505 string comname = lyxaction.getActionName(cmd.action);
1507 bool argsadded = false;
1509 if (!cmd.argument.empty()) {
1510 if (cmd.action != LFUN_UNKNOWN_ACTION) {
1511 comname += ' ' + cmd.argument;
1516 string const shortcuts = toplevel_keymap->printbindings(cmd);
1518 if (!shortcuts.empty()) {
1519 comname += ": " + shortcuts;
1520 } else if (!argsadded && !cmd.argument.empty()) {
1521 comname += ' ' + cmd.argument;
1524 if (!comname.empty()) {
1525 comname = rtrim(comname);
1526 dispatch_msg += '(' + comname + ')';
1529 lyxerr[Debug::ACTION] << "verbose dispatch msg " << dispatch_msg << endl;
1530 if (!dispatch_msg.empty())
1531 owner->message(dispatch_msg);
1535 void LyXFunc::setupLocalKeymap()
1537 keyseq.stdmap = toplevel_keymap.get();
1538 keyseq.curmap = toplevel_keymap.get();
1539 cancel_meta_seq.stdmap = toplevel_keymap.get();
1540 cancel_meta_seq.curmap = toplevel_keymap.get();
1544 void LyXFunc::menuNew(string const & name, bool fromTemplate)
1546 string initpath = lyxrc.document_path;
1547 string filename(name);
1549 if (view()->available()) {
1550 string const trypath = owner->buffer()->filePath();
1551 // If directory is writeable, use this as default.
1552 if (IsDirWriteable(trypath))
1556 static int newfile_number;
1558 if (filename.empty()) {
1559 filename = AddName(lyxrc.document_path,
1560 "newfile" + tostr(++newfile_number) + ".lyx");
1561 FileInfo fi(filename);
1562 while (bufferlist.exists(filename) || fi.readable()) {
1564 filename = AddName(lyxrc.document_path,
1565 "newfile" + tostr(newfile_number) +
1567 fi.newFile(filename);
1571 // The template stuff
1574 FileDialog fileDlg(_("Select template file"),
1575 LFUN_SELECT_FILE_SYNC,
1576 make_pair(string(_("Documents|#o#O")),
1577 string(lyxrc.document_path)),
1578 make_pair(string(_("Templates|#T#t")),
1579 string(lyxrc.template_path)));
1581 FileDialog::Result result =
1582 fileDlg.open(lyxrc.template_path,
1583 FileFilterList(_("LyX Documents (*.lyx)")),
1586 if (result.first == FileDialog::Later)
1588 if (result.second.empty())
1590 templname = result.second;
1593 view()->newFile(filename, templname, !name.empty());
1597 void LyXFunc::open(string const & fname)
1599 string initpath = lyxrc.document_path;
1601 if (view()->available()) {
1602 string const trypath = owner->buffer()->filePath();
1603 // If directory is writeable, use this as default.
1604 if (IsDirWriteable(trypath))
1610 if (fname.empty()) {
1611 FileDialog fileDlg(_("Select document to open"),
1613 make_pair(string(_("Documents|#o#O")),
1614 string(lyxrc.document_path)),
1615 make_pair(string(_("Examples|#E#e")),
1616 string(AddPath(system_lyxdir(), "examples"))));
1618 FileDialog::Result result =
1619 fileDlg.open(initpath,
1620 FileFilterList(_("LyX Documents (*.lyx)")),
1623 if (result.first == FileDialog::Later)
1626 filename = result.second;
1628 // check selected filename
1629 if (filename.empty()) {
1630 owner->message(_("Canceled."));
1636 // get absolute path of file and add ".lyx" to the filename if
1638 string const fullpath = FileSearch(string(), filename, "lyx");
1639 if (!fullpath.empty()) {
1640 filename = fullpath;
1643 string const disp_fn(MakeDisplayPath(filename));
1645 // if the file doesn't exist, let the user create one
1646 FileInfo const f(filename, true);
1648 // the user specifically chose this name. Believe them.
1649 view()->newFile(filename, "", true);
1653 owner->message(bformat(_("Opening document %1$s..."), disp_fn));
1656 if (view()->loadLyXFile(filename)) {
1657 str2 = bformat(_("Document %1$s opened."), disp_fn);
1659 str2 = bformat(_("Could not open document %1$s"), disp_fn);
1661 owner->message(str2);
1665 void LyXFunc::doImport(string const & argument)
1668 string filename = split(argument, format, ' ');
1670 lyxerr[Debug::INFO] << "LyXFunc::doImport: " << format
1671 << " file: " << filename << endl;
1673 // need user interaction
1674 if (filename.empty()) {
1675 string initpath = lyxrc.document_path;
1677 if (view()->available()) {
1678 string const trypath = owner->buffer()->filePath();
1679 // If directory is writeable, use this as default.
1680 if (IsDirWriteable(trypath))
1684 string const text = bformat(_("Select %1$s file to import"),
1685 formats.prettyName(format));
1687 FileDialog fileDlg(text,
1689 make_pair(string(_("Documents|#o#O")),
1690 string(lyxrc.document_path)),
1691 make_pair(string(_("Examples|#E#e")),
1692 string(AddPath(system_lyxdir(), "examples"))));
1694 string const filter = formats.prettyName(format)
1695 + " (*." + formats.extension(format) + ')';
1697 FileDialog::Result result =
1698 fileDlg.open(initpath,
1699 FileFilterList(filter),
1702 if (result.first == FileDialog::Later)
1705 filename = result.second;
1707 // check selected filename
1708 if (filename.empty())
1709 owner->message(_("Canceled."));
1712 if (filename.empty())
1715 // get absolute path of file
1716 filename = MakeAbsPath(filename);
1718 string const lyxfile = ChangeExtension(filename, ".lyx");
1720 // Check if the document already is open
1721 if (lyx_gui::use_gui && bufferlist.exists(lyxfile)) {
1722 if (!bufferlist.close(bufferlist.getBuffer(lyxfile), true)) {
1723 owner->message(_("Canceled."));
1728 // if the file exists already, and we didn't do
1729 // -i lyx thefile.lyx, warn
1730 if (FileInfo(lyxfile, true).exist() && filename != lyxfile) {
1731 string const file = MakeDisplayPath(lyxfile, 30);
1733 string text = bformat(_("The document %1$s already exists.\n\n"
1734 "Do you want to over-write that document?"), file);
1735 int const ret = Alert::prompt(_("Over-write document?"),
1736 text, 0, 1, _("&Over-write"), _("&Cancel"));
1739 owner->message(_("Canceled."));
1744 Importer::Import(owner, filename, format);
1748 void LyXFunc::closeBuffer()
1750 if (bufferlist.close(owner->buffer(), true) && !quitting) {
1751 if (bufferlist.empty()) {
1752 // need this otherwise SEGV may occur while
1753 // trying to set variables that don't exist
1754 // since there's no current buffer
1755 owner->getDialogs().hideBufferDependent();
1757 view()->setBuffer(bufferlist.first());
1763 // Each "owner" should have it's own message method. lyxview and
1764 // the minibuffer would use the minibuffer, but lyxserver would
1765 // send an ERROR signal to its client. Alejandro 970603
1766 // This function is bit problematic when it comes to NLS, to make the
1767 // lyx servers client be language indepenent we must not translate
1768 // strings sent to this func.
1769 void LyXFunc::setErrorMessage(string const & m) const
1771 dispatch_buffer = m;
1776 void LyXFunc::setMessage(string const & m) const
1778 dispatch_buffer = m;
1782 void LyXFunc::setStatusMessage(string const & m) const
1788 string const LyXFunc::viewStatusMessage()
1790 // When meta-fake key is pressed, show the key sequence so far + "M-".
1792 return keyseq.print() + "M-";
1794 // Else, when a non-complete key sequence is pressed,
1795 // show the available options.
1796 if (keyseq.length() > 0 && !keyseq.deleted())
1797 return keyseq.printOptions();
1799 if (!view()->available())
1800 return _("Welcome to LyX!");
1802 return view()->cursor().currentState();
1806 BufferView * LyXFunc::view() const
1808 BOOST_ASSERT(owner);
1809 return owner->view().get();
1813 bool LyXFunc::wasMetaKey() const
1815 return (meta_fake_bit != key_modifier::none);