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;
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 if (!flag.enabled()) {
312 if (flag.message().empty())
313 flag.message(N_("Command disabled"));
317 // Check whether we need a buffer
318 if (!lyxaction.funcHasFlag(cmd.action, LyXAction::NoBuffer) && !buf) {
320 flag.message(N_("Command not allowed with"
321 "out any document open"));
326 // I would really like to avoid having this switch and rather try to
327 // encode this in the function itself.
328 // -- And I'd rather let an inset decide which LFUNs it is willing
329 // to handle (Andre')
331 switch (cmd.action) {
332 case LFUN_TOOLTIPS_TOGGLE:
333 flag.setOnOff(owner->getDialogs().tooltipsEnabled());
336 case LFUN_READ_ONLY_TOGGLE:
337 flag.setOnOff(buf->isReadonly());
340 case LFUN_SWITCHBUFFER:
341 // toggle on the current buffer, but do not toggle off
342 // the other ones (is that a good idea?)
343 if (cmd.argument == buf->fileName())
348 enable = cmd.argument == "custom"
349 || Exporter::IsExportable(*buf, cmd.argument);
353 enable = cur.selection();
357 enable = buf->isLatex() && lyxrc.chktex_command != "none";
361 enable = Exporter::IsExportable(*buf, "program");
364 case LFUN_LAYOUT_TABULAR:
365 enable = cur.innerInsetOfType(InsetBase::TABULAR_CODE);
369 case LFUN_LAYOUT_PARAGRAPH:
370 enable = !cur.inset().forceDefaultParagraphs(&cur.inset());
373 case LFUN_VC_REGISTER:
374 enable = !buf->lyxvc().inUse();
376 case LFUN_VC_CHECKIN:
377 enable = buf->lyxvc().inUse() && !buf->isReadonly();
379 case LFUN_VC_CHECKOUT:
380 enable = buf->lyxvc().inUse() && buf->isReadonly();
384 enable = buf->lyxvc().inUse();
386 case LFUN_MENURELOAD:
387 enable = !buf->isUnnamed() && !buf->isClean();
391 case LFUN_INSET_SETTINGS: {
395 UpdatableInset * inset = cur.inset().asUpdatableInset();
396 lyxerr << "inset: " << inset << endl;
400 InsetBase::Code code = inset->lyxCode();
402 case InsetBase::TABULAR_CODE:
403 enable = cmd.argument == "tabular";
405 case InsetBase::ERT_CODE:
406 enable = cmd.argument == "ert";
408 case InsetBase::FLOAT_CODE:
409 enable = cmd.argument == "float";
411 case InsetBase::WRAP_CODE:
412 enable = cmd.argument == "wrap";
414 case InsetBase::NOTE_CODE:
415 enable = cmd.argument == "note";
417 case InsetBase::BRANCH_CODE:
418 enable = cmd.argument == "branch";
420 case InsetBase::BOX_CODE:
421 enable = cmd.argument == "box";
429 case LFUN_DIALOG_SHOW: {
430 string const name = cmd.getArg(0);
432 enable = name == "aboutlyx"
436 || name == "texinfo";
437 else if (name == "print")
438 enable = Exporter::IsExportable(*buf, "dvi")
439 && lyxrc.print_command != "none";
440 else if (name == "character")
441 enable = cur.inset().lyxCode() != InsetBase::ERT_CODE;
442 else if (name == "vclog")
443 enable = buf->lyxvc().inUse();
444 else if (name == "latexlog")
445 enable = IsFileReadable(buf->getLogName().second);
449 case LFUN_DIALOG_UPDATE: {
450 string const name = cmd.getArg(0);
452 enable = name == "prefs";
456 // this one is difficult to get right. As a half-baked
457 // solution, we consider only the first action of the sequence
458 case LFUN_SEQUENCE: {
459 // argument contains ';'-terminated commands
460 string const firstcmd = token(cmd.argument, ';', 0);
461 FuncRequest func(lyxaction.lookupFunc(firstcmd));
462 func.origin = cmd.origin;
463 flag = getStatus(func);
467 case LFUN_MENUNEWTMPLT:
468 case LFUN_WORDFINDFORWARD:
469 case LFUN_WORDFINDBACKWARD:
471 case LFUN_EXEC_COMMAND:
474 case LFUN_CLOSEBUFFER:
483 case LFUN_RECONFIGURE:
487 case LFUN_DROP_LAYOUTS_CHOICE:
488 case LFUN_MENU_OPEN_BY_NAME:
491 case LFUN_GOTOFILEROW:
492 case LFUN_GOTO_PARAGRAPH:
493 case LFUN_DIALOG_SHOW_NEW_INSET:
494 case LFUN_DIALOG_SHOW_NEXT_INSET:
495 case LFUN_DIALOG_HIDE:
496 case LFUN_DIALOG_DISCONNECT_INSET:
498 case LFUN_TOGGLECURSORFOLLOW:
502 case LFUN_KMAP_TOGGLE:
504 case LFUN_EXPORT_CUSTOM:
506 case LFUN_SAVEPREFERENCES:
507 case LFUN_SCREEN_FONT_UPDATE:
510 case LFUN_EXTERNAL_EDIT:
511 case LFUN_GRAPHICS_EDIT:
512 case LFUN_ALL_INSETS_TOGGLE:
513 case LFUN_LANGUAGE_BUFFER:
514 case LFUN_TEXTCLASS_APPLY:
515 case LFUN_TEXTCLASS_LOAD:
516 case LFUN_SAVE_AS_DEFAULT:
517 case LFUN_BUFFERPARAMS_APPLY:
518 case LFUN_LYXRC_APPLY:
519 case LFUN_NEXTBUFFER:
520 case LFUN_PREVIOUSBUFFER:
521 // these are handled in our dispatch()
526 if (!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 flag.message(N_("Document is read-only"));
541 // the default error message if we disable the command
542 if (!flag.enabled() && flag.message().empty())
543 flag.message(N_("Command disabled"));
551 bool ensureBufferClean(BufferView * bv)
553 Buffer & buf = *bv->buffer();
557 string const file = MakeDisplayPath(buf.fileName(), 30);
558 string text = bformat(_("The document %1$s has unsaved "
559 "changes.\n\nDo you want to save "
560 "the document?"), file);
561 int const ret = Alert::prompt(_("Save changed document?"),
562 text, 0, 1, _("&Save"),
566 bv->owner()->dispatch(FuncRequest(LFUN_MENUWRITE));
568 return buf.isClean();
572 void showPrintError(string const & name)
574 string str = bformat(_("Could not print the document %1$s.\n"
575 "Check that your printer is set up correctly."),
576 MakeDisplayPath(name, 50));
577 Alert::error(_("Print document failed"), str);
581 void loadTextclass(string const & name)
583 std::pair<bool, lyx::textclass_type> const tc_pair =
584 textclasslist.NumberOfClass(name);
586 if (!tc_pair.first) {
587 lyxerr << "Document class \"" << name
588 << "\" does not exist."
593 lyx::textclass_type const tc = tc_pair.second;
595 if (!textclasslist[tc].load()) {
596 string s = bformat(_("The document could not be converted\n"
597 "into the document class %1$s."),
598 textclasslist[tc].name());
599 Alert::error(_("Could not change class"), s);
606 void LyXFunc::dispatch(FuncRequest const & cmd)
608 BOOST_ASSERT(view());
609 string const argument = cmd.argument;
610 kb_action const action = cmd.action;
612 lyxerr[Debug::ACTION] << "LyXFunc::dispatch: cmd: " << cmd << endl;
613 //lyxerr << "LyXFunc::dispatch: cmd: " << cmd << endl;
615 // we have not done anything wrong yet.
617 dispatch_buffer.erase();
618 selection_possible = false;
622 FuncStatus const flag = getStatus(cmd);
623 if (!flag.enabled()) {
624 // We cannot use this function here
625 lyxerr[Debug::ACTION] << "LyXFunc::dispatch: "
626 << lyxaction.getActionName(action)
627 << " [" << action << "] is disabled at this location"
629 setErrorMessage(flag.message());
632 if (view()->available())
633 view()->hideCursor();
637 case LFUN_WORDFINDFORWARD:
638 case LFUN_WORDFINDBACKWARD: {
639 static string last_search;
640 string searched_string;
642 if (!argument.empty()) {
643 last_search = argument;
644 searched_string = argument;
646 searched_string = last_search;
649 if (searched_string.empty())
652 bool const fw = action == LFUN_WORDFINDFORWARD;
654 lyx::find::find2string(searched_string, true, false, fw);
655 lyx::find::find(view(), FuncRequest(LFUN_WORD_FIND, data));
660 owner->message(keyseq.printOptions());
663 case LFUN_EXEC_COMMAND:
664 owner->getToolbars().display("minibuffer", true);
665 owner->focus_command_buffer();
670 meta_fake_bit = key_modifier::none;
671 if (view()->available())
672 // cancel any selection
673 dispatch(FuncRequest(LFUN_MARK_OFF));
674 setMessage(N_("Cancel"));
678 meta_fake_bit = key_modifier::alt;
679 setMessage(keyseq.print());
682 case LFUN_READ_ONLY_TOGGLE:
683 if (owner->buffer()->lyxvc().inUse())
684 owner->buffer()->lyxvc().toggleReadOnly();
686 owner->buffer()->setReadonly(
687 !owner->buffer()->isReadonly());
690 // --- Menus -----------------------------------------------
692 menuNew(argument, false);
695 case LFUN_MENUNEWTMPLT:
696 menuNew(argument, true);
699 case LFUN_CLOSEBUFFER:
704 if (!owner->buffer()->isUnnamed()) {
705 string const str = bformat(_("Saving document %1$s..."),
706 MakeDisplayPath(owner->buffer()->fileName()));
708 MenuWrite(owner->buffer());
709 owner->message(str + _(" done."));
711 WriteAs(owner->buffer());
715 WriteAs(owner->buffer(), argument);
718 case LFUN_MENURELOAD: {
719 string const file = MakeDisplayPath(view()->buffer()->fileName(), 20);
720 string text = bformat(_("Any changes will be lost. Are you sure "
721 "you want to revert to the saved version of the document %1$s?"), file);
722 int const ret = Alert::prompt(_("Revert to saved document?"),
723 text, 0, 1, _("&Revert"), _("&Cancel"));
731 Exporter::Export(owner->buffer(), argument, true);
732 view()->showErrorList(BufferFormat(*owner->buffer()));
736 Exporter::Preview(owner->buffer(), argument);
737 view()->showErrorList(BufferFormat(*owner->buffer()));
741 Exporter::Export(owner->buffer(), "program", true);
742 view()->showErrorList(_("Build"));
746 owner->buffer()->runChktex();
747 view()->showErrorList(_("ChkTeX"));
751 if (argument == "custom")
752 owner->getDialogs().show("sendto");
754 Exporter::Export(owner->buffer(), argument, false);
755 view()->showErrorList(BufferFormat(*owner->buffer()));
759 case LFUN_EXPORT_CUSTOM: {
761 string command = split(argument, format_name, ' ');
762 Format const * format = formats.getFormat(format_name);
764 lyxerr << "Format \"" << format_name
765 << "\" not recognized!"
770 Buffer * buffer = owner->buffer();
772 // The name of the file created by the conversion process
775 // Output to filename
776 if (format->name() == "lyx") {
777 string const latexname =
778 buffer->getLatexName(false);
779 filename = ChangeExtension(latexname,
780 format->extension());
781 filename = AddName(buffer->temppath(), filename);
783 if (!buffer->writeFile(filename))
787 Exporter::Export(buffer, format_name, true,
791 // Substitute $$FName for filename
792 if (!contains(command, "$$FName"))
793 command = "( " + command + " ) < $$FName";
794 command = subst(command, "$$FName", filename);
796 // Execute the command in the background
798 call.startscript(Systemcall::DontWait, command);
805 string command = split(split(argument, target, ' '),
809 || target_name.empty()
810 || command.empty()) {
811 lyxerr << "Unable to parse \""
812 << argument << '"' << std::endl;
815 if (target != "printer" && target != "file") {
816 lyxerr << "Unrecognized target \""
817 << target << '"' << std::endl;
821 Buffer * buffer = owner->buffer();
823 if (!Exporter::Export(buffer, "dvi", true)) {
824 showPrintError(buffer->fileName());
828 // Push directory path.
829 string const path = buffer->temppath();
832 // there are three cases here:
833 // 1. we print to a file
834 // 2. we print directly to a printer
835 // 3. we print using a spool command (print to file first)
838 string const dviname =
839 ChangeExtension(buffer->getLatexName(true),
842 if (target == "printer") {
843 if (!lyxrc.print_spool_command.empty()) {
844 // case 3: print using a spool
845 string const psname =
846 ChangeExtension(dviname,".ps");
847 command += lyxrc.print_to_file
850 + QuoteName(dviname);
853 lyxrc.print_spool_command +' ';
854 if (target_name != "default") {
855 command2 += lyxrc.print_spool_printerprefix
859 command2 += QuoteName(psname);
861 // If successful, then spool command
862 res = one.startscript(
867 res = one.startscript(
868 Systemcall::DontWait,
871 // case 2: print directly to a printer
872 res = one.startscript(
873 Systemcall::DontWait,
874 command + QuoteName(dviname));
878 // case 1: print to a file
879 command += lyxrc.print_to_file
880 + QuoteName(MakeAbsPath(target_name,
883 + QuoteName(dviname);
884 res = one.startscript(Systemcall::DontWait,
889 showPrintError(buffer->fileName());
902 InsetCommandParams p("tableofcontents");
903 string const data = InsetCommandMailer::params2string("toc", p);
904 owner->getDialogs().show("toc", data, 0);
912 case LFUN_RECONFIGURE:
916 case LFUN_HELP_OPEN: {
917 string const arg = argument;
919 setErrorMessage(N_("Missing argument"));
922 string const fname = i18nLibFileSearch("doc", arg, "lyx");
924 lyxerr << "LyX: unable to find documentation file `"
925 << arg << "'. Bad installation?" << endl;
928 owner->message(bformat(_("Opening help file %1$s..."),
929 MakeDisplayPath(fname)));
930 view()->loadLyXFile(fname, false);
934 // --- version control -------------------------------
935 case LFUN_VC_REGISTER:
936 if (!ensureBufferClean(view()))
938 if (!owner->buffer()->lyxvc().inUse()) {
939 owner->buffer()->lyxvc().registrer();
944 case LFUN_VC_CHECKIN:
945 if (!ensureBufferClean(view()))
947 if (owner->buffer()->lyxvc().inUse()
948 && !owner->buffer()->isReadonly()) {
949 owner->buffer()->lyxvc().checkIn();
954 case LFUN_VC_CHECKOUT:
955 if (!ensureBufferClean(view()))
957 if (owner->buffer()->lyxvc().inUse()
958 && owner->buffer()->isReadonly()) {
959 owner->buffer()->lyxvc().checkOut();
965 owner->buffer()->lyxvc().revert();
970 owner->buffer()->lyxvc().undoLast();
974 // --- buffers ----------------------------------------
975 case LFUN_SWITCHBUFFER:
976 view()->setBuffer(bufferlist.getBuffer(argument));
979 case LFUN_NEXTBUFFER:
980 view()->setBuffer(bufferlist.next(view()->buffer()));
983 case LFUN_PREVIOUSBUFFER:
984 view()->setBuffer(bufferlist.previous(view()->buffer()));
988 NewFile(view(), argument);
995 case LFUN_DROP_LAYOUTS_CHOICE:
996 owner->getToolbars().openLayoutList();
999 case LFUN_MENU_OPEN_BY_NAME:
1000 owner->getMenubar().openByName(argument);
1003 // --- lyxserver commands ----------------------------
1005 setMessage(owner->buffer()->fileName());
1006 lyxerr[Debug::INFO] << "FNAME["
1007 << owner->buffer()->fileName()
1012 dispatch_buffer = keyseq.print();
1013 lyxserver->notifyClient(dispatch_buffer);
1016 case LFUN_GOTOFILEROW: {
1019 istringstream is(argument);
1020 is >> file_name >> row;
1021 if (prefixIs(file_name, getTmpDir())) {
1022 // Needed by inverse dvi search. If it is a file
1023 // in tmpdir, call the apropriated function
1024 view()->setBuffer(bufferlist.getBufferFromTmp(file_name));
1026 // Must replace extension of the file to be .lyx
1027 // and get full path
1028 string const s = ChangeExtension(file_name, ".lyx");
1029 // Either change buffer or load the file
1030 if (bufferlist.exists(s)) {
1031 view()->setBuffer(bufferlist.getBuffer(s));
1033 view()->loadLyXFile(s);
1037 view()->setCursorFromRow(row);
1040 // see BufferView_pimpl::center()
1041 view()->updateScrollbar();
1045 case LFUN_GOTO_PARAGRAPH: {
1046 istringstream is(argument);
1049 ParIterator par = owner->buffer()->getParFromID(id);
1050 if (par == owner->buffer()->par_iterator_end()) {
1051 lyxerr[Debug::INFO] << "No matching paragraph found! ["
1052 << id << ']' << endl;
1055 lyxerr[Debug::INFO] << "Paragraph " << par->id()
1056 << " found." << endl;
1060 view()->setCursor(par, 0);
1062 view()->switchKeyMap();
1063 owner->view_state_changed();
1066 // see BufferView_pimpl::center()
1067 view()->updateScrollbar();
1071 case LFUN_DIALOG_SHOW: {
1072 string const name = cmd.getArg(0);
1073 string data = trim(cmd.argument.substr(name.size()));
1075 if (name == "character") {
1076 data = freefont2string();
1078 owner->getDialogs().show("character", data);
1081 else if (name == "latexlog") {
1082 pair<Buffer::LogType, string> const logfile =
1083 owner->buffer()->getLogName();
1084 switch (logfile.first) {
1085 case Buffer::latexlog:
1088 case Buffer::buildlog:
1092 data += logfile.second;
1093 owner->getDialogs().show("log", data);
1095 else if (name == "vclog") {
1096 string const data = "vc " +
1097 owner->buffer()->lyxvc().getLogFile();
1098 owner->getDialogs().show("log", data);
1101 owner->getDialogs().show(name, data);
1105 case LFUN_DIALOG_SHOW_NEW_INSET: {
1106 string const name = cmd.getArg(0);
1107 string data = trim(cmd.argument.substr(name.size()));
1108 if (name == "bibitem" ||
1110 name == "include" ||
1116 InsetCommandParams p(name);
1117 data = InsetCommandMailer::params2string(name, p);
1118 } else if (name == "box") {
1119 // \c data == "Boxed" || "Frameless" etc
1120 InsetBoxParams p(data);
1121 data = InsetBoxMailer::params2string(p);
1122 } else if (name == "branch") {
1123 InsetBranchParams p;
1124 data = InsetBranchMailer::params2string(p);
1125 } else if (name == "citation") {
1126 InsetCommandParams p("cite");
1127 data = InsetCommandMailer::params2string(name, p);
1128 } else if (name == "ert") {
1129 data = InsetERTMailer::params2string(InsetCollapsable::Open);
1130 } else if (name == "external") {
1131 InsetExternalParams p;
1132 Buffer const & buffer = *owner->buffer();
1133 data = InsetExternalMailer::params2string(p, buffer);
1134 } else if (name == "float") {
1136 data = InsetFloatMailer::params2string(p);
1137 } else if (name == "graphics") {
1138 InsetGraphicsParams p;
1139 Buffer const & buffer = *owner->buffer();
1140 data = InsetGraphicsMailer::params2string(p, buffer);
1141 } else if (name == "note") {
1143 data = InsetNoteMailer::params2string(p);
1144 } else if (name == "vspace") {
1146 data = InsetVSpaceMailer::params2string(space);
1147 } else if (name == "wrap") {
1149 data = InsetWrapMailer::params2string(p);
1151 owner->getDialogs().show(name, data, 0);
1155 case LFUN_DIALOG_SHOW_NEXT_INSET:
1158 case LFUN_DIALOG_UPDATE: {
1159 string const & name = argument;
1160 // Can only update a dialog connected to an existing inset
1161 InsetBase * inset = owner->getDialogs().getOpenInset(name);
1163 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument);
1164 inset->dispatch(view()->cursor(), fr);
1165 } else if (name == "paragraph") {
1166 dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
1167 } else if (name == "prefs") {
1168 owner->getDialogs().update(name, string());
1173 case LFUN_DIALOG_HIDE:
1174 Dialogs::hide(argument, 0);
1177 case LFUN_DIALOG_DISCONNECT_INSET:
1178 owner->getDialogs().disconnect(argument);
1181 case LFUN_CHILDOPEN: {
1182 string const filename =
1183 MakeAbsPath(argument, owner->buffer()->filePath());
1184 setMessage(N_("Opening child document ") +
1185 MakeDisplayPath(filename) + "...");
1186 view()->savePosition(0);
1187 string const parentfilename = owner->buffer()->fileName();
1188 if (bufferlist.exists(filename))
1189 view()->setBuffer(bufferlist.getBuffer(filename));
1191 view()->loadLyXFile(filename);
1192 // Set the parent name of the child document.
1193 // This makes insertion of citations and references in the child work,
1194 // when the target is in the parent or another child document.
1195 owner->buffer()->setParentName(parentfilename);
1199 case LFUN_TOGGLECURSORFOLLOW:
1200 lyxrc.cursor_follows_scrollbar = !lyxrc.cursor_follows_scrollbar;
1204 owner->getIntl().KeyMapOn(false);
1207 case LFUN_KMAP_PRIM:
1208 owner->getIntl().KeyMapPrim();
1212 owner->getIntl().KeyMapSec();
1215 case LFUN_KMAP_TOGGLE:
1216 owner->getIntl().ToggleKeyMap();
1222 string rest = split(argument, countstr, ' ');
1223 istringstream is(countstr);
1226 lyxerr << "repeat: count: " << count << " cmd: " << rest << endl;
1227 for (int i = 0; i < count; ++i)
1228 dispatch(lyxaction.lookupFunc(rest));
1232 case LFUN_SEQUENCE: {
1233 // argument contains ';'-terminated commands
1234 string arg = argument;
1235 while (!arg.empty()) {
1237 arg = split(arg, first, ';');
1238 FuncRequest func(lyxaction.lookupFunc(first));
1239 func.origin = cmd.origin;
1245 case LFUN_SAVEPREFERENCES: {
1246 Path p(user_lyxdir());
1247 lyxrc.write("preferences", false);
1251 case LFUN_SCREEN_FONT_UPDATE:
1252 // handle the screen font changes.
1253 lyxrc.set_font_norm_type();
1254 lyx_gui::update_fonts();
1255 // All visible buffers will need resize
1259 case LFUN_SET_COLOR: {
1261 string const x11_name = split(argument, lyx_name, ' ');
1262 if (lyx_name.empty() || x11_name.empty()) {
1263 setErrorMessage(N_("Syntax: set-color <lyx_name>"
1268 bool const graphicsbg_changed =
1269 (lyx_name == lcolor.getLyXName(LColor::graphicsbg) &&
1270 x11_name != lcolor.getX11Name(LColor::graphicsbg));
1272 if (!lcolor.setColor(lyx_name, x11_name)) {
1274 bformat(_("Set-color \"%1$s\" failed "
1275 "- color is undefined or "
1276 "may not be redefined"), lyx_name));
1280 lyx_gui::update_color(lcolor.getFromLyXName(lyx_name));
1282 if (graphicsbg_changed) {
1283 #ifdef WITH_WARNINGS
1284 #warning FIXME!! The graphics cache no longer has a changeDisplay method.
1287 lyx::graphics::GCache::get().changeDisplay(true);
1294 owner->message(argument);
1297 case LFUN_TOOLTIPS_TOGGLE:
1298 owner->getDialogs().toggleTooltips();
1301 case LFUN_EXTERNAL_EDIT: {
1302 FuncRequest fr(action, argument);
1303 InsetExternal().dispatch(view()->cursor(), fr);
1307 case LFUN_GRAPHICS_EDIT: {
1308 FuncRequest fr(action, argument);
1309 InsetGraphics().dispatch(view()->cursor(), fr);
1313 case LFUN_ALL_INSETS_TOGGLE: {
1315 string const name = split(argument, action, ' ');
1316 InsetBase::Code const inset_code =
1317 InsetBase::translate(name);
1319 LCursor & cur = view()->cursor();
1320 FuncRequest fr(LFUN_INSET_TOGGLE, action);
1322 InsetBase & inset = owner->buffer()->inset();
1323 InsetIterator it = inset_iterator_begin(inset);
1324 InsetIterator const end = inset_iterator_end(inset);
1325 for (; it != end; ++it) {
1326 if (inset_code == InsetBase::NO_CODE
1327 || inset_code == it->lyxCode())
1328 it->dispatch(cur, fr);
1333 case LFUN_LANGUAGE_BUFFER: {
1334 Buffer & buffer = *owner->buffer();
1335 Language const * oldL = buffer.params().language;
1336 Language const * newL = languages.getLanguage(argument);
1337 if (!newL || oldL == newL)
1340 if (oldL->RightToLeft() == newL->RightToLeft()
1341 && !buffer.isMultiLingual())
1342 buffer.changeLanguage(oldL, newL);
1344 buffer.updateDocLang(newL);
1348 case LFUN_SAVE_AS_DEFAULT: {
1349 string const fname =
1350 AddName(AddPath(user_lyxdir(), "templates/"),
1352 Buffer defaults(fname);
1354 istringstream ss(argument);
1357 int const unknown_tokens = defaults.readHeader(lex);
1359 if (unknown_tokens != 0) {
1360 lyxerr << "Warning in LFUN_SAVE_AS_DEFAULT!\n"
1361 << unknown_tokens << " unknown token"
1362 << (unknown_tokens == 1 ? "" : "s")
1366 if (defaults.writeFile(defaults.fileName()))
1367 setMessage(_("Document defaults saved in ")
1368 + MakeDisplayPath(fname));
1370 setErrorMessage(_("Unable to save document defaults"));
1374 case LFUN_BUFFERPARAMS_APPLY: {
1375 biblio::CiteEngine const engine =
1376 owner->buffer()->params().cite_engine;
1378 istringstream ss(argument);
1381 int const unknown_tokens =
1382 owner->buffer()->readHeader(lex);
1384 if (unknown_tokens != 0) {
1385 lyxerr << "Warning in LFUN_BUFFERPARAMS_APPLY!\n"
1386 << unknown_tokens << " unknown token"
1387 << (unknown_tokens == 1 ? "" : "s")
1390 if (engine == owner->buffer()->params().cite_engine)
1393 LCursor & cur = view()->cursor();
1394 FuncRequest fr(LFUN_INSET_REFRESH);
1396 InsetBase & inset = owner->buffer()->inset();
1397 InsetIterator it = inset_iterator_begin(inset);
1398 InsetIterator const end = inset_iterator_end(inset);
1399 for (; it != end; ++it)
1400 if (it->lyxCode() == InsetBase::CITE_CODE)
1401 it->dispatch(cur, fr);
1405 case LFUN_TEXTCLASS_APPLY: {
1406 Buffer * buffer = owner->buffer();
1408 lyx::textclass_type const old_class =
1409 buffer->params().textclass;
1411 loadTextclass(argument);
1413 std::pair<bool, lyx::textclass_type> const tc_pair =
1414 textclasslist.NumberOfClass(argument);
1419 lyx::textclass_type const new_class = tc_pair.second;
1420 if (old_class == new_class)
1424 owner->message(_("Converting document to new document class..."));
1426 lyx::cap::SwitchLayoutsBetweenClasses(
1427 old_class, new_class,
1428 buffer->paragraphs(), el);
1430 bufferErrors(*buffer, el);
1431 view()->showErrorList(_("Class switch"));
1435 case LFUN_TEXTCLASS_LOAD:
1436 loadTextclass(argument);
1439 case LFUN_LYXRC_APPLY: {
1440 istringstream ss(argument);
1441 bool const success = lyxrc.read(ss) == 0;
1444 lyxerr << "Warning in LFUN_LYXRC_APPLY!\n"
1445 << "Unable to read lyxrc data"
1453 view()->cursor().dispatch(cmd);
1454 if (view()->cursor().result().dispatched())
1455 update |= view()->cursor().result().update();
1457 update |= view()->dispatch(cmd);
1462 if (view()->available()) {
1463 // Redraw screen unless explicitly told otherwise.
1464 // This also initializes the position cache for all insets
1465 // in (at least partially) visible top-level paragraphs.
1466 view()->update(true, update);
1468 // if we executed a mutating lfun, mark the buffer as dirty
1469 if (getStatus(cmd).enabled()
1470 && !lyxaction.funcHasFlag(cmd.action, LyXAction::NoBuffer)
1471 && !lyxaction.funcHasFlag(cmd.action, LyXAction::ReadOnly))
1472 view()->buffer()->markDirty();
1475 if (view()->cursor().inTexted()) {
1476 view()->owner()->updateLayoutChoice();
1479 sendDispatchMessage(getMessage(), cmd);
1483 void LyXFunc::sendDispatchMessage(string const & msg, FuncRequest const & cmd)
1485 owner->updateMenubar();
1486 owner->updateToolbars();
1488 const bool verbose = (cmd.origin == FuncRequest::UI
1489 || cmd.origin == FuncRequest::COMMANDBUFFER);
1491 if (cmd.action == LFUN_SELFINSERT || !verbose) {
1492 lyxerr[Debug::ACTION] << "dispatch msg is " << msg << endl;
1494 owner->message(msg);
1498 string dispatch_msg = msg;
1499 if (!dispatch_msg.empty())
1500 dispatch_msg += ' ';
1502 string comname = lyxaction.getActionName(cmd.action);
1504 bool argsadded = false;
1506 if (!cmd.argument.empty()) {
1507 if (cmd.action != LFUN_UNKNOWN_ACTION) {
1508 comname += ' ' + cmd.argument;
1513 string const shortcuts = toplevel_keymap->printbindings(cmd);
1515 if (!shortcuts.empty()) {
1516 comname += ": " + shortcuts;
1517 } else if (!argsadded && !cmd.argument.empty()) {
1518 comname += ' ' + cmd.argument;
1521 if (!comname.empty()) {
1522 comname = rtrim(comname);
1523 dispatch_msg += '(' + comname + ')';
1526 lyxerr[Debug::ACTION] << "verbose dispatch msg " << dispatch_msg << endl;
1527 if (!dispatch_msg.empty())
1528 owner->message(dispatch_msg);
1532 void LyXFunc::setupLocalKeymap()
1534 keyseq.stdmap = toplevel_keymap.get();
1535 keyseq.curmap = toplevel_keymap.get();
1536 cancel_meta_seq.stdmap = toplevel_keymap.get();
1537 cancel_meta_seq.curmap = toplevel_keymap.get();
1541 void LyXFunc::menuNew(string const & name, bool fromTemplate)
1543 string initpath = lyxrc.document_path;
1544 string filename(name);
1546 if (view()->available()) {
1547 string const trypath = owner->buffer()->filePath();
1548 // If directory is writeable, use this as default.
1549 if (IsDirWriteable(trypath))
1553 static int newfile_number;
1555 if (filename.empty()) {
1556 filename = AddName(lyxrc.document_path,
1557 "newfile" + tostr(++newfile_number) + ".lyx");
1558 FileInfo fi(filename);
1559 while (bufferlist.exists(filename) || fi.readable()) {
1561 filename = AddName(lyxrc.document_path,
1562 "newfile" + tostr(newfile_number) +
1564 fi.newFile(filename);
1568 // The template stuff
1571 FileDialog fileDlg(_("Select template file"),
1572 LFUN_SELECT_FILE_SYNC,
1573 make_pair(string(_("Documents|#o#O")),
1574 string(lyxrc.document_path)),
1575 make_pair(string(_("Templates|#T#t")),
1576 string(lyxrc.template_path)));
1578 FileDialog::Result result =
1579 fileDlg.open(lyxrc.template_path,
1580 FileFilterList(_("LyX Documents (*.lyx)")),
1583 if (result.first == FileDialog::Later)
1585 if (result.second.empty())
1587 templname = result.second;
1590 view()->newFile(filename, templname, !name.empty());
1594 void LyXFunc::open(string const & fname)
1596 string initpath = lyxrc.document_path;
1598 if (view()->available()) {
1599 string const trypath = owner->buffer()->filePath();
1600 // If directory is writeable, use this as default.
1601 if (IsDirWriteable(trypath))
1607 if (fname.empty()) {
1608 FileDialog fileDlg(_("Select document to open"),
1610 make_pair(string(_("Documents|#o#O")),
1611 string(lyxrc.document_path)),
1612 make_pair(string(_("Examples|#E#e")),
1613 string(AddPath(system_lyxdir(), "examples"))));
1615 FileDialog::Result result =
1616 fileDlg.open(initpath,
1617 FileFilterList(_("LyX Documents (*.lyx)")),
1620 if (result.first == FileDialog::Later)
1623 filename = result.second;
1625 // check selected filename
1626 if (filename.empty()) {
1627 owner->message(_("Canceled."));
1633 // get absolute path of file and add ".lyx" to the filename if
1635 string const fullpath = FileSearch(string(), filename, "lyx");
1636 if (!fullpath.empty()) {
1637 filename = fullpath;
1640 string const disp_fn(MakeDisplayPath(filename));
1642 // if the file doesn't exist, let the user create one
1643 FileInfo const f(filename, true);
1645 // the user specifically chose this name. Believe them.
1646 view()->newFile(filename, "", true);
1650 owner->message(bformat(_("Opening document %1$s..."), disp_fn));
1653 if (view()->loadLyXFile(filename)) {
1654 str2 = bformat(_("Document %1$s opened."), disp_fn);
1656 str2 = bformat(_("Could not open document %1$s"), disp_fn);
1658 owner->message(str2);
1662 void LyXFunc::doImport(string const & argument)
1665 string filename = split(argument, format, ' ');
1667 lyxerr[Debug::INFO] << "LyXFunc::doImport: " << format
1668 << " file: " << filename << endl;
1670 // need user interaction
1671 if (filename.empty()) {
1672 string initpath = lyxrc.document_path;
1674 if (view()->available()) {
1675 string const trypath = owner->buffer()->filePath();
1676 // If directory is writeable, use this as default.
1677 if (IsDirWriteable(trypath))
1681 string const text = bformat(_("Select %1$s file to import"),
1682 formats.prettyName(format));
1684 FileDialog fileDlg(text,
1686 make_pair(string(_("Documents|#o#O")),
1687 string(lyxrc.document_path)),
1688 make_pair(string(_("Examples|#E#e")),
1689 string(AddPath(system_lyxdir(), "examples"))));
1691 string const filter = formats.prettyName(format)
1692 + " (*." + formats.extension(format) + ')';
1694 FileDialog::Result result =
1695 fileDlg.open(initpath,
1696 FileFilterList(filter),
1699 if (result.first == FileDialog::Later)
1702 filename = result.second;
1704 // check selected filename
1705 if (filename.empty())
1706 owner->message(_("Canceled."));
1709 if (filename.empty())
1712 // get absolute path of file
1713 filename = MakeAbsPath(filename);
1715 string const lyxfile = ChangeExtension(filename, ".lyx");
1717 // Check if the document already is open
1718 if (lyx_gui::use_gui && bufferlist.exists(lyxfile)) {
1719 if (!bufferlist.close(bufferlist.getBuffer(lyxfile), true)) {
1720 owner->message(_("Canceled."));
1725 // if the file exists already, and we didn't do
1726 // -i lyx thefile.lyx, warn
1727 if (FileInfo(lyxfile, true).exist() && filename != lyxfile) {
1728 string const file = MakeDisplayPath(lyxfile, 30);
1730 string text = bformat(_("The document %1$s already exists.\n\n"
1731 "Do you want to over-write that document?"), file);
1732 int const ret = Alert::prompt(_("Over-write document?"),
1733 text, 0, 1, _("&Over-write"), _("&Cancel"));
1736 owner->message(_("Canceled."));
1741 Importer::Import(owner, filename, format);
1745 void LyXFunc::closeBuffer()
1747 if (bufferlist.close(owner->buffer(), true) && !quitting) {
1748 if (bufferlist.empty()) {
1749 // need this otherwise SEGV may occur while
1750 // trying to set variables that don't exist
1751 // since there's no current buffer
1752 owner->getDialogs().hideBufferDependent();
1754 view()->setBuffer(bufferlist.first());
1760 // Each "owner" should have it's own message method. lyxview and
1761 // the minibuffer would use the minibuffer, but lyxserver would
1762 // send an ERROR signal to its client. Alejandro 970603
1763 // This function is bit problematic when it comes to NLS, to make the
1764 // lyx servers client be language indepenent we must not translate
1765 // strings sent to this func.
1766 void LyXFunc::setErrorMessage(string const & m) const
1768 dispatch_buffer = m;
1773 void LyXFunc::setMessage(string const & m) const
1775 dispatch_buffer = m;
1779 string const LyXFunc::viewStatusMessage()
1781 // When meta-fake key is pressed, show the key sequence so far + "M-".
1783 return keyseq.print() + "M-";
1785 // Else, when a non-complete key sequence is pressed,
1786 // show the available options.
1787 if (keyseq.length() > 0 && !keyseq.deleted())
1788 return keyseq.printOptions();
1790 if (!view()->available())
1791 return _("Welcome to LyX!");
1793 return view()->cursor().currentState();
1797 BufferView * LyXFunc::view() const
1799 BOOST_ASSERT(owner);
1800 return owner->view().get();
1804 bool LyXFunc::wasMetaKey() const
1806 return (meta_fake_bit != key_modifier::none);