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/filefilterlist.h"
82 #include "support/FileInfo.h"
83 #include "support/filetools.h"
84 #include "support/forkedcontr.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;
138 extern boost::scoped_ptr<kb_keymap> toplevel_keymap;
141 extern tex_accent_struct get_accent(kb_action action);
144 LyXFunc::LyXFunc(LyXView * lv)
147 keyseq(toplevel_keymap.get(), toplevel_keymap.get()),
148 cancel_meta_seq(toplevel_keymap.get(), toplevel_keymap.get()),
149 meta_fake_bit(key_modifier::none)
154 void LyXFunc::handleKeyFunc(kb_action action)
156 char c = encoded_last_key;
158 if (keyseq.length()) {
162 owner->getIntl().getTransManager()
163 .deadkey(c, get_accent(action).accent, view()->getLyXText());
164 // Need to clear, in case the minibuffer calls these
167 // copied verbatim from do_accent_char
168 view()->cursor().resetAnchor();
173 void LyXFunc::processKeySym(LyXKeySymPtr keysym, key_modifier::state state)
175 lyxerr[Debug::KEY] << "KeySym is " << keysym->getSymbolName() << endl;
177 // Do nothing if we have nothing (JMarc)
178 if (!keysym->isOK()) {
179 lyxerr[Debug::KEY] << "Empty kbd action (probably composing)"
184 if (keysym->isModifier()) {
185 lyxerr[Debug::KEY] << "isModifier true" << endl;
189 Encoding const * encoding = view()->cursor().getEncoding();
191 encoded_last_key = keysym->getISOEncoded(encoding ? encoding->Name() : "");
193 // Do a one-deep top-level lookup for
194 // cancel and meta-fake keys. RVDK_PATCH_5
195 cancel_meta_seq.reset();
197 FuncRequest func = cancel_meta_seq.addkey(keysym, state);
198 lyxerr[Debug::KEY] << "action first set to [" << func.action << ']' << endl;
200 // When not cancel or meta-fake, do the normal lookup.
201 // Note how the meta_fake Mod1 bit is OR-ed in and reset afterwards.
202 // Mostly, meta_fake_bit = key_modifier::none. RVDK_PATCH_5.
203 if ((func.action != LFUN_CANCEL) && (func.action != LFUN_META_FAKE)) {
204 // remove Caps Lock and Mod2 as a modifiers
205 func = keyseq.addkey(keysym, (state | meta_fake_bit));
206 lyxerr[Debug::KEY] << "action now set to ["
207 << func.action << ']' << endl;
210 // Dont remove this unless you know what you are doing.
211 meta_fake_bit = key_modifier::none;
213 // can this happen now ?
214 if (func.action == LFUN_NOACTION) {
215 func = FuncRequest(LFUN_PREFIX);
218 if (lyxerr.debugging(Debug::KEY)) {
219 lyxerr << "Key [action="
220 << func.action << "]["
221 << keyseq.print() << ']'
225 // already here we know if it any point in going further
226 // why not return already here if action == -1 and
227 // num_bytes == 0? (Lgb)
229 if (keyseq.length() > 1) {
230 owner->message(keyseq.print());
234 // Maybe user can only reach the key via holding down shift.
235 // Let's see. But only if shift is the only modifier
236 if (func.action == LFUN_UNKNOWN_ACTION &&
237 state == key_modifier::shift) {
238 lyxerr[Debug::KEY] << "Trying without shift" << endl;
239 func = keyseq.addkey(keysym, key_modifier::none);
240 lyxerr[Debug::KEY] << "Action now " << func.action << endl;
243 if (func.action == LFUN_UNKNOWN_ACTION) {
244 // Hmm, we didn't match any of the keysequences. See
245 // if it's normal insertable text not already covered
247 if (keysym->isText() && keyseq.length() == 1) {
248 lyxerr[Debug::KEY] << "isText() is true, inserting." << endl;
249 func = FuncRequest(LFUN_SELFINSERT);
251 lyxerr[Debug::KEY] << "Unknown, !isText() - giving up" << endl;
252 owner->message(_("Unknown function."));
257 if (func.action == LFUN_SELFINSERT) {
258 if (encoded_last_key != 0) {
259 string arg(1, encoded_last_key);
260 dispatch(FuncRequest(LFUN_SELFINSERT, arg));
262 << "SelfInsert arg[`" << arg << "']" << endl;
270 FuncStatus LyXFunc::getStatus(FuncRequest const & cmd) const
272 //lyxerr << "LyXFunc::getStatus: cmd: " << cmd << endl;
274 LCursor & cur = view()->cursor();
276 /* In LyX/Mac, when a dialog is open, the menus of the
277 application can still be accessed without giving focus to
278 the main window. In this case, we want to disable the menu
279 entries that are buffer-related.
282 if (cmd.origin == FuncRequest::UI && !owner->hasFocus())
285 buf = owner->buffer();
287 if (cmd.action == LFUN_NOACTION) {
288 flag.message(N_("Nothing to do"));
293 switch (cmd.action) {
294 case LFUN_UNKNOWN_ACTION:
295 #ifndef HAVE_LIBAIKSAURUS
296 case LFUN_THESAURUS_ENTRY:
302 flag |= lyx_gui::getStatus(cmd);
305 if (flag.unknown()) {
306 flag.message(N_("Unknown action"));
310 if (!flag.enabled()) {
311 if (flag.message().empty())
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 InsetBase::Code code = inset->lyxCode();
401 case InsetBase::TABULAR_CODE:
402 enable = cmd.argument == "tabular";
404 case InsetBase::ERT_CODE:
405 enable = cmd.argument == "ert";
407 case InsetBase::FLOAT_CODE:
408 enable = cmd.argument == "float";
410 case InsetBase::WRAP_CODE:
411 enable = cmd.argument == "wrap";
413 case InsetBase::NOTE_CODE:
414 enable = cmd.argument == "note";
416 case InsetBase::BRANCH_CODE:
417 enable = cmd.argument == "branch";
419 case InsetBase::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() != InsetBase::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 // the default error message if we disable the command
541 if (!flag.enabled() && flag.message().empty())
542 flag.message(N_("Command disabled"));
550 bool ensureBufferClean(BufferView * bv)
552 Buffer & buf = *bv->buffer();
556 string const file = MakeDisplayPath(buf.fileName(), 30);
557 string text = bformat(_("The document %1$s has unsaved "
558 "changes.\n\nDo you want to save "
559 "the document?"), file);
560 int const ret = Alert::prompt(_("Save changed document?"),
561 text, 0, 1, _("&Save"),
565 bv->owner()->dispatch(FuncRequest(LFUN_MENUWRITE));
567 return buf.isClean();
571 void showPrintError(string const & name)
573 string str = bformat(_("Could not print the document %1$s.\n"
574 "Check that your printer is set up correctly."),
575 MakeDisplayPath(name, 50));
576 Alert::error(_("Print document failed"), str);
580 void loadTextclass(string const & name)
582 std::pair<bool, lyx::textclass_type> const tc_pair =
583 textclasslist.NumberOfClass(name);
585 if (!tc_pair.first) {
586 lyxerr << "Document class \"" << name
587 << "\" does not exist."
592 lyx::textclass_type const tc = tc_pair.second;
594 if (!textclasslist[tc].load()) {
595 string s = bformat(_("The document could not be converted\n"
596 "into the document class %1$s."),
597 textclasslist[tc].name());
598 Alert::error(_("Could not change class"), s);
605 void LyXFunc::dispatch(FuncRequest const & cmd)
607 BOOST_ASSERT(view());
608 string const argument = cmd.argument;
609 kb_action const action = cmd.action;
611 lyxerr[Debug::ACTION] << "LyXFunc::dispatch: cmd: " << cmd << endl;
612 //lyxerr << "LyXFunc::dispatch: cmd: " << cmd << endl;
614 // we have not done anything wrong yet.
616 dispatch_buffer.erase();
620 FuncStatus const flag = getStatus(cmd);
621 if (!flag.enabled()) {
622 // We cannot use this function here
623 lyxerr[Debug::ACTION] << "LyXFunc::dispatch: "
624 << lyxaction.getActionName(action)
625 << " [" << action << "] is disabled at this location"
627 setErrorMessage(flag.message());
630 if (view()->available())
631 view()->hideCursor();
635 case LFUN_WORDFINDFORWARD:
636 case LFUN_WORDFINDBACKWARD: {
637 static string last_search;
638 string searched_string;
640 if (!argument.empty()) {
641 last_search = argument;
642 searched_string = argument;
644 searched_string = last_search;
647 if (searched_string.empty())
650 bool const fw = action == LFUN_WORDFINDFORWARD;
652 lyx::find::find2string(searched_string, true, false, fw);
653 lyx::find::find(view(), FuncRequest(LFUN_WORD_FIND, data));
658 owner->message(keyseq.printOptions());
661 case LFUN_EXEC_COMMAND:
662 owner->getToolbars().display("minibuffer", true);
663 owner->focus_command_buffer();
668 meta_fake_bit = key_modifier::none;
669 if (view()->available())
670 // cancel any selection
671 dispatch(FuncRequest(LFUN_MARK_OFF));
672 setMessage(N_("Cancel"));
676 meta_fake_bit = key_modifier::alt;
677 setMessage(keyseq.print());
680 case LFUN_READ_ONLY_TOGGLE:
681 if (owner->buffer()->lyxvc().inUse())
682 owner->buffer()->lyxvc().toggleReadOnly();
684 owner->buffer()->setReadonly(
685 !owner->buffer()->isReadonly());
688 // --- Menus -----------------------------------------------
690 menuNew(argument, false);
693 case LFUN_MENUNEWTMPLT:
694 menuNew(argument, true);
697 case LFUN_CLOSEBUFFER:
702 if (!owner->buffer()->isUnnamed()) {
703 string const str = bformat(_("Saving document %1$s..."),
704 MakeDisplayPath(owner->buffer()->fileName()));
706 MenuWrite(owner->buffer());
707 owner->message(str + _(" done."));
709 WriteAs(owner->buffer());
713 WriteAs(owner->buffer(), argument);
716 case LFUN_MENURELOAD: {
717 string const file = MakeDisplayPath(view()->buffer()->fileName(), 20);
718 string text = bformat(_("Any changes will be lost. Are you sure "
719 "you want to revert to the saved version of the document %1$s?"), file);
720 int const ret = Alert::prompt(_("Revert to saved document?"),
721 text, 0, 1, _("&Revert"), _("&Cancel"));
729 Exporter::Export(owner->buffer(), argument, true);
730 view()->showErrorList(BufferFormat(*owner->buffer()));
734 Exporter::Preview(owner->buffer(), argument);
735 view()->showErrorList(BufferFormat(*owner->buffer()));
739 Exporter::Export(owner->buffer(), "program", true);
740 view()->showErrorList(_("Build"));
744 owner->buffer()->runChktex();
745 view()->showErrorList(_("ChkTeX"));
749 if (argument == "custom")
750 owner->getDialogs().show("sendto");
752 Exporter::Export(owner->buffer(), argument, false);
753 view()->showErrorList(BufferFormat(*owner->buffer()));
757 case LFUN_EXPORT_CUSTOM: {
759 string command = split(argument, format_name, ' ');
760 Format const * format = formats.getFormat(format_name);
762 lyxerr << "Format \"" << format_name
763 << "\" not recognized!"
768 Buffer * buffer = owner->buffer();
770 // The name of the file created by the conversion process
773 // Output to filename
774 if (format->name() == "lyx") {
775 string const latexname =
776 buffer->getLatexName(false);
777 filename = ChangeExtension(latexname,
778 format->extension());
779 filename = AddName(buffer->temppath(), filename);
781 if (!buffer->writeFile(filename))
785 Exporter::Export(buffer, format_name, true,
789 // Substitute $$FName for filename
790 if (!contains(command, "$$FName"))
791 command = "( " + command + " ) < $$FName";
792 command = subst(command, "$$FName", filename);
794 // Execute the command in the background
796 call.startscript(Systemcall::DontWait, command);
803 string command = split(split(argument, target, ' '),
807 || target_name.empty()
808 || command.empty()) {
809 lyxerr << "Unable to parse \""
810 << argument << '"' << std::endl;
813 if (target != "printer" && target != "file") {
814 lyxerr << "Unrecognized target \""
815 << target << '"' << std::endl;
819 Buffer * buffer = owner->buffer();
821 if (!Exporter::Export(buffer, "dvi", true)) {
822 showPrintError(buffer->fileName());
826 // Push directory path.
827 string const path = buffer->temppath();
830 // there are three cases here:
831 // 1. we print to a file
832 // 2. we print directly to a printer
833 // 3. we print using a spool command (print to file first)
836 string const dviname =
837 ChangeExtension(buffer->getLatexName(true),
840 if (target == "printer") {
841 if (!lyxrc.print_spool_command.empty()) {
842 // case 3: print using a spool
843 string const psname =
844 ChangeExtension(dviname,".ps");
845 command += lyxrc.print_to_file
848 + QuoteName(dviname);
851 lyxrc.print_spool_command +' ';
852 if (target_name != "default") {
853 command2 += lyxrc.print_spool_printerprefix
857 command2 += QuoteName(psname);
859 // If successful, then spool command
860 res = one.startscript(
865 res = one.startscript(
866 Systemcall::DontWait,
869 // case 2: print directly to a printer
870 res = one.startscript(
871 Systemcall::DontWait,
872 command + QuoteName(dviname));
876 // case 1: print to a file
877 command += lyxrc.print_to_file
878 + QuoteName(MakeAbsPath(target_name,
881 + QuoteName(dviname);
882 res = one.startscript(Systemcall::DontWait,
887 showPrintError(buffer->fileName());
900 InsetCommandParams p("tableofcontents");
901 string const data = InsetCommandMailer::params2string("toc", p);
902 owner->getDialogs().show("toc", data, 0);
910 case LFUN_RECONFIGURE:
914 case LFUN_HELP_OPEN: {
915 string const arg = argument;
917 setErrorMessage(N_("Missing argument"));
920 string const fname = i18nLibFileSearch("doc", arg, "lyx");
922 lyxerr << "LyX: unable to find documentation file `"
923 << arg << "'. Bad installation?" << endl;
926 owner->message(bformat(_("Opening help file %1$s..."),
927 MakeDisplayPath(fname)));
928 view()->loadLyXFile(fname, false);
932 // --- version control -------------------------------
933 case LFUN_VC_REGISTER:
934 if (!ensureBufferClean(view()))
936 if (!owner->buffer()->lyxvc().inUse()) {
937 owner->buffer()->lyxvc().registrer();
942 case LFUN_VC_CHECKIN:
943 if (!ensureBufferClean(view()))
945 if (owner->buffer()->lyxvc().inUse()
946 && !owner->buffer()->isReadonly()) {
947 owner->buffer()->lyxvc().checkIn();
952 case LFUN_VC_CHECKOUT:
953 if (!ensureBufferClean(view()))
955 if (owner->buffer()->lyxvc().inUse()
956 && owner->buffer()->isReadonly()) {
957 owner->buffer()->lyxvc().checkOut();
963 owner->buffer()->lyxvc().revert();
968 owner->buffer()->lyxvc().undoLast();
972 // --- buffers ----------------------------------------
973 case LFUN_SWITCHBUFFER:
974 view()->setBuffer(bufferlist.getBuffer(argument));
977 case LFUN_NEXTBUFFER:
978 view()->setBuffer(bufferlist.next(view()->buffer()));
981 case LFUN_PREVIOUSBUFFER:
982 view()->setBuffer(bufferlist.previous(view()->buffer()));
986 NewFile(view(), argument);
993 case LFUN_DROP_LAYOUTS_CHOICE:
994 owner->getToolbars().openLayoutList();
997 case LFUN_MENU_OPEN_BY_NAME:
998 owner->getMenubar().openByName(argument);
1001 // --- lyxserver commands ----------------------------
1003 setMessage(owner->buffer()->fileName());
1004 lyxerr[Debug::INFO] << "FNAME["
1005 << owner->buffer()->fileName()
1010 dispatch_buffer = keyseq.print();
1011 lyxserver->notifyClient(dispatch_buffer);
1014 case LFUN_GOTOFILEROW: {
1017 istringstream is(argument);
1018 is >> file_name >> row;
1019 if (prefixIs(file_name, getTmpDir())) {
1020 // Needed by inverse dvi search. If it is a file
1021 // in tmpdir, call the apropriated function
1022 view()->setBuffer(bufferlist.getBufferFromTmp(file_name));
1024 // Must replace extension of the file to be .lyx
1025 // and get full path
1026 string const s = ChangeExtension(file_name, ".lyx");
1027 // Either change buffer or load the file
1028 if (bufferlist.exists(s)) {
1029 view()->setBuffer(bufferlist.getBuffer(s));
1031 view()->loadLyXFile(s);
1035 view()->setCursorFromRow(row);
1038 // see BufferView_pimpl::center()
1039 view()->updateScrollbar();
1043 case LFUN_GOTO_PARAGRAPH: {
1044 istringstream is(argument);
1047 ParIterator par = owner->buffer()->getParFromID(id);
1048 if (par == owner->buffer()->par_iterator_end()) {
1049 lyxerr[Debug::INFO] << "No matching paragraph found! ["
1050 << id << ']' << endl;
1053 lyxerr[Debug::INFO] << "Paragraph " << par->id()
1054 << " found." << endl;
1058 view()->setCursor(par, 0);
1060 view()->switchKeyMap();
1061 owner->view_state_changed();
1064 // see BufferView_pimpl::center()
1065 view()->updateScrollbar();
1069 case LFUN_DIALOG_SHOW: {
1070 string const name = cmd.getArg(0);
1071 string data = trim(cmd.argument.substr(name.size()));
1073 if (name == "character") {
1074 data = freefont2string();
1076 owner->getDialogs().show("character", data);
1079 else if (name == "latexlog") {
1080 pair<Buffer::LogType, string> const logfile =
1081 owner->buffer()->getLogName();
1082 switch (logfile.first) {
1083 case Buffer::latexlog:
1086 case Buffer::buildlog:
1090 data += logfile.second;
1091 owner->getDialogs().show("log", data);
1093 else if (name == "vclog") {
1094 string const data = "vc " +
1095 owner->buffer()->lyxvc().getLogFile();
1096 owner->getDialogs().show("log", data);
1099 owner->getDialogs().show(name, data);
1103 case LFUN_DIALOG_SHOW_NEW_INSET: {
1104 string const name = cmd.getArg(0);
1105 string data = trim(cmd.argument.substr(name.size()));
1106 if (name == "bibitem" ||
1108 name == "include" ||
1114 InsetCommandParams p(name);
1115 data = InsetCommandMailer::params2string(name, p);
1116 } else if (name == "box") {
1117 // \c data == "Boxed" || "Frameless" etc
1118 InsetBoxParams p(data);
1119 data = InsetBoxMailer::params2string(p);
1120 } else if (name == "branch") {
1121 InsetBranchParams p;
1122 data = InsetBranchMailer::params2string(p);
1123 } else if (name == "citation") {
1124 InsetCommandParams p("cite");
1125 data = InsetCommandMailer::params2string(name, p);
1126 } else if (name == "ert") {
1127 data = InsetERTMailer::params2string(InsetCollapsable::Open);
1128 } else if (name == "external") {
1129 InsetExternalParams p;
1130 Buffer const & buffer = *owner->buffer();
1131 data = InsetExternalMailer::params2string(p, buffer);
1132 } else if (name == "float") {
1134 data = InsetFloatMailer::params2string(p);
1135 } else if (name == "graphics") {
1136 InsetGraphicsParams p;
1137 Buffer const & buffer = *owner->buffer();
1138 data = InsetGraphicsMailer::params2string(p, buffer);
1139 } else if (name == "note") {
1141 data = InsetNoteMailer::params2string(p);
1142 } else if (name == "vspace") {
1144 data = InsetVSpaceMailer::params2string(space);
1145 } else if (name == "wrap") {
1147 data = InsetWrapMailer::params2string(p);
1149 owner->getDialogs().show(name, data, 0);
1153 case LFUN_DIALOG_SHOW_NEXT_INSET:
1156 case LFUN_DIALOG_UPDATE: {
1157 string const & name = argument;
1158 // Can only update a dialog connected to an existing inset
1159 InsetBase * inset = owner->getDialogs().getOpenInset(name);
1161 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument);
1162 inset->dispatch(view()->cursor(), fr);
1163 } else if (name == "paragraph") {
1164 dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
1165 } else if (name == "prefs") {
1166 owner->getDialogs().update(name, string());
1171 case LFUN_DIALOG_HIDE:
1172 Dialogs::hide(argument, 0);
1175 case LFUN_DIALOG_DISCONNECT_INSET:
1176 owner->getDialogs().disconnect(argument);
1179 case LFUN_CHILDOPEN: {
1180 string const filename =
1181 MakeAbsPath(argument, owner->buffer()->filePath());
1182 setMessage(N_("Opening child document ") +
1183 MakeDisplayPath(filename) + "...");
1184 view()->savePosition(0);
1185 string const parentfilename = owner->buffer()->fileName();
1186 if (bufferlist.exists(filename))
1187 view()->setBuffer(bufferlist.getBuffer(filename));
1189 view()->loadLyXFile(filename);
1190 // Set the parent name of the child document.
1191 // This makes insertion of citations and references in the child work,
1192 // when the target is in the parent or another child document.
1193 owner->buffer()->setParentName(parentfilename);
1197 case LFUN_TOGGLECURSORFOLLOW:
1198 lyxrc.cursor_follows_scrollbar = !lyxrc.cursor_follows_scrollbar;
1202 owner->getIntl().KeyMapOn(false);
1205 case LFUN_KMAP_PRIM:
1206 owner->getIntl().KeyMapPrim();
1210 owner->getIntl().KeyMapSec();
1213 case LFUN_KMAP_TOGGLE:
1214 owner->getIntl().ToggleKeyMap();
1220 string rest = split(argument, countstr, ' ');
1221 istringstream is(countstr);
1224 lyxerr << "repeat: count: " << count << " cmd: " << rest << endl;
1225 for (int i = 0; i < count; ++i)
1226 dispatch(lyxaction.lookupFunc(rest));
1230 case LFUN_SEQUENCE: {
1231 // argument contains ';'-terminated commands
1232 string arg = argument;
1233 while (!arg.empty()) {
1235 arg = split(arg, first, ';');
1236 FuncRequest func(lyxaction.lookupFunc(first));
1237 func.origin = cmd.origin;
1243 case LFUN_SAVEPREFERENCES: {
1244 Path p(user_lyxdir());
1245 lyxrc.write("preferences", false);
1249 case LFUN_SCREEN_FONT_UPDATE:
1250 // handle the screen font changes.
1251 lyxrc.set_font_norm_type();
1252 lyx_gui::update_fonts();
1253 // All visible buffers will need resize
1257 case LFUN_SET_COLOR: {
1259 string const x11_name = split(argument, lyx_name, ' ');
1260 if (lyx_name.empty() || x11_name.empty()) {
1261 setErrorMessage(N_("Syntax: set-color <lyx_name>"
1266 bool const graphicsbg_changed =
1267 (lyx_name == lcolor.getLyXName(LColor::graphicsbg) &&
1268 x11_name != lcolor.getX11Name(LColor::graphicsbg));
1270 if (!lcolor.setColor(lyx_name, x11_name)) {
1272 bformat(_("Set-color \"%1$s\" failed "
1273 "- color is undefined or "
1274 "may not be redefined"), lyx_name));
1278 lyx_gui::update_color(lcolor.getFromLyXName(lyx_name));
1280 if (graphicsbg_changed) {
1281 #ifdef WITH_WARNINGS
1282 #warning FIXME!! The graphics cache no longer has a changeDisplay method.
1285 lyx::graphics::GCache::get().changeDisplay(true);
1292 owner->message(argument);
1295 case LFUN_TOOLTIPS_TOGGLE:
1296 owner->getDialogs().toggleTooltips();
1299 case LFUN_EXTERNAL_EDIT: {
1300 FuncRequest fr(action, argument);
1301 InsetExternal().dispatch(view()->cursor(), fr);
1305 case LFUN_GRAPHICS_EDIT: {
1306 FuncRequest fr(action, argument);
1307 InsetGraphics().dispatch(view()->cursor(), fr);
1311 case LFUN_ALL_INSETS_TOGGLE: {
1313 string const name = split(argument, action, ' ');
1314 InsetBase::Code const inset_code =
1315 InsetBase::translate(name);
1317 LCursor & cur = view()->cursor();
1318 FuncRequest fr(LFUN_INSET_TOGGLE, action);
1320 InsetBase & inset = owner->buffer()->inset();
1321 InsetIterator it = inset_iterator_begin(inset);
1322 InsetIterator const end = inset_iterator_end(inset);
1323 for (; it != end; ++it) {
1324 if (inset_code == InsetBase::NO_CODE
1325 || inset_code == it->lyxCode())
1326 it->dispatch(cur, fr);
1331 case LFUN_LANGUAGE_BUFFER: {
1332 Buffer & buffer = *owner->buffer();
1333 Language const * oldL = buffer.params().language;
1334 Language const * newL = languages.getLanguage(argument);
1335 if (!newL || oldL == newL)
1338 if (oldL->RightToLeft() == newL->RightToLeft()
1339 && !buffer.isMultiLingual())
1340 buffer.changeLanguage(oldL, newL);
1342 buffer.updateDocLang(newL);
1346 case LFUN_SAVE_AS_DEFAULT: {
1347 string const fname =
1348 AddName(AddPath(user_lyxdir(), "templates/"),
1350 Buffer defaults(fname);
1352 istringstream ss(argument);
1355 int const unknown_tokens = defaults.readHeader(lex);
1357 if (unknown_tokens != 0) {
1358 lyxerr << "Warning in LFUN_SAVE_AS_DEFAULT!\n"
1359 << unknown_tokens << " unknown token"
1360 << (unknown_tokens == 1 ? "" : "s")
1364 if (defaults.writeFile(defaults.fileName()))
1365 setMessage(_("Document defaults saved in ")
1366 + MakeDisplayPath(fname));
1368 setErrorMessage(_("Unable to save document defaults"));
1372 case LFUN_BUFFERPARAMS_APPLY: {
1373 biblio::CiteEngine const engine =
1374 owner->buffer()->params().cite_engine;
1376 istringstream ss(argument);
1379 int const unknown_tokens =
1380 owner->buffer()->readHeader(lex);
1382 if (unknown_tokens != 0) {
1383 lyxerr << "Warning in LFUN_BUFFERPARAMS_APPLY!\n"
1384 << unknown_tokens << " unknown token"
1385 << (unknown_tokens == 1 ? "" : "s")
1388 if (engine == owner->buffer()->params().cite_engine)
1391 LCursor & cur = view()->cursor();
1392 FuncRequest fr(LFUN_INSET_REFRESH);
1394 InsetBase & inset = owner->buffer()->inset();
1395 InsetIterator it = inset_iterator_begin(inset);
1396 InsetIterator const end = inset_iterator_end(inset);
1397 for (; it != end; ++it)
1398 if (it->lyxCode() == InsetBase::CITE_CODE)
1399 it->dispatch(cur, fr);
1403 case LFUN_TEXTCLASS_APPLY: {
1404 Buffer * buffer = owner->buffer();
1406 lyx::textclass_type const old_class =
1407 buffer->params().textclass;
1409 loadTextclass(argument);
1411 std::pair<bool, lyx::textclass_type> const tc_pair =
1412 textclasslist.NumberOfClass(argument);
1417 lyx::textclass_type const new_class = tc_pair.second;
1418 if (old_class == new_class)
1422 owner->message(_("Converting document to new document class..."));
1424 lyx::cap::SwitchLayoutsBetweenClasses(
1425 old_class, new_class,
1426 buffer->paragraphs(), el);
1428 bufferErrors(*buffer, el);
1429 view()->showErrorList(_("Class switch"));
1433 case LFUN_TEXTCLASS_LOAD:
1434 loadTextclass(argument);
1437 case LFUN_LYXRC_APPLY: {
1438 istringstream ss(argument);
1439 bool const success = lyxrc.read(ss) == 0;
1442 lyxerr << "Warning in LFUN_LYXRC_APPLY!\n"
1443 << "Unable to read lyxrc data"
1451 view()->cursor().dispatch(cmd);
1452 if (view()->cursor().result().dispatched())
1453 update |= view()->cursor().result().update();
1455 update |= view()->dispatch(cmd);
1460 if (view()->available()) {
1461 // Redraw screen unless explicitly told otherwise.
1462 // This also initializes the position cache for all insets
1463 // in (at least partially) visible top-level paragraphs.
1464 view()->update(true, update);
1466 // if we executed a mutating lfun, mark the buffer as dirty
1467 if (getStatus(cmd).enabled()
1468 && !lyxaction.funcHasFlag(cmd.action, LyXAction::NoBuffer)
1469 && !lyxaction.funcHasFlag(cmd.action, LyXAction::ReadOnly))
1470 view()->buffer()->markDirty();
1473 if (view()->cursor().inTexted()) {
1474 view()->owner()->updateLayoutChoice();
1477 sendDispatchMessage(getMessage(), cmd);
1481 void LyXFunc::sendDispatchMessage(string const & msg, FuncRequest const & cmd)
1483 owner->updateMenubar();
1484 owner->updateToolbars();
1486 const bool verbose = (cmd.origin == FuncRequest::UI
1487 || cmd.origin == FuncRequest::COMMANDBUFFER);
1489 if (cmd.action == LFUN_SELFINSERT || !verbose) {
1490 lyxerr[Debug::ACTION] << "dispatch msg is " << msg << endl;
1492 owner->message(msg);
1496 string dispatch_msg = msg;
1497 if (!dispatch_msg.empty())
1498 dispatch_msg += ' ';
1500 string comname = lyxaction.getActionName(cmd.action);
1502 bool argsadded = false;
1504 if (!cmd.argument.empty()) {
1505 if (cmd.action != LFUN_UNKNOWN_ACTION) {
1506 comname += ' ' + cmd.argument;
1511 string const shortcuts = toplevel_keymap->printbindings(cmd);
1513 if (!shortcuts.empty()) {
1514 comname += ": " + shortcuts;
1515 } else if (!argsadded && !cmd.argument.empty()) {
1516 comname += ' ' + cmd.argument;
1519 if (!comname.empty()) {
1520 comname = rtrim(comname);
1521 dispatch_msg += '(' + comname + ')';
1524 lyxerr[Debug::ACTION] << "verbose dispatch msg " << dispatch_msg << endl;
1525 if (!dispatch_msg.empty())
1526 owner->message(dispatch_msg);
1530 void LyXFunc::setupLocalKeymap()
1532 keyseq.stdmap = toplevel_keymap.get();
1533 keyseq.curmap = toplevel_keymap.get();
1534 cancel_meta_seq.stdmap = toplevel_keymap.get();
1535 cancel_meta_seq.curmap = toplevel_keymap.get();
1539 void LyXFunc::menuNew(string const & name, bool fromTemplate)
1541 string initpath = lyxrc.document_path;
1542 string filename(name);
1544 if (view()->available()) {
1545 string const trypath = owner->buffer()->filePath();
1546 // If directory is writeable, use this as default.
1547 if (IsDirWriteable(trypath))
1551 static int newfile_number;
1553 if (filename.empty()) {
1554 filename = AddName(lyxrc.document_path,
1555 "newfile" + tostr(++newfile_number) + ".lyx");
1556 FileInfo fi(filename);
1557 while (bufferlist.exists(filename) || fi.readable()) {
1559 filename = AddName(lyxrc.document_path,
1560 "newfile" + tostr(newfile_number) +
1562 fi.newFile(filename);
1566 // The template stuff
1569 FileDialog fileDlg(_("Select template file"),
1570 LFUN_SELECT_FILE_SYNC,
1571 make_pair(string(_("Documents|#o#O")),
1572 string(lyxrc.document_path)),
1573 make_pair(string(_("Templates|#T#t")),
1574 string(lyxrc.template_path)));
1576 FileDialog::Result result =
1577 fileDlg.open(lyxrc.template_path,
1578 FileFilterList(_("LyX Documents (*.lyx)")),
1581 if (result.first == FileDialog::Later)
1583 if (result.second.empty())
1585 templname = result.second;
1588 view()->newFile(filename, templname, !name.empty());
1592 void LyXFunc::open(string const & fname)
1594 string initpath = lyxrc.document_path;
1596 if (view()->available()) {
1597 string const trypath = owner->buffer()->filePath();
1598 // If directory is writeable, use this as default.
1599 if (IsDirWriteable(trypath))
1605 if (fname.empty()) {
1606 FileDialog fileDlg(_("Select document to open"),
1608 make_pair(string(_("Documents|#o#O")),
1609 string(lyxrc.document_path)),
1610 make_pair(string(_("Examples|#E#e")),
1611 string(AddPath(system_lyxdir(), "examples"))));
1613 FileDialog::Result result =
1614 fileDlg.open(initpath,
1615 FileFilterList(_("LyX Documents (*.lyx)")),
1618 if (result.first == FileDialog::Later)
1621 filename = result.second;
1623 // check selected filename
1624 if (filename.empty()) {
1625 owner->message(_("Canceled."));
1631 // get absolute path of file and add ".lyx" to the filename if
1633 string const fullpath = FileSearch(string(), filename, "lyx");
1634 if (!fullpath.empty()) {
1635 filename = fullpath;
1638 string const disp_fn(MakeDisplayPath(filename));
1640 // if the file doesn't exist, let the user create one
1641 FileInfo const f(filename, true);
1643 // the user specifically chose this name. Believe them.
1644 view()->newFile(filename, "", true);
1648 owner->message(bformat(_("Opening document %1$s..."), disp_fn));
1651 if (view()->loadLyXFile(filename)) {
1652 str2 = bformat(_("Document %1$s opened."), disp_fn);
1654 str2 = bformat(_("Could not open document %1$s"), disp_fn);
1656 owner->message(str2);
1660 void LyXFunc::doImport(string const & argument)
1663 string filename = split(argument, format, ' ');
1665 lyxerr[Debug::INFO] << "LyXFunc::doImport: " << format
1666 << " file: " << filename << endl;
1668 // need user interaction
1669 if (filename.empty()) {
1670 string initpath = lyxrc.document_path;
1672 if (view()->available()) {
1673 string const trypath = owner->buffer()->filePath();
1674 // If directory is writeable, use this as default.
1675 if (IsDirWriteable(trypath))
1679 string const text = bformat(_("Select %1$s file to import"),
1680 formats.prettyName(format));
1682 FileDialog fileDlg(text,
1684 make_pair(string(_("Documents|#o#O")),
1685 string(lyxrc.document_path)),
1686 make_pair(string(_("Examples|#E#e")),
1687 string(AddPath(system_lyxdir(), "examples"))));
1689 string const filter = formats.prettyName(format)
1690 + " (*." + formats.extension(format) + ')';
1692 FileDialog::Result result =
1693 fileDlg.open(initpath,
1694 FileFilterList(filter),
1697 if (result.first == FileDialog::Later)
1700 filename = result.second;
1702 // check selected filename
1703 if (filename.empty())
1704 owner->message(_("Canceled."));
1707 if (filename.empty())
1710 // get absolute path of file
1711 filename = MakeAbsPath(filename);
1713 string const lyxfile = ChangeExtension(filename, ".lyx");
1715 // Check if the document already is open
1716 if (lyx_gui::use_gui && bufferlist.exists(lyxfile)) {
1717 if (!bufferlist.close(bufferlist.getBuffer(lyxfile), true)) {
1718 owner->message(_("Canceled."));
1723 // if the file exists already, and we didn't do
1724 // -i lyx thefile.lyx, warn
1725 if (FileInfo(lyxfile, true).exist() && filename != lyxfile) {
1726 string const file = MakeDisplayPath(lyxfile, 30);
1728 string text = bformat(_("The document %1$s already exists.\n\n"
1729 "Do you want to over-write that document?"), file);
1730 int const ret = Alert::prompt(_("Over-write document?"),
1731 text, 0, 1, _("&Over-write"), _("&Cancel"));
1734 owner->message(_("Canceled."));
1739 Importer::Import(owner, filename, format);
1743 void LyXFunc::closeBuffer()
1745 if (bufferlist.close(owner->buffer(), true) && !quitting) {
1746 if (bufferlist.empty()) {
1747 // need this otherwise SEGV may occur while
1748 // trying to set variables that don't exist
1749 // since there's no current buffer
1750 owner->getDialogs().hideBufferDependent();
1752 view()->setBuffer(bufferlist.first());
1758 // Each "owner" should have it's own message method. lyxview and
1759 // the minibuffer would use the minibuffer, but lyxserver would
1760 // send an ERROR signal to its client. Alejandro 970603
1761 // This function is bit problematic when it comes to NLS, to make the
1762 // lyx servers client be language indepenent we must not translate
1763 // strings sent to this func.
1764 void LyXFunc::setErrorMessage(string const & m) const
1766 dispatch_buffer = m;
1771 void LyXFunc::setMessage(string const & m) const
1773 dispatch_buffer = m;
1777 string const LyXFunc::viewStatusMessage()
1779 // When meta-fake key is pressed, show the key sequence so far + "M-".
1781 return keyseq.print() + "M-";
1783 // Else, when a non-complete key sequence is pressed,
1784 // show the available options.
1785 if (keyseq.length() > 0 && !keyseq.deleted())
1786 return keyseq.printOptions();
1788 if (!view()->available())
1789 return _("Welcome to LyX!");
1791 return view()->cursor().currentState();
1795 BufferView * LyXFunc::view() const
1797 BOOST_ASSERT(owner);
1798 return owner->view().get();
1802 bool LyXFunc::wasMetaKey() const
1804 return (meta_fake_bit != key_modifier::none);