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/package.h"
88 #include "support/systemcall.h"
89 #include "support/convert.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::package;
112 using lyx::support::Path;
113 using lyx::support::QuoteName;
114 using lyx::support::rtrim;
115 using lyx::support::split;
116 using lyx::support::strToInt;
117 using lyx::support::strToUnsignedInt;
118 using lyx::support::subst;
119 using lyx::support::Systemcall;
120 using lyx::support::token;
121 using lyx::support::trim;
122 using lyx::support::prefixIs;
125 using std::make_pair;
128 using std::istringstream;
130 namespace biblio = lyx::biblio;
133 extern BufferList bufferlist;
134 extern LyXServer * lyxserver;
136 extern boost::scoped_ptr<kb_keymap> toplevel_keymap;
139 extern tex_accent_struct get_accent(kb_action action);
144 bool getStatus(LCursor cursor,
145 FuncRequest const & cmd, FuncStatus & status)
147 // This is, of course, a mess. Better create a new doc iterator and use
148 // this in Inset::getStatus. This might require an additional
149 // BufferView * arg, though (which should be avoided)
150 //LCursor safe = *this;
152 for ( ; cursor.size(); cursor.pop()) {
153 //lyxerr << "\nLCursor::getStatus: cmd: " << cmd << endl << *this << endl;
154 DocIterator::idx_type & idx = cursor.idx();
155 DocIterator::idx_type const lastidx = cursor.lastidx();
158 lyxerr << "wrong idx " << idx << ", max is " << lastidx
159 << ". Trying to correct this." << endl;
163 DocIterator::pit_type & pit = cursor.pit();
164 DocIterator::pit_type const lastpit = cursor.lastpit();
167 lyxerr << "wrong par " << pit << ", max is " << lastpit
168 << ". Trying to correct this." << endl;
172 DocIterator::pos_type & pos = cursor.pos();
173 DocIterator::pos_type const lastpos = cursor.lastpos();
176 lyxerr << "wrong pos " << pos << ", max is " << lastpos
177 << ". Trying to correct this." << endl;
181 // The inset's getStatus() will return 'true' if it made
182 // a definitive decision on whether it want to handle the
183 // request or not. The result of this decision is put into
184 // the 'status' parameter.
185 if (cursor.inset().getStatus(cursor, cmd, status)) {
195 LyXFunc::LyXFunc(LyXView * lv)
198 keyseq(toplevel_keymap.get(), toplevel_keymap.get()),
199 cancel_meta_seq(toplevel_keymap.get(), toplevel_keymap.get()),
200 meta_fake_bit(key_modifier::none)
205 void LyXFunc::handleKeyFunc(kb_action action)
207 char c = encoded_last_key;
209 if (keyseq.length()) {
213 owner->getIntl().getTransManager()
214 .deadkey(c, get_accent(action).accent, view()->getLyXText());
215 // Need to clear, in case the minibuffer calls these
218 // copied verbatim from do_accent_char
219 view()->cursor().resetAnchor();
224 void LyXFunc::processKeySym(LyXKeySymPtr keysym, key_modifier::state state)
226 lyxerr[Debug::KEY] << "KeySym is " << keysym->getSymbolName() << endl;
228 // Do nothing if we have nothing (JMarc)
229 if (!keysym->isOK()) {
230 lyxerr[Debug::KEY] << "Empty kbd action (probably composing)"
235 if (keysym->isModifier()) {
236 lyxerr[Debug::KEY] << "isModifier true" << endl;
240 Encoding const * encoding = view()->cursor().getEncoding();
242 encoded_last_key = keysym->getISOEncoded(encoding ? encoding->Name() : "");
244 // Do a one-deep top-level lookup for
245 // cancel and meta-fake keys. RVDK_PATCH_5
246 cancel_meta_seq.reset();
248 FuncRequest func = cancel_meta_seq.addkey(keysym, state);
249 lyxerr[Debug::KEY] << "action first set to [" << func.action << ']' << endl;
251 // When not cancel or meta-fake, do the normal lookup.
252 // Note how the meta_fake Mod1 bit is OR-ed in and reset afterwards.
253 // Mostly, meta_fake_bit = key_modifier::none. RVDK_PATCH_5.
254 if ((func.action != LFUN_CANCEL) && (func.action != LFUN_META_FAKE)) {
255 // remove Caps Lock and Mod2 as a modifiers
256 func = keyseq.addkey(keysym, (state | meta_fake_bit));
257 lyxerr[Debug::KEY] << "action now set to ["
258 << func.action << ']' << endl;
261 // Dont remove this unless you know what you are doing.
262 meta_fake_bit = key_modifier::none;
264 // can this happen now ?
265 if (func.action == LFUN_NOACTION) {
266 func = FuncRequest(LFUN_PREFIX);
269 if (lyxerr.debugging(Debug::KEY)) {
270 lyxerr << "Key [action="
271 << func.action << "]["
272 << keyseq.print() << ']'
276 // already here we know if it any point in going further
277 // why not return already here if action == -1 and
278 // num_bytes == 0? (Lgb)
280 if (keyseq.length() > 1) {
281 owner->message(keyseq.print());
285 // Maybe user can only reach the key via holding down shift.
286 // Let's see. But only if shift is the only modifier
287 if (func.action == LFUN_UNKNOWN_ACTION &&
288 state == key_modifier::shift) {
289 lyxerr[Debug::KEY] << "Trying without shift" << endl;
290 func = keyseq.addkey(keysym, key_modifier::none);
291 lyxerr[Debug::KEY] << "Action now " << func.action << endl;
294 if (func.action == LFUN_UNKNOWN_ACTION) {
295 // Hmm, we didn't match any of the keysequences. See
296 // if it's normal insertable text not already covered
298 if (keysym->isText() && keyseq.length() == 1) {
299 lyxerr[Debug::KEY] << "isText() is true, inserting." << endl;
300 func = FuncRequest(LFUN_SELFINSERT);
302 lyxerr[Debug::KEY] << "Unknown, !isText() - giving up" << endl;
303 owner->message(_("Unknown function."));
308 if (func.action == LFUN_SELFINSERT) {
309 if (encoded_last_key != 0) {
310 string arg(1, encoded_last_key);
311 dispatch(FuncRequest(LFUN_SELFINSERT, arg));
313 << "SelfInsert arg[`" << arg << "']" << endl;
321 FuncStatus LyXFunc::getStatus(FuncRequest const & cmd) const
323 //lyxerr << "LyXFunc::getStatus: cmd: " << cmd << endl;
325 LCursor & cur = view()->cursor();
327 /* In LyX/Mac, when a dialog is open, the menus of the
328 application can still be accessed without giving focus to
329 the main window. In this case, we want to disable the menu
330 entries that are buffer-related.
333 if (cmd.origin == FuncRequest::UI && !owner->hasFocus())
336 buf = owner->buffer();
338 if (cmd.action == LFUN_NOACTION) {
339 flag.message(N_("Nothing to do"));
344 switch (cmd.action) {
345 case LFUN_UNKNOWN_ACTION:
346 #ifndef HAVE_LIBAIKSAURUS
347 case LFUN_THESAURUS_ENTRY:
353 flag |= lyx_gui::getStatus(cmd);
356 if (flag.unknown()) {
357 flag.message(N_("Unknown action"));
361 if (!flag.enabled()) {
362 if (flag.message().empty())
363 flag.message(N_("Command disabled"));
367 // Check whether we need a buffer
368 if (!lyxaction.funcHasFlag(cmd.action, LyXAction::NoBuffer) && !buf) {
370 flag.message(N_("Command not allowed with"
371 "out any document open"));
376 // I would really like to avoid having this switch and rather try to
377 // encode this in the function itself.
378 // -- And I'd rather let an inset decide which LFUNs it is willing
379 // to handle (Andre')
381 switch (cmd.action) {
382 case LFUN_TOOLTIPS_TOGGLE:
383 flag.setOnOff(owner->getDialogs().tooltipsEnabled());
386 case LFUN_READ_ONLY_TOGGLE:
387 flag.setOnOff(buf->isReadonly());
390 case LFUN_SWITCHBUFFER:
391 // toggle on the current buffer, but do not toggle off
392 // the other ones (is that a good idea?)
393 if (cmd.argument == buf->fileName())
398 enable = cmd.argument == "custom"
399 || Exporter::IsExportable(*buf, cmd.argument);
403 enable = cur.selection();
407 enable = buf->isLatex() && lyxrc.chktex_command != "none";
411 enable = Exporter::IsExportable(*buf, "program");
414 case LFUN_LAYOUT_TABULAR:
415 enable = cur.innerInsetOfType(InsetBase::TABULAR_CODE);
419 case LFUN_LAYOUT_PARAGRAPH:
420 enable = !cur.inset().forceDefaultParagraphs(&cur.inset());
423 case LFUN_VC_REGISTER:
424 enable = !buf->lyxvc().inUse();
426 case LFUN_VC_CHECKIN:
427 enable = buf->lyxvc().inUse() && !buf->isReadonly();
429 case LFUN_VC_CHECKOUT:
430 enable = buf->lyxvc().inUse() && buf->isReadonly();
434 enable = buf->lyxvc().inUse();
436 case LFUN_MENURELOAD:
437 enable = !buf->isUnnamed() && !buf->isClean();
441 case LFUN_INSET_SETTINGS: {
445 UpdatableInset * inset = cur.inset().asUpdatableInset();
446 lyxerr << "inset: " << inset << endl;
450 InsetBase::Code code = inset->lyxCode();
452 case InsetBase::TABULAR_CODE:
453 enable = cmd.argument == "tabular";
455 case InsetBase::ERT_CODE:
456 enable = cmd.argument == "ert";
458 case InsetBase::FLOAT_CODE:
459 enable = cmd.argument == "float";
461 case InsetBase::WRAP_CODE:
462 enable = cmd.argument == "wrap";
464 case InsetBase::NOTE_CODE:
465 enable = cmd.argument == "note";
467 case InsetBase::BRANCH_CODE:
468 enable = cmd.argument == "branch";
470 case InsetBase::BOX_CODE:
471 enable = cmd.argument == "box";
479 case LFUN_DIALOG_SHOW: {
480 string const name = cmd.getArg(0);
482 enable = name == "aboutlyx"
486 || name == "texinfo";
487 else if (name == "print")
488 enable = Exporter::IsExportable(*buf, "dvi")
489 && lyxrc.print_command != "none";
490 else if (name == "character")
491 enable = cur.inset().lyxCode() != InsetBase::ERT_CODE;
492 else if (name == "vclog")
493 enable = buf->lyxvc().inUse();
494 else if (name == "latexlog")
495 enable = IsFileReadable(buf->getLogName().second);
499 case LFUN_DIALOG_UPDATE: {
500 string const name = cmd.getArg(0);
502 enable = name == "prefs";
506 // this one is difficult to get right. As a half-baked
507 // solution, we consider only the first action of the sequence
508 case LFUN_SEQUENCE: {
509 // argument contains ';'-terminated commands
510 string const firstcmd = token(cmd.argument, ';', 0);
511 FuncRequest func(lyxaction.lookupFunc(firstcmd));
512 func.origin = cmd.origin;
513 flag = getStatus(func);
517 case LFUN_MENUNEWTMPLT:
518 case LFUN_WORDFINDFORWARD:
519 case LFUN_WORDFINDBACKWARD:
521 case LFUN_EXEC_COMMAND:
524 case LFUN_CLOSEBUFFER:
533 case LFUN_RECONFIGURE:
537 case LFUN_DROP_LAYOUTS_CHOICE:
538 case LFUN_MENU_OPEN_BY_NAME:
541 case LFUN_GOTOFILEROW:
542 case LFUN_GOTO_PARAGRAPH:
543 case LFUN_DIALOG_SHOW_NEW_INSET:
544 case LFUN_DIALOG_SHOW_NEXT_INSET:
545 case LFUN_DIALOG_HIDE:
546 case LFUN_DIALOG_DISCONNECT_INSET:
548 case LFUN_TOGGLECURSORFOLLOW:
552 case LFUN_KMAP_TOGGLE:
554 case LFUN_EXPORT_CUSTOM:
556 case LFUN_SAVEPREFERENCES:
557 case LFUN_SCREEN_FONT_UPDATE:
560 case LFUN_EXTERNAL_EDIT:
561 case LFUN_GRAPHICS_EDIT:
562 case LFUN_ALL_INSETS_TOGGLE:
563 case LFUN_LANGUAGE_BUFFER:
564 case LFUN_TEXTCLASS_APPLY:
565 case LFUN_TEXTCLASS_LOAD:
566 case LFUN_SAVE_AS_DEFAULT:
567 case LFUN_BUFFERPARAMS_APPLY:
568 case LFUN_LYXRC_APPLY:
569 case LFUN_NEXTBUFFER:
570 case LFUN_PREVIOUSBUFFER:
571 // these are handled in our dispatch()
576 if (!::getStatus(cur, cmd, flag))
577 flag = view()->getStatus(cmd);
583 // Can we use a readonly buffer?
584 if (buf && buf->isReadonly()
585 && !lyxaction.funcHasFlag(cmd.action, LyXAction::ReadOnly)
586 && !lyxaction.funcHasFlag(cmd.action, LyXAction::NoBuffer)) {
587 flag.message(N_("Document is read-only"));
591 // the default error message if we disable the command
592 if (!flag.enabled() && flag.message().empty())
593 flag.message(N_("Command disabled"));
601 bool ensureBufferClean(BufferView * bv)
603 Buffer & buf = *bv->buffer();
607 string const file = MakeDisplayPath(buf.fileName(), 30);
608 string text = bformat(_("The document %1$s has unsaved "
609 "changes.\n\nDo you want to save "
610 "the document?"), file);
611 int const ret = Alert::prompt(_("Save changed document?"),
612 text, 0, 1, _("&Save"),
616 bv->owner()->dispatch(FuncRequest(LFUN_MENUWRITE));
618 return buf.isClean();
622 void showPrintError(string const & name)
624 string str = bformat(_("Could not print the document %1$s.\n"
625 "Check that your printer is set up correctly."),
626 MakeDisplayPath(name, 50));
627 Alert::error(_("Print document failed"), str);
631 void loadTextclass(string const & name)
633 std::pair<bool, lyx::textclass_type> const tc_pair =
634 textclasslist.NumberOfClass(name);
636 if (!tc_pair.first) {
637 lyxerr << "Document class \"" << name
638 << "\" does not exist."
643 lyx::textclass_type const tc = tc_pair.second;
645 if (!textclasslist[tc].load()) {
646 string s = bformat(_("The document could not be converted\n"
647 "into the document class %1$s."),
648 textclasslist[tc].name());
649 Alert::error(_("Could not change class"), s);
656 void LyXFunc::dispatch(FuncRequest const & cmd)
658 BOOST_ASSERT(view());
659 string const argument = cmd.argument;
660 kb_action const action = cmd.action;
662 lyxerr[Debug::ACTION] << "LyXFunc::dispatch: cmd: " << cmd << endl;
663 //lyxerr << "LyXFunc::dispatch: cmd: " << cmd << endl;
665 // we have not done anything wrong yet.
667 dispatch_buffer.erase();
671 FuncStatus const flag = getStatus(cmd);
672 if (!flag.enabled()) {
673 // We cannot use this function here
674 lyxerr[Debug::ACTION] << "LyXFunc::dispatch: "
675 << lyxaction.getActionName(action)
676 << " [" << action << "] is disabled at this location"
678 setErrorMessage(flag.message());
681 if (view()->available())
682 view()->hideCursor();
686 case LFUN_WORDFINDFORWARD:
687 case LFUN_WORDFINDBACKWARD: {
688 static string last_search;
689 string searched_string;
691 if (!argument.empty()) {
692 last_search = argument;
693 searched_string = argument;
695 searched_string = last_search;
698 if (searched_string.empty())
701 bool const fw = action == LFUN_WORDFINDFORWARD;
703 lyx::find::find2string(searched_string, true, false, fw);
704 lyx::find::find(view(), FuncRequest(LFUN_WORD_FIND, data));
709 owner->message(keyseq.printOptions());
712 case LFUN_EXEC_COMMAND:
713 owner->getToolbars().display("minibuffer", true);
714 owner->focus_command_buffer();
719 meta_fake_bit = key_modifier::none;
720 if (view()->available())
721 // cancel any selection
722 dispatch(FuncRequest(LFUN_MARK_OFF));
723 setMessage(N_("Cancel"));
727 meta_fake_bit = key_modifier::alt;
728 setMessage(keyseq.print());
731 case LFUN_READ_ONLY_TOGGLE:
732 if (owner->buffer()->lyxvc().inUse())
733 owner->buffer()->lyxvc().toggleReadOnly();
735 owner->buffer()->setReadonly(
736 !owner->buffer()->isReadonly());
739 // --- Menus -----------------------------------------------
741 menuNew(argument, false);
744 case LFUN_MENUNEWTMPLT:
745 menuNew(argument, true);
748 case LFUN_CLOSEBUFFER:
753 if (!owner->buffer()->isUnnamed()) {
754 string const str = bformat(_("Saving document %1$s..."),
755 MakeDisplayPath(owner->buffer()->fileName()));
757 MenuWrite(owner->buffer());
758 owner->message(str + _(" done."));
760 WriteAs(owner->buffer());
764 WriteAs(owner->buffer(), argument);
767 case LFUN_MENURELOAD: {
768 string const file = MakeDisplayPath(view()->buffer()->fileName(), 20);
769 string text = bformat(_("Any changes will be lost. Are you sure "
770 "you want to revert to the saved version of the document %1$s?"), file);
771 int const ret = Alert::prompt(_("Revert to saved document?"),
772 text, 0, 1, _("&Revert"), _("&Cancel"));
780 Exporter::Export(owner->buffer(), argument, true);
781 view()->showErrorList(BufferFormat(*owner->buffer()));
785 Exporter::Preview(owner->buffer(), argument);
786 view()->showErrorList(BufferFormat(*owner->buffer()));
790 Exporter::Export(owner->buffer(), "program", true);
791 view()->showErrorList(_("Build"));
795 owner->buffer()->runChktex();
796 view()->showErrorList(_("ChkTeX"));
800 if (argument == "custom")
801 owner->getDialogs().show("sendto");
803 Exporter::Export(owner->buffer(), argument, false);
804 view()->showErrorList(BufferFormat(*owner->buffer()));
808 case LFUN_EXPORT_CUSTOM: {
810 string command = split(argument, format_name, ' ');
811 Format const * format = formats.getFormat(format_name);
813 lyxerr << "Format \"" << format_name
814 << "\" not recognized!"
819 Buffer * buffer = owner->buffer();
821 // The name of the file created by the conversion process
824 // Output to filename
825 if (format->name() == "lyx") {
826 string const latexname =
827 buffer->getLatexName(false);
828 filename = ChangeExtension(latexname,
829 format->extension());
830 filename = AddName(buffer->temppath(), filename);
832 if (!buffer->writeFile(filename))
836 Exporter::Export(buffer, format_name, true,
840 // Substitute $$FName for filename
841 if (!contains(command, "$$FName"))
842 command = "( " + command + " ) < $$FName";
843 command = subst(command, "$$FName", filename);
845 // Execute the command in the background
847 call.startscript(Systemcall::DontWait, command);
854 string command = split(split(argument, target, ' '),
858 || target_name.empty()
859 || command.empty()) {
860 lyxerr << "Unable to parse \""
861 << argument << '"' << std::endl;
864 if (target != "printer" && target != "file") {
865 lyxerr << "Unrecognized target \""
866 << target << '"' << std::endl;
870 Buffer * buffer = owner->buffer();
872 if (!Exporter::Export(buffer, "dvi", true)) {
873 showPrintError(buffer->fileName());
877 // Push directory path.
878 string const path = buffer->temppath();
881 // there are three cases here:
882 // 1. we print to a file
883 // 2. we print directly to a printer
884 // 3. we print using a spool command (print to file first)
887 string const dviname =
888 ChangeExtension(buffer->getLatexName(true),
891 if (target == "printer") {
892 if (!lyxrc.print_spool_command.empty()) {
893 // case 3: print using a spool
894 string const psname =
895 ChangeExtension(dviname,".ps");
896 command += lyxrc.print_to_file
899 + QuoteName(dviname);
902 lyxrc.print_spool_command +' ';
903 if (target_name != "default") {
904 command2 += lyxrc.print_spool_printerprefix
908 command2 += QuoteName(psname);
910 // If successful, then spool command
911 res = one.startscript(
916 res = one.startscript(
917 Systemcall::DontWait,
920 // case 2: print directly to a printer
921 res = one.startscript(
922 Systemcall::DontWait,
923 command + QuoteName(dviname));
927 // case 1: print to a file
928 command += lyxrc.print_to_file
929 + QuoteName(MakeAbsPath(target_name,
932 + QuoteName(dviname);
933 res = one.startscript(Systemcall::DontWait,
938 showPrintError(buffer->fileName());
951 InsetCommandParams p("tableofcontents");
952 string const data = InsetCommandMailer::params2string("toc", p);
953 owner->getDialogs().show("toc", data, 0);
961 case LFUN_RECONFIGURE:
965 case LFUN_HELP_OPEN: {
966 string const arg = argument;
968 setErrorMessage(N_("Missing argument"));
971 string const fname = i18nLibFileSearch("doc", arg, "lyx");
973 lyxerr << "LyX: unable to find documentation file `"
974 << arg << "'. Bad installation?" << endl;
977 owner->message(bformat(_("Opening help file %1$s..."),
978 MakeDisplayPath(fname)));
979 view()->loadLyXFile(fname, false);
983 // --- version control -------------------------------
984 case LFUN_VC_REGISTER:
985 if (!ensureBufferClean(view()))
987 if (!owner->buffer()->lyxvc().inUse()) {
988 owner->buffer()->lyxvc().registrer();
993 case LFUN_VC_CHECKIN:
994 if (!ensureBufferClean(view()))
996 if (owner->buffer()->lyxvc().inUse()
997 && !owner->buffer()->isReadonly()) {
998 owner->buffer()->lyxvc().checkIn();
1003 case LFUN_VC_CHECKOUT:
1004 if (!ensureBufferClean(view()))
1006 if (owner->buffer()->lyxvc().inUse()
1007 && owner->buffer()->isReadonly()) {
1008 owner->buffer()->lyxvc().checkOut();
1013 case LFUN_VC_REVERT:
1014 owner->buffer()->lyxvc().revert();
1019 owner->buffer()->lyxvc().undoLast();
1023 // --- buffers ----------------------------------------
1024 case LFUN_SWITCHBUFFER:
1025 view()->setBuffer(bufferlist.getBuffer(argument));
1028 case LFUN_NEXTBUFFER:
1029 view()->setBuffer(bufferlist.next(view()->buffer()));
1032 case LFUN_PREVIOUSBUFFER:
1033 view()->setBuffer(bufferlist.previous(view()->buffer()));
1037 NewFile(view(), argument);
1040 case LFUN_FILE_OPEN:
1044 case LFUN_DROP_LAYOUTS_CHOICE:
1045 owner->getToolbars().openLayoutList();
1048 case LFUN_MENU_OPEN_BY_NAME:
1049 owner->getMenubar().openByName(argument);
1052 // --- lyxserver commands ----------------------------
1054 setMessage(owner->buffer()->fileName());
1055 lyxerr[Debug::INFO] << "FNAME["
1056 << owner->buffer()->fileName()
1061 dispatch_buffer = keyseq.print();
1062 lyxserver->notifyClient(dispatch_buffer);
1065 case LFUN_GOTOFILEROW: {
1068 istringstream is(argument);
1069 is >> file_name >> row;
1070 if (prefixIs(file_name, package().temp_dir())) {
1071 // Needed by inverse dvi search. If it is a file
1072 // in tmpdir, call the apropriated function
1073 view()->setBuffer(bufferlist.getBufferFromTmp(file_name));
1075 // Must replace extension of the file to be .lyx
1076 // and get full path
1077 string const s = ChangeExtension(file_name, ".lyx");
1078 // Either change buffer or load the file
1079 if (bufferlist.exists(s)) {
1080 view()->setBuffer(bufferlist.getBuffer(s));
1082 view()->loadLyXFile(s);
1086 view()->setCursorFromRow(row);
1089 // see BufferView_pimpl::center()
1090 view()->updateScrollbar();
1094 case LFUN_GOTO_PARAGRAPH: {
1095 istringstream is(argument);
1098 ParIterator par = owner->buffer()->getParFromID(id);
1099 if (par == owner->buffer()->par_iterator_end()) {
1100 lyxerr[Debug::INFO] << "No matching paragraph found! ["
1101 << id << ']' << endl;
1104 lyxerr[Debug::INFO] << "Paragraph " << par->id()
1105 << " found." << endl;
1109 view()->setCursor(par, 0);
1111 view()->switchKeyMap();
1112 owner->view_state_changed();
1115 // see BufferView_pimpl::center()
1116 view()->updateScrollbar();
1120 case LFUN_DIALOG_SHOW: {
1121 string const name = cmd.getArg(0);
1122 string data = trim(cmd.argument.substr(name.size()));
1124 if (name == "character") {
1125 data = freefont2string();
1127 owner->getDialogs().show("character", data);
1130 else if (name == "latexlog") {
1131 pair<Buffer::LogType, string> const logfile =
1132 owner->buffer()->getLogName();
1133 switch (logfile.first) {
1134 case Buffer::latexlog:
1137 case Buffer::buildlog:
1141 data += logfile.second;
1142 owner->getDialogs().show("log", data);
1144 else if (name == "vclog") {
1145 string const data = "vc " +
1146 owner->buffer()->lyxvc().getLogFile();
1147 owner->getDialogs().show("log", data);
1150 owner->getDialogs().show(name, data);
1154 case LFUN_DIALOG_SHOW_NEW_INSET: {
1155 string const name = cmd.getArg(0);
1156 string data = trim(cmd.argument.substr(name.size()));
1157 if (name == "bibitem" ||
1159 name == "include" ||
1165 InsetCommandParams p(name);
1166 data = InsetCommandMailer::params2string(name, p);
1167 } else if (name == "box") {
1168 // \c data == "Boxed" || "Frameless" etc
1169 InsetBoxParams p(data);
1170 data = InsetBoxMailer::params2string(p);
1171 } else if (name == "branch") {
1172 InsetBranchParams p;
1173 data = InsetBranchMailer::params2string(p);
1174 } else if (name == "citation") {
1175 InsetCommandParams p("cite");
1176 data = InsetCommandMailer::params2string(name, p);
1177 } else if (name == "ert") {
1178 data = InsetERTMailer::params2string(InsetCollapsable::Open);
1179 } else if (name == "external") {
1180 InsetExternalParams p;
1181 Buffer const & buffer = *owner->buffer();
1182 data = InsetExternalMailer::params2string(p, buffer);
1183 } else if (name == "float") {
1185 data = InsetFloatMailer::params2string(p);
1186 } else if (name == "graphics") {
1187 InsetGraphicsParams p;
1188 Buffer const & buffer = *owner->buffer();
1189 data = InsetGraphicsMailer::params2string(p, buffer);
1190 } else if (name == "note") {
1192 data = InsetNoteMailer::params2string(p);
1193 } else if (name == "vspace") {
1195 data = InsetVSpaceMailer::params2string(space);
1196 } else if (name == "wrap") {
1198 data = InsetWrapMailer::params2string(p);
1200 owner->getDialogs().show(name, data, 0);
1204 case LFUN_DIALOG_SHOW_NEXT_INSET:
1207 case LFUN_DIALOG_UPDATE: {
1208 string const & name = argument;
1209 // Can only update a dialog connected to an existing inset
1210 InsetBase * inset = owner->getDialogs().getOpenInset(name);
1212 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument);
1213 inset->dispatch(view()->cursor(), fr);
1214 } else if (name == "paragraph") {
1215 dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
1216 } else if (name == "prefs") {
1217 owner->getDialogs().update(name, string());
1222 case LFUN_DIALOG_HIDE:
1223 Dialogs::hide(argument, 0);
1226 case LFUN_DIALOG_DISCONNECT_INSET:
1227 owner->getDialogs().disconnect(argument);
1230 case LFUN_CHILDOPEN: {
1231 string const filename =
1232 MakeAbsPath(argument, owner->buffer()->filePath());
1233 setMessage(N_("Opening child document ") +
1234 MakeDisplayPath(filename) + "...");
1235 view()->savePosition(0);
1236 string const parentfilename = owner->buffer()->fileName();
1237 if (bufferlist.exists(filename))
1238 view()->setBuffer(bufferlist.getBuffer(filename));
1240 view()->loadLyXFile(filename);
1241 // Set the parent name of the child document.
1242 // This makes insertion of citations and references in the child work,
1243 // when the target is in the parent or another child document.
1244 owner->buffer()->setParentName(parentfilename);
1248 case LFUN_TOGGLECURSORFOLLOW:
1249 lyxrc.cursor_follows_scrollbar = !lyxrc.cursor_follows_scrollbar;
1253 owner->getIntl().KeyMapOn(false);
1256 case LFUN_KMAP_PRIM:
1257 owner->getIntl().KeyMapPrim();
1261 owner->getIntl().KeyMapSec();
1264 case LFUN_KMAP_TOGGLE:
1265 owner->getIntl().ToggleKeyMap();
1271 string rest = split(argument, countstr, ' ');
1272 istringstream is(countstr);
1275 lyxerr << "repeat: count: " << count << " cmd: " << rest << endl;
1276 for (int i = 0; i < count; ++i)
1277 dispatch(lyxaction.lookupFunc(rest));
1281 case LFUN_SEQUENCE: {
1282 // argument contains ';'-terminated commands
1283 string arg = argument;
1284 while (!arg.empty()) {
1286 arg = split(arg, first, ';');
1287 FuncRequest func(lyxaction.lookupFunc(first));
1288 func.origin = cmd.origin;
1294 case LFUN_SAVEPREFERENCES: {
1295 Path p(package().user_support());
1296 lyxrc.write("preferences", false);
1300 case LFUN_SCREEN_FONT_UPDATE:
1301 // handle the screen font changes.
1302 lyxrc.set_font_norm_type();
1303 lyx_gui::update_fonts();
1304 // All visible buffers will need resize
1308 case LFUN_SET_COLOR: {
1310 string const x11_name = split(argument, lyx_name, ' ');
1311 if (lyx_name.empty() || x11_name.empty()) {
1312 setErrorMessage(N_("Syntax: set-color <lyx_name>"
1317 bool const graphicsbg_changed =
1318 (lyx_name == lcolor.getLyXName(LColor::graphicsbg) &&
1319 x11_name != lcolor.getX11Name(LColor::graphicsbg));
1321 if (!lcolor.setColor(lyx_name, x11_name)) {
1323 bformat(_("Set-color \"%1$s\" failed "
1324 "- color is undefined or "
1325 "may not be redefined"), lyx_name));
1329 lyx_gui::update_color(lcolor.getFromLyXName(lyx_name));
1331 if (graphicsbg_changed) {
1332 #ifdef WITH_WARNINGS
1333 #warning FIXME!! The graphics cache no longer has a changeDisplay method.
1336 lyx::graphics::GCache::get().changeDisplay(true);
1343 owner->message(argument);
1346 case LFUN_TOOLTIPS_TOGGLE:
1347 owner->getDialogs().toggleTooltips();
1350 case LFUN_EXTERNAL_EDIT: {
1351 FuncRequest fr(action, argument);
1352 InsetExternal().dispatch(view()->cursor(), fr);
1356 case LFUN_GRAPHICS_EDIT: {
1357 FuncRequest fr(action, argument);
1358 InsetGraphics().dispatch(view()->cursor(), fr);
1362 case LFUN_ALL_INSETS_TOGGLE: {
1364 string const name = split(argument, action, ' ');
1365 InsetBase::Code const inset_code =
1366 InsetBase::translate(name);
1368 LCursor & cur = view()->cursor();
1369 FuncRequest fr(LFUN_INSET_TOGGLE, action);
1371 InsetBase & inset = owner->buffer()->inset();
1372 InsetIterator it = inset_iterator_begin(inset);
1373 InsetIterator const end = inset_iterator_end(inset);
1374 for (; it != end; ++it) {
1375 if (inset_code == InsetBase::NO_CODE
1376 || inset_code == it->lyxCode())
1377 it->dispatch(cur, fr);
1382 case LFUN_LANGUAGE_BUFFER: {
1383 Buffer & buffer = *owner->buffer();
1384 Language const * oldL = buffer.params().language;
1385 Language const * newL = languages.getLanguage(argument);
1386 if (!newL || oldL == newL)
1389 if (oldL->RightToLeft() == newL->RightToLeft()
1390 && !buffer.isMultiLingual())
1391 buffer.changeLanguage(oldL, newL);
1393 buffer.updateDocLang(newL);
1397 case LFUN_SAVE_AS_DEFAULT: {
1398 string const fname =
1399 AddName(AddPath(package().user_support(), "templates/"),
1401 Buffer defaults(fname);
1403 istringstream ss(argument);
1406 int const unknown_tokens = defaults.readHeader(lex);
1408 if (unknown_tokens != 0) {
1409 lyxerr << "Warning in LFUN_SAVE_AS_DEFAULT!\n"
1410 << unknown_tokens << " unknown token"
1411 << (unknown_tokens == 1 ? "" : "s")
1415 if (defaults.writeFile(defaults.fileName()))
1416 setMessage(_("Document defaults saved in ")
1417 + MakeDisplayPath(fname));
1419 setErrorMessage(_("Unable to save document defaults"));
1423 case LFUN_BUFFERPARAMS_APPLY: {
1424 biblio::CiteEngine const engine =
1425 owner->buffer()->params().cite_engine;
1427 istringstream ss(argument);
1430 int const unknown_tokens =
1431 owner->buffer()->readHeader(lex);
1433 if (unknown_tokens != 0) {
1434 lyxerr << "Warning in LFUN_BUFFERPARAMS_APPLY!\n"
1435 << unknown_tokens << " unknown token"
1436 << (unknown_tokens == 1 ? "" : "s")
1439 if (engine == owner->buffer()->params().cite_engine)
1442 LCursor & cur = view()->cursor();
1443 FuncRequest fr(LFUN_INSET_REFRESH);
1445 InsetBase & inset = owner->buffer()->inset();
1446 InsetIterator it = inset_iterator_begin(inset);
1447 InsetIterator const end = inset_iterator_end(inset);
1448 for (; it != end; ++it)
1449 if (it->lyxCode() == InsetBase::CITE_CODE)
1450 it->dispatch(cur, fr);
1454 case LFUN_TEXTCLASS_APPLY: {
1455 Buffer * buffer = owner->buffer();
1457 lyx::textclass_type const old_class =
1458 buffer->params().textclass;
1460 loadTextclass(argument);
1462 std::pair<bool, lyx::textclass_type> const tc_pair =
1463 textclasslist.NumberOfClass(argument);
1468 lyx::textclass_type const new_class = tc_pair.second;
1469 if (old_class == new_class)
1473 owner->message(_("Converting document to new document class..."));
1475 lyx::cap::SwitchLayoutsBetweenClasses(
1476 old_class, new_class,
1477 buffer->paragraphs(), el);
1479 bufferErrors(*buffer, el);
1480 view()->showErrorList(_("Class switch"));
1484 case LFUN_TEXTCLASS_LOAD:
1485 loadTextclass(argument);
1488 case LFUN_LYXRC_APPLY: {
1489 istringstream ss(argument);
1490 bool const success = lyxrc.read(ss) == 0;
1493 lyxerr << "Warning in LFUN_LYXRC_APPLY!\n"
1494 << "Unable to read lyxrc data"
1502 view()->cursor().dispatch(cmd);
1503 if (view()->cursor().result().dispatched())
1504 update |= view()->cursor().result().update();
1506 update |= view()->dispatch(cmd);
1511 if (view()->available()) {
1512 // Redraw screen unless explicitly told otherwise.
1513 // This also initializes the position cache for all insets
1514 // in (at least partially) visible top-level paragraphs.
1515 view()->update(true, update);
1517 // if we executed a mutating lfun, mark the buffer as dirty
1518 if (getStatus(cmd).enabled()
1519 && !lyxaction.funcHasFlag(cmd.action, LyXAction::NoBuffer)
1520 && !lyxaction.funcHasFlag(cmd.action, LyXAction::ReadOnly))
1521 view()->buffer()->markDirty();
1524 if (view()->cursor().inTexted()) {
1525 view()->owner()->updateLayoutChoice();
1528 sendDispatchMessage(getMessage(), cmd);
1532 void LyXFunc::sendDispatchMessage(string const & msg, FuncRequest const & cmd)
1534 owner->updateMenubar();
1535 owner->updateToolbars();
1537 const bool verbose = (cmd.origin == FuncRequest::UI
1538 || cmd.origin == FuncRequest::COMMANDBUFFER);
1540 if (cmd.action == LFUN_SELFINSERT || !verbose) {
1541 lyxerr[Debug::ACTION] << "dispatch msg is " << msg << endl;
1543 owner->message(msg);
1547 string dispatch_msg = msg;
1548 if (!dispatch_msg.empty())
1549 dispatch_msg += ' ';
1551 string comname = lyxaction.getActionName(cmd.action);
1553 bool argsadded = false;
1555 if (!cmd.argument.empty()) {
1556 if (cmd.action != LFUN_UNKNOWN_ACTION) {
1557 comname += ' ' + cmd.argument;
1562 string const shortcuts = toplevel_keymap->printbindings(cmd);
1564 if (!shortcuts.empty()) {
1565 comname += ": " + shortcuts;
1566 } else if (!argsadded && !cmd.argument.empty()) {
1567 comname += ' ' + cmd.argument;
1570 if (!comname.empty()) {
1571 comname = rtrim(comname);
1572 dispatch_msg += '(' + comname + ')';
1575 lyxerr[Debug::ACTION] << "verbose dispatch msg " << dispatch_msg << endl;
1576 if (!dispatch_msg.empty())
1577 owner->message(dispatch_msg);
1581 void LyXFunc::setupLocalKeymap()
1583 keyseq.stdmap = toplevel_keymap.get();
1584 keyseq.curmap = toplevel_keymap.get();
1585 cancel_meta_seq.stdmap = toplevel_keymap.get();
1586 cancel_meta_seq.curmap = toplevel_keymap.get();
1590 void LyXFunc::menuNew(string const & name, bool fromTemplate)
1592 string initpath = lyxrc.document_path;
1593 string filename(name);
1595 if (view()->available()) {
1596 string const trypath = owner->buffer()->filePath();
1597 // If directory is writeable, use this as default.
1598 if (IsDirWriteable(trypath))
1602 static int newfile_number;
1604 if (filename.empty()) {
1605 filename = AddName(lyxrc.document_path,
1606 "newfile" + convert<string>(++newfile_number) + ".lyx");
1607 FileInfo fi(filename);
1608 while (bufferlist.exists(filename) || fi.readable()) {
1610 filename = AddName(lyxrc.document_path,
1611 "newfile" + convert<string>(newfile_number) +
1613 fi.newFile(filename);
1617 // The template stuff
1620 FileDialog fileDlg(_("Select template file"),
1621 LFUN_SELECT_FILE_SYNC,
1622 make_pair(string(_("Documents|#o#O")),
1623 string(lyxrc.document_path)),
1624 make_pair(string(_("Templates|#T#t")),
1625 string(lyxrc.template_path)));
1627 FileDialog::Result result =
1628 fileDlg.open(lyxrc.template_path,
1629 FileFilterList(_("LyX Documents (*.lyx)")),
1632 if (result.first == FileDialog::Later)
1634 if (result.second.empty())
1636 templname = result.second;
1639 view()->newFile(filename, templname, !name.empty());
1643 void LyXFunc::open(string const & fname)
1645 string initpath = lyxrc.document_path;
1647 if (view()->available()) {
1648 string const trypath = owner->buffer()->filePath();
1649 // If directory is writeable, use this as default.
1650 if (IsDirWriteable(trypath))
1656 if (fname.empty()) {
1657 FileDialog fileDlg(_("Select document to open"),
1659 make_pair(string(_("Documents|#o#O")),
1660 string(lyxrc.document_path)),
1661 make_pair(string(_("Examples|#E#e")),
1662 string(AddPath(package().system_support(), "examples"))));
1664 FileDialog::Result result =
1665 fileDlg.open(initpath,
1666 FileFilterList(_("LyX Documents (*.lyx)")),
1669 if (result.first == FileDialog::Later)
1672 filename = result.second;
1674 // check selected filename
1675 if (filename.empty()) {
1676 owner->message(_("Canceled."));
1682 // get absolute path of file and add ".lyx" to the filename if
1684 string const fullpath = FileSearch(string(), filename, "lyx");
1685 if (!fullpath.empty()) {
1686 filename = fullpath;
1689 string const disp_fn(MakeDisplayPath(filename));
1691 // if the file doesn't exist, let the user create one
1692 FileInfo const f(filename, true);
1694 // the user specifically chose this name. Believe them.
1695 view()->newFile(filename, "", true);
1699 owner->message(bformat(_("Opening document %1$s..."), disp_fn));
1702 if (view()->loadLyXFile(filename)) {
1703 str2 = bformat(_("Document %1$s opened."), disp_fn);
1705 str2 = bformat(_("Could not open document %1$s"), disp_fn);
1707 owner->message(str2);
1711 void LyXFunc::doImport(string const & argument)
1714 string filename = split(argument, format, ' ');
1716 lyxerr[Debug::INFO] << "LyXFunc::doImport: " << format
1717 << " file: " << filename << endl;
1719 // need user interaction
1720 if (filename.empty()) {
1721 string initpath = lyxrc.document_path;
1723 if (view()->available()) {
1724 string const trypath = owner->buffer()->filePath();
1725 // If directory is writeable, use this as default.
1726 if (IsDirWriteable(trypath))
1730 string const text = bformat(_("Select %1$s file to import"),
1731 formats.prettyName(format));
1733 FileDialog fileDlg(text,
1735 make_pair(string(_("Documents|#o#O")),
1736 string(lyxrc.document_path)),
1737 make_pair(string(_("Examples|#E#e")),
1738 string(AddPath(package().system_support(), "examples"))));
1740 string const filter = formats.prettyName(format)
1741 + " (*." + formats.extension(format) + ')';
1743 FileDialog::Result result =
1744 fileDlg.open(initpath,
1745 FileFilterList(filter),
1748 if (result.first == FileDialog::Later)
1751 filename = result.second;
1753 // check selected filename
1754 if (filename.empty())
1755 owner->message(_("Canceled."));
1758 if (filename.empty())
1761 // get absolute path of file
1762 filename = MakeAbsPath(filename);
1764 string const lyxfile = ChangeExtension(filename, ".lyx");
1766 // Check if the document already is open
1767 if (lyx_gui::use_gui && bufferlist.exists(lyxfile)) {
1768 if (!bufferlist.close(bufferlist.getBuffer(lyxfile), true)) {
1769 owner->message(_("Canceled."));
1774 // if the file exists already, and we didn't do
1775 // -i lyx thefile.lyx, warn
1776 if (FileInfo(lyxfile, true).exist() && filename != lyxfile) {
1777 string const file = MakeDisplayPath(lyxfile, 30);
1779 string text = bformat(_("The document %1$s already exists.\n\n"
1780 "Do you want to over-write that document?"), file);
1781 int const ret = Alert::prompt(_("Over-write document?"),
1782 text, 0, 1, _("&Over-write"), _("&Cancel"));
1785 owner->message(_("Canceled."));
1790 Importer::Import(owner, filename, format);
1794 void LyXFunc::closeBuffer()
1796 if (bufferlist.close(owner->buffer(), true) && !quitting) {
1797 if (bufferlist.empty()) {
1798 // need this otherwise SEGV may occur while
1799 // trying to set variables that don't exist
1800 // since there's no current buffer
1801 owner->getDialogs().hideBufferDependent();
1803 view()->setBuffer(bufferlist.first());
1809 // Each "owner" should have it's own message method. lyxview and
1810 // the minibuffer would use the minibuffer, but lyxserver would
1811 // send an ERROR signal to its client. Alejandro 970603
1812 // This function is bit problematic when it comes to NLS, to make the
1813 // lyx servers client be language indepenent we must not translate
1814 // strings sent to this func.
1815 void LyXFunc::setErrorMessage(string const & m) const
1817 dispatch_buffer = m;
1822 void LyXFunc::setMessage(string const & m) const
1824 dispatch_buffer = m;
1828 string const LyXFunc::viewStatusMessage()
1830 // When meta-fake key is pressed, show the key sequence so far + "M-".
1832 return keyseq.print() + "M-";
1834 // Else, when a non-complete key sequence is pressed,
1835 // show the available options.
1836 if (keyseq.length() > 0 && !keyseq.deleted())
1837 return keyseq.printOptions();
1839 if (!view()->available())
1840 return _("Welcome to LyX!");
1842 return view()->cursor().currentState();
1846 BufferView * LyXFunc::view() const
1848 BOOST_ASSERT(owner);
1849 return owner->view().get();
1853 bool LyXFunc::wasMetaKey() const
1855 return (meta_fake_bit != key_modifier::none);