3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Asger Alstrup
7 * \author Lars Gullik Bjønnes
8 * \author Alfredo Braunstein
9 * \author Angus Leeming
11 * \author André Pönitz
13 * Full author contact details are available in file CREDITS.
21 #include "BranchList.h"
22 #include "FloatList.h"
23 #include "FuncStatus.h"
25 #include "buffer_funcs.h"
26 #include "BufferParams.h"
27 #include "BufferView.h"
30 #include "CutAndPaste.h"
31 #include "DispatchResult.h"
32 #include "ErrorList.h"
34 #include "FuncRequest.h"
35 #include "InsetList.h"
39 #include "LyXAction.h"
43 #include "Paragraph.h"
44 #include "ParagraphParameters.h"
45 #include "SpellChecker.h"
46 #include "TextClass.h"
47 #include "TextMetrics.h"
48 #include "WordLangTuple.h"
50 #include "frontends/Application.h"
51 #include "frontends/Clipboard.h"
52 #include "frontends/Selection.h"
54 #include "insets/InsetCollapsable.h"
55 #include "insets/InsetCommand.h"
56 #include "insets/InsetExternal.h"
57 #include "insets/InsetFloat.h"
58 #include "insets/InsetFloatList.h"
59 #include "insets/InsetGraphics.h"
60 #include "insets/InsetGraphicsParams.h"
61 #include "insets/InsetIPAMacro.h"
62 #include "insets/InsetNewline.h"
63 #include "insets/InsetQuotes.h"
64 #include "insets/InsetSpecialChar.h"
65 #include "insets/InsetText.h"
66 #include "insets/InsetWrap.h"
68 #include "support/convert.h"
69 #include "support/debug.h"
70 #include "support/gettext.h"
71 #include "support/lassert.h"
72 #include "support/lstrings.h"
73 #include "support/lyxtime.h"
74 #include "support/os.h"
76 #include "mathed/InsetMathHull.h"
77 #include "mathed/MathMacroTemplate.h"
79 #include <boost/next_prior.hpp>
85 using namespace lyx::support;
89 using cap::copySelection;
90 using cap::cutSelection;
91 using cap::pasteFromStack;
92 using cap::pasteClipboardText;
93 using cap::pasteClipboardGraphics;
94 using cap::replaceSelection;
95 using cap::grabAndEraseSelection;
96 using cap::selClearOrDel;
97 using cap::pasteSimpleText;
100 static Font freefont(ignore_font, ignore_language);
101 static bool toggleall = false;
103 static void toggleAndShow(Cursor & cur, Text * text,
104 Font const & font, bool toggleall = true)
106 text->toggleFree(cur, font, toggleall);
108 if (font.language() != ignore_language ||
109 font.fontInfo().number() != FONT_IGNORE) {
110 TextMetrics const & tm = cur.bv().textMetrics(text);
111 if (cur.boundary() != tm.isRTLBoundary(cur.pit(), cur.pos(),
112 cur.real_current_font))
113 text->setCursor(cur, cur.pit(), cur.pos(),
114 false, !cur.boundary());
119 static void moveCursor(Cursor & cur, bool selecting)
121 if (selecting || cur.mark())
126 static void finishChange(Cursor & cur, bool selecting)
129 moveCursor(cur, selecting);
133 static void mathDispatch(Cursor & cur, FuncRequest const & cmd, bool display)
136 docstring sel = cur.selectionAsString(false);
138 // It may happen that sel is empty but there is a selection
139 replaceSelection(cur);
141 // Is this a valid formula?
145 #ifdef ENABLE_ASSERTIONS
146 const int old_pos = cur.pos();
148 cur.insert(new InsetMathHull(cur.buffer(), hullSimple));
149 #ifdef ENABLE_ASSERTIONS
150 LASSERT(old_pos == cur.pos(), /**/);
152 cur.nextInset()->edit(cur, true);
153 // don't do that also for LFUN_MATH_MODE
154 // unless you want end up with always changing
155 // to mathrm when opening an inlined inset --
156 // I really hate "LyXfunc overloading"...
158 cur.dispatch(FuncRequest(LFUN_MATH_DISPLAY));
159 // Avoid an unnecessary undo step if cmd.argument
161 if (!cmd.argument().empty())
162 cur.dispatch(FuncRequest(LFUN_MATH_INSERT,
165 // create a macro if we see "\\newcommand"
166 // somewhere, and an ordinary formula
168 if (sel.find(from_ascii("\\newcommand")) == string::npos
169 && sel.find(from_ascii("\\newlyxcommand")) == string::npos
170 && sel.find(from_ascii("\\def")) == string::npos)
172 InsetMathHull * formula = new InsetMathHull(cur.buffer());
173 string const selstr = to_utf8(sel);
174 istringstream is(selstr);
177 if (!formula->readQuiet(lex)) {
178 // No valid formula, let's try with delims
179 is.str("$" + selstr + "$");
181 if (!formula->readQuiet(lex)) {
182 // Still not valid, leave it as is
191 cur.insert(new MathMacroTemplate(cur.buffer(), sel));
195 cur.message(from_utf8(N_("Math editor mode")));
197 cur.message(from_utf8(N_("No valid math formula")));
201 void regexpDispatch(Cursor & cur, FuncRequest const & cmd)
203 LASSERT(cmd.action() == LFUN_REGEXP_MODE, return);
204 if (cur.inRegexped()) {
205 cur.message(_("Already in regular expression mode"));
209 docstring sel = cur.selectionAsString(false);
211 // It may happen that sel is empty but there is a selection
212 replaceSelection(cur);
214 cur.insert(new InsetMathHull(cur.buffer(), hullRegexp));
215 cur.nextInset()->edit(cur, true);
218 cur.message(_("Regexp editor mode"));
222 static void specialChar(Cursor & cur, InsetSpecialChar::Kind kind)
225 cap::replaceSelection(cur);
226 cur.insert(new InsetSpecialChar(kind));
231 static void ipaChar(Cursor & cur, InsetIPAChar::Kind kind)
234 cap::replaceSelection(cur);
235 cur.insert(new InsetIPAChar(kind));
240 static bool doInsertInset(Cursor & cur, Text * text,
241 FuncRequest const & cmd, bool edit, bool pastesel)
243 Buffer & buffer = cur.bv().buffer();
244 BufferParams const & bparams = buffer.params();
245 Inset * inset = createInset(&buffer, cmd);
249 if (InsetCollapsable * ci = inset->asInsetCollapsable())
250 ci->setButtonLabel();
253 if (cmd.action() == LFUN_INDEX_INSERT) {
254 docstring ds = subst(text->getStringToIndex(cur), '\n', ' ');
255 text->insertInset(cur, inset);
257 inset->edit(cur, true);
258 // Now put this into inset
259 Font const f(inherit_font, cur.current_font.language());
261 cur.text()->insertStringAsLines(cur, ds, f);
262 cur.leaveInset(*inset);
268 if (cur.selection()) {
269 cutSelection(cur, false, pastesel);
270 cur.clearSelection();
273 text->insertInset(cur, inset);
276 inset->edit(cur, true);
278 if (!gotsel || !pastesel)
281 pasteFromStack(cur, cur.buffer()->errorList("Paste"), 0);
282 cur.buffer()->errors("Paste");
283 cur.clearSelection(); // bug 393
285 InsetText * inset_text = inset->asInsetText();
287 inset_text->fixParagraphsFont();
288 if (!inset_text->allowMultiPar() || cur.lastpit() == 0) {
289 // reset first par to default
290 cur.text()->paragraphs().begin()
291 ->setPlainOrDefaultLayout(bparams.documentClass());
294 // Merge multiple paragraphs -- hack
295 while (cur.lastpit() > 0)
296 mergeParagraph(bparams, cur.text()->paragraphs(), 0);
297 cur.leaveInset(*inset);
300 cur.leaveInset(*inset);
301 // reset surrounding par to default
302 DocumentClass const & dc = bparams.documentClass();
303 docstring const layoutname = inset->usePlainLayout()
304 ? dc.plainLayoutName()
305 : dc.defaultLayoutName();
306 text->setLayout(cur, layoutname);
312 string const freefont2string()
314 return freefont.toString(toggleall);
318 /// the type of outline operation
320 OutlineUp, // Move this header with text down
321 OutlineDown, // Move this header with text up
322 OutlineIn, // Make this header deeper
323 OutlineOut // Make this header shallower
327 static void outline(OutlineOp mode, Cursor & cur)
329 Buffer & buf = *cur.buffer();
330 pit_type & pit = cur.pit();
331 ParagraphList & pars = buf.text().paragraphs();
332 ParagraphList::iterator const bgn = pars.begin();
333 // The first paragraph of the area to be copied:
334 ParagraphList::iterator start = boost::next(bgn, pit);
335 // The final paragraph of area to be copied:
336 ParagraphList::iterator finish = start;
337 ParagraphList::iterator const end = pars.end();
339 DocumentClass const & tc = buf.params().documentClass();
341 int const thistoclevel = start->layout().toclevel;
344 // Move out (down) from this section header
348 // Seek the one (on same level) below
349 for (; finish != end; ++finish) {
350 toclevel = finish->layout().toclevel;
351 if (toclevel != Layout::NOT_IN_TOC && toclevel <= thistoclevel)
357 if (start == pars.begin())
360 ParagraphList::iterator dest = start;
361 // Move out (up) from this header
364 // Search previous same-level header above
367 toclevel = dest->layout().toclevel;
369 && (toclevel == Layout::NOT_IN_TOC
370 || toclevel > thistoclevel));
371 // Not found; do nothing
372 if (toclevel == Layout::NOT_IN_TOC || toclevel > thistoclevel)
374 pit_type const newpit = distance(bgn, dest);
375 pit_type const len = distance(start, finish);
376 pit_type const deletepit = pit + len;
377 buf.undo().recordUndo(cur, ATOMIC_UNDO, newpit, deletepit - 1);
378 pars.splice(dest, start, finish);
386 // Go one down from *this* header:
387 ParagraphList::iterator dest = boost::next(finish, 1);
388 // Go further down to find header to insert in front of:
389 for (; dest != end; ++dest) {
390 toclevel = dest->layout().toclevel;
391 if (toclevel != Layout::NOT_IN_TOC
392 && toclevel <= thistoclevel)
395 // One such was found:
396 pit_type newpit = distance(bgn, dest);
397 buf.undo().recordUndo(cur, ATOMIC_UNDO, pit, newpit - 1);
398 pit_type const len = distance(start, finish);
399 pars.splice(dest, start, finish);
400 cur.pit() = newpit - len;
404 pit_type const len = distance(start, finish);
405 buf.undo().recordUndo(cur, ATOMIC_UNDO, pit, pit + len - 1);
406 for (; start != finish; ++start) {
407 toclevel = start->layout().toclevel;
408 if (toclevel == Layout::NOT_IN_TOC)
410 DocumentClass::const_iterator lit = tc.begin();
411 DocumentClass::const_iterator len = tc.end();
412 for (; lit != len; ++lit) {
413 if (lit->toclevel == toclevel + 1 &&
414 start->layout().labeltype == lit->labeltype) {
415 start->setLayout(*lit);
423 pit_type const len = distance(start, finish);
424 buf.undo().recordUndo(cur, ATOMIC_UNDO, pit, pit + len - 1);
425 for (; start != finish; ++start) {
426 toclevel = start->layout().toclevel;
427 if (toclevel == Layout::NOT_IN_TOC)
429 DocumentClass::const_iterator lit = tc.begin();
430 DocumentClass::const_iterator len = tc.end();
431 for (; lit != len; ++lit) {
432 if (lit->toclevel == toclevel - 1 &&
433 start->layout().labeltype == lit->labeltype) {
434 start->setLayout(*lit);
445 void Text::number(Cursor & cur)
447 FontInfo font = ignore_font;
448 font.setNumber(FONT_TOGGLE);
449 toggleAndShow(cur, this, Font(font, ignore_language));
453 bool Text::isRTL(Paragraph const & par) const
455 Buffer const & buffer = owner_->buffer();
456 return par.isRTL(buffer.params());
460 void Text::dispatch(Cursor & cur, FuncRequest & cmd)
462 LYXERR(Debug::ACTION, "Text::dispatch: cmd: " << cmd);
464 // Dispatch if the cursor is inside the text. It is not the
465 // case for context menus (bug 5797).
466 if (cur.text() != this) {
471 BufferView * bv = &cur.bv();
472 TextMetrics * tm = &bv->textMetrics(this);
473 if (!tm->contains(cur.pit())) {
474 lyx::dispatch(FuncRequest(LFUN_SCREEN_SHOW_CURSOR));
475 tm = &bv->textMetrics(this);
478 // FIXME: We use the update flag to indicates wether a singlePar or a
479 // full screen update is needed. We reset it here but shall we restore it
481 cur.noScreenUpdate();
483 LASSERT(cur.text() == this, /**/);
484 CursorSlice const oldTopSlice = cur.top();
485 bool const oldBoundary = cur.boundary();
486 bool const oldSelection = cur.selection();
487 // Signals that, even if needsUpdate == false, an update of the
488 // cursor paragraph is required
489 bool singleParUpdate = lyxaction.funcHasFlag(cmd.action(),
490 LyXAction::SingleParUpdate);
491 // Signals that a full-screen update is required
492 bool needsUpdate = !(lyxaction.funcHasFlag(cmd.action(),
493 LyXAction::NoUpdate) || singleParUpdate);
494 bool const last_misspelled = lyxrc.spellcheck_continuously
495 && cur.paragraph().isMisspelled(cur.pos(), true);
497 FuncCode const act = cmd.action();
500 case LFUN_PARAGRAPH_MOVE_DOWN: {
501 pit_type const pit = cur.pit();
502 recUndo(cur, pit, pit + 1);
504 pars_.swap(pit, pit + 1);
506 cur.forceBufferUpdate();
511 case LFUN_PARAGRAPH_MOVE_UP: {
512 pit_type const pit = cur.pit();
513 recUndo(cur, pit - 1, pit);
515 pars_.swap(pit, pit - 1);
518 cur.forceBufferUpdate();
522 case LFUN_APPENDIX: {
523 Paragraph & par = cur.paragraph();
524 bool start = !par.params().startOfAppendix();
526 // FIXME: The code below only makes sense at top level.
527 // Should LFUN_APPENDIX be restricted to top-level paragraphs?
528 // ensure that we have only one start_of_appendix in this document
529 // FIXME: this don't work for multipart document!
530 for (pit_type tmp = 0, end = pars_.size(); tmp != end; ++tmp) {
531 if (pars_[tmp].params().startOfAppendix()) {
533 pars_[tmp].params().startOfAppendix(false);
539 par.params().startOfAppendix(start);
541 // we can set the refreshing parameters now
542 cur.forceBufferUpdate();
546 case LFUN_WORD_DELETE_FORWARD:
548 cutSelection(cur, true, false);
550 deleteWordForward(cur);
551 finishChange(cur, false);
554 case LFUN_WORD_DELETE_BACKWARD:
556 cutSelection(cur, true, false);
558 deleteWordBackward(cur);
559 finishChange(cur, false);
562 case LFUN_LINE_DELETE:
564 cutSelection(cur, true, false);
566 tm->deleteLineForward(cur);
567 finishChange(cur, false);
570 case LFUN_BUFFER_BEGIN:
571 case LFUN_BUFFER_BEGIN_SELECT:
572 needsUpdate |= cur.selHandle(act == LFUN_BUFFER_BEGIN_SELECT);
573 if (cur.depth() == 1)
574 needsUpdate |= cursorTop(cur);
577 cur.screenUpdateFlags(Update::FitCursor);
580 case LFUN_BUFFER_END:
581 case LFUN_BUFFER_END_SELECT:
582 needsUpdate |= cur.selHandle(act == LFUN_BUFFER_END_SELECT);
583 if (cur.depth() == 1)
584 needsUpdate |= cursorBottom(cur);
587 cur.screenUpdateFlags(Update::FitCursor);
590 case LFUN_INSET_BEGIN:
591 case LFUN_INSET_BEGIN_SELECT:
592 needsUpdate |= cur.selHandle(act == LFUN_INSET_BEGIN_SELECT);
593 if (cur.depth() == 1 || !cur.top().at_begin())
594 needsUpdate |= cursorTop(cur);
597 cur.screenUpdateFlags(Update::FitCursor);
601 case LFUN_INSET_END_SELECT:
602 needsUpdate |= cur.selHandle(act == LFUN_INSET_END_SELECT);
603 if (cur.depth() == 1 || !cur.top().at_end())
604 needsUpdate |= cursorBottom(cur);
607 cur.screenUpdateFlags(Update::FitCursor);
610 case LFUN_INSET_SELECT_ALL:
611 if (cur.depth() == 1 || !cur.selection() || !cur.selBegin().at_begin()
612 || !cur.selEnd().at_end()) {
613 needsUpdate |= cur.selHandle(false);
614 needsUpdate |= cursorTop(cur);
615 needsUpdate |= cur.selHandle(true);
616 needsUpdate |= cursorBottom(cur);
619 cur.screenUpdateFlags(Update::FitCursor);
622 case LFUN_CHAR_FORWARD:
623 case LFUN_CHAR_FORWARD_SELECT:
624 //LYXERR0(" LFUN_CHAR_FORWARD[SEL]:\n" << cur);
625 needsUpdate |= cur.selHandle(act == LFUN_CHAR_FORWARD_SELECT);
626 needsUpdate |= cursorForward(cur);
628 if (!needsUpdate && oldTopSlice == cur.top()
629 && cur.boundary() == oldBoundary) {
631 cmd = FuncRequest(LFUN_FINISHED_FORWARD);
633 // we will probably be moving out the inset, so we should execute
634 // the depm-mechanism, but only when the cursor has a place to
635 // go outside this inset, i.e. in a slice above.
636 if (cur.depth() > 1 && cur.pos() == cur.lastpos()
637 && cur.pit() == cur.lastpit()) {
638 // The cursor hasn't changed yet. To give the
639 // DEPM the possibility of doing something we must
640 // provide it with two different cursors.
642 dummy.pos() = dummy.pit() = 0;
643 if (cur.bv().checkDepm(dummy, cur)) {
644 cur.forceBufferUpdate();
645 // DEPM may have requested a screen update
646 cur.screenUpdateFlags(
647 cur.screenUpdate() | dummy.screenUpdate());
653 case LFUN_CHAR_BACKWARD:
654 case LFUN_CHAR_BACKWARD_SELECT:
655 //lyxerr << "handle LFUN_CHAR_BACKWARD[_SELECT]:\n" << cur << endl;
656 needsUpdate |= cur.selHandle(act == LFUN_CHAR_BACKWARD_SELECT);
657 needsUpdate |= cursorBackward(cur);
659 if (!needsUpdate && oldTopSlice == cur.top()
660 && cur.boundary() == oldBoundary) {
662 cmd = FuncRequest(LFUN_FINISHED_BACKWARD);
664 // we will probably be moving out the inset, so we should execute
665 // the depm-mechanism, but only when the cursor has a place to
666 // go outside this inset, i.e. in a slice above.
667 if (cur.depth() > 1 && cur.pos() == 0 && cur.pit() == 0) {
668 // The cursor hasn't changed yet. To give the
669 // DEPM the possibility of doing something we must
670 // provide it with two different cursors.
672 dummy.pos() = cur.lastpos();
673 dummy.pit() = cur.lastpit();
674 if (cur.bv().checkDepm(dummy, cur)) {
675 cur.forceBufferUpdate();
676 // DEPM may have requested a screen update
677 cur.screenUpdateFlags(
678 cur.screenUpdate() | dummy.screenUpdate());
685 case LFUN_CHAR_LEFT_SELECT:
686 if (lyxrc.visual_cursor) {
687 needsUpdate |= cur.selHandle(act == LFUN_CHAR_LEFT_SELECT);
688 needsUpdate |= cursorVisLeft(cur);
689 if (!needsUpdate && oldTopSlice == cur.top()
690 && cur.boundary() == oldBoundary) {
692 cmd = FuncRequest(LFUN_FINISHED_LEFT);
695 if (reverseDirectionNeeded(cur)) {
696 cmd.setAction(cmd.action() == LFUN_CHAR_LEFT_SELECT ?
697 LFUN_CHAR_FORWARD_SELECT : LFUN_CHAR_FORWARD);
699 cmd.setAction(cmd.action() == LFUN_CHAR_LEFT_SELECT ?
700 LFUN_CHAR_BACKWARD_SELECT : LFUN_CHAR_BACKWARD);
707 case LFUN_CHAR_RIGHT:
708 case LFUN_CHAR_RIGHT_SELECT:
709 if (lyxrc.visual_cursor) {
710 needsUpdate |= cur.selHandle(cmd.action() == LFUN_CHAR_RIGHT_SELECT);
711 needsUpdate |= cursorVisRight(cur);
712 if (!needsUpdate && oldTopSlice == cur.top()
713 && cur.boundary() == oldBoundary) {
715 cmd = FuncRequest(LFUN_FINISHED_RIGHT);
718 if (reverseDirectionNeeded(cur)) {
719 cmd.setAction(cmd.action() == LFUN_CHAR_RIGHT_SELECT ?
720 LFUN_CHAR_BACKWARD_SELECT : LFUN_CHAR_BACKWARD);
722 cmd.setAction(cmd.action() == LFUN_CHAR_RIGHT_SELECT ?
723 LFUN_CHAR_FORWARD_SELECT : LFUN_CHAR_FORWARD);
732 case LFUN_DOWN_SELECT:
735 // stop/start the selection
736 bool select = cmd.action() == LFUN_DOWN_SELECT ||
737 cmd.action() == LFUN_UP_SELECT;
739 // move cursor up/down
740 bool up = cmd.action() == LFUN_UP_SELECT || cmd.action() == LFUN_UP;
741 bool const atFirstOrLastRow = cur.atFirstOrLastRow(up);
743 if (!atFirstOrLastRow) {
744 needsUpdate |= cur.selHandle(select);
745 cur.selHandle(select);
746 cur.upDownInText(up, needsUpdate);
747 needsUpdate |= cur.beforeDispatchCursor().inMathed();
749 // if the cursor cannot be moved up or down do not remove
750 // the selection right now, but wait for the next dispatch.
752 needsUpdate |= cur.selHandle(select);
753 cur.upDownInText(up, needsUpdate);
760 case LFUN_PARAGRAPH_UP:
761 case LFUN_PARAGRAPH_UP_SELECT:
762 needsUpdate |= cur.selHandle(cmd.action() == LFUN_PARAGRAPH_UP_SELECT);
763 needsUpdate |= cursorUpParagraph(cur);
766 case LFUN_PARAGRAPH_DOWN:
767 case LFUN_PARAGRAPH_DOWN_SELECT:
768 needsUpdate |= cur.selHandle(cmd.action() == LFUN_PARAGRAPH_DOWN_SELECT);
769 needsUpdate |= cursorDownParagraph(cur);
772 case LFUN_LINE_BEGIN:
773 case LFUN_LINE_BEGIN_SELECT:
774 needsUpdate |= cur.selHandle(cmd.action() == LFUN_LINE_BEGIN_SELECT);
775 needsUpdate |= tm->cursorHome(cur);
779 case LFUN_LINE_END_SELECT:
780 needsUpdate |= cur.selHandle(cmd.action() == LFUN_LINE_END_SELECT);
781 needsUpdate |= tm->cursorEnd(cur);
784 case LFUN_SECTION_SELECT: {
785 Buffer const & buf = *cur.buffer();
786 pit_type const pit = cur.pit();
787 ParagraphList & pars = buf.text().paragraphs();
788 ParagraphList::iterator bgn = pars.begin();
789 // The first paragraph of the area to be selected:
790 ParagraphList::iterator start = boost::next(bgn, pit);
791 // The final paragraph of area to be selected:
792 ParagraphList::iterator finish = start;
793 ParagraphList::iterator end = pars.end();
795 int const thistoclevel = start->layout().toclevel;
796 if (thistoclevel == Layout::NOT_IN_TOC)
800 Cursor const old_cur = cur;
801 needsUpdate |= cur.selHandle(true);
803 // Move out (down) from this section header
807 // Seek the one (on same level) below
808 for (; finish != end; ++finish, ++cur.pit()) {
809 int const toclevel = finish->layout().toclevel;
810 if (toclevel != Layout::NOT_IN_TOC && toclevel <= thistoclevel)
813 cur.pos() = cur.lastpos();
815 needsUpdate |= cur != old_cur;
819 case LFUN_WORD_RIGHT:
820 case LFUN_WORD_RIGHT_SELECT:
821 if (lyxrc.visual_cursor) {
822 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_RIGHT_SELECT);
823 needsUpdate |= cursorVisRightOneWord(cur);
824 if (!needsUpdate && oldTopSlice == cur.top()
825 && cur.boundary() == oldBoundary) {
827 cmd = FuncRequest(LFUN_FINISHED_RIGHT);
830 if (reverseDirectionNeeded(cur)) {
831 cmd.setAction(cmd.action() == LFUN_WORD_RIGHT_SELECT ?
832 LFUN_WORD_BACKWARD_SELECT : LFUN_WORD_BACKWARD);
834 cmd.setAction(cmd.action() == LFUN_WORD_RIGHT_SELECT ?
835 LFUN_WORD_FORWARD_SELECT : LFUN_WORD_FORWARD);
842 case LFUN_WORD_FORWARD:
843 case LFUN_WORD_FORWARD_SELECT:
844 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_FORWARD_SELECT);
845 needsUpdate |= cursorForwardOneWord(cur);
847 if (!needsUpdate && oldTopSlice == cur.top()
848 && cur.boundary() == oldBoundary) {
850 cmd = FuncRequest(LFUN_FINISHED_FORWARD);
852 // we will probably be moving out the inset, so we should execute
853 // the depm-mechanism, but only when the cursor has a place to
854 // go outside this inset, i.e. in a slice above.
855 if (cur.depth() > 1 && cur.pos() == cur.lastpos()
856 && cur.pit() == cur.lastpit()) {
857 // The cursor hasn't changed yet. To give the
858 // DEPM the possibility of doing something we must
859 // provide it with two different cursors.
861 dummy.pos() = dummy.pit() = 0;
862 if (cur.bv().checkDepm(dummy, cur)) {
863 cur.forceBufferUpdate();
864 // DEPM may have requested a screen update
865 cur.screenUpdateFlags(
866 cur.screenUpdate() | dummy.screenUpdate());
873 case LFUN_WORD_LEFT_SELECT:
874 if (lyxrc.visual_cursor) {
875 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_LEFT_SELECT);
876 needsUpdate |= cursorVisLeftOneWord(cur);
877 if (!needsUpdate && oldTopSlice == cur.top()
878 && cur.boundary() == oldBoundary) {
880 cmd = FuncRequest(LFUN_FINISHED_LEFT);
883 if (reverseDirectionNeeded(cur)) {
884 cmd.setAction(cmd.action() == LFUN_WORD_LEFT_SELECT ?
885 LFUN_WORD_FORWARD_SELECT : LFUN_WORD_FORWARD);
887 cmd.setAction(cmd.action() == LFUN_WORD_LEFT_SELECT ?
888 LFUN_WORD_BACKWARD_SELECT : LFUN_WORD_BACKWARD);
895 case LFUN_WORD_BACKWARD:
896 case LFUN_WORD_BACKWARD_SELECT:
897 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_BACKWARD_SELECT);
898 needsUpdate |= cursorBackwardOneWord(cur);
900 if (!needsUpdate && oldTopSlice == cur.top()
901 && cur.boundary() == oldBoundary) {
903 cmd = FuncRequest(LFUN_FINISHED_BACKWARD);
905 // we will probably be moving out the inset, so we should execute
906 // the depm-mechanism, but only when the cursor has a place to
907 // go outside this inset, i.e. in a slice above.
908 if (cur.depth() > 1 && cur.pos() == 0
910 // The cursor hasn't changed yet. To give the
911 // DEPM the possibility of doing something we must
912 // provide it with two different cursors.
914 dummy.pos() = cur.lastpos();
915 dummy.pit() = cur.lastpit();
916 if (cur.bv().checkDepm(dummy, cur)) {
917 cur.forceBufferUpdate();
918 // DEPM may have requested a screen update
919 cur.screenUpdateFlags(
920 cur.screenUpdate() | dummy.screenUpdate());
926 case LFUN_WORD_SELECT: {
927 selectWord(cur, WHOLE_WORD);
928 finishChange(cur, true);
932 case LFUN_NEWLINE_INSERT: {
933 InsetNewlineParams inp;
934 docstring arg = cmd.argument();
935 // this avoids a double undo
936 // FIXME: should not be needed, ideally
937 if (!cur.selection())
939 cap::replaceSelection(cur);
940 if (arg == "linebreak")
941 inp.kind = InsetNewlineParams::LINEBREAK;
943 inp.kind = InsetNewlineParams::NEWLINE;
944 cur.insert(new InsetNewline(inp));
946 moveCursor(cur, false);
950 case LFUN_TAB_INSERT: {
951 bool const multi_par_selection = cur.selection() &&
952 cur.selBegin().pit() != cur.selEnd().pit();
953 if (multi_par_selection) {
954 // If there is a multi-paragraph selection, a tab is inserted
955 // at the beginning of each paragraph.
956 cur.recordUndoSelection();
957 pit_type const pit_end = cur.selEnd().pit();
958 for (pit_type pit = cur.selBegin().pit(); pit <= pit_end; pit++) {
959 pars_[pit].insertChar(0, '\t',
960 bv->buffer().params().trackChanges);
961 // Update the selection pos to make sure the selection does not
962 // change as the inserted tab will increase the logical pos.
963 if (cur.realAnchor().pit() == pit)
964 cur.realAnchor().forwardPos();
965 if (cur.pit() == pit)
970 // Maybe we shouldn't allow tabs within a line, because they
971 // are not (yet) aligned as one might do expect.
972 FuncRequest cmd(LFUN_SELF_INSERT, from_ascii("\t"));
978 case LFUN_TAB_DELETE: {
979 bool const tc = bv->buffer().params().trackChanges;
980 if (cur.selection()) {
981 // If there is a selection, a tab (if present) is removed from
982 // the beginning of each paragraph.
983 cur.recordUndoSelection();
984 pit_type const pit_end = cur.selEnd().pit();
985 for (pit_type pit = cur.selBegin().pit(); pit <= pit_end; pit++) {
986 Paragraph & par = paragraphs()[pit];
989 char_type const c = par.getChar(0);
990 if (c == '\t' || c == ' ') {
991 // remove either 1 tab or 4 spaces.
992 int const n = (c == ' ' ? 4 : 1);
993 for (int i = 0; i < n
994 && !par.empty() && par.getChar(0) == c; ++i) {
995 if (cur.pit() == pit)
997 if (cur.realAnchor().pit() == pit
998 && cur.realAnchor().pos() > 0 )
999 cur.realAnchor().backwardPos();
1000 par.eraseChar(0, tc);
1006 // If there is no selection, try to remove a tab or some spaces
1007 // before the position of the cursor.
1008 Paragraph & par = paragraphs()[cur.pit()];
1009 pos_type const pos = cur.pos();
1014 char_type const c = par.getChar(pos - 1);
1018 par.eraseChar(cur.pos(), tc);
1020 for (int n_spaces = 0;
1022 && par.getChar(cur.pos() - 1) == ' '
1026 par.eraseChar(cur.pos(), tc);
1033 case LFUN_CHAR_DELETE_FORWARD:
1034 if (!cur.selection()) {
1035 if (cur.pos() == cur.paragraph().size())
1036 // Par boundary, force full-screen update
1037 singleParUpdate = false;
1038 needsUpdate |= erase(cur);
1040 // It is possible to make it a lot faster still
1041 // just comment out the line below...
1043 cutSelection(cur, true, false);
1044 singleParUpdate = false;
1046 moveCursor(cur, false);
1049 case LFUN_CHAR_DELETE_BACKWARD:
1050 if (!cur.selection()) {
1051 if (bv->getIntl().getTransManager().backspace()) {
1052 // Par boundary, full-screen update
1054 singleParUpdate = false;
1055 needsUpdate |= backspace(cur);
1057 // It is possible to make it a lot faster still
1058 // just comment out the line below...
1061 cutSelection(cur, true, false);
1062 singleParUpdate = false;
1066 case LFUN_BREAK_PARAGRAPH:
1067 cap::replaceSelection(cur);
1068 breakParagraph(cur, cmd.argument() == "inverse");
1072 case LFUN_INSET_INSERT: {
1075 // We have to avoid triggering InstantPreview loading
1076 // before inserting into the document. See bug #5626.
1077 bool loaded = bv->buffer().isFullyLoaded();
1078 bv->buffer().setFullyLoaded(false);
1079 Inset * inset = createInset(&bv->buffer(), cmd);
1080 bv->buffer().setFullyLoaded(loaded);
1083 // FIXME (Abdel 01/02/2006):
1084 // What follows would be a partial fix for bug 2154:
1085 // http://www.lyx.org/trac/ticket/2154
1086 // This automatically put the label inset _after_ a
1087 // numbered section. It should be possible to extend the mechanism
1088 // to any kind of LateX environement.
1089 // The correct way to fix that bug would be at LateX generation.
1090 // I'll let the code here for reference as it could be used for some
1091 // other feature like "automatic labelling".
1093 Paragraph & par = pars_[cur.pit()];
1094 if (inset->lyxCode() == LABEL_CODE
1095 && par.layout().labeltype == LABEL_COUNTER) {
1096 // Go to the end of the paragraph
1097 // Warning: Because of Change-Tracking, the last
1098 // position is 'size()' and not 'size()-1':
1099 cur.pos() = par.size();
1100 // Insert a new paragraph
1101 FuncRequest fr(LFUN_BREAK_PARAGRAPH);
1105 if (cur.selection())
1106 cutSelection(cur, true, false);
1108 if (inset->editable() && inset->asInsetText())
1109 inset->edit(cur, true);
1113 // trigger InstantPreview now
1114 if (inset->lyxCode() == EXTERNAL_CODE) {
1115 InsetExternal & ins =
1116 static_cast<InsetExternal &>(*inset);
1117 ins.updatePreview();
1124 case LFUN_INSET_DISSOLVE: {
1125 if (dissolveInset(cur)) {
1127 cur.forceBufferUpdate();
1132 case LFUN_SET_GRAPHICS_GROUP: {
1133 InsetGraphics * ins = graphics::getCurrentGraphicsInset(cur);
1139 string id = to_utf8(cmd.argument());
1140 string grp = graphics::getGroupParams(bv->buffer(), id);
1141 InsetGraphicsParams tmp, inspar = ins->getParams();
1144 inspar.groupId = to_utf8(cmd.argument());
1146 InsetGraphics::string2params(grp, bv->buffer(), tmp);
1147 tmp.filename = inspar.filename;
1151 ins->setParams(inspar);
1154 case LFUN_SPACE_INSERT:
1155 if (cur.paragraph().layout().free_spacing)
1156 insertChar(cur, ' ');
1158 doInsertInset(cur, this, cmd, false, false);
1161 moveCursor(cur, false);
1164 case LFUN_SPECIALCHAR_INSERT: {
1165 string const name = to_utf8(cmd.argument());
1166 if (name == "hyphenation")
1167 specialChar(cur, InsetSpecialChar::HYPHENATION);
1168 else if (name == "ligature-break")
1169 specialChar(cur, InsetSpecialChar::LIGATURE_BREAK);
1170 else if (name == "slash")
1171 specialChar(cur, InsetSpecialChar::SLASH);
1172 else if (name == "nobreakdash")
1173 specialChar(cur, InsetSpecialChar::NOBREAKDASH);
1174 else if (name == "dots")
1175 specialChar(cur, InsetSpecialChar::LDOTS);
1176 else if (name == "end-of-sentence")
1177 specialChar(cur, InsetSpecialChar::END_OF_SENTENCE);
1178 else if (name == "menu-separator")
1179 specialChar(cur, InsetSpecialChar::MENU_SEPARATOR);
1180 else if (name.empty())
1181 lyxerr << "LyX function 'specialchar-insert' needs an argument." << endl;
1183 lyxerr << "Wrong argument for LyX function 'specialchar-insert'." << endl;
1187 case LFUN_IPAMACRO_INSERT: {
1188 string const arg = cmd.getArg(0);
1189 if (arg == "deco") {
1190 // Open the inset, and move the current selection
1192 doInsertInset(cur, this, cmd, true, true);
1194 // Some insets are numbered, others are shown in the outline pane so
1195 // let's update the labels and the toc backend.
1196 cur.forceBufferUpdate();
1199 if (arg == "tone-falling")
1200 ipaChar(cur, InsetIPAChar::TONE_FALLING);
1201 else if (arg == "tone-rising")
1202 ipaChar(cur, InsetIPAChar::TONE_RISING);
1203 else if (arg == "tone-high-rising")
1204 ipaChar(cur, InsetIPAChar::TONE_HIGH_RISING);
1205 else if (arg == "tone-low-rising")
1206 ipaChar(cur, InsetIPAChar::TONE_LOW_RISING);
1207 else if (arg == "tone-high-rising-falling")
1208 ipaChar(cur, InsetIPAChar::TONE_HIGH_RISING_FALLING);
1209 else if (arg.empty())
1210 lyxerr << "LyX function 'ipamacro-insert' needs an argument." << endl;
1212 lyxerr << "Wrong argument for LyX function 'ipamacro-insert'." << endl;
1216 case LFUN_WORD_UPCASE:
1217 changeCase(cur, text_uppercase);
1220 case LFUN_WORD_LOWCASE:
1221 changeCase(cur, text_lowercase);
1224 case LFUN_WORD_CAPITALIZE:
1225 changeCase(cur, text_capitalization);
1228 case LFUN_CHARS_TRANSPOSE:
1229 charsTranspose(cur);
1233 cur.message(_("Paste"));
1234 LASSERT(cur.selBegin().idx() == cur.selEnd().idx(), /**/);
1235 cap::replaceSelection(cur);
1237 // without argument?
1238 string const arg = to_utf8(cmd.argument());
1240 if (theClipboard().isInternal())
1241 pasteFromStack(cur, bv->buffer().errorList("Paste"), 0);
1242 else if (theClipboard().hasGraphicsContents()
1243 && !theClipboard().hasTextContents())
1244 pasteClipboardGraphics(cur, bv->buffer().errorList("Paste"));
1246 pasteClipboardText(cur, bv->buffer().errorList("Paste"));
1247 } else if (isStrUnsignedInt(arg)) {
1248 // we have a numerical argument
1249 pasteFromStack(cur, bv->buffer().errorList("Paste"),
1250 convert<unsigned int>(arg));
1252 Clipboard::GraphicsType type = Clipboard::AnyGraphicsType;
1254 type = Clipboard::PdfGraphicsType;
1255 else if (arg == "png")
1256 type = Clipboard::PngGraphicsType;
1257 else if (arg == "jpeg")
1258 type = Clipboard::JpegGraphicsType;
1259 else if (arg == "linkback")
1260 type = Clipboard::LinkBackGraphicsType;
1261 else if (arg == "emf")
1262 type = Clipboard::EmfGraphicsType;
1263 else if (arg == "wmf")
1264 type = Clipboard::WmfGraphicsType;
1267 LASSERT(false, /**/);
1269 pasteClipboardGraphics(cur, bv->buffer().errorList("Paste"), type);
1272 bv->buffer().errors("Paste");
1273 cur.clearSelection(); // bug 393
1279 cutSelection(cur, true, true);
1280 cur.message(_("Cut"));
1285 cur.message(_("Copy"));
1288 case LFUN_SERVER_GET_XY:
1289 cur.message(from_utf8(
1290 convert<string>(tm->cursorX(cur.top(), cur.boundary()))
1291 + ' ' + convert<string>(tm->cursorY(cur.top(), cur.boundary()))));
1294 case LFUN_SERVER_SET_XY: {
1297 istringstream is(to_utf8(cmd.argument()));
1300 lyxerr << "SETXY: Could not parse coordinates in '"
1301 << to_utf8(cmd.argument()) << endl;
1303 tm->setCursorFromCoordinates(cur, x, y);
1307 case LFUN_SERVER_GET_LAYOUT:
1308 cur.message(cur.paragraph().layout().name());
1312 docstring layout = cmd.argument();
1313 LYXERR(Debug::INFO, "LFUN_LAYOUT: (arg) " << to_utf8(layout));
1315 Paragraph const & para = cur.paragraph();
1316 docstring const old_layout = para.layout().name();
1317 DocumentClass const & tclass = bv->buffer().params().documentClass();
1320 layout = tclass.defaultLayoutName();
1322 if (owner_->forcePlainLayout())
1323 // in this case only the empty layout is allowed
1324 layout = tclass.plainLayoutName();
1325 else if (para.usePlainLayout()) {
1326 // in this case, default layout maps to empty layout
1327 if (layout == tclass.defaultLayoutName())
1328 layout = tclass.plainLayoutName();
1330 // otherwise, the empty layout maps to the default
1331 if (layout == tclass.plainLayoutName())
1332 layout = tclass.defaultLayoutName();
1335 bool hasLayout = tclass.hasLayout(layout);
1337 // If the entry is obsolete, use the new one instead.
1339 docstring const & obs = tclass[layout].obsoleted_by();
1345 cur.errorMessage(from_utf8(N_("Layout ")) + cmd.argument() +
1346 from_utf8(N_(" not known")));
1350 bool change_layout = (old_layout != layout);
1352 if (!change_layout && cur.selection() &&
1353 cur.selBegin().pit() != cur.selEnd().pit())
1355 pit_type spit = cur.selBegin().pit();
1356 pit_type epit = cur.selEnd().pit() + 1;
1357 while (spit != epit) {
1358 if (pars_[spit].layout().name() != old_layout) {
1359 change_layout = true;
1367 setLayout(cur, layout);
1372 case LFUN_CLIPBOARD_PASTE:
1373 cap::replaceSelection(cur);
1374 pasteClipboardText(cur, bv->buffer().errorList("Paste"),
1375 cmd.argument() == "paragraph");
1376 bv->buffer().errors("Paste");
1379 case LFUN_CLIPBOARD_PASTE_SIMPLE:
1380 cap::replaceSelection(cur);
1381 pasteSimpleText(cur, cmd.argument() == "paragraph");
1384 case LFUN_PRIMARY_SELECTION_PASTE:
1385 cap::replaceSelection(cur);
1386 pasteString(cur, theSelection().get(),
1387 cmd.argument() == "paragraph");
1390 case LFUN_SELECTION_PASTE:
1391 // Copy the selection buffer to the clipboard stack,
1392 // because we want it to appear in the "Edit->Paste
1394 cap::replaceSelection(cur);
1395 cap::copySelectionToStack();
1396 cap::pasteSelection(bv->cursor(), bv->buffer().errorList("Paste"));
1397 bv->buffer().errors("Paste");
1400 case LFUN_UNICODE_INSERT: {
1401 if (cmd.argument().empty())
1403 docstring hexstring = cmd.argument();
1404 if (isHex(hexstring)) {
1405 char_type c = hexToInt(hexstring);
1406 if (c >= 32 && c < 0x10ffff) {
1407 lyxerr << "Inserting c: " << c << endl;
1408 docstring s = docstring(1, c);
1409 lyx::dispatch(FuncRequest(LFUN_SELF_INSERT, s));
1415 case LFUN_QUOTE_INSERT: {
1416 // this avoids a double undo
1417 // FIXME: should not be needed, ideally
1418 if (!cur.selection())
1420 cap::replaceSelection(cur);
1422 Paragraph const & par = cur.paragraph();
1423 pos_type pos = cur.pos();
1425 BufferParams const & bufparams = bv->buffer().params();
1427 par.getFontSettings(bufparams, pos).language()->lang() == "hebrew";
1428 bool const allow_inset_quote = !(par.isPassThru() || hebrew);
1430 if (allow_inset_quote) {
1432 if (pos > 0 && (!cur.prevInset() || !cur.prevInset()->isSpace()))
1433 c = par.getChar(pos - 1);
1434 string const arg = to_utf8(cmd.argument());
1435 InsetQuotes::QuoteTimes const quote_type = (arg == "single")
1436 ? InsetQuotes::SingleQuotes : InsetQuotes::DoubleQuotes;
1437 cur.insert(new InsetQuotes(cur.buffer(), c, quote_type));
1440 // The cursor might have been invalidated by the replaceSelection.
1441 cur.buffer()->changed(true);
1442 lyx::dispatch(FuncRequest(LFUN_SELF_INSERT, "\""));
1447 case LFUN_DATE_INSERT: {
1448 string const format = cmd.argument().empty()
1449 ? lyxrc.date_insert_format : to_utf8(cmd.argument());
1450 string const time = formatted_time(current_time(), format);
1451 lyx::dispatch(FuncRequest(LFUN_SELF_INSERT, time));
1455 case LFUN_MOUSE_TRIPLE:
1456 if (cmd.button() == mouse_button::button1) {
1457 tm->cursorHome(cur);
1465 case LFUN_MOUSE_DOUBLE:
1466 if (cmd.button() == mouse_button::button1) {
1467 selectWord(cur, WHOLE_WORD_STRICT);
1472 // Single-click on work area
1473 case LFUN_MOUSE_PRESS: {
1474 // We are not marking a selection with the keyboard in any case.
1475 Cursor & bvcur = cur.bv().cursor();
1476 bvcur.setMark(false);
1477 switch (cmd.button()) {
1478 case mouse_button::button1:
1480 if (!bv->mouseSetCursor(cur, cmd.argument() == "region-select"))
1481 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
1482 if (bvcur.wordSelection())
1483 selectWord(bvcur, WHOLE_WORD);
1486 case mouse_button::button2:
1487 // Middle mouse pasting.
1488 bv->mouseSetCursor(cur);
1490 FuncRequest(LFUN_COMMAND_ALTERNATIVES,
1491 "selection-paste ; primary-selection-paste paragraph"));
1492 cur.noScreenUpdate();
1495 case mouse_button::button3: {
1496 // Don't do anything if we right-click a
1497 // selection, a context menu will popup.
1498 if (bvcur.selection() && cur >= bvcur.selectionBegin()
1499 && cur < bvcur.selectionEnd()) {
1500 cur.noScreenUpdate();
1503 if (!bv->mouseSetCursor(cur, false))
1504 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
1510 } // switch (cmd.button())
1513 case LFUN_MOUSE_MOTION: {
1514 // Mouse motion with right or middle mouse do nothing for now.
1515 if (cmd.button() != mouse_button::button1) {
1516 cur.noScreenUpdate();
1519 // ignore motions deeper nested than the real anchor
1520 Cursor & bvcur = cur.bv().cursor();
1521 if (!bvcur.realAnchor().hasPart(cur)) {
1525 CursorSlice old = bvcur.top();
1527 int const wh = bv->workHeight();
1528 int const y = max(0, min(wh - 1, cmd.y()));
1530 tm->setCursorFromCoordinates(cur, cmd.x(), y);
1531 cur.setTargetX(cmd.x());
1533 lyx::dispatch(FuncRequest(LFUN_DOWN_SELECT));
1534 else if (cmd.y() < 0)
1535 lyx::dispatch(FuncRequest(LFUN_UP_SELECT));
1536 // This is to allow jumping over large insets
1537 if (cur.top() == old) {
1539 lyx::dispatch(FuncRequest(LFUN_DOWN_SELECT));
1540 else if (cmd.y() < 0)
1541 lyx::dispatch(FuncRequest(LFUN_UP_SELECT));
1543 // We continue with our existing selection or start a new one, so don't
1544 // reset the anchor.
1545 bvcur.setCursor(cur);
1546 bvcur.setSelection(true);
1547 if (cur.top() == old) {
1548 // We didn't move one iota, so no need to update the screen.
1549 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
1550 //cur.noScreenUpdate();
1556 case LFUN_MOUSE_RELEASE:
1557 switch (cmd.button()) {
1558 case mouse_button::button1:
1559 // Cursor was set at LFUN_MOUSE_PRESS or LFUN_MOUSE_MOTION time.
1560 // If there is a new selection, update persistent selection;
1561 // otherwise, single click does not clear persistent selection
1563 if (cur.selection()) {
1564 // Finish selection. If double click,
1565 // cur is moved to the end of word by
1566 // selectWord but bvcur is current
1568 cur.bv().cursor().setSelection();
1569 // We might have removed an empty but drawn selection
1570 // (probably a margin)
1571 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
1573 cur.noScreenUpdate();
1574 // FIXME: We could try to handle drag and drop of selection here.
1577 case mouse_button::button2:
1578 // Middle mouse pasting is handled at mouse press time,
1579 // see LFUN_MOUSE_PRESS.
1580 cur.noScreenUpdate();
1583 case mouse_button::button3:
1584 // Cursor was set at LFUN_MOUSE_PRESS time.
1585 // FIXME: If there is a selection we could try to handle a special
1586 // drag & drop context menu.
1587 cur.noScreenUpdate();
1590 case mouse_button::none:
1591 case mouse_button::button4:
1592 case mouse_button::button5:
1594 } // switch (cmd.button())
1598 case LFUN_SELF_INSERT: {
1599 if (cmd.argument().empty())
1602 // Automatically delete the currently selected
1603 // text and replace it with what is being
1604 // typed in now. Depends on lyxrc settings
1605 // "auto_region_delete", which defaults to
1608 if (lyxrc.auto_region_delete && cur.selection())
1609 cutSelection(cur, false, false);
1611 cur.clearSelection();
1613 docstring::const_iterator cit = cmd.argument().begin();
1614 docstring::const_iterator const end = cmd.argument().end();
1615 for (; cit != end; ++cit)
1616 bv->translateAndInsert(*cit, this, cur);
1619 moveCursor(cur, false);
1620 cur.markNewWordPosition();
1621 bv->bookmarkEditPosition();
1625 case LFUN_HYPERLINK_INSERT: {
1626 InsetCommandParams p(HYPERLINK_CODE);
1628 if (cur.selection()) {
1629 content = cur.selectionAsString(false);
1630 cutSelection(cur, true, false);
1632 p["target"] = (cmd.argument().empty()) ?
1633 content : cmd.argument();
1634 string const data = InsetCommand::params2string(p);
1635 if (p["target"].empty()) {
1636 bv->showDialog("href", data);
1638 FuncRequest fr(LFUN_INSET_INSERT, data);
1644 case LFUN_LABEL_INSERT: {
1645 InsetCommandParams p(LABEL_CODE);
1646 // Try to generate a valid label
1647 p["name"] = (cmd.argument().empty()) ?
1648 cur.getPossibleLabel() :
1650 string const data = InsetCommand::params2string(p);
1652 if (cmd.argument().empty()) {
1653 bv->showDialog("label", data);
1655 FuncRequest fr(LFUN_INSET_INSERT, data);
1661 case LFUN_INFO_INSERT: {
1663 if (cmd.argument().empty() && cur.selection()) {
1664 // if command argument is empty use current selection as parameter.
1665 docstring ds = cur.selectionAsString(false);
1666 cutSelection(cur, true, false);
1667 FuncRequest cmd0(cmd, ds);
1668 inset = createInset(cur.buffer(), cmd0);
1670 inset = createInset(cur.buffer(), cmd);
1675 insertInset(cur, inset);
1679 case LFUN_CAPTION_INSERT:
1680 case LFUN_FOOTNOTE_INSERT:
1681 case LFUN_NOTE_INSERT:
1682 case LFUN_FLEX_INSERT:
1683 case LFUN_BOX_INSERT:
1684 case LFUN_BRANCH_INSERT:
1685 case LFUN_PHANTOM_INSERT:
1686 case LFUN_ERT_INSERT:
1687 case LFUN_LISTING_INSERT:
1688 case LFUN_MARGINALNOTE_INSERT:
1689 case LFUN_ARGUMENT_INSERT:
1690 case LFUN_INDEX_INSERT:
1691 case LFUN_PREVIEW_INSERT:
1692 case LFUN_SCRIPT_INSERT:
1693 case LFUN_IPA_INSERT:
1694 // Open the inset, and move the current selection
1696 doInsertInset(cur, this, cmd, true, true);
1698 // Some insets are numbered, others are shown in the outline pane so
1699 // let's update the labels and the toc backend.
1700 cur.forceBufferUpdate();
1703 case LFUN_TABULAR_INSERT:
1704 // if there were no arguments, just open the dialog
1705 if (doInsertInset(cur, this, cmd, false, true))
1708 bv->showDialog("tabularcreate");
1712 case LFUN_FLOAT_INSERT:
1713 case LFUN_FLOAT_WIDE_INSERT:
1714 case LFUN_WRAP_INSERT: {
1715 // will some content be moved into the inset?
1716 bool const content = cur.selection();
1717 // does the content consist of multiple paragraphs?
1718 bool const singlepar = (cur.selBegin().pit() == cur.selEnd().pit());
1720 doInsertInset(cur, this, cmd, true, true);
1723 // If some single-par content is moved into the inset,
1724 // doInsertInset puts the cursor outside the inset.
1725 // To insert the caption we put it back into the inset.
1726 // FIXME cleanup doInsertInset to avoid such dances!
1727 if (content && singlepar)
1730 ParagraphList & pars = cur.text()->paragraphs();
1732 DocumentClass const & tclass = bv->buffer().params().documentClass();
1734 // add a separate paragraph for the caption inset
1735 pars.push_back(Paragraph());
1736 pars.back().setInsetOwner(&cur.text()->inset());
1737 pars.back().setPlainOrDefaultLayout(tclass);
1738 int cap_pit = pars.size() - 1;
1740 // if an empty inset was created, we create an additional empty
1741 // paragraph at the bottom so that the user can choose where to put
1742 // the graphics (or table).
1744 pars.push_back(Paragraph());
1745 pars.back().setInsetOwner(&cur.text()->inset());
1746 pars.back().setPlainOrDefaultLayout(tclass);
1749 // reposition the cursor to the caption
1750 cur.pit() = cap_pit;
1752 // FIXME: This Text/Cursor dispatch handling is a mess!
1753 // We cannot use Cursor::dispatch here it needs access to up to
1755 FuncRequest cmd_caption(LFUN_CAPTION_INSERT);
1756 doInsertInset(cur, cur.text(), cmd_caption, true, false);
1757 cur.forceBufferUpdate();
1758 cur.screenUpdateFlags(Update::Force);
1759 // FIXME: When leaving the Float (or Wrap) inset we should
1760 // delete any empty paragraph left above or below the
1765 case LFUN_NOMENCL_INSERT: {
1766 InsetCommandParams p(NOMENCL_CODE);
1767 if (cmd.argument().empty())
1768 p["symbol"] = bv->cursor().innerText()->getStringToIndex(bv->cursor());
1770 p["symbol"] = cmd.argument();
1771 string const data = InsetCommand::params2string(p);
1772 bv->showDialog("nomenclature", data);
1776 case LFUN_INDEX_PRINT: {
1777 InsetCommandParams p(INDEX_PRINT_CODE);
1778 if (cmd.argument().empty())
1779 p["type"] = from_ascii("idx");
1781 p["type"] = cmd.argument();
1782 string const data = InsetCommand::params2string(p);
1783 FuncRequest fr(LFUN_INSET_INSERT, data);
1788 case LFUN_NOMENCL_PRINT:
1789 case LFUN_NEWPAGE_INSERT:
1791 doInsertInset(cur, this, cmd, false, false);
1795 case LFUN_DEPTH_DECREMENT:
1796 changeDepth(cur, DEC_DEPTH);
1799 case LFUN_DEPTH_INCREMENT:
1800 changeDepth(cur, INC_DEPTH);
1803 case LFUN_MATH_DISPLAY:
1804 mathDispatch(cur, cmd, true);
1807 case LFUN_REGEXP_MODE:
1808 regexpDispatch(cur, cmd);
1811 case LFUN_MATH_MODE:
1812 if (cmd.argument() == "on")
1813 // don't pass "on" as argument
1814 // (it would appear literally in the first cell)
1815 mathDispatch(cur, FuncRequest(LFUN_MATH_MODE), false);
1817 mathDispatch(cur, cmd, false);
1820 case LFUN_MATH_MACRO:
1821 if (cmd.argument().empty())
1822 cur.errorMessage(from_utf8(N_("Missing argument")));
1825 string s = to_utf8(cmd.argument());
1826 string const s1 = token(s, ' ', 1);
1827 int const nargs = s1.empty() ? 0 : convert<int>(s1);
1828 string const s2 = token(s, ' ', 2);
1829 MacroType type = MacroTypeNewcommand;
1831 type = MacroTypeDef;
1832 MathMacroTemplate * inset = new MathMacroTemplate(cur.buffer(),
1833 from_utf8(token(s, ' ', 0)), nargs, false, type);
1834 inset->setBuffer(bv->buffer());
1835 insertInset(cur, inset);
1837 // enter macro inset and select the name
1839 cur.top().pos() = cur.top().lastpos();
1841 cur.setSelection(true);
1842 cur.top().pos() = 0;
1846 // passthrough hat and underscore outside mathed:
1847 case LFUN_MATH_SUBSCRIPT:
1848 mathDispatch(cur, FuncRequest(LFUN_SELF_INSERT, "_"), false);
1850 case LFUN_MATH_SUPERSCRIPT:
1851 mathDispatch(cur, FuncRequest(LFUN_SELF_INSERT, "^"), false);
1854 case LFUN_MATH_INSERT:
1855 case LFUN_MATH_AMS_MATRIX:
1856 case LFUN_MATH_MATRIX:
1857 case LFUN_MATH_DELIM:
1858 case LFUN_MATH_BIGDELIM: {
1860 cap::replaceSelection(cur);
1861 cur.insert(new InsetMathHull(cur.buffer(), hullSimple));
1862 checkAndActivateInset(cur, true);
1863 LASSERT(cur.inMathed(), /**/);
1868 case LFUN_FONT_EMPH: {
1869 Font font(ignore_font, ignore_language);
1870 font.fontInfo().setEmph(FONT_TOGGLE);
1871 toggleAndShow(cur, this, font);
1875 case LFUN_FONT_ITAL: {
1876 Font font(ignore_font, ignore_language);
1877 font.fontInfo().setShape(ITALIC_SHAPE);
1878 toggleAndShow(cur, this, font);
1882 case LFUN_FONT_BOLD:
1883 case LFUN_FONT_BOLDSYMBOL: {
1884 Font font(ignore_font, ignore_language);
1885 font.fontInfo().setSeries(BOLD_SERIES);
1886 toggleAndShow(cur, this, font);
1890 case LFUN_FONT_NOUN: {
1891 Font font(ignore_font, ignore_language);
1892 font.fontInfo().setNoun(FONT_TOGGLE);
1893 toggleAndShow(cur, this, font);
1897 case LFUN_FONT_TYPEWRITER: {
1898 Font font(ignore_font, ignore_language);
1899 font.fontInfo().setFamily(TYPEWRITER_FAMILY); // no good
1900 toggleAndShow(cur, this, font);
1904 case LFUN_FONT_SANS: {
1905 Font font(ignore_font, ignore_language);
1906 font.fontInfo().setFamily(SANS_FAMILY);
1907 toggleAndShow(cur, this, font);
1911 case LFUN_FONT_ROMAN: {
1912 Font font(ignore_font, ignore_language);
1913 font.fontInfo().setFamily(ROMAN_FAMILY);
1914 toggleAndShow(cur, this, font);
1918 case LFUN_FONT_DEFAULT: {
1919 Font font(inherit_font, ignore_language);
1920 toggleAndShow(cur, this, font);
1924 case LFUN_FONT_STRIKEOUT: {
1925 Font font(ignore_font, ignore_language);
1926 font.fontInfo().setStrikeout(FONT_TOGGLE);
1927 toggleAndShow(cur, this, font);
1931 case LFUN_FONT_UULINE: {
1932 Font font(ignore_font, ignore_language);
1933 font.fontInfo().setUuline(FONT_TOGGLE);
1934 toggleAndShow(cur, this, font);
1938 case LFUN_FONT_UWAVE: {
1939 Font font(ignore_font, ignore_language);
1940 font.fontInfo().setUwave(FONT_TOGGLE);
1941 toggleAndShow(cur, this, font);
1945 case LFUN_FONT_UNDERLINE: {
1946 Font font(ignore_font, ignore_language);
1947 font.fontInfo().setUnderbar(FONT_TOGGLE);
1948 toggleAndShow(cur, this, font);
1952 case LFUN_FONT_SIZE: {
1953 Font font(ignore_font, ignore_language);
1954 setLyXSize(to_utf8(cmd.argument()), font.fontInfo());
1955 toggleAndShow(cur, this, font);
1959 case LFUN_LANGUAGE: {
1960 Language const * lang = languages.getLanguage(to_utf8(cmd.argument()));
1963 selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
1964 Font font(ignore_font, lang);
1965 toggleAndShow(cur, this, font, false);
1969 case LFUN_TEXTSTYLE_APPLY:
1970 toggleAndShow(cur, this, freefont, toggleall);
1971 cur.message(_("Character set"));
1974 // Set the freefont using the contents of \param data dispatched from
1975 // the frontends and apply it at the current cursor location.
1976 case LFUN_TEXTSTYLE_UPDATE: {
1979 if (font.fromString(to_utf8(cmd.argument()), toggle)) {
1982 toggleAndShow(cur, this, freefont, toggleall);
1983 cur.message(_("Character set"));
1985 lyxerr << "Argument not ok";
1990 case LFUN_FINISHED_LEFT:
1991 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_LEFT:\n" << cur);
1992 // We're leaving an inset, going left. If the inset is LTR, we're
1993 // leaving from the front, so we should not move (remain at --- but
1994 // not in --- the inset). If the inset is RTL, move left, without
1995 // entering the inset itself; i.e., move to after the inset.
1996 if (cur.paragraph().getFontSettings(
1997 cur.bv().buffer().params(), cur.pos()).isRightToLeft())
1998 cursorVisLeft(cur, true);
2001 case LFUN_FINISHED_RIGHT:
2002 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_RIGHT:\n" << cur);
2003 // We're leaving an inset, going right. If the inset is RTL, we're
2004 // leaving from the front, so we should not move (remain at --- but
2005 // not in --- the inset). If the inset is LTR, move right, without
2006 // entering the inset itself; i.e., move to after the inset.
2007 if (!cur.paragraph().getFontSettings(
2008 cur.bv().buffer().params(), cur.pos()).isRightToLeft())
2009 cursorVisRight(cur, true);
2012 case LFUN_FINISHED_BACKWARD:
2013 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_BACKWARD:\n" << cur);
2016 case LFUN_FINISHED_FORWARD:
2017 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_FORWARD:\n" << cur);
2019 cur.setCurrentFont();
2022 case LFUN_LAYOUT_PARAGRAPH: {
2024 params2string(cur.paragraph(), data);
2025 data = "show\n" + data;
2026 bv->showDialog("paragraph", data);
2030 case LFUN_PARAGRAPH_UPDATE: {
2032 params2string(cur.paragraph(), data);
2034 // Will the paragraph accept changes from the dialog?
2036 cur.inset().allowParagraphCustomization(cur.idx());
2038 data = "update " + convert<string>(accept) + '\n' + data;
2039 bv->updateDialog("paragraph", data);
2043 case LFUN_ACCENT_UMLAUT:
2044 case LFUN_ACCENT_CIRCUMFLEX:
2045 case LFUN_ACCENT_GRAVE:
2046 case LFUN_ACCENT_ACUTE:
2047 case LFUN_ACCENT_TILDE:
2048 case LFUN_ACCENT_CEDILLA:
2049 case LFUN_ACCENT_MACRON:
2050 case LFUN_ACCENT_DOT:
2051 case LFUN_ACCENT_UNDERDOT:
2052 case LFUN_ACCENT_UNDERBAR:
2053 case LFUN_ACCENT_CARON:
2054 case LFUN_ACCENT_BREVE:
2055 case LFUN_ACCENT_TIE:
2056 case LFUN_ACCENT_HUNGARIAN_UMLAUT:
2057 case LFUN_ACCENT_CIRCLE:
2058 case LFUN_ACCENT_OGONEK:
2059 theApp()->handleKeyFunc(cmd.action());
2060 if (!cmd.argument().empty())
2061 // FIXME: Are all these characters encoded in one byte in utf8?
2062 bv->translateAndInsert(cmd.argument()[0], this, cur);
2063 cur.screenUpdateFlags(Update::FitCursor);
2066 case LFUN_FLOAT_LIST_INSERT: {
2067 DocumentClass const & tclass = bv->buffer().params().documentClass();
2068 if (tclass.floats().typeExist(to_utf8(cmd.argument()))) {
2070 if (cur.selection())
2071 cutSelection(cur, true, false);
2072 breakParagraph(cur);
2074 if (cur.lastpos() != 0) {
2075 cursorBackward(cur);
2076 breakParagraph(cur);
2079 docstring const laystr = cur.inset().usePlainLayout() ?
2080 tclass.plainLayoutName() :
2081 tclass.defaultLayoutName();
2082 setLayout(cur, laystr);
2083 ParagraphParameters p;
2084 // FIXME If this call were replaced with one to clearParagraphParams(),
2085 // then we could get rid of this method altogether.
2086 setParagraphs(cur, p);
2087 // FIXME This should be simplified when InsetFloatList takes a
2088 // Buffer in its constructor.
2089 InsetFloatList * ifl = new InsetFloatList(cur.buffer(), to_utf8(cmd.argument()));
2090 ifl->setBuffer(bv->buffer());
2091 insertInset(cur, ifl);
2094 lyxerr << "Non-existent float type: "
2095 << to_utf8(cmd.argument()) << endl;
2100 case LFUN_CHANGE_ACCEPT: {
2101 acceptOrRejectChanges(cur, ACCEPT);
2105 case LFUN_CHANGE_REJECT: {
2106 acceptOrRejectChanges(cur, REJECT);
2110 case LFUN_THESAURUS_ENTRY: {
2111 docstring arg = cmd.argument();
2113 arg = cur.selectionAsString(false);
2115 if (arg.size() > 100 || arg.empty()) {
2116 // Get word or selection
2117 selectWordWhenUnderCursor(cur, WHOLE_WORD);
2118 arg = cur.selectionAsString(false);
2119 arg += " lang=" + from_ascii(cur.getFont().language()->lang());
2122 bv->showDialog("thesaurus", to_utf8(arg));
2126 case LFUN_SPELLING_ADD: {
2127 docstring word = from_utf8(cmd.getArg(0));
2130 word = cur.selectionAsString(false);
2132 if (word.size() > 100 || word.empty()) {
2133 // Get word or selection
2134 selectWordWhenUnderCursor(cur, WHOLE_WORD);
2135 word = cur.selectionAsString(false);
2137 lang = const_cast<Language *>(cur.getFont().language());
2139 lang = const_cast<Language *>(languages.getLanguage(cmd.getArg(1)));
2140 WordLangTuple wl(word, lang);
2141 theSpellChecker()->insert(wl);
2145 case LFUN_SPELLING_IGNORE: {
2146 docstring word = from_utf8(cmd.getArg(0));
2149 word = cur.selectionAsString(false);
2151 if (word.size() > 100 || word.empty()) {
2152 // Get word or selection
2153 selectWordWhenUnderCursor(cur, WHOLE_WORD);
2154 word = cur.selectionAsString(false);
2156 lang = const_cast<Language *>(cur.getFont().language());
2158 lang = const_cast<Language *>(languages.getLanguage(cmd.getArg(1)));
2159 WordLangTuple wl(word, lang);
2160 theSpellChecker()->accept(wl);
2164 case LFUN_SPELLING_REMOVE: {
2165 docstring word = from_utf8(cmd.getArg(0));
2168 word = cur.selectionAsString(false);
2170 if (word.size() > 100 || word.empty()) {
2171 // Get word or selection
2172 selectWordWhenUnderCursor(cur, WHOLE_WORD);
2173 word = cur.selectionAsString(false);
2175 lang = const_cast<Language *>(cur.getFont().language());
2177 lang = const_cast<Language *>(languages.getLanguage(cmd.getArg(1)));
2178 WordLangTuple wl(word, lang);
2179 theSpellChecker()->remove(wl);
2183 case LFUN_PARAGRAPH_PARAMS_APPLY: {
2184 // Given data, an encoding of the ParagraphParameters
2185 // generated in the Paragraph dialog, this function sets
2186 // the current paragraph, or currently selected paragraphs,
2188 // NOTE: This function overrides all existing settings.
2189 setParagraphs(cur, cmd.argument());
2190 cur.message(_("Paragraph layout set"));
2194 case LFUN_PARAGRAPH_PARAMS: {
2195 // Given data, an encoding of the ParagraphParameters as we'd
2196 // find them in a LyX file, this function modifies the current paragraph,
2197 // or currently selected paragraphs.
2198 // NOTE: This function only modifies, and does not override, existing
2200 setParagraphs(cur, cmd.argument(), true);
2201 cur.message(_("Paragraph layout set"));
2206 if (cur.selection()) {
2207 cur.setSelection(false);
2210 // This used to be LFUN_FINISHED_RIGHT, I think FORWARD is more
2211 // correct, but I'm not 100% sure -- dov, 071019
2212 cmd = FuncRequest(LFUN_FINISHED_FORWARD);
2216 case LFUN_OUTLINE_UP:
2217 outline(OutlineUp, cur);
2218 setCursor(cur, cur.pit(), 0);
2219 cur.forceBufferUpdate();
2223 case LFUN_OUTLINE_DOWN:
2224 outline(OutlineDown, cur);
2225 setCursor(cur, cur.pit(), 0);
2226 cur.forceBufferUpdate();
2230 case LFUN_OUTLINE_IN:
2231 outline(OutlineIn, cur);
2232 cur.forceBufferUpdate();
2236 case LFUN_OUTLINE_OUT:
2237 outline(OutlineOut, cur);
2238 cur.forceBufferUpdate();
2243 LYXERR(Debug::ACTION, "Command " << cmd << " not DISPATCHED by Text");
2248 needsUpdate |= (cur.pos() != cur.lastpos()) && cur.selection();
2250 if (lyxrc.spellcheck_continuously && !needsUpdate) {
2251 // Check for misspelled text
2252 // The redraw is useful because of the painting of
2253 // misspelled markers depends on the cursor position.
2254 // Trigger a redraw for cursor moves inside misspelled text.
2255 if (!cur.inTexted()) {
2256 // move from regular text to math
2257 needsUpdate = last_misspelled;
2258 } else if (oldTopSlice != cur.top() || oldBoundary != cur.boundary()) {
2259 // move inside regular text
2260 needsUpdate = last_misspelled
2261 || cur.paragraph().isMisspelled(cur.pos(), true);
2265 // FIXME: The cursor flag is reset two lines below
2266 // so we need to check here if some of the LFUN did touch that.
2267 // for now only Text::erase() and Text::backspace() do that.
2268 // The plan is to verify all the LFUNs and then to remove this
2269 // singleParUpdate boolean altogether.
2270 if (cur.result().screenUpdate() & Update::Force) {
2271 singleParUpdate = false;
2275 // FIXME: the following code should go in favor of fine grained
2276 // update flag treatment.
2277 if (singleParUpdate) {
2278 // Inserting characters does not change par height in general. So, try
2279 // to update _only_ this paragraph. BufferView will detect if a full
2280 // metrics update is needed anyway.
2281 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
2285 && &oldTopSlice.inset() == &cur.inset()
2286 && oldTopSlice.idx() == cur.idx()
2287 && !oldSelection // oldSelection is a backup of cur.selection() at the beginning of the function.
2288 && !cur.selection())
2289 // FIXME: it would be better if we could just do this
2291 //if (cur.result().update() != Update::FitCursor)
2292 // cur.noScreenUpdate();
2294 // But some LFUNs do not set Update::FitCursor when needed, so we
2295 // do it for all. This is not very harmfull as FitCursor will provoke
2296 // a full redraw only if needed but still, a proper review of all LFUN
2297 // should be done and this needsUpdate boolean can then be removed.
2298 cur.screenUpdateFlags(Update::FitCursor);
2300 cur.screenUpdateFlags(Update::Force | Update::FitCursor);
2304 bool Text::getStatus(Cursor & cur, FuncRequest const & cmd,
2305 FuncStatus & flag) const
2307 LASSERT(cur.text() == this, /**/);
2309 FontInfo const & fontinfo = cur.real_current_font.fontInfo();
2311 InsetCode code = NO_CODE;
2313 switch (cmd.action()) {
2315 case LFUN_DEPTH_DECREMENT:
2316 enable = changeDepthAllowed(cur, DEC_DEPTH);
2319 case LFUN_DEPTH_INCREMENT:
2320 enable = changeDepthAllowed(cur, INC_DEPTH);
2324 // FIXME We really should not allow this to be put, e.g.,
2325 // in a footnote, or in ERT. But it would make sense in a
2326 // branch, so I'm not sure what to do.
2327 flag.setOnOff(cur.paragraph().params().startOfAppendix());
2330 case LFUN_DIALOG_SHOW_NEW_INSET:
2331 if (cmd.argument() == "bibitem")
2332 code = BIBITEM_CODE;
2333 else if (cmd.argument() == "bibtex") {
2335 // not allowed in description items
2336 enable = !inDescriptionItem(cur);
2338 else if (cmd.argument() == "box")
2340 else if (cmd.argument() == "branch")
2342 else if (cmd.argument() == "citation")
2344 else if (cmd.argument() == "ert")
2346 else if (cmd.argument() == "external")
2347 code = EXTERNAL_CODE;
2348 else if (cmd.argument() == "float")
2350 else if (cmd.argument() == "graphics")
2351 code = GRAPHICS_CODE;
2352 else if (cmd.argument() == "href")
2353 code = HYPERLINK_CODE;
2354 else if (cmd.argument() == "include")
2355 code = INCLUDE_CODE;
2356 else if (cmd.argument() == "index")
2358 else if (cmd.argument() == "index_print")
2359 code = INDEX_PRINT_CODE;
2360 else if (cmd.argument() == "nomenclature")
2361 code = NOMENCL_CODE;
2362 else if (cmd.argument() == "nomencl_print")
2363 code = NOMENCL_PRINT_CODE;
2364 else if (cmd.argument() == "label")
2366 else if (cmd.argument() == "line")
2368 else if (cmd.argument() == "note")
2370 else if (cmd.argument() == "phantom")
2371 code = PHANTOM_CODE;
2372 else if (cmd.argument() == "ref")
2374 else if (cmd.argument() == "space")
2376 else if (cmd.argument() == "toc")
2378 else if (cmd.argument() == "vspace")
2380 else if (cmd.argument() == "wrap")
2382 else if (cmd.argument() == "listings")
2383 code = LISTINGS_CODE;
2386 case LFUN_ERT_INSERT:
2389 case LFUN_LISTING_INSERT:
2390 code = LISTINGS_CODE;
2391 // not allowed in description items
2392 enable = !inDescriptionItem(cur);
2394 case LFUN_FOOTNOTE_INSERT:
2397 case LFUN_TABULAR_INSERT:
2398 code = TABULAR_CODE;
2400 case LFUN_MARGINALNOTE_INSERT:
2403 case LFUN_FLOAT_INSERT:
2404 case LFUN_FLOAT_WIDE_INSERT:
2405 // FIXME: If there is a selection, we should check whether there
2406 // are floats in the selection, but this has performance issues, see
2407 // LFUN_CHANGE_ACCEPT/REJECT.
2409 if (inDescriptionItem(cur))
2410 // not allowed in description items
2413 InsetCode const inset_code = cur.inset().lyxCode();
2415 // algorithm floats cannot be put in another float
2416 if (to_utf8(cmd.argument()) == "algorithm") {
2417 enable = inset_code != WRAP_CODE && inset_code != FLOAT_CODE;
2421 // for figures and tables: only allow in another
2422 // float or wrap if it is of the same type and
2423 // not a subfloat already
2424 if(cur.inset().lyxCode() == code) {
2425 InsetFloat const & ins =
2426 static_cast<InsetFloat const &>(cur.inset());
2427 enable = ins.params().type == to_utf8(cmd.argument())
2428 && !ins.params().subfloat;
2429 } else if(cur.inset().lyxCode() == WRAP_CODE) {
2430 InsetWrap const & ins =
2431 static_cast<InsetWrap const &>(cur.inset());
2432 enable = ins.params().type == to_utf8(cmd.argument());
2436 case LFUN_WRAP_INSERT:
2438 // not allowed in description items
2439 enable = !inDescriptionItem(cur);
2441 case LFUN_FLOAT_LIST_INSERT: {
2442 code = FLOAT_LIST_CODE;
2443 // not allowed in description items
2444 enable = !inDescriptionItem(cur);
2446 FloatList const & floats = cur.buffer()->params().documentClass().floats();
2447 FloatList::const_iterator cit = floats[to_ascii(cmd.argument())];
2448 // make sure we know about such floats
2449 if (cit == floats.end() ||
2450 // and that we know how to generate a list of them
2451 (!cit->second.usesFloatPkg() && cit->second.listCommand().empty())) {
2452 flag.setUnknown(true);
2453 // probably not necessary, but...
2459 case LFUN_CAPTION_INSERT:
2460 code = CAPTION_CODE;
2461 // not allowed in description items
2462 enable = !inDescriptionItem(cur);
2464 case LFUN_NOTE_INSERT:
2466 // in commands (sections etc.) and description items,
2467 // only Notes are allowed
2468 enable = (cmd.argument().empty() || cmd.getArg(0) == "Note" ||
2469 (!cur.paragraph().layout().isCommand()
2470 && !inDescriptionItem(cur)));
2472 case LFUN_FLEX_INSERT: {
2474 string s = cmd.getArg(0);
2476 cur.buffer()->params().documentClass().insetLayout(from_utf8(s));
2477 if (il.lyxtype() != InsetLayout::CHARSTYLE &&
2478 il.lyxtype() != InsetLayout::CUSTOM &&
2479 il.lyxtype() != InsetLayout::ELEMENT &&
2480 il.lyxtype ()!= InsetLayout::STANDARD)
2484 case LFUN_BOX_INSERT:
2487 case LFUN_BRANCH_INSERT:
2489 if (cur.buffer()->masterBuffer()->params().branchlist().empty()
2490 && cur.buffer()->params().branchlist().empty())
2493 case LFUN_IPA_INSERT:
2496 case LFUN_PHANTOM_INSERT:
2497 code = PHANTOM_CODE;
2499 case LFUN_LABEL_INSERT:
2502 case LFUN_INFO_INSERT:
2505 case LFUN_ARGUMENT_INSERT: {
2507 Layout const & lay = cur.paragraph().layout();
2508 int const numargs = lay.reqargs + lay.optargs;
2509 enable = cur.paragraph().insetList().count(ARG_CODE) < numargs;
2512 case LFUN_INDEX_INSERT:
2515 case LFUN_INDEX_PRINT:
2516 code = INDEX_PRINT_CODE;
2517 // not allowed in description items
2518 enable = !inDescriptionItem(cur);
2520 case LFUN_NOMENCL_INSERT:
2521 if (cur.selIsMultiCell() || cur.selIsMultiLine()) {
2525 code = NOMENCL_CODE;
2527 case LFUN_NOMENCL_PRINT:
2528 code = NOMENCL_PRINT_CODE;
2529 // not allowed in description items
2530 enable = !inDescriptionItem(cur);
2532 case LFUN_HYPERLINK_INSERT:
2533 if (cur.selIsMultiCell() || cur.selIsMultiLine()) {
2537 code = HYPERLINK_CODE;
2539 case LFUN_IPAMACRO_INSERT: {
2540 string const arg = cmd.getArg(0);
2542 code = IPADECO_CODE;
2544 code = IPACHAR_CODE;
2547 case LFUN_QUOTE_INSERT:
2548 // always allow this, since we will inset a raw quote
2549 // if an inset is not allowed.
2551 case LFUN_SPECIALCHAR_INSERT:
2552 code = SPECIALCHAR_CODE;
2554 case LFUN_SPACE_INSERT:
2555 // slight hack: we know this is allowed in math mode
2559 case LFUN_PREVIEW_INSERT:
2560 code = PREVIEW_CODE;
2562 case LFUN_SCRIPT_INSERT:
2566 case LFUN_MATH_INSERT:
2567 case LFUN_MATH_AMS_MATRIX:
2568 case LFUN_MATH_MATRIX:
2569 case LFUN_MATH_DELIM:
2570 case LFUN_MATH_BIGDELIM:
2571 case LFUN_MATH_DISPLAY:
2572 case LFUN_MATH_MODE:
2573 case LFUN_MATH_MACRO:
2574 case LFUN_MATH_SUBSCRIPT:
2575 case LFUN_MATH_SUPERSCRIPT:
2576 code = MATH_HULL_CODE;
2579 case LFUN_REGEXP_MODE:
2580 code = MATH_HULL_CODE;
2581 enable = cur.buffer()->isInternal() && !cur.inRegexped();
2584 case LFUN_INSET_MODIFY:
2585 // We need to disable this, because we may get called for a
2587 // InsetTabular::getStatus() -> InsetText::getStatus()
2588 // and we don't handle LFUN_INSET_MODIFY.
2592 case LFUN_FONT_EMPH:
2593 flag.setOnOff(fontinfo.emph() == FONT_ON);
2594 enable = !cur.paragraph().isPassThru();
2597 case LFUN_FONT_ITAL:
2598 flag.setOnOff(fontinfo.shape() == ITALIC_SHAPE);
2599 enable = !cur.paragraph().isPassThru();
2602 case LFUN_FONT_NOUN:
2603 flag.setOnOff(fontinfo.noun() == FONT_ON);
2604 enable = !cur.paragraph().isPassThru();
2607 case LFUN_FONT_BOLD:
2608 case LFUN_FONT_BOLDSYMBOL:
2609 flag.setOnOff(fontinfo.series() == BOLD_SERIES);
2610 enable = !cur.paragraph().isPassThru();
2613 case LFUN_FONT_SANS:
2614 flag.setOnOff(fontinfo.family() == SANS_FAMILY);
2615 enable = !cur.paragraph().isPassThru();
2618 case LFUN_FONT_ROMAN:
2619 flag.setOnOff(fontinfo.family() == ROMAN_FAMILY);
2620 enable = !cur.paragraph().isPassThru();
2623 case LFUN_FONT_TYPEWRITER:
2624 flag.setOnOff(fontinfo.family() == TYPEWRITER_FAMILY);
2625 enable = !cur.paragraph().isPassThru();
2630 enable = cur.selection();
2634 if (cmd.argument().empty()) {
2635 if (theClipboard().isInternal())
2636 enable = cap::numberOfSelections() > 0;
2638 enable = !theClipboard().empty();
2642 // we have an argument
2643 string const arg = to_utf8(cmd.argument());
2644 if (isStrUnsignedInt(arg)) {
2645 // it's a number and therefore means the internal stack
2646 unsigned int n = convert<unsigned int>(arg);
2647 enable = cap::numberOfSelections() > n;
2651 // explicit graphics type?
2652 Clipboard::GraphicsType type = Clipboard::AnyGraphicsType;
2653 if ((arg == "pdf" && (type = Clipboard::PdfGraphicsType))
2654 || (arg == "png" && (type = Clipboard::PngGraphicsType))
2655 || (arg == "jpeg" && (type = Clipboard::JpegGraphicsType))
2656 || (arg == "linkback" && (type = Clipboard::LinkBackGraphicsType))
2657 || (arg == "emf" && (type = Clipboard::EmfGraphicsType))
2658 || (arg == "wmf" && (type = Clipboard::WmfGraphicsType))) {
2659 enable = theClipboard().hasGraphicsContents(type);
2668 case LFUN_CLIPBOARD_PASTE:
2669 case LFUN_CLIPBOARD_PASTE_SIMPLE:
2670 enable = !theClipboard().empty();
2673 case LFUN_PRIMARY_SELECTION_PASTE:
2674 enable = cur.selection() || !theSelection().empty();
2677 case LFUN_SELECTION_PASTE:
2678 enable = cap::selection();
2681 case LFUN_PARAGRAPH_MOVE_UP:
2682 enable = cur.pit() > 0 && !cur.selection();
2685 case LFUN_PARAGRAPH_MOVE_DOWN:
2686 enable = cur.pit() < cur.lastpit() && !cur.selection();
2689 case LFUN_CHANGE_ACCEPT:
2690 case LFUN_CHANGE_REJECT:
2691 // In principle, these LFUNs should only be enabled if there
2692 // is a change at the current position/in the current selection.
2693 // However, without proper optimizations, this will inevitably
2694 // result in unacceptable performance - just imagine a user who
2695 // wants to select the complete content of a long document.
2696 if (!cur.selection())
2697 enable = cur.paragraph().isChanged(cur.pos());
2699 // TODO: context-sensitive enabling of LFUN_CHANGE_ACCEPT/REJECT
2704 case LFUN_OUTLINE_UP:
2705 case LFUN_OUTLINE_DOWN:
2706 case LFUN_OUTLINE_IN:
2707 case LFUN_OUTLINE_OUT:
2708 // FIXME: LyX is not ready for outlining within inset.
2709 enable = isMainText()
2710 && cur.paragraph().layout().toclevel != Layout::NOT_IN_TOC;
2713 case LFUN_NEWLINE_INSERT:
2714 // LaTeX restrictions (labels or empty par)
2715 enable = (cur.pos() > cur.paragraph().beginOfBody());
2718 case LFUN_TAB_INSERT:
2719 case LFUN_TAB_DELETE:
2720 enable = cur.paragraph().isPassThru();
2723 case LFUN_SET_GRAPHICS_GROUP: {
2724 InsetGraphics * ins = graphics::getCurrentGraphicsInset(cur);
2728 flag.setOnOff(to_utf8(cmd.argument()) == ins->getParams().groupId);
2732 case LFUN_NEWPAGE_INSERT:
2733 // not allowed in description items
2734 code = NEWPAGE_CODE;
2735 enable = !inDescriptionItem(cur);
2738 case LFUN_DATE_INSERT: {
2739 string const format = cmd.argument().empty()
2740 ? lyxrc.date_insert_format : to_utf8(cmd.argument());
2741 enable = support::os::is_valid_strftime(format);
2746 enable = !cur.paragraph().isPassThru();
2747 flag.setOnOff(to_utf8(cmd.argument()) == cur.real_current_font.language()->lang());
2750 case LFUN_BREAK_PARAGRAPH:
2751 enable = cur.inset().getLayout().isMultiPar();
2754 case LFUN_SPELLING_ADD:
2755 case LFUN_SPELLING_IGNORE:
2756 case LFUN_SPELLING_REMOVE:
2757 enable = theSpellChecker();
2761 enable = !cur.inset().forcePlainLayout();
2764 case LFUN_LAYOUT_PARAGRAPH:
2765 case LFUN_PARAGRAPH_PARAMS:
2766 case LFUN_PARAGRAPH_PARAMS_APPLY:
2767 case LFUN_PARAGRAPH_UPDATE:
2768 enable = cur.inset().allowParagraphCustomization();
2771 // FIXME: why are accent lfuns forbidden with pass_thru layouts?
2772 case LFUN_ACCENT_ACUTE:
2773 case LFUN_ACCENT_BREVE:
2774 case LFUN_ACCENT_CARON:
2775 case LFUN_ACCENT_CEDILLA:
2776 case LFUN_ACCENT_CIRCLE:
2777 case LFUN_ACCENT_CIRCUMFLEX:
2778 case LFUN_ACCENT_DOT:
2779 case LFUN_ACCENT_GRAVE:
2780 case LFUN_ACCENT_HUNGARIAN_UMLAUT:
2781 case LFUN_ACCENT_MACRON:
2782 case LFUN_ACCENT_OGONEK:
2783 case LFUN_ACCENT_TIE:
2784 case LFUN_ACCENT_TILDE:
2785 case LFUN_ACCENT_UMLAUT:
2786 case LFUN_ACCENT_UNDERBAR:
2787 case LFUN_ACCENT_UNDERDOT:
2788 case LFUN_FONT_DEFAULT:
2789 case LFUN_FONT_FRAK:
2790 case LFUN_FONT_SIZE:
2791 case LFUN_FONT_STATE:
2792 case LFUN_FONT_UNDERLINE:
2793 case LFUN_FONT_STRIKEOUT:
2794 case LFUN_FONT_UULINE:
2795 case LFUN_FONT_UWAVE:
2796 case LFUN_TEXTSTYLE_APPLY:
2797 case LFUN_TEXTSTYLE_UPDATE:
2798 enable = !cur.paragraph().isPassThru();
2801 case LFUN_WORD_DELETE_FORWARD:
2802 case LFUN_WORD_DELETE_BACKWARD:
2803 case LFUN_LINE_DELETE:
2804 case LFUN_WORD_FORWARD:
2805 case LFUN_WORD_BACKWARD:
2806 case LFUN_WORD_RIGHT:
2807 case LFUN_WORD_LEFT:
2808 case LFUN_CHAR_FORWARD:
2809 case LFUN_CHAR_FORWARD_SELECT:
2810 case LFUN_CHAR_BACKWARD:
2811 case LFUN_CHAR_BACKWARD_SELECT:
2812 case LFUN_CHAR_LEFT:
2813 case LFUN_CHAR_LEFT_SELECT:
2814 case LFUN_CHAR_RIGHT:
2815 case LFUN_CHAR_RIGHT_SELECT:
2817 case LFUN_UP_SELECT:
2819 case LFUN_DOWN_SELECT:
2820 case LFUN_PARAGRAPH_UP_SELECT:
2821 case LFUN_PARAGRAPH_DOWN_SELECT:
2822 case LFUN_LINE_BEGIN_SELECT:
2823 case LFUN_LINE_END_SELECT:
2824 case LFUN_WORD_FORWARD_SELECT:
2825 case LFUN_WORD_BACKWARD_SELECT:
2826 case LFUN_WORD_RIGHT_SELECT:
2827 case LFUN_WORD_LEFT_SELECT:
2828 case LFUN_WORD_SELECT:
2829 case LFUN_SECTION_SELECT:
2830 case LFUN_BUFFER_BEGIN:
2831 case LFUN_BUFFER_END:
2832 case LFUN_BUFFER_BEGIN_SELECT:
2833 case LFUN_BUFFER_END_SELECT:
2834 case LFUN_INSET_BEGIN:
2835 case LFUN_INSET_END:
2836 case LFUN_INSET_BEGIN_SELECT:
2837 case LFUN_INSET_END_SELECT:
2838 case LFUN_INSET_SELECT_ALL:
2839 case LFUN_PARAGRAPH_UP:
2840 case LFUN_PARAGRAPH_DOWN:
2841 case LFUN_LINE_BEGIN:
2843 case LFUN_CHAR_DELETE_FORWARD:
2844 case LFUN_CHAR_DELETE_BACKWARD:
2845 case LFUN_WORD_UPCASE:
2846 case LFUN_WORD_LOWCASE:
2847 case LFUN_WORD_CAPITALIZE:
2848 case LFUN_CHARS_TRANSPOSE:
2849 case LFUN_SERVER_GET_XY:
2850 case LFUN_SERVER_SET_XY:
2851 case LFUN_SERVER_GET_LAYOUT:
2852 case LFUN_SELF_INSERT:
2853 case LFUN_UNICODE_INSERT:
2854 case LFUN_THESAURUS_ENTRY:
2856 // these are handled in our dispatch()
2860 case LFUN_INSET_INSERT: {
2861 string const type = cmd.getArg(0);
2862 if (type == "toc") {
2864 // not allowed in description items
2865 //FIXME: couldn't this be merged in Inset::insetAllowed()?
2866 enable = !inDescriptionItem(cur);
2879 || !cur.inset().insetAllowed(code)
2880 || cur.paragraph().layout().pass_thru))
2883 flag.setEnabled(enable);
2888 void Text::pasteString(Cursor & cur, docstring const & clip,
2891 if (!clip.empty()) {
2894 insertStringAsParagraphs(cur, clip, cur.current_font);
2896 insertStringAsLines(cur, clip, cur.current_font);
2901 // FIXME: an item inset would make things much easier.
2902 bool Text::inDescriptionItem(Cursor & cur) const
2904 Paragraph & par = cur.paragraph();
2905 pos_type const pos = cur.pos();
2906 pos_type const body_pos = par.beginOfBody();
2908 if (par.layout().latextype != LATEX_LIST_ENVIRONMENT
2909 && (par.layout().latextype != LATEX_ITEM_ENVIRONMENT
2910 || par.layout().margintype != MARGIN_FIRST_DYNAMIC))
2913 return (pos < body_pos
2915 && (pos == 0 || par.getChar(pos - 1) != ' ')));