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"
32 #include "dispatchresult.h"
36 #include "funcrequest.h"
40 #include "iterators.h"
44 #include "LyXAction.h"
48 #include "lyxserver.h"
50 #include "paragraph.h"
51 #include "ParagraphParameters.h"
54 #include "insets/insetbox.h"
55 #include "insets/insetbranch.h"
56 #include "insets/insetcommand.h"
57 #include "insets/insetert.h"
58 #include "insets/insetexternal.h"
59 #include "insets/insetfloat.h"
60 #include "insets/insetgraphics.h"
61 #include "insets/insetnote.h"
62 #include "insets/insettabular.h"
63 #include "insets/insetvspace.h"
64 #include "insets/insetwrap.h"
66 #include "frontends/Alert.h"
67 #include "frontends/Dialogs.h"
68 #include "frontends/FileDialog.h"
69 #include "frontends/lyx_gui.h"
70 #include "frontends/LyXKeySym.h"
71 #include "frontends/LyXView.h"
72 #include "frontends/Menubar.h"
73 #include "frontends/Toolbar.h"
75 #include "support/FileInfo.h"
76 #include "support/filetools.h"
77 #include "support/forkedcontr.h"
78 #include "support/globbing.h"
79 #include "support/path.h"
80 #include "support/path_defines.h"
81 #include "support/tostr.h"
82 #include "support/std_sstream.h"
83 #include "support/os.h"
85 using bv_funcs::changeDepth;
86 using bv_funcs::currentState;
87 using bv_funcs::DEC_DEPTH;
88 using bv_funcs::freefont2string;
89 using bv_funcs::INC_DEPTH;
91 using lyx::support::AddName;
92 using lyx::support::AddPath;
93 using lyx::support::bformat;
94 using lyx::support::ChangeExtension;
95 using lyx::support::FileFilterList;
96 using lyx::support::FileInfo;
97 using lyx::support::FileSearch;
98 using lyx::support::ForkedcallsController;
99 using lyx::support::i18nLibFileSearch;
100 using lyx::support::IsDirWriteable;
101 using lyx::support::IsFileReadable;
102 using lyx::support::isStrInt;
103 using lyx::support::MakeAbsPath;
104 using lyx::support::MakeDisplayPath;
105 using lyx::support::Path;
106 using lyx::support::rtrim;
107 using lyx::support::split;
108 using lyx::support::strToInt;
109 using lyx::support::strToUnsignedInt;
110 using lyx::support::system_lyxdir;
111 using lyx::support::token;
112 using lyx::support::trim;
113 using lyx::support::user_lyxdir;
114 using lyx::support::prefixIs;
115 using lyx::support::os::getTmpDir;
118 using std::make_pair;
121 using std::istringstream;
124 extern BufferList bufferlist;
125 extern LyXServer * lyxserver;
126 extern bool selection_possible;
128 extern boost::scoped_ptr<kb_keymap> toplevel_keymap;
131 extern tex_accent_struct get_accent(kb_action action);
134 LyXFunc::LyXFunc(LyXView * lv)
137 keyseq(toplevel_keymap.get(), toplevel_keymap.get()),
138 cancel_meta_seq(toplevel_keymap.get(), toplevel_keymap.get()),
139 meta_fake_bit(key_modifier::none)
144 void LyXFunc::handleKeyFunc(kb_action action)
146 char c = encoded_last_key;
148 if (keyseq.length()) {
152 owner->getIntl().getTransManager()
153 .deadkey(c, get_accent(action).accent, view()->getLyXText());
154 // Need to clear, in case the minibuffer calls these
157 // copied verbatim from do_accent_char
158 view()->cursor().resetAnchor();
163 void LyXFunc::processKeySym(LyXKeySymPtr keysym, key_modifier::state state)
165 lyxerr[Debug::KEY] << "KeySym is " << keysym->getSymbolName() << endl;
167 // Do nothing if we have nothing (JMarc)
168 if (!keysym->isOK()) {
169 lyxerr[Debug::KEY] << "Empty kbd action (probably composing)"
174 if (keysym->isModifier()) {
175 lyxerr[Debug::KEY] << "isModifier true" << endl;
179 Encoding const * encoding = view()->getEncoding();
181 encoded_last_key = keysym->getISOEncoded(encoding ? encoding->Name() : "");
183 // Do a one-deep top-level lookup for
184 // cancel and meta-fake keys. RVDK_PATCH_5
185 cancel_meta_seq.reset();
187 FuncRequest func = cancel_meta_seq.addkey(keysym, state);
188 lyxerr[Debug::KEY] << "action first set to [" << func.action << ']' << endl;
190 // When not cancel or meta-fake, do the normal lookup.
191 // Note how the meta_fake Mod1 bit is OR-ed in and reset afterwards.
192 // Mostly, meta_fake_bit = key_modifier::none. RVDK_PATCH_5.
193 if ((func.action != LFUN_CANCEL) && (func.action != LFUN_META_FAKE)) {
194 // remove Caps Lock and Mod2 as a modifiers
195 func = keyseq.addkey(keysym, (state | meta_fake_bit));
196 lyxerr[Debug::KEY] << "action now set to ["
197 << func.action << ']' << endl;
200 // Dont remove this unless you know what you are doing.
201 meta_fake_bit = key_modifier::none;
203 // can this happen now ?
204 if (func.action == LFUN_NOACTION) {
205 func = FuncRequest(LFUN_PREFIX);
208 if (lyxerr.debugging(Debug::KEY)) {
209 lyxerr << "Key [action="
210 << func.action << "]["
211 << keyseq.print() << ']'
215 // already here we know if it any point in going further
216 // why not return already here if action == -1 and
217 // num_bytes == 0? (Lgb)
219 if (keyseq.length() > 1) {
220 owner->message(keyseq.print());
224 // Maybe user can only reach the key via holding down shift.
225 // Let's see. But only if shift is the only modifier
226 if (func.action == LFUN_UNKNOWN_ACTION &&
227 state == key_modifier::shift) {
228 lyxerr[Debug::KEY] << "Trying without shift" << endl;
229 func = keyseq.addkey(keysym, key_modifier::none);
230 lyxerr[Debug::KEY] << "Action now " << func.action << endl;
233 if (func.action == LFUN_UNKNOWN_ACTION) {
234 // Hmm, we didn't match any of the keysequences. See
235 // if it's normal insertable text not already covered
237 if (keysym->isText() && keyseq.length() == 1) {
238 lyxerr[Debug::KEY] << "isText() is true, inserting." << endl;
239 func = FuncRequest(LFUN_SELFINSERT);
241 lyxerr[Debug::KEY] << "Unknown, !isText() - giving up" << endl;
242 owner->message(_("Unknown function."));
247 if (func.action == LFUN_SELFINSERT) {
248 if (encoded_last_key != 0) {
250 arg += encoded_last_key;
251 lyxerr << "SelfInsert arg[`"
252 << arg << "']" << endl;
253 dispatch(FuncRequest(LFUN_SELFINSERT, arg));
255 << "SelfInsert arg[`" << arg << "']" << endl;
263 FuncStatus LyXFunc::getStatus(FuncRequest const & ev) const
266 Buffer * buf = owner->buffer();
268 if (ev.action == LFUN_NOACTION) {
269 setStatusMessage(N_("Nothing to do"));
275 case LFUN_UNKNOWN_ACTION:
276 #ifndef HAVE_LIBAIKSAURUS
277 case LFUN_THESAURUS_ENTRY:
283 flag |= lyx_gui::getStatus(ev);
286 if (flag.unknown()) {
287 setStatusMessage(N_("Unknown action"));
291 // the default error message if we disable the command
292 setStatusMessage(N_("Command disabled"));
294 // Check whether we need a buffer
295 if (!lyxaction.funcHasFlag(ev.action, LyXAction::NoBuffer)) {
296 // Yes we need a buffer, do we have one?
299 // Can we use a readonly buffer?
300 if (buf->isReadonly() &&
301 !lyxaction.funcHasFlag(ev.action,
302 LyXAction::ReadOnly)) {
304 setStatusMessage(N_("Document is read-only"));
309 setStatusMessage(N_("Command not allowed with"
310 "out any document open"));
316 UpdatableInset * tli = view()->cursor().inset()
317 ? view()->cursor().inset()->asUpdatableInset() : 0;
318 InsetTabular * tab = view()->cursor().innerInsetTabular();
320 // I would really like to avoid having this switch and rather try to
321 // encode this in the function itself.
322 bool disable = false;
325 disable = ev.argument != "custom"
326 && !Exporter::IsExportable(*buf, ev.argument);
329 disable = buf->undostack().empty();
332 disable = buf->redostack().empty();
336 if (tab && tab->hasSelection())
339 disable = !view()->cursor().inMathed() && !view()->cursor().selection();
343 disable = !buf->isLatex() || lyxrc.chktex_command == "none";
347 disable = !Exporter::IsExportable(*buf, "program");
350 case LFUN_LAYOUT_TABULAR:
351 disable = !view()->cursor().innerInsetTabular();
355 disable = !changeDepthAllowed(view(), view()->getLyXText(), DEC_DEPTH);
358 case LFUN_DEPTH_PLUS:
359 disable = !changeDepthAllowed(view(), view()->getLyXText(), INC_DEPTH);
363 case LFUN_LAYOUT_PARAGRAPH: {
364 InsetOld * inset = view()->getLyXText()->cursorPar()->inInset();
365 disable = inset && inset->forceDefaultParagraphs(inset);
369 case LFUN_INSET_OPTARG:
370 disable = (view()->getLyXText()->cursorPar()->layout()->optionalargs == 0);
373 case LFUN_TABULAR_FEATURE:
375 if (view()->cursor().inMathed()) {
376 // FIXME: check temporarily disabled
378 char align = mathcursor::valign();
383 if (ev.argument.empty()) {
387 if (!contains("tcb", ev.argument[0])) {
391 flag.setOnOff(ev.argument[0] == align);
395 char align = mathcursor::halign();
400 if (ev.argument.empty()) {
404 if (!contains("lcr", ev.argument[0])) {
408 flag.setOnOff(ev.argument[0] == align);
410 disable = !mathcursor::halign();
416 //ret.disabled(true);
417 InsetTabular * tab = view()->cursor().innerInsetTabular();
419 ret = tab->getStatus(ev.argument);
426 static InsetTabular inset(*buf, 1, 1);
428 FuncStatus ret = inset.getStatus(ev.argument);
429 if (ret.onoff(true) || ret.onoff(false))
430 flag.setOnOff(false);
435 case LFUN_VC_REGISTER:
436 disable = buf->lyxvc().inUse();
438 case LFUN_VC_CHECKIN:
439 disable = !buf->lyxvc().inUse() || buf->isReadonly();
441 case LFUN_VC_CHECKOUT:
442 disable = !buf->lyxvc().inUse() || !buf->isReadonly();
446 disable = !buf->lyxvc().inUse();
448 case LFUN_MENURELOAD:
449 disable = buf->isUnnamed() || buf->isClean();
451 case LFUN_BOOKMARK_GOTO:
453 isSavedPosition(strToUnsignedInt(ev.argument));
456 case LFUN_MERGE_CHANGES:
457 case LFUN_ACCEPT_CHANGE:
458 case LFUN_REJECT_CHANGE:
459 case LFUN_ACCEPT_ALL_CHANGES:
460 case LFUN_REJECT_ALL_CHANGES:
461 disable = !buf->params().tracking_changes;
464 case LFUN_INSET_SETTINGS: {
466 UpdatableInset * inset = view()->cursor().inset()
467 ? view()->cursor().inset()->asUpdatableInset() : 0;
472 // jump back to owner if an InsetText, so
473 // we get back to the InsetTabular or whatever
474 if (inset->lyxCode() == InsetOld::TEXT_CODE)
475 inset = inset->owner();
477 InsetOld::Code code = inset->lyxCode();
479 case InsetOld::TABULAR_CODE:
480 disable = ev.argument != "tabular";
482 case InsetOld::ERT_CODE:
483 disable = ev.argument != "ert";
485 case InsetOld::FLOAT_CODE:
486 disable = ev.argument != "float";
488 case InsetOld::WRAP_CODE:
489 disable = ev.argument != "wrap";
491 case InsetOld::NOTE_CODE:
492 disable = ev.argument != "note";
494 case InsetOld::BRANCH_CODE:
495 disable = ev.argument != "branch";
497 case InsetOld::BOX_CODE:
498 disable = ev.argument != "box";
506 case LFUN_MATH_MUTATE:
507 if (view()->cursor().inMathed())
508 //flag.setOnOff(mathcursor::formula()->hullType() == ev.argument);
509 flag.setOnOff(false);
514 // we just need to be in math mode to enable that
516 case LFUN_MATH_SPACE:
517 case LFUN_MATH_LIMITS:
518 case LFUN_MATH_NONUMBER:
519 case LFUN_MATH_NUMBER:
520 case LFUN_MATH_EXTERN:
521 disable = !view()->cursor().inMathed();
524 case LFUN_DIALOG_SHOW: {
525 string const name = ev.getArg(0);
527 disable = !(name == "aboutlyx" ||
530 name == "preferences" ||
535 if (name == "print") {
536 disable = !Exporter::IsExportable(*buf, "dvi") ||
537 lyxrc.print_command == "none";
538 } else if (name == "character") {
539 InsetBase * inset = view()->cursor().inset();
540 disable = inset && inset->lyxCode() == InsetOld::ERT_CODE;
541 } else if (name == "vclog") {
542 disable = !buf->lyxvc().inUse();
543 } else if (name == "latexlog") {
544 disable = !IsFileReadable(buf->getLogName().second);
553 // the functions which insert insets
554 InsetOld::Code code = InsetOld::NO_CODE;
556 case LFUN_DIALOG_SHOW_NEW_INSET:
557 if (ev.argument == "bibitem")
558 code = InsetOld::BIBITEM_CODE;
559 else if (ev.argument == "bibtex")
560 code = InsetOld::BIBTEX_CODE;
561 else if (ev.argument == "box")
562 code = InsetOld::BOX_CODE;
563 else if (ev.argument == "branch")
564 code = InsetOld::BRANCH_CODE;
565 else if (ev.argument == "citation")
566 code = InsetOld::CITE_CODE;
567 else if (ev.argument == "ert")
568 code = InsetOld::ERT_CODE;
569 else if (ev.argument == "external")
570 code = InsetOld::EXTERNAL_CODE;
571 else if (ev.argument == "float")
572 code = InsetOld::FLOAT_CODE;
573 else if (ev.argument == "graphics")
574 code = InsetOld::GRAPHICS_CODE;
575 else if (ev.argument == "include")
576 code = InsetOld::INCLUDE_CODE;
577 else if (ev.argument == "index")
578 code = InsetOld::INDEX_CODE;
579 else if (ev.argument == "label")
580 code = InsetOld::LABEL_CODE;
581 else if (ev.argument == "note")
582 code = InsetOld::NOTE_CODE;
583 else if (ev.argument == "ref")
584 code = InsetOld::REF_CODE;
585 else if (ev.argument == "toc")
586 code = InsetOld::TOC_CODE;
587 else if (ev.argument == "url")
588 code = InsetOld::URL_CODE;
589 else if (ev.argument == "vspace")
590 code = InsetOld::VSPACE_CODE;
591 else if (ev.argument == "wrap")
592 code = InsetOld::WRAP_CODE;
596 code = InsetOld::ERT_CODE;
598 case LFUN_INSET_FOOTNOTE:
599 code = InsetOld::FOOT_CODE;
601 case LFUN_TABULAR_INSERT:
602 code = InsetOld::TABULAR_CODE;
604 case LFUN_INSET_MARGINAL:
605 code = InsetOld::MARGIN_CODE;
607 case LFUN_INSET_FLOAT:
608 case LFUN_INSET_WIDE_FLOAT:
609 code = InsetOld::FLOAT_CODE;
611 case LFUN_INSET_WRAP:
612 code = InsetOld::WRAP_CODE;
614 case LFUN_FLOAT_LIST:
615 code = InsetOld::FLOAT_LIST_CODE;
618 case LFUN_INSET_LIST:
619 code = InsetOld::LIST_CODE;
621 case LFUN_INSET_THEOREM:
622 code = InsetOld::THEOREM_CODE;
625 case LFUN_INSET_CAPTION:
626 code = InsetOld::CAPTION_CODE;
628 case LFUN_INSERT_NOTE:
629 code = InsetOld::NOTE_CODE;
631 case LFUN_INSERT_CHARSTYLE:
632 code = InsetOld::CHARSTYLE_CODE;
633 if (buf->params().getLyXTextClass().charstyles().empty())
636 case LFUN_INSERT_BOX:
637 code = InsetOld::BOX_CODE;
639 case LFUN_INSERT_BRANCH:
640 code = InsetOld::BRANCH_CODE;
641 if (buf->params().branchlist().empty())
644 case LFUN_INSERT_LABEL:
645 code = InsetOld::LABEL_CODE;
647 case LFUN_INSET_OPTARG:
648 code = InsetOld::OPTARG_CODE;
650 case LFUN_ENVIRONMENT_INSERT:
651 code = InsetOld::BOX_CODE;
653 case LFUN_INDEX_INSERT:
654 code = InsetOld::INDEX_CODE;
656 case LFUN_INDEX_PRINT:
657 code = InsetOld::INDEX_PRINT_CODE;
659 case LFUN_TOC_INSERT:
660 code = InsetOld::TOC_CODE;
664 code = InsetOld::URL_CODE;
667 // always allow this, since we will inset a raw quote
668 // if an inset is not allowed.
670 case LFUN_HYPHENATION:
671 case LFUN_LIGATURE_BREAK:
673 case LFUN_MENU_SEPARATOR:
675 case LFUN_END_OF_SENTENCE:
676 code = InsetOld::SPECIALCHAR_CODE;
678 case LFUN_SPACE_INSERT:
679 // slight hack: we know this is allowed in math mode
680 if (!view()->cursor().inMathed())
681 code = InsetOld::SPACE_CODE;
683 case LFUN_INSET_DIALOG_SHOW: {
684 InsetBase * inset = view()->cursor().nextInset();
687 code = inset->lyxCode();
688 if (!(code == InsetOld::INCLUDE_CODE
689 || code == InsetOld::BIBTEX_CODE
690 || code == InsetOld::FLOAT_LIST_CODE
691 || code == InsetOld::TOC_CODE))
699 if (code != InsetOld::NO_CODE && tli && !tli->insetAllowed(code))
705 // A few general toggles
707 case LFUN_TOOLTIPS_TOGGLE:
708 flag.setOnOff(owner->getDialogs().tooltipsEnabled());
711 case LFUN_READ_ONLY_TOGGLE:
712 flag.setOnOff(buf->isReadonly());
715 flag.setOnOff(view()->getLyXText()->cursorPar()->params().startOfAppendix());
717 case LFUN_SWITCHBUFFER:
718 // toggle on the current buffer, but do not toggle off
719 // the other ones (is that a good idea?)
720 if (ev.argument == buf->fileName())
723 case LFUN_TRACK_CHANGES:
724 flag.setOnOff(buf->params().tracking_changes);
731 // the font related toggles
732 if (!view()->cursor().inMathed()) {
733 LyXFont const & font = view()->getLyXText()->real_current_font;
736 flag.setOnOff(font.emph() == LyXFont::ON);
739 flag.setOnOff(font.noun() == LyXFont::ON);
742 flag.setOnOff(font.series() == LyXFont::BOLD_SERIES);
745 flag.setOnOff(font.family() == LyXFont::SANS_FAMILY);
748 flag.setOnOff(font.family() == LyXFont::ROMAN_FAMILY);
751 flag.setOnOff(font.family() == LyXFont::TYPEWRITER_FAMILY);
757 string tc = mathcursor::getLastCode();
760 flag.setOnOff(tc == "mathbf");
763 flag.setOnOff(tc == "mathsf");
766 flag.setOnOff(tc == "mathcal");
769 flag.setOnOff(tc == "mathrm");
772 flag.setOnOff(tc == "mathtt");
775 flag.setOnOff(tc == "mathbb");
778 flag.setOnOff(tc == "mathnormal");
786 // this one is difficult to get right. As a half-baked
787 // solution, we consider only the first action of the sequence
788 if (ev.action == LFUN_SEQUENCE) {
789 // argument contains ';'-terminated commands
790 #warning LyXAction arguments not handled here.
791 flag = getStatus(FuncRequest(lyxaction.lookupFunc(token(ev.argument, ';', 0))));
800 bool ensureBufferClean(BufferView * bv)
802 Buffer & buf = *bv->buffer();
806 string const file = MakeDisplayPath(buf.fileName(), 30);
807 string text = bformat(_("The document %1$s has unsaved "
808 "changes.\n\nDo you want to save "
809 "the document?"), file);
810 int const ret = Alert::prompt(_("Save changed document?"),
811 text, 0, 1, _("&Save"),
815 bv->owner()->dispatch(FuncRequest(LFUN_MENUWRITE));
817 return buf.isClean();
823 void LyXFunc::dispatch(FuncRequest const & cmd, bool verbose)
825 string argument = cmd.argument;
826 kb_action action = cmd.action;
828 lyxerr[Debug::ACTION] << "LyXFunc::dispatch: cmd: " << cmd << endl;
829 //lyxerr << "*** LyXFunc::dispatch: cmd: " << cmd << endl;
831 // we have not done anything wrong yet.
833 dispatch_buffer.erase();
834 selection_possible = false;
836 // We cannot use this function here
837 if (getStatus(cmd).disabled()) {
838 lyxerr[Debug::ACTION] << "LyXFunc::dispatch: "
839 << lyxaction.getActionName(action)
840 << " [" << action << "] is disabled at this location"
842 setErrorMessage(getStatusMessage());
846 if (view()->available())
847 view()->hideCursor();
852 if (!view()->available())
854 view()->cursor().pop();
855 // Tell the paragraph dialog that we changed paragraph
856 dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
860 case LFUN_WORDFINDFORWARD:
861 case LFUN_WORDFINDBACKWARD: {
862 static string last_search;
863 string searched_string;
865 if (!argument.empty()) {
866 last_search = argument;
867 searched_string = argument;
869 searched_string = last_search;
871 if (searched_string.empty())
874 bool const fw = action == LFUN_WORDFINDFORWARD;
876 lyx::find::find2string(searched_string, true, false, fw);
877 view()->dispatch(FuncRequest(LFUN_WORD_FIND, data));
882 owner->message(keyseq.printOptions());
885 case LFUN_EXEC_COMMAND:
886 owner->focus_command_buffer();
891 meta_fake_bit = key_modifier::none;
892 if (view()->available())
893 // cancel any selection
894 dispatch(FuncRequest(LFUN_MARK_OFF));
895 setMessage(N_("Cancel"));
899 meta_fake_bit = key_modifier::alt;
900 setMessage(keyseq.print());
903 case LFUN_READ_ONLY_TOGGLE:
904 if (owner->buffer()->lyxvc().inUse())
905 owner->buffer()->lyxvc().toggleReadOnly();
907 owner->buffer()->setReadonly(
908 !owner->buffer()->isReadonly());
911 case LFUN_CENTER: // this is center and redraw.
915 // --- Menus -----------------------------------------------
917 menuNew(argument, false);
920 case LFUN_MENUNEWTMPLT:
921 menuNew(argument, true);
924 case LFUN_CLOSEBUFFER:
929 if (!owner->buffer()->isUnnamed()) {
930 string const str = bformat(_("Saving document %1$s..."),
931 MakeDisplayPath(owner->buffer()->fileName()));
933 MenuWrite(owner->buffer());
934 owner->message(str + _(" done."));
936 WriteAs(owner->buffer());
940 WriteAs(owner->buffer(), argument);
943 case LFUN_MENURELOAD: {
944 string const file = MakeDisplayPath(view()->buffer()->fileName(), 20);
945 string text = bformat(_("Any changes will be lost. Are you sure "
946 "you want to revert to the saved version of the document %1$s?"), file);
947 int const ret = Alert::prompt(_("Revert to saved document?"),
948 text, 0, 1, _("&Revert"), _("&Cancel"));
956 Exporter::Export(owner->buffer(), argument, true);
957 view()->showErrorList(BufferFormat(*owner->buffer()));
961 Exporter::Preview(owner->buffer(), argument);
962 view()->showErrorList(BufferFormat(*owner->buffer()));
966 Exporter::Export(owner->buffer(), "program", true);
967 view()->showErrorList(_("Build"));
971 owner->buffer()->runChktex();
972 view()->showErrorList(_("ChkTeX"));
976 if (argument == "custom")
977 owner->getDialogs().showSendto();
979 Exporter::Export(owner->buffer(), argument, false);
980 view()->showErrorList(BufferFormat(*owner->buffer()));
993 InsetCommandParams p("tableofcontents");
994 string const data = InsetCommandMailer::params2string("toc", p);
995 owner->getDialogs().show("toc", data, 0);
1011 case LFUN_RECONFIGURE:
1012 Reconfigure(view());
1015 case LFUN_HELP_OPEN: {
1016 string const arg = argument;
1018 setErrorMessage(N_("Missing argument"));
1021 string const fname = i18nLibFileSearch("doc", arg, "lyx");
1022 if (fname.empty()) {
1023 lyxerr << "LyX: unable to find documentation file `"
1024 << arg << "'. Bad installation?" << endl;
1027 owner->message(bformat(_("Opening help file %1$s..."),
1028 MakeDisplayPath(fname)));
1029 view()->loadLyXFile(fname, false);
1033 // --- version control -------------------------------
1034 case LFUN_VC_REGISTER:
1035 if (!ensureBufferClean(view()))
1037 if (!owner->buffer()->lyxvc().inUse()) {
1038 owner->buffer()->lyxvc().registrer();
1043 case LFUN_VC_CHECKIN:
1044 if (!ensureBufferClean(view()))
1046 if (owner->buffer()->lyxvc().inUse()
1047 && !owner->buffer()->isReadonly()) {
1048 owner->buffer()->lyxvc().checkIn();
1053 case LFUN_VC_CHECKOUT:
1054 if (!ensureBufferClean(view()))
1056 if (owner->buffer()->lyxvc().inUse()
1057 && owner->buffer()->isReadonly()) {
1058 owner->buffer()->lyxvc().checkOut();
1063 case LFUN_VC_REVERT:
1064 owner->buffer()->lyxvc().revert();
1069 owner->buffer()->lyxvc().undoLast();
1073 // --- buffers ----------------------------------------
1074 case LFUN_SWITCHBUFFER:
1075 view()->buffer(bufferlist.getBuffer(argument));
1079 NewFile(view(), argument);
1082 case LFUN_FILE_OPEN:
1086 case LFUN_LAYOUT_TABULAR:
1087 if (InsetTabular * tab = view()->cursor().innerInsetTabular())
1088 tab->openLayoutDialog(view());
1091 case LFUN_DROP_LAYOUTS_CHOICE:
1092 owner->getToolbar().openLayoutList();
1095 case LFUN_MENU_OPEN_BY_NAME:
1096 owner->getMenubar().openByName(argument);
1099 // --- lyxserver commands ----------------------------
1101 setMessage(owner->buffer()->fileName());
1102 lyxerr[Debug::INFO] << "FNAME["
1103 << owner->buffer()->fileName()
1108 dispatch_buffer = keyseq.print();
1109 lyxserver->notifyClient(dispatch_buffer);
1112 case LFUN_GOTOFILEROW: {
1115 istringstream is(argument);
1116 is >> file_name >> row;
1117 if (prefixIs(file_name, getTmpDir())) {
1118 // Needed by inverse dvi search. If it is a file
1119 // in tmpdir, call the apropriated function
1120 view()->buffer(bufferlist.getBufferFromTmp(file_name));
1122 // Must replace extension of the file to be .lyx
1123 // and get full path
1124 string const s = ChangeExtension(file_name, ".lyx");
1125 // Either change buffer or load the file
1126 if (bufferlist.exists(s)) {
1127 view()->buffer(bufferlist.getBuffer(s));
1129 view()->loadLyXFile(s);
1133 view()->setCursorFromRow(row);
1136 // see BufferView_pimpl::center()
1137 view()->updateScrollbar();
1141 case LFUN_GOTO_PARAGRAPH: {
1142 istringstream is(argument);
1145 ParIterator par = owner->buffer()->getParFromID(id);
1146 if (par == owner->buffer()->par_iterator_end()) {
1147 lyxerr[Debug::INFO] << "No matching paragraph found! ["
1148 << id << ']' << endl;
1151 lyxerr[Debug::INFO] << "Paragraph " << par->id()
1152 << " found." << endl;
1156 view()->setCursor(par, 0);
1158 view()->switchKeyMap();
1159 owner->view_state_changed();
1162 // see BufferView_pimpl::center()
1163 view()->updateScrollbar();
1167 // --- Mathed stuff. If we are here, there is no locked inset yet.
1168 case LFUN_MATH_EXTERN:
1169 case LFUN_MATH_NUMBER:
1170 case LFUN_MATH_NONUMBER:
1171 case LFUN_MATH_LIMITS:
1172 setErrorMessage(N_("This is only allowed in math mode!"));
1175 case LFUN_DIALOG_SHOW: {
1176 string const name = cmd.getArg(0);
1177 string data = trim(cmd.argument.substr(name.size()));
1179 if (name == "character") {
1180 data = freefont2string();
1182 owner->getDialogs().show("character", data);
1184 else if (name == "document")
1185 owner->getDialogs().showDocument();
1186 else if (name == "forks")
1187 owner->getDialogs().showForks();
1188 else if (name == "preamble")
1189 owner->getDialogs().showPreamble();
1190 else if (name == "preferences")
1191 owner->getDialogs().showPreferences();
1192 else if (name == "print")
1193 owner->getDialogs().showPrint();
1194 else if (name == "spellchecker")
1195 owner->getDialogs().showSpellchecker();
1197 else if (name == "latexlog") {
1198 pair<Buffer::LogType, string> const logfile =
1199 owner->buffer()->getLogName();
1200 switch (logfile.first) {
1201 case Buffer::latexlog:
1204 case Buffer::buildlog:
1208 data += logfile.second;
1209 owner->getDialogs().show("log", data);
1211 else if (name == "vclog") {
1212 string const data = "vc " +
1213 owner->buffer()->lyxvc().getLogFile();
1214 owner->getDialogs().show("log", data);
1217 owner->getDialogs().show(name, data);
1221 case LFUN_DIALOG_SHOW_NEW_INSET: {
1222 string const name = cmd.getArg(0);
1223 string data = trim(cmd.argument.substr(name.size()));
1224 if (name == "bibitem" ||
1226 name == "include" ||
1232 InsetCommandParams p(name);
1233 data = InsetCommandMailer::params2string(name, p);
1234 } else if (name == "box") {
1235 // \c data == "Boxed" || "Frameless" etc
1236 InsetBoxParams p(data);
1237 data = InsetBoxMailer::params2string(p);
1238 } else if (name == "branch") {
1239 InsetBranchParams p;
1240 data = InsetBranchMailer::params2string(p);
1241 } else if (name == "citation") {
1242 InsetCommandParams p("cite");
1243 data = InsetCommandMailer::params2string(name, p);
1244 } else if (name == "ert") {
1245 data = InsetERTMailer::params2string(InsetCollapsable::Open);
1246 } else if (name == "external") {
1247 InsetExternalParams p;
1248 Buffer const & buffer = *owner->buffer();
1249 data = InsetExternalMailer::params2string(p, buffer);
1250 } else if (name == "float") {
1252 data = InsetFloatMailer::params2string(p);
1253 } else if (name == "graphics") {
1254 InsetGraphicsParams p;
1255 Buffer const & buffer = *owner->buffer();
1256 data = InsetGraphicsMailer::params2string(p, buffer);
1257 } else if (name == "note") {
1259 data = InsetNoteMailer::params2string(p);
1260 } else if (name == "vspace") {
1262 data = InsetVSpaceMailer::params2string(space);
1263 } else if (name == "wrap") {
1265 data = InsetWrapMailer::params2string(p);
1267 owner->getDialogs().show(name, data, 0);
1271 case LFUN_DIALOG_SHOW_NEXT_INSET:
1274 case LFUN_INSET_DIALOG_SHOW: {
1275 InsetBase * inset = view()->cursor().nextInset();
1277 inset->dispatch(view()->cursor(), FuncRequest(LFUN_INSET_DIALOG_SHOW));
1281 case LFUN_DIALOG_UPDATE: {
1282 string const & name = argument;
1283 // Can only update a dialog connected to an existing inset
1284 InsetBase * inset = owner->getDialogs().getOpenInset(name);
1286 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument);
1287 inset->dispatch(view()->cursor(), fr);
1288 } else if (name == "paragraph") {
1289 dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
1294 case LFUN_DIALOG_HIDE:
1295 Dialogs::hide(argument, 0);
1298 case LFUN_DIALOG_DISCONNECT_INSET:
1299 owner->getDialogs().disconnect(argument);
1302 case LFUN_CHILDOPEN: {
1303 string const filename =
1304 MakeAbsPath(argument, owner->buffer()->filePath());
1305 setMessage(N_("Opening child document ") +
1306 MakeDisplayPath(filename) + "...");
1307 view()->savePosition(0);
1308 string const parentfilename = owner->buffer()->fileName();
1309 if (bufferlist.exists(filename))
1310 view()->buffer(bufferlist.getBuffer(filename));
1312 view()->loadLyXFile(filename);
1313 // Set the parent name of the child document.
1314 // This makes insertion of citations and references in the child work,
1315 // when the target is in the parent or another child document.
1316 owner->buffer()->setParentName(parentfilename);
1320 case LFUN_TOGGLECURSORFOLLOW:
1321 lyxrc.cursor_follows_scrollbar = !lyxrc.cursor_follows_scrollbar;
1325 owner->getIntl().KeyMapOn(false);
1328 case LFUN_KMAP_PRIM:
1329 owner->getIntl().KeyMapPrim();
1333 owner->getIntl().KeyMapSec();
1336 case LFUN_KMAP_TOGGLE:
1337 owner->getIntl().ToggleKeyMap();
1343 argument = split(argument, countstr, ' ');
1344 istringstream is(countstr);
1347 lyxerr << "repeat: count: " << count << " cmd: " << argument << endl;
1348 for (int i = 0; i < count; ++i)
1349 dispatch(lyxaction.lookupFunc(argument));
1354 // argument contains ';'-terminated commands
1355 while (!argument.empty()) {
1357 argument = split(argument, first, ';');
1358 dispatch(lyxaction.lookupFunc(first));
1362 case LFUN_SAVEPREFERENCES: {
1363 Path p(user_lyxdir());
1364 lyxrc.write("preferences");
1368 case LFUN_SCREEN_FONT_UPDATE:
1369 // handle the screen font changes.
1370 lyxrc.set_font_norm_type();
1371 lyx_gui::update_fonts();
1372 // All visible buffers will need resize
1376 case LFUN_SET_COLOR: {
1378 string const x11_name = split(argument, lyx_name, ' ');
1379 if (lyx_name.empty() || x11_name.empty()) {
1380 setErrorMessage(N_("Syntax: set-color <lyx_name>"
1385 bool const graphicsbg_changed =
1386 (lyx_name == lcolor.getLyXName(LColor::graphicsbg) &&
1387 x11_name != lcolor.getX11Name(LColor::graphicsbg));
1389 LColor::color col = lcolor.getFromLyXName(lyx_name);
1390 if (!lcolor.setColor(col, x11_name)) {
1392 bformat(_("Set-color \"%1$s\" failed "
1393 "- color is undefined or "
1394 "may not be redefined"), lyx_name));
1398 lyx_gui::update_color(lcolor.getFromLyXName(lyx_name));
1400 if (graphicsbg_changed) {
1401 #ifdef WITH_WARNINGS
1402 #warning FIXME!! The graphics cache no longer has a changeDisplay method.
1405 lyx::graphics::GCache::get().changeDisplay(true);
1412 owner->message(argument);
1415 case LFUN_FORKS_KILL:
1416 if (isStrInt(argument))
1417 ForkedcallsController::get().kill(strToInt(argument));
1420 case LFUN_TOOLTIPS_TOGGLE:
1421 owner->getDialogs().toggleTooltips();
1424 case LFUN_EXTERNAL_EDIT:
1425 InsetExternal().dispatch(view()->cursor(), FuncRequest(action, argument));
1429 DispatchResult res = view()->cursor().dispatch(cmd);
1430 if (!res.dispatched());
1431 view()->dispatch(cmd);
1436 if (!view()->cursor().inMathed())
1437 view()->owner()->updateLayoutChoice();
1439 if (view()->available()) {
1440 view()->fitCursor();
1442 view()->cursor().updatePos();
1443 // if we executed a mutating lfun, mark the buffer as dirty
1444 if (!getStatus(cmd).disabled()
1445 && !lyxaction.funcHasFlag(cmd.action, LyXAction::NoBuffer)
1446 && !lyxaction.funcHasFlag(cmd.action, LyXAction::ReadOnly))
1447 view()->buffer()->markDirty();
1450 if (!view()->cursor().inMathed())
1451 sendDispatchMessage(getMessage(), cmd, verbose);
1455 void LyXFunc::sendDispatchMessage(string const & msg,
1456 FuncRequest const & cmd, bool verbose)
1458 owner->updateMenubar();
1459 owner->updateToolbar();
1461 if (cmd.action == LFUN_SELFINSERT || !verbose) {
1462 lyxerr[Debug::ACTION] << "dispatch msg is " << msg << endl;
1464 owner->message(msg);
1468 string dispatch_msg = msg;
1469 if (!dispatch_msg.empty())
1470 dispatch_msg += ' ';
1472 string comname = lyxaction.getActionName(cmd.action);
1474 bool argsadded = false;
1476 if (!cmd.argument.empty()) {
1477 if (cmd.action != LFUN_UNKNOWN_ACTION) {
1478 comname += ' ' + cmd.argument;
1483 string const shortcuts = toplevel_keymap->findbinding(cmd);
1485 if (!shortcuts.empty()) {
1486 comname += ": " + shortcuts;
1487 } else if (!argsadded && !cmd.argument.empty()) {
1488 comname += ' ' + cmd.argument;
1491 if (!comname.empty()) {
1492 comname = rtrim(comname);
1493 dispatch_msg += '(' + comname + ')';
1496 lyxerr[Debug::ACTION] << "verbose dispatch msg " << dispatch_msg << endl;
1497 if (!dispatch_msg.empty())
1498 owner->message(dispatch_msg);
1502 void LyXFunc::setupLocalKeymap()
1504 keyseq.stdmap = toplevel_keymap.get();
1505 keyseq.curmap = toplevel_keymap.get();
1506 cancel_meta_seq.stdmap = toplevel_keymap.get();
1507 cancel_meta_seq.curmap = toplevel_keymap.get();
1511 void LyXFunc::menuNew(string const & name, bool fromTemplate)
1513 string initpath = lyxrc.document_path;
1514 string filename(name);
1516 if (view()->available()) {
1517 string const trypath = owner->buffer()->filePath();
1518 // If directory is writeable, use this as default.
1519 if (IsDirWriteable(trypath))
1523 static int newfile_number;
1525 if (filename.empty()) {
1526 filename = AddName(lyxrc.document_path,
1527 "newfile" + tostr(++newfile_number) + ".lyx");
1528 FileInfo fi(filename);
1529 while (bufferlist.exists(filename) || fi.readable()) {
1531 filename = AddName(lyxrc.document_path,
1532 "newfile" + tostr(newfile_number) +
1534 fi.newFile(filename);
1538 // The template stuff
1541 FileDialog fileDlg(_("Select template file"),
1542 LFUN_SELECT_FILE_SYNC,
1543 make_pair(string(_("Documents|#o#O")),
1544 string(lyxrc.document_path)),
1545 make_pair(string(_("Templates|#T#t")),
1546 string(lyxrc.template_path)));
1548 FileDialog::Result result =
1549 fileDlg.open(lyxrc.template_path,
1550 FileFilterList(_("LyX Documents (*.lyx)")),
1553 if (result.first == FileDialog::Later)
1555 if (result.second.empty())
1557 templname = result.second;
1560 view()->newFile(filename, templname, !name.empty());
1564 void LyXFunc::open(string const & fname)
1566 string initpath = lyxrc.document_path;
1568 if (view()->available()) {
1569 string const trypath = owner->buffer()->filePath();
1570 // If directory is writeable, use this as default.
1571 if (IsDirWriteable(trypath))
1577 if (fname.empty()) {
1578 FileDialog fileDlg(_("Select document to open"),
1580 make_pair(string(_("Documents|#o#O")),
1581 string(lyxrc.document_path)),
1582 make_pair(string(_("Examples|#E#e")),
1583 string(AddPath(system_lyxdir(), "examples"))));
1585 FileDialog::Result result =
1586 fileDlg.open(initpath,
1587 FileFilterList(_("LyX Documents (*.lyx)")),
1590 if (result.first == FileDialog::Later)
1593 filename = result.second;
1595 // check selected filename
1596 if (filename.empty()) {
1597 owner->message(_("Canceled."));
1603 // get absolute path of file and add ".lyx" to the filename if
1605 string const fullpath = FileSearch(string(), filename, "lyx");
1606 if (!fullpath.empty()) {
1607 filename = fullpath;
1610 string const disp_fn(MakeDisplayPath(filename));
1612 // if the file doesn't exist, let the user create one
1613 FileInfo const f(filename, true);
1615 // the user specifically chose this name. Believe them.
1616 view()->newFile(filename, "", true);
1620 owner->message(bformat(_("Opening document %1$s..."), disp_fn));
1623 if (view()->loadLyXFile(filename)) {
1624 str2 = bformat(_("Document %1$s opened."), disp_fn);
1626 str2 = bformat(_("Could not open document %1$s"), disp_fn);
1628 owner->message(str2);
1632 void LyXFunc::doImport(string const & argument)
1635 string filename = split(argument, format, ' ');
1637 lyxerr[Debug::INFO] << "LyXFunc::doImport: " << format
1638 << " file: " << filename << endl;
1640 // need user interaction
1641 if (filename.empty()) {
1642 string initpath = lyxrc.document_path;
1644 if (view()->available()) {
1645 string const trypath = owner->buffer()->filePath();
1646 // If directory is writeable, use this as default.
1647 if (IsDirWriteable(trypath))
1651 string const text = bformat(_("Select %1$s file to import"),
1652 formats.prettyName(format));
1654 FileDialog fileDlg(text,
1656 make_pair(string(_("Documents|#o#O")),
1657 string(lyxrc.document_path)),
1658 make_pair(string(_("Examples|#E#e")),
1659 string(AddPath(system_lyxdir(), "examples"))));
1661 string const filter = formats.prettyName(format)
1662 + " (*." + formats.extension(format) + ')';
1664 FileDialog::Result result =
1665 fileDlg.open(initpath,
1666 FileFilterList(filter),
1669 if (result.first == FileDialog::Later)
1672 filename = result.second;
1674 // check selected filename
1675 if (filename.empty())
1676 owner->message(_("Canceled."));
1679 if (filename.empty())
1682 // get absolute path of file
1683 filename = MakeAbsPath(filename);
1685 string const lyxfile = ChangeExtension(filename, ".lyx");
1687 // Check if the document already is open
1688 if (lyx_gui::use_gui && bufferlist.exists(lyxfile)) {
1689 if (!bufferlist.close(bufferlist.getBuffer(lyxfile), true)) {
1690 owner->message(_("Canceled."));
1695 // if the file exists already, and we didn't do
1696 // -i lyx thefile.lyx, warn
1697 if (FileInfo(lyxfile, true).exist() && filename != lyxfile) {
1698 string const file = MakeDisplayPath(lyxfile, 30);
1700 string text = bformat(_("The document %1$s already exists.\n\n"
1701 "Do you want to over-write that document?"), file);
1702 int const ret = Alert::prompt(_("Over-write document?"),
1703 text, 0, 1, _("&Over-write"), _("&Cancel"));
1706 owner->message(_("Canceled."));
1711 Importer::Import(owner, filename, format);
1715 void LyXFunc::closeBuffer()
1717 if (bufferlist.close(owner->buffer(), true) && !quitting) {
1718 if (bufferlist.empty()) {
1719 // need this otherwise SEGV may occur while
1720 // trying to set variables that don't exist
1721 // since there's no current buffer
1722 owner->getDialogs().hideBufferDependent();
1724 view()->buffer(bufferlist.first());
1730 // Each "owner" should have it's own message method. lyxview and
1731 // the minibuffer would use the minibuffer, but lyxserver would
1732 // send an ERROR signal to its client. Alejandro 970603
1733 // This function is bit problematic when it comes to NLS, to make the
1734 // lyx servers client be language indepenent we must not translate
1735 // strings sent to this func.
1736 void LyXFunc::setErrorMessage(string const & m) const
1738 dispatch_buffer = m;
1743 void LyXFunc::setMessage(string const & m) const
1745 dispatch_buffer = m;
1749 void LyXFunc::setStatusMessage(string const & m) const
1755 string const LyXFunc::view_status_message()
1757 // When meta-fake key is pressed, show the key sequence so far + "M-".
1759 return keyseq.print() + "M-";
1762 // Else, when a non-complete key sequence is pressed,
1763 // show the available options.
1764 if (keyseq.length() > 0 && !keyseq.deleted()) {
1765 return keyseq.printOptions();
1768 if (!view()->available())
1769 return _("Welcome to LyX!");
1771 return currentState(view());
1775 BufferView * LyXFunc::view() const
1777 BOOST_ASSERT(owner);
1778 return owner->view().get();
1782 bool LyXFunc::wasMetaKey() const
1784 return (meta_fake_bit != key_modifier::none);