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);
146 bool getStatus(LCursor cursor,
147 FuncRequest const & cmd, FuncStatus & status)
149 // This is, of course, a mess. Better create a new doc iterator and use
150 // this in Inset::getStatus. This might require an additional
151 // BufferView * arg, though (which should be avoided)
152 //LCursor safe = *this;
154 for ( ; cursor.size(); cursor.pop()) {
155 //lyxerr << "\nLCursor::getStatus: cmd: " << cmd << endl << *this << endl;
156 DocIterator::idx_type & idx = cursor.idx();
157 DocIterator::idx_type const lastidx = cursor.lastidx();
160 lyxerr << "wrong idx " << idx << ", max is " << lastidx
161 << ". Trying to correct this." << endl;
165 DocIterator::pit_type & pit = cursor.pit();
166 DocIterator::pit_type const lastpit = cursor.lastpit();
169 lyxerr << "wrong par " << pit << ", max is " << lastpit
170 << ". Trying to correct this." << endl;
174 DocIterator::pos_type & pos = cursor.pos();
175 DocIterator::pos_type const lastpos = cursor.lastpos();
178 lyxerr << "wrong pos " << pos << ", max is " << lastpos
179 << ". Trying to correct this." << endl;
183 // The inset's getStatus() will return 'true' if it made
184 // a definitive decision on whether it want to handle the
185 // request or not. The result of this decision is put into
186 // the 'status' parameter.
187 if (cursor.inset().getStatus(cursor, cmd, status)) {
197 LyXFunc::LyXFunc(LyXView * lv)
200 keyseq(toplevel_keymap.get(), toplevel_keymap.get()),
201 cancel_meta_seq(toplevel_keymap.get(), toplevel_keymap.get()),
202 meta_fake_bit(key_modifier::none)
207 void LyXFunc::handleKeyFunc(kb_action action)
209 char c = encoded_last_key;
211 if (keyseq.length()) {
215 owner->getIntl().getTransManager()
216 .deadkey(c, get_accent(action).accent, view()->getLyXText());
217 // Need to clear, in case the minibuffer calls these
220 // copied verbatim from do_accent_char
221 view()->cursor().resetAnchor();
226 void LyXFunc::processKeySym(LyXKeySymPtr keysym, key_modifier::state state)
228 lyxerr[Debug::KEY] << "KeySym is " << keysym->getSymbolName() << endl;
230 // Do nothing if we have nothing (JMarc)
231 if (!keysym->isOK()) {
232 lyxerr[Debug::KEY] << "Empty kbd action (probably composing)"
237 if (keysym->isModifier()) {
238 lyxerr[Debug::KEY] << "isModifier true" << endl;
242 Encoding const * encoding = view()->cursor().getEncoding();
244 encoded_last_key = keysym->getISOEncoded(encoding ? encoding->Name() : "");
246 // Do a one-deep top-level lookup for
247 // cancel and meta-fake keys. RVDK_PATCH_5
248 cancel_meta_seq.reset();
250 FuncRequest func = cancel_meta_seq.addkey(keysym, state);
251 lyxerr[Debug::KEY] << "action first set to [" << func.action << ']' << endl;
253 // When not cancel or meta-fake, do the normal lookup.
254 // Note how the meta_fake Mod1 bit is OR-ed in and reset afterwards.
255 // Mostly, meta_fake_bit = key_modifier::none. RVDK_PATCH_5.
256 if ((func.action != LFUN_CANCEL) && (func.action != LFUN_META_FAKE)) {
257 // remove Caps Lock and Mod2 as a modifiers
258 func = keyseq.addkey(keysym, (state | meta_fake_bit));
259 lyxerr[Debug::KEY] << "action now set to ["
260 << func.action << ']' << endl;
263 // Dont remove this unless you know what you are doing.
264 meta_fake_bit = key_modifier::none;
266 // can this happen now ?
267 if (func.action == LFUN_NOACTION) {
268 func = FuncRequest(LFUN_PREFIX);
271 if (lyxerr.debugging(Debug::KEY)) {
272 lyxerr << "Key [action="
273 << func.action << "]["
274 << keyseq.print() << ']'
278 // already here we know if it any point in going further
279 // why not return already here if action == -1 and
280 // num_bytes == 0? (Lgb)
282 if (keyseq.length() > 1) {
283 owner->message(keyseq.print());
287 // Maybe user can only reach the key via holding down shift.
288 // Let's see. But only if shift is the only modifier
289 if (func.action == LFUN_UNKNOWN_ACTION &&
290 state == key_modifier::shift) {
291 lyxerr[Debug::KEY] << "Trying without shift" << endl;
292 func = keyseq.addkey(keysym, key_modifier::none);
293 lyxerr[Debug::KEY] << "Action now " << func.action << endl;
296 if (func.action == LFUN_UNKNOWN_ACTION) {
297 // Hmm, we didn't match any of the keysequences. See
298 // if it's normal insertable text not already covered
300 if (keysym->isText() && keyseq.length() == 1) {
301 lyxerr[Debug::KEY] << "isText() is true, inserting." << endl;
302 func = FuncRequest(LFUN_SELFINSERT);
304 lyxerr[Debug::KEY] << "Unknown, !isText() - giving up" << endl;
305 owner->message(_("Unknown function."));
310 if (func.action == LFUN_SELFINSERT) {
311 if (encoded_last_key != 0) {
312 string arg(1, encoded_last_key);
313 dispatch(FuncRequest(LFUN_SELFINSERT, arg));
315 << "SelfInsert arg[`" << arg << "']" << endl;
323 FuncStatus LyXFunc::getStatus(FuncRequest const & cmd) const
325 //lyxerr << "LyXFunc::getStatus: cmd: " << cmd << endl;
327 LCursor & cur = view()->cursor();
329 /* In LyX/Mac, when a dialog is open, the menus of the
330 application can still be accessed without giving focus to
331 the main window. In this case, we want to disable the menu
332 entries that are buffer-related.
335 if (cmd.origin == FuncRequest::UI && !owner->hasFocus())
338 buf = owner->buffer();
340 if (cmd.action == LFUN_NOACTION) {
341 flag.message(N_("Nothing to do"));
346 switch (cmd.action) {
347 case LFUN_UNKNOWN_ACTION:
348 #ifndef HAVE_LIBAIKSAURUS
349 case LFUN_THESAURUS_ENTRY:
355 flag |= lyx_gui::getStatus(cmd);
358 if (flag.unknown()) {
359 flag.message(N_("Unknown action"));
363 if (!flag.enabled()) {
364 if (flag.message().empty())
365 flag.message(N_("Command disabled"));
369 // Check whether we need a buffer
370 if (!lyxaction.funcHasFlag(cmd.action, LyXAction::NoBuffer) && !buf) {
372 flag.message(N_("Command not allowed with"
373 "out any document open"));
378 // I would really like to avoid having this switch and rather try to
379 // encode this in the function itself.
380 // -- And I'd rather let an inset decide which LFUNs it is willing
381 // to handle (Andre')
383 switch (cmd.action) {
384 case LFUN_TOOLTIPS_TOGGLE:
385 flag.setOnOff(owner->getDialogs().tooltipsEnabled());
388 case LFUN_READ_ONLY_TOGGLE:
389 flag.setOnOff(buf->isReadonly());
392 case LFUN_SWITCHBUFFER:
393 // toggle on the current buffer, but do not toggle off
394 // the other ones (is that a good idea?)
395 if (cmd.argument == buf->fileName())
400 enable = cmd.argument == "custom"
401 || Exporter::IsExportable(*buf, cmd.argument);
405 enable = cur.selection();
409 enable = buf->isLatex() && lyxrc.chktex_command != "none";
413 enable = Exporter::IsExportable(*buf, "program");
416 case LFUN_LAYOUT_TABULAR:
417 enable = cur.innerInsetOfType(InsetBase::TABULAR_CODE);
421 case LFUN_LAYOUT_PARAGRAPH:
422 enable = !cur.inset().forceDefaultParagraphs(&cur.inset());
425 case LFUN_VC_REGISTER:
426 enable = !buf->lyxvc().inUse();
428 case LFUN_VC_CHECKIN:
429 enable = buf->lyxvc().inUse() && !buf->isReadonly();
431 case LFUN_VC_CHECKOUT:
432 enable = buf->lyxvc().inUse() && buf->isReadonly();
436 enable = buf->lyxvc().inUse();
438 case LFUN_MENURELOAD:
439 enable = !buf->isUnnamed() && !buf->isClean();
443 case LFUN_INSET_SETTINGS: {
447 UpdatableInset * inset = cur.inset().asUpdatableInset();
448 lyxerr << "inset: " << inset << endl;
452 InsetBase::Code code = inset->lyxCode();
454 case InsetBase::TABULAR_CODE:
455 enable = cmd.argument == "tabular";
457 case InsetBase::ERT_CODE:
458 enable = cmd.argument == "ert";
460 case InsetBase::FLOAT_CODE:
461 enable = cmd.argument == "float";
463 case InsetBase::WRAP_CODE:
464 enable = cmd.argument == "wrap";
466 case InsetBase::NOTE_CODE:
467 enable = cmd.argument == "note";
469 case InsetBase::BRANCH_CODE:
470 enable = cmd.argument == "branch";
472 case InsetBase::BOX_CODE:
473 enable = cmd.argument == "box";
481 case LFUN_DIALOG_SHOW: {
482 string const name = cmd.getArg(0);
484 enable = name == "aboutlyx"
488 || name == "texinfo";
489 else if (name == "print")
490 enable = Exporter::IsExportable(*buf, "dvi")
491 && lyxrc.print_command != "none";
492 else if (name == "character")
493 enable = cur.inset().lyxCode() != InsetBase::ERT_CODE;
494 else if (name == "vclog")
495 enable = buf->lyxvc().inUse();
496 else if (name == "latexlog")
497 enable = IsFileReadable(buf->getLogName().second);
501 case LFUN_DIALOG_UPDATE: {
502 string const name = cmd.getArg(0);
504 enable = name == "prefs";
508 // this one is difficult to get right. As a half-baked
509 // solution, we consider only the first action of the sequence
510 case LFUN_SEQUENCE: {
511 // argument contains ';'-terminated commands
512 string const firstcmd = token(cmd.argument, ';', 0);
513 FuncRequest func(lyxaction.lookupFunc(firstcmd));
514 func.origin = cmd.origin;
515 flag = getStatus(func);
519 case LFUN_MENUNEWTMPLT:
520 case LFUN_WORDFINDFORWARD:
521 case LFUN_WORDFINDBACKWARD:
523 case LFUN_EXEC_COMMAND:
526 case LFUN_CLOSEBUFFER:
535 case LFUN_RECONFIGURE:
539 case LFUN_DROP_LAYOUTS_CHOICE:
540 case LFUN_MENU_OPEN_BY_NAME:
543 case LFUN_GOTOFILEROW:
544 case LFUN_GOTO_PARAGRAPH:
545 case LFUN_DIALOG_SHOW_NEW_INSET:
546 case LFUN_DIALOG_SHOW_NEXT_INSET:
547 case LFUN_DIALOG_HIDE:
548 case LFUN_DIALOG_DISCONNECT_INSET:
550 case LFUN_TOGGLECURSORFOLLOW:
554 case LFUN_KMAP_TOGGLE:
556 case LFUN_EXPORT_CUSTOM:
558 case LFUN_SAVEPREFERENCES:
559 case LFUN_SCREEN_FONT_UPDATE:
562 case LFUN_EXTERNAL_EDIT:
563 case LFUN_GRAPHICS_EDIT:
564 case LFUN_ALL_INSETS_TOGGLE:
565 case LFUN_LANGUAGE_BUFFER:
566 case LFUN_TEXTCLASS_APPLY:
567 case LFUN_TEXTCLASS_LOAD:
568 case LFUN_SAVE_AS_DEFAULT:
569 case LFUN_BUFFERPARAMS_APPLY:
570 case LFUN_LYXRC_APPLY:
571 case LFUN_NEXTBUFFER:
572 case LFUN_PREVIOUSBUFFER:
573 // these are handled in our dispatch()
578 if (!::getStatus(cur, cmd, flag))
579 flag = view()->getStatus(cmd);
585 // Can we use a readonly buffer?
586 if (buf && buf->isReadonly()
587 && !lyxaction.funcHasFlag(cmd.action, LyXAction::ReadOnly)
588 && !lyxaction.funcHasFlag(cmd.action, LyXAction::NoBuffer)) {
589 flag.message(N_("Document is read-only"));
593 // the default error message if we disable the command
594 if (!flag.enabled() && flag.message().empty())
595 flag.message(N_("Command disabled"));
603 bool ensureBufferClean(BufferView * bv)
605 Buffer & buf = *bv->buffer();
609 string const file = MakeDisplayPath(buf.fileName(), 30);
610 string text = bformat(_("The document %1$s has unsaved "
611 "changes.\n\nDo you want to save "
612 "the document?"), file);
613 int const ret = Alert::prompt(_("Save changed document?"),
614 text, 0, 1, _("&Save"),
618 bv->owner()->dispatch(FuncRequest(LFUN_MENUWRITE));
620 return buf.isClean();
624 void showPrintError(string const & name)
626 string str = bformat(_("Could not print the document %1$s.\n"
627 "Check that your printer is set up correctly."),
628 MakeDisplayPath(name, 50));
629 Alert::error(_("Print document failed"), str);
633 void loadTextclass(string const & name)
635 std::pair<bool, lyx::textclass_type> const tc_pair =
636 textclasslist.NumberOfClass(name);
638 if (!tc_pair.first) {
639 lyxerr << "Document class \"" << name
640 << "\" does not exist."
645 lyx::textclass_type const tc = tc_pair.second;
647 if (!textclasslist[tc].load()) {
648 string s = bformat(_("The document could not be converted\n"
649 "into the document class %1$s."),
650 textclasslist[tc].name());
651 Alert::error(_("Could not change class"), s);
658 void LyXFunc::dispatch(FuncRequest const & cmd)
660 BOOST_ASSERT(view());
661 string const argument = cmd.argument;
662 kb_action const action = cmd.action;
664 lyxerr[Debug::ACTION] << "LyXFunc::dispatch: cmd: " << cmd << endl;
665 //lyxerr << "LyXFunc::dispatch: cmd: " << cmd << endl;
667 // we have not done anything wrong yet.
669 dispatch_buffer.erase();
673 FuncStatus const flag = getStatus(cmd);
674 if (!flag.enabled()) {
675 // We cannot use this function here
676 lyxerr[Debug::ACTION] << "LyXFunc::dispatch: "
677 << lyxaction.getActionName(action)
678 << " [" << action << "] is disabled at this location"
680 setErrorMessage(flag.message());
683 if (view()->available())
684 view()->hideCursor();
688 case LFUN_WORDFINDFORWARD:
689 case LFUN_WORDFINDBACKWARD: {
690 static string last_search;
691 string searched_string;
693 if (!argument.empty()) {
694 last_search = argument;
695 searched_string = argument;
697 searched_string = last_search;
700 if (searched_string.empty())
703 bool const fw = action == LFUN_WORDFINDFORWARD;
705 lyx::find::find2string(searched_string, true, false, fw);
706 lyx::find::find(view(), FuncRequest(LFUN_WORD_FIND, data));
711 owner->message(keyseq.printOptions());
714 case LFUN_EXEC_COMMAND:
715 owner->getToolbars().display("minibuffer", true);
716 owner->focus_command_buffer();
721 meta_fake_bit = key_modifier::none;
722 if (view()->available())
723 // cancel any selection
724 dispatch(FuncRequest(LFUN_MARK_OFF));
725 setMessage(N_("Cancel"));
729 meta_fake_bit = key_modifier::alt;
730 setMessage(keyseq.print());
733 case LFUN_READ_ONLY_TOGGLE:
734 if (owner->buffer()->lyxvc().inUse())
735 owner->buffer()->lyxvc().toggleReadOnly();
737 owner->buffer()->setReadonly(
738 !owner->buffer()->isReadonly());
741 // --- Menus -----------------------------------------------
743 menuNew(argument, false);
746 case LFUN_MENUNEWTMPLT:
747 menuNew(argument, true);
750 case LFUN_CLOSEBUFFER:
755 if (!owner->buffer()->isUnnamed()) {
756 string const str = bformat(_("Saving document %1$s..."),
757 MakeDisplayPath(owner->buffer()->fileName()));
759 MenuWrite(owner->buffer());
760 owner->message(str + _(" done."));
762 WriteAs(owner->buffer());
766 WriteAs(owner->buffer(), argument);
769 case LFUN_MENURELOAD: {
770 string const file = MakeDisplayPath(view()->buffer()->fileName(), 20);
771 string text = bformat(_("Any changes will be lost. Are you sure "
772 "you want to revert to the saved version of the document %1$s?"), file);
773 int const ret = Alert::prompt(_("Revert to saved document?"),
774 text, 0, 1, _("&Revert"), _("&Cancel"));
782 Exporter::Export(owner->buffer(), argument, true);
783 view()->showErrorList(BufferFormat(*owner->buffer()));
787 Exporter::Preview(owner->buffer(), argument);
788 view()->showErrorList(BufferFormat(*owner->buffer()));
792 Exporter::Export(owner->buffer(), "program", true);
793 view()->showErrorList(_("Build"));
797 owner->buffer()->runChktex();
798 view()->showErrorList(_("ChkTeX"));
802 if (argument == "custom")
803 owner->getDialogs().show("sendto");
805 Exporter::Export(owner->buffer(), argument, false);
806 view()->showErrorList(BufferFormat(*owner->buffer()));
810 case LFUN_EXPORT_CUSTOM: {
812 string command = split(argument, format_name, ' ');
813 Format const * format = formats.getFormat(format_name);
815 lyxerr << "Format \"" << format_name
816 << "\" not recognized!"
821 Buffer * buffer = owner->buffer();
823 // The name of the file created by the conversion process
826 // Output to filename
827 if (format->name() == "lyx") {
828 string const latexname =
829 buffer->getLatexName(false);
830 filename = ChangeExtension(latexname,
831 format->extension());
832 filename = AddName(buffer->temppath(), filename);
834 if (!buffer->writeFile(filename))
838 Exporter::Export(buffer, format_name, true,
842 // Substitute $$FName for filename
843 if (!contains(command, "$$FName"))
844 command = "( " + command + " ) < $$FName";
845 command = subst(command, "$$FName", filename);
847 // Execute the command in the background
849 call.startscript(Systemcall::DontWait, command);
856 string command = split(split(argument, target, ' '),
860 || target_name.empty()
861 || command.empty()) {
862 lyxerr << "Unable to parse \""
863 << argument << '"' << std::endl;
866 if (target != "printer" && target != "file") {
867 lyxerr << "Unrecognized target \""
868 << target << '"' << std::endl;
872 Buffer * buffer = owner->buffer();
874 if (!Exporter::Export(buffer, "dvi", true)) {
875 showPrintError(buffer->fileName());
879 // Push directory path.
880 string const path = buffer->temppath();
883 // there are three cases here:
884 // 1. we print to a file
885 // 2. we print directly to a printer
886 // 3. we print using a spool command (print to file first)
889 string const dviname =
890 ChangeExtension(buffer->getLatexName(true),
893 if (target == "printer") {
894 if (!lyxrc.print_spool_command.empty()) {
895 // case 3: print using a spool
896 string const psname =
897 ChangeExtension(dviname,".ps");
898 command += lyxrc.print_to_file
901 + QuoteName(dviname);
904 lyxrc.print_spool_command +' ';
905 if (target_name != "default") {
906 command2 += lyxrc.print_spool_printerprefix
910 command2 += QuoteName(psname);
912 // If successful, then spool command
913 res = one.startscript(
918 res = one.startscript(
919 Systemcall::DontWait,
922 // case 2: print directly to a printer
923 res = one.startscript(
924 Systemcall::DontWait,
925 command + QuoteName(dviname));
929 // case 1: print to a file
930 command += lyxrc.print_to_file
931 + QuoteName(MakeAbsPath(target_name,
934 + QuoteName(dviname);
935 res = one.startscript(Systemcall::DontWait,
940 showPrintError(buffer->fileName());
953 InsetCommandParams p("tableofcontents");
954 string const data = InsetCommandMailer::params2string("toc", p);
955 owner->getDialogs().show("toc", data, 0);
963 case LFUN_RECONFIGURE:
967 case LFUN_HELP_OPEN: {
968 string const arg = argument;
970 setErrorMessage(N_("Missing argument"));
973 string const fname = i18nLibFileSearch("doc", arg, "lyx");
975 lyxerr << "LyX: unable to find documentation file `"
976 << arg << "'. Bad installation?" << endl;
979 owner->message(bformat(_("Opening help file %1$s..."),
980 MakeDisplayPath(fname)));
981 view()->loadLyXFile(fname, false);
985 // --- version control -------------------------------
986 case LFUN_VC_REGISTER:
987 if (!ensureBufferClean(view()))
989 if (!owner->buffer()->lyxvc().inUse()) {
990 owner->buffer()->lyxvc().registrer();
995 case LFUN_VC_CHECKIN:
996 if (!ensureBufferClean(view()))
998 if (owner->buffer()->lyxvc().inUse()
999 && !owner->buffer()->isReadonly()) {
1000 owner->buffer()->lyxvc().checkIn();
1005 case LFUN_VC_CHECKOUT:
1006 if (!ensureBufferClean(view()))
1008 if (owner->buffer()->lyxvc().inUse()
1009 && owner->buffer()->isReadonly()) {
1010 owner->buffer()->lyxvc().checkOut();
1015 case LFUN_VC_REVERT:
1016 owner->buffer()->lyxvc().revert();
1021 owner->buffer()->lyxvc().undoLast();
1025 // --- buffers ----------------------------------------
1026 case LFUN_SWITCHBUFFER:
1027 view()->setBuffer(bufferlist.getBuffer(argument));
1030 case LFUN_NEXTBUFFER:
1031 view()->setBuffer(bufferlist.next(view()->buffer()));
1034 case LFUN_PREVIOUSBUFFER:
1035 view()->setBuffer(bufferlist.previous(view()->buffer()));
1039 NewFile(view(), argument);
1042 case LFUN_FILE_OPEN:
1046 case LFUN_DROP_LAYOUTS_CHOICE:
1047 owner->getToolbars().openLayoutList();
1050 case LFUN_MENU_OPEN_BY_NAME:
1051 owner->getMenubar().openByName(argument);
1054 // --- lyxserver commands ----------------------------
1056 setMessage(owner->buffer()->fileName());
1057 lyxerr[Debug::INFO] << "FNAME["
1058 << owner->buffer()->fileName()
1063 dispatch_buffer = keyseq.print();
1064 lyxserver->notifyClient(dispatch_buffer);
1067 case LFUN_GOTOFILEROW: {
1070 istringstream is(argument);
1071 is >> file_name >> row;
1072 if (prefixIs(file_name, getTmpDir())) {
1073 // Needed by inverse dvi search. If it is a file
1074 // in tmpdir, call the apropriated function
1075 view()->setBuffer(bufferlist.getBufferFromTmp(file_name));
1077 // Must replace extension of the file to be .lyx
1078 // and get full path
1079 string const s = ChangeExtension(file_name, ".lyx");
1080 // Either change buffer or load the file
1081 if (bufferlist.exists(s)) {
1082 view()->setBuffer(bufferlist.getBuffer(s));
1084 view()->loadLyXFile(s);
1088 view()->setCursorFromRow(row);
1091 // see BufferView_pimpl::center()
1092 view()->updateScrollbar();
1096 case LFUN_GOTO_PARAGRAPH: {
1097 istringstream is(argument);
1100 ParIterator par = owner->buffer()->getParFromID(id);
1101 if (par == owner->buffer()->par_iterator_end()) {
1102 lyxerr[Debug::INFO] << "No matching paragraph found! ["
1103 << id << ']' << endl;
1106 lyxerr[Debug::INFO] << "Paragraph " << par->id()
1107 << " found." << endl;
1111 view()->setCursor(par, 0);
1113 view()->switchKeyMap();
1114 owner->view_state_changed();
1117 // see BufferView_pimpl::center()
1118 view()->updateScrollbar();
1122 case LFUN_DIALOG_SHOW: {
1123 string const name = cmd.getArg(0);
1124 string data = trim(cmd.argument.substr(name.size()));
1126 if (name == "character") {
1127 data = freefont2string();
1129 owner->getDialogs().show("character", data);
1132 else if (name == "latexlog") {
1133 pair<Buffer::LogType, string> const logfile =
1134 owner->buffer()->getLogName();
1135 switch (logfile.first) {
1136 case Buffer::latexlog:
1139 case Buffer::buildlog:
1143 data += logfile.second;
1144 owner->getDialogs().show("log", data);
1146 else if (name == "vclog") {
1147 string const data = "vc " +
1148 owner->buffer()->lyxvc().getLogFile();
1149 owner->getDialogs().show("log", data);
1152 owner->getDialogs().show(name, data);
1156 case LFUN_DIALOG_SHOW_NEW_INSET: {
1157 string const name = cmd.getArg(0);
1158 string data = trim(cmd.argument.substr(name.size()));
1159 if (name == "bibitem" ||
1161 name == "include" ||
1167 InsetCommandParams p(name);
1168 data = InsetCommandMailer::params2string(name, p);
1169 } else if (name == "box") {
1170 // \c data == "Boxed" || "Frameless" etc
1171 InsetBoxParams p(data);
1172 data = InsetBoxMailer::params2string(p);
1173 } else if (name == "branch") {
1174 InsetBranchParams p;
1175 data = InsetBranchMailer::params2string(p);
1176 } else if (name == "citation") {
1177 InsetCommandParams p("cite");
1178 data = InsetCommandMailer::params2string(name, p);
1179 } else if (name == "ert") {
1180 data = InsetERTMailer::params2string(InsetCollapsable::Open);
1181 } else if (name == "external") {
1182 InsetExternalParams p;
1183 Buffer const & buffer = *owner->buffer();
1184 data = InsetExternalMailer::params2string(p, buffer);
1185 } else if (name == "float") {
1187 data = InsetFloatMailer::params2string(p);
1188 } else if (name == "graphics") {
1189 InsetGraphicsParams p;
1190 Buffer const & buffer = *owner->buffer();
1191 data = InsetGraphicsMailer::params2string(p, buffer);
1192 } else if (name == "note") {
1194 data = InsetNoteMailer::params2string(p);
1195 } else if (name == "vspace") {
1197 data = InsetVSpaceMailer::params2string(space);
1198 } else if (name == "wrap") {
1200 data = InsetWrapMailer::params2string(p);
1202 owner->getDialogs().show(name, data, 0);
1206 case LFUN_DIALOG_SHOW_NEXT_INSET:
1209 case LFUN_DIALOG_UPDATE: {
1210 string const & name = argument;
1211 // Can only update a dialog connected to an existing inset
1212 InsetBase * inset = owner->getDialogs().getOpenInset(name);
1214 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument);
1215 inset->dispatch(view()->cursor(), fr);
1216 } else if (name == "paragraph") {
1217 dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
1218 } else if (name == "prefs") {
1219 owner->getDialogs().update(name, string());
1224 case LFUN_DIALOG_HIDE:
1225 Dialogs::hide(argument, 0);
1228 case LFUN_DIALOG_DISCONNECT_INSET:
1229 owner->getDialogs().disconnect(argument);
1232 case LFUN_CHILDOPEN: {
1233 string const filename =
1234 MakeAbsPath(argument, owner->buffer()->filePath());
1235 setMessage(N_("Opening child document ") +
1236 MakeDisplayPath(filename) + "...");
1237 view()->savePosition(0);
1238 string const parentfilename = owner->buffer()->fileName();
1239 if (bufferlist.exists(filename))
1240 view()->setBuffer(bufferlist.getBuffer(filename));
1242 view()->loadLyXFile(filename);
1243 // Set the parent name of the child document.
1244 // This makes insertion of citations and references in the child work,
1245 // when the target is in the parent or another child document.
1246 owner->buffer()->setParentName(parentfilename);
1250 case LFUN_TOGGLECURSORFOLLOW:
1251 lyxrc.cursor_follows_scrollbar = !lyxrc.cursor_follows_scrollbar;
1255 owner->getIntl().KeyMapOn(false);
1258 case LFUN_KMAP_PRIM:
1259 owner->getIntl().KeyMapPrim();
1263 owner->getIntl().KeyMapSec();
1266 case LFUN_KMAP_TOGGLE:
1267 owner->getIntl().ToggleKeyMap();
1273 string rest = split(argument, countstr, ' ');
1274 istringstream is(countstr);
1277 lyxerr << "repeat: count: " << count << " cmd: " << rest << endl;
1278 for (int i = 0; i < count; ++i)
1279 dispatch(lyxaction.lookupFunc(rest));
1283 case LFUN_SEQUENCE: {
1284 // argument contains ';'-terminated commands
1285 string arg = argument;
1286 while (!arg.empty()) {
1288 arg = split(arg, first, ';');
1289 FuncRequest func(lyxaction.lookupFunc(first));
1290 func.origin = cmd.origin;
1296 case LFUN_SAVEPREFERENCES: {
1297 Path p(user_lyxdir());
1298 lyxrc.write("preferences", false);
1302 case LFUN_SCREEN_FONT_UPDATE:
1303 // handle the screen font changes.
1304 lyxrc.set_font_norm_type();
1305 lyx_gui::update_fonts();
1306 // All visible buffers will need resize
1310 case LFUN_SET_COLOR: {
1312 string const x11_name = split(argument, lyx_name, ' ');
1313 if (lyx_name.empty() || x11_name.empty()) {
1314 setErrorMessage(N_("Syntax: set-color <lyx_name>"
1319 bool const graphicsbg_changed =
1320 (lyx_name == lcolor.getLyXName(LColor::graphicsbg) &&
1321 x11_name != lcolor.getX11Name(LColor::graphicsbg));
1323 if (!lcolor.setColor(lyx_name, x11_name)) {
1325 bformat(_("Set-color \"%1$s\" failed "
1326 "- color is undefined or "
1327 "may not be redefined"), lyx_name));
1331 lyx_gui::update_color(lcolor.getFromLyXName(lyx_name));
1333 if (graphicsbg_changed) {
1334 #ifdef WITH_WARNINGS
1335 #warning FIXME!! The graphics cache no longer has a changeDisplay method.
1338 lyx::graphics::GCache::get().changeDisplay(true);
1345 owner->message(argument);
1348 case LFUN_TOOLTIPS_TOGGLE:
1349 owner->getDialogs().toggleTooltips();
1352 case LFUN_EXTERNAL_EDIT: {
1353 FuncRequest fr(action, argument);
1354 InsetExternal().dispatch(view()->cursor(), fr);
1358 case LFUN_GRAPHICS_EDIT: {
1359 FuncRequest fr(action, argument);
1360 InsetGraphics().dispatch(view()->cursor(), fr);
1364 case LFUN_ALL_INSETS_TOGGLE: {
1366 string const name = split(argument, action, ' ');
1367 InsetBase::Code const inset_code =
1368 InsetBase::translate(name);
1370 LCursor & cur = view()->cursor();
1371 FuncRequest fr(LFUN_INSET_TOGGLE, action);
1373 InsetBase & inset = owner->buffer()->inset();
1374 InsetIterator it = inset_iterator_begin(inset);
1375 InsetIterator const end = inset_iterator_end(inset);
1376 for (; it != end; ++it) {
1377 if (inset_code == InsetBase::NO_CODE
1378 || inset_code == it->lyxCode())
1379 it->dispatch(cur, fr);
1384 case LFUN_LANGUAGE_BUFFER: {
1385 Buffer & buffer = *owner->buffer();
1386 Language const * oldL = buffer.params().language;
1387 Language const * newL = languages.getLanguage(argument);
1388 if (!newL || oldL == newL)
1391 if (oldL->RightToLeft() == newL->RightToLeft()
1392 && !buffer.isMultiLingual())
1393 buffer.changeLanguage(oldL, newL);
1395 buffer.updateDocLang(newL);
1399 case LFUN_SAVE_AS_DEFAULT: {
1400 string const fname =
1401 AddName(AddPath(user_lyxdir(), "templates/"),
1403 Buffer defaults(fname);
1405 istringstream ss(argument);
1408 int const unknown_tokens = defaults.readHeader(lex);
1410 if (unknown_tokens != 0) {
1411 lyxerr << "Warning in LFUN_SAVE_AS_DEFAULT!\n"
1412 << unknown_tokens << " unknown token"
1413 << (unknown_tokens == 1 ? "" : "s")
1417 if (defaults.writeFile(defaults.fileName()))
1418 setMessage(_("Document defaults saved in ")
1419 + MakeDisplayPath(fname));
1421 setErrorMessage(_("Unable to save document defaults"));
1425 case LFUN_BUFFERPARAMS_APPLY: {
1426 biblio::CiteEngine const engine =
1427 owner->buffer()->params().cite_engine;
1429 istringstream ss(argument);
1432 int const unknown_tokens =
1433 owner->buffer()->readHeader(lex);
1435 if (unknown_tokens != 0) {
1436 lyxerr << "Warning in LFUN_BUFFERPARAMS_APPLY!\n"
1437 << unknown_tokens << " unknown token"
1438 << (unknown_tokens == 1 ? "" : "s")
1441 if (engine == owner->buffer()->params().cite_engine)
1444 LCursor & cur = view()->cursor();
1445 FuncRequest fr(LFUN_INSET_REFRESH);
1447 InsetBase & inset = owner->buffer()->inset();
1448 InsetIterator it = inset_iterator_begin(inset);
1449 InsetIterator const end = inset_iterator_end(inset);
1450 for (; it != end; ++it)
1451 if (it->lyxCode() == InsetBase::CITE_CODE)
1452 it->dispatch(cur, fr);
1456 case LFUN_TEXTCLASS_APPLY: {
1457 Buffer * buffer = owner->buffer();
1459 lyx::textclass_type const old_class =
1460 buffer->params().textclass;
1462 loadTextclass(argument);
1464 std::pair<bool, lyx::textclass_type> const tc_pair =
1465 textclasslist.NumberOfClass(argument);
1470 lyx::textclass_type const new_class = tc_pair.second;
1471 if (old_class == new_class)
1475 owner->message(_("Converting document to new document class..."));
1477 lyx::cap::SwitchLayoutsBetweenClasses(
1478 old_class, new_class,
1479 buffer->paragraphs(), el);
1481 bufferErrors(*buffer, el);
1482 view()->showErrorList(_("Class switch"));
1486 case LFUN_TEXTCLASS_LOAD:
1487 loadTextclass(argument);
1490 case LFUN_LYXRC_APPLY: {
1491 istringstream ss(argument);
1492 bool const success = lyxrc.read(ss) == 0;
1495 lyxerr << "Warning in LFUN_LYXRC_APPLY!\n"
1496 << "Unable to read lyxrc data"
1504 view()->cursor().dispatch(cmd);
1505 if (view()->cursor().result().dispatched())
1506 update |= view()->cursor().result().update();
1508 update |= view()->dispatch(cmd);
1513 if (view()->available()) {
1514 // Redraw screen unless explicitly told otherwise.
1515 // This also initializes the position cache for all insets
1516 // in (at least partially) visible top-level paragraphs.
1517 view()->update(true, update);
1519 // if we executed a mutating lfun, mark the buffer as dirty
1520 if (getStatus(cmd).enabled()
1521 && !lyxaction.funcHasFlag(cmd.action, LyXAction::NoBuffer)
1522 && !lyxaction.funcHasFlag(cmd.action, LyXAction::ReadOnly))
1523 view()->buffer()->markDirty();
1526 if (view()->cursor().inTexted()) {
1527 view()->owner()->updateLayoutChoice();
1530 sendDispatchMessage(getMessage(), cmd);
1534 void LyXFunc::sendDispatchMessage(string const & msg, FuncRequest const & cmd)
1536 owner->updateMenubar();
1537 owner->updateToolbars();
1539 const bool verbose = (cmd.origin == FuncRequest::UI
1540 || cmd.origin == FuncRequest::COMMANDBUFFER);
1542 if (cmd.action == LFUN_SELFINSERT || !verbose) {
1543 lyxerr[Debug::ACTION] << "dispatch msg is " << msg << endl;
1545 owner->message(msg);
1549 string dispatch_msg = msg;
1550 if (!dispatch_msg.empty())
1551 dispatch_msg += ' ';
1553 string comname = lyxaction.getActionName(cmd.action);
1555 bool argsadded = false;
1557 if (!cmd.argument.empty()) {
1558 if (cmd.action != LFUN_UNKNOWN_ACTION) {
1559 comname += ' ' + cmd.argument;
1564 string const shortcuts = toplevel_keymap->printbindings(cmd);
1566 if (!shortcuts.empty()) {
1567 comname += ": " + shortcuts;
1568 } else if (!argsadded && !cmd.argument.empty()) {
1569 comname += ' ' + cmd.argument;
1572 if (!comname.empty()) {
1573 comname = rtrim(comname);
1574 dispatch_msg += '(' + comname + ')';
1577 lyxerr[Debug::ACTION] << "verbose dispatch msg " << dispatch_msg << endl;
1578 if (!dispatch_msg.empty())
1579 owner->message(dispatch_msg);
1583 void LyXFunc::setupLocalKeymap()
1585 keyseq.stdmap = toplevel_keymap.get();
1586 keyseq.curmap = toplevel_keymap.get();
1587 cancel_meta_seq.stdmap = toplevel_keymap.get();
1588 cancel_meta_seq.curmap = toplevel_keymap.get();
1592 void LyXFunc::menuNew(string const & name, bool fromTemplate)
1594 string initpath = lyxrc.document_path;
1595 string filename(name);
1597 if (view()->available()) {
1598 string const trypath = owner->buffer()->filePath();
1599 // If directory is writeable, use this as default.
1600 if (IsDirWriteable(trypath))
1604 static int newfile_number;
1606 if (filename.empty()) {
1607 filename = AddName(lyxrc.document_path,
1608 "newfile" + convert<string>(++newfile_number) + ".lyx");
1609 FileInfo fi(filename);
1610 while (bufferlist.exists(filename) || fi.readable()) {
1612 filename = AddName(lyxrc.document_path,
1613 "newfile" + convert<string>(newfile_number) +
1615 fi.newFile(filename);
1619 // The template stuff
1622 FileDialog fileDlg(_("Select template file"),
1623 LFUN_SELECT_FILE_SYNC,
1624 make_pair(string(_("Documents|#o#O")),
1625 string(lyxrc.document_path)),
1626 make_pair(string(_("Templates|#T#t")),
1627 string(lyxrc.template_path)));
1629 FileDialog::Result result =
1630 fileDlg.open(lyxrc.template_path,
1631 FileFilterList(_("LyX Documents (*.lyx)")),
1634 if (result.first == FileDialog::Later)
1636 if (result.second.empty())
1638 templname = result.second;
1641 view()->newFile(filename, templname, !name.empty());
1645 void LyXFunc::open(string const & fname)
1647 string initpath = lyxrc.document_path;
1649 if (view()->available()) {
1650 string const trypath = owner->buffer()->filePath();
1651 // If directory is writeable, use this as default.
1652 if (IsDirWriteable(trypath))
1658 if (fname.empty()) {
1659 FileDialog fileDlg(_("Select document to open"),
1661 make_pair(string(_("Documents|#o#O")),
1662 string(lyxrc.document_path)),
1663 make_pair(string(_("Examples|#E#e")),
1664 string(AddPath(system_lyxdir(), "examples"))));
1666 FileDialog::Result result =
1667 fileDlg.open(initpath,
1668 FileFilterList(_("LyX Documents (*.lyx)")),
1671 if (result.first == FileDialog::Later)
1674 filename = result.second;
1676 // check selected filename
1677 if (filename.empty()) {
1678 owner->message(_("Canceled."));
1684 // get absolute path of file and add ".lyx" to the filename if
1686 string const fullpath = FileSearch(string(), filename, "lyx");
1687 if (!fullpath.empty()) {
1688 filename = fullpath;
1691 string const disp_fn(MakeDisplayPath(filename));
1693 // if the file doesn't exist, let the user create one
1694 FileInfo const f(filename, true);
1696 // the user specifically chose this name. Believe them.
1697 view()->newFile(filename, "", true);
1701 owner->message(bformat(_("Opening document %1$s..."), disp_fn));
1704 if (view()->loadLyXFile(filename)) {
1705 str2 = bformat(_("Document %1$s opened."), disp_fn);
1707 str2 = bformat(_("Could not open document %1$s"), disp_fn);
1709 owner->message(str2);
1713 void LyXFunc::doImport(string const & argument)
1716 string filename = split(argument, format, ' ');
1718 lyxerr[Debug::INFO] << "LyXFunc::doImport: " << format
1719 << " file: " << filename << endl;
1721 // need user interaction
1722 if (filename.empty()) {
1723 string initpath = lyxrc.document_path;
1725 if (view()->available()) {
1726 string const trypath = owner->buffer()->filePath();
1727 // If directory is writeable, use this as default.
1728 if (IsDirWriteable(trypath))
1732 string const text = bformat(_("Select %1$s file to import"),
1733 formats.prettyName(format));
1735 FileDialog fileDlg(text,
1737 make_pair(string(_("Documents|#o#O")),
1738 string(lyxrc.document_path)),
1739 make_pair(string(_("Examples|#E#e")),
1740 string(AddPath(system_lyxdir(), "examples"))));
1742 string const filter = formats.prettyName(format)
1743 + " (*." + formats.extension(format) + ')';
1745 FileDialog::Result result =
1746 fileDlg.open(initpath,
1747 FileFilterList(filter),
1750 if (result.first == FileDialog::Later)
1753 filename = result.second;
1755 // check selected filename
1756 if (filename.empty())
1757 owner->message(_("Canceled."));
1760 if (filename.empty())
1763 // get absolute path of file
1764 filename = MakeAbsPath(filename);
1766 string const lyxfile = ChangeExtension(filename, ".lyx");
1768 // Check if the document already is open
1769 if (lyx_gui::use_gui && bufferlist.exists(lyxfile)) {
1770 if (!bufferlist.close(bufferlist.getBuffer(lyxfile), true)) {
1771 owner->message(_("Canceled."));
1776 // if the file exists already, and we didn't do
1777 // -i lyx thefile.lyx, warn
1778 if (FileInfo(lyxfile, true).exist() && filename != lyxfile) {
1779 string const file = MakeDisplayPath(lyxfile, 30);
1781 string text = bformat(_("The document %1$s already exists.\n\n"
1782 "Do you want to over-write that document?"), file);
1783 int const ret = Alert::prompt(_("Over-write document?"),
1784 text, 0, 1, _("&Over-write"), _("&Cancel"));
1787 owner->message(_("Canceled."));
1792 Importer::Import(owner, filename, format);
1796 void LyXFunc::closeBuffer()
1798 if (bufferlist.close(owner->buffer(), true) && !quitting) {
1799 if (bufferlist.empty()) {
1800 // need this otherwise SEGV may occur while
1801 // trying to set variables that don't exist
1802 // since there's no current buffer
1803 owner->getDialogs().hideBufferDependent();
1805 view()->setBuffer(bufferlist.first());
1811 // Each "owner" should have it's own message method. lyxview and
1812 // the minibuffer would use the minibuffer, but lyxserver would
1813 // send an ERROR signal to its client. Alejandro 970603
1814 // This function is bit problematic when it comes to NLS, to make the
1815 // lyx servers client be language indepenent we must not translate
1816 // strings sent to this func.
1817 void LyXFunc::setErrorMessage(string const & m) const
1819 dispatch_buffer = m;
1824 void LyXFunc::setMessage(string const & m) const
1826 dispatch_buffer = m;
1830 string const LyXFunc::viewStatusMessage()
1832 // When meta-fake key is pressed, show the key sequence so far + "M-".
1834 return keyseq.print() + "M-";
1836 // Else, when a non-complete key sequence is pressed,
1837 // show the available options.
1838 if (keyseq.length() > 0 && !keyseq.deleted())
1839 return keyseq.printOptions();
1841 if (!view()->available())
1842 return _("Welcome to LyX!");
1844 return view()->cursor().currentState();
1848 BufferView * LyXFunc::view() const
1850 BOOST_ASSERT(owner);
1851 return owner->view().get();
1855 bool LyXFunc::wasMetaKey() const
1857 return (meta_fake_bit != key_modifier::none);