2 * \file InsetMathNest.cpp
3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
8 * Full author contact details are available in file CREDITS.
13 #include "InsetMathNest.h"
15 #include "InsetMathArray.h"
16 #include "InsetMathAMSArray.h"
17 #include "InsetMathBig.h"
18 #include "InsetMathBox.h"
19 #include "InsetMathBrace.h"
20 #include "InsetMathChar.h"
21 #include "InsetMathColor.h"
22 #include "InsetMathComment.h"
23 #include "InsetMathDelim.h"
24 #include "InsetMathEnsureMath.h"
25 #include "InsetMathHull.h"
26 #include "InsetMathRef.h"
27 #include "InsetMathScript.h"
28 #include "InsetMathSpace.h"
29 #include "InsetMathSymbol.h"
30 #include "InsetMathUnknown.h"
31 #include "MathAutoCorrect.h"
32 #include "MathCompletionList.h"
34 #include "MathFactory.h"
35 #include "MathMacro.h"
36 #include "MathMacroArgument.h"
37 #include "MathParser.h"
38 #include "MathStream.h"
39 #include "MathSupport.h"
43 #include "BufferView.h"
44 #include "CoordCache.h"
46 #include "CutAndPaste.h"
47 #include "DispatchResult.h"
49 #include "FuncRequest.h"
50 #include "FuncStatus.h"
53 #include "OutputParams.h"
56 #include "frontends/Clipboard.h"
57 #include "frontends/Painter.h"
58 #include "frontends/Selection.h"
60 #include "support/lassert.h"
61 #include "support/debug.h"
62 #include "support/gettext.h"
63 #include "support/lstrings.h"
64 #include "support/textutils.h"
65 #include "support/docstream.h"
71 using namespace lyx::support
;
75 using cap::copySelection
;
76 using cap::grabAndEraseSelection
;
77 using cap::cutSelection
;
78 using cap::replaceSelection
;
79 using cap::selClearOrDel
;
82 InsetMathNest::InsetMathNest(Buffer
* buf
, idx_type nargs
)
83 : InsetMath(buf
), cells_(nargs
), lock_(false), mouse_hover_(false)
89 InsetMathNest::InsetMathNest(InsetMathNest
const & inset
)
90 : InsetMath(inset
), cells_(inset
.cells_
), lock_(inset
.lock_
),
95 InsetMathNest
& InsetMathNest::operator=(InsetMathNest
const & inset
)
97 cells_
= inset
.cells_
;
100 InsetMath::operator=(inset
);
105 void InsetMathNest::setBuffer(Buffer
& buffer
)
107 InsetMath::setBuffer(buffer
);
108 for (idx_type i
= 0, n
= nargs(); i
!= n
; ++i
) {
109 MathData
& data
= cell(i
);
110 for (size_t j
= 0; j
!= data
.size(); ++j
)
111 data
[j
].nucleus()->setBuffer(buffer
);
116 InsetMath::idx_type
InsetMathNest::nargs() const
118 return cells_
.size();
122 void InsetMathNest::cursorPos(BufferView
const & bv
,
123 CursorSlice
const & sl
, bool /*boundary*/,
124 int & x
, int & y
) const
126 // FIXME: This is a hack. Ideally, the coord cache should not store
127 // absolute positions, but relative ones. This would mean to call
128 // setXY() not in MathData::draw(), but in the parent insets' draw()
129 // with the correctly adjusted x,y values. But this means that we'd have
130 // to touch all (math)inset's draw() methods. Right now, we'll store
131 // absolute value, and make them here relative, only to make them
132 // absolute again when actually drawing the cursor. What a mess.
133 LASSERT(&sl
.inset() == this, /**/);
134 MathData
const & ar
= sl
.cell();
135 CoordCache
const & coord_cache
= bv
.coordCache();
136 if (!coord_cache
.getArrays().has(&ar
)) {
137 // this can (semi-)legally happen if we just created this cell
138 // and it never has been drawn before. So don't ASSERT.
139 //lyxerr << "no cached data for array " << &ar << endl;
144 Point
const pt
= coord_cache
.getArrays().xy(&ar
);
145 if (!coord_cache
.getInsets().has(this)) {
147 //lyxerr << "no cached data for inset " << this << endl;
152 Point
const pt2
= coord_cache
.getInsets().xy(this);
153 //lyxerr << "retrieving position cache for MathData "
154 // << pt.x_ << ' ' << pt.y_ << endl;
155 x
= pt
.x_
- pt2
.x_
+ ar
.pos2x(&bv
, sl
.pos());
157 // lyxerr << "pt.y_ : " << pt.y_ << " pt2_.y_ : " << pt2.y_
158 // << " asc: " << ascent() << " des: " << descent()
159 // << " ar.asc: " << ar.ascent() << " ar.des: " << ar.descent() << endl;
160 // move cursor visually into empty cells ("blue rectangles");
166 void InsetMathNest::metrics(MetricsInfo
const & mi
) const
169 for (idx_type i
= 0, n
= nargs(); i
!= n
; ++i
) {
171 cell(i
).metrics(m
, dim
);
176 bool InsetMathNest::idxNext(Cursor
& cur
) const
178 LASSERT(&cur
.inset() == this, /**/);
179 if (cur
.idx() == cur
.lastidx())
187 bool InsetMathNest::idxForward(Cursor
& cur
) const
193 bool InsetMathNest::idxPrev(Cursor
& cur
) const
195 LASSERT(&cur
.inset() == this, /**/);
199 cur
.pos() = cur
.lastpos();
204 bool InsetMathNest::idxBackward(Cursor
& cur
) const
210 bool InsetMathNest::idxFirst(Cursor
& cur
) const
212 LASSERT(&cur
.inset() == this, /**/);
221 bool InsetMathNest::idxLast(Cursor
& cur
) const
223 LASSERT(&cur
.inset() == this, /**/);
226 cur
.idx() = cur
.lastidx();
227 cur
.pos() = cur
.lastpos();
232 void InsetMathNest::dump() const
234 odocstringstream oss
;
236 os
<< "---------------------------------------------\n";
239 for (idx_type i
= 0, n
= nargs(); i
!= n
; ++i
)
240 os
<< cell(i
) << "\n";
241 os
<< "---------------------------------------------\n";
242 lyxerr
<< to_utf8(oss
.str());
246 void InsetMathNest::draw(PainterInfo
& pi
, int x
, int y
) const
250 pi
.pain
.fillRectangle(x
, y
- ascent(), width(), height(),
253 setPosCache(pi
, x
, y
);
257 void InsetMathNest::drawSelection(PainterInfo
& pi
, int x
, int y
) const
259 BufferView
& bv
= *pi
.base
.bv
;
260 // this should use the x/y values given, not the cached values
261 Cursor
& cur
= bv
.cursor();
262 if (!cur
.selection())
264 if (&cur
.inset() != this)
267 // FIXME: hack to get position cache warm
268 bool const original_drawing_state
= pi
.pain
.isDrawingEnabled();
269 pi
.pain
.setDrawingEnabled(false);
271 pi
.pain
.setDrawingEnabled(original_drawing_state
);
273 CursorSlice s1
= cur
.selBegin();
274 CursorSlice s2
= cur
.selEnd();
276 //lyxerr << "InsetMathNest::drawing selection: "
277 // << " s1: " << s1 << " s2: " << s2 << endl;
278 if (s1
.idx() == s2
.idx()) {
279 MathData
const & c
= cell(s1
.idx());
280 Geometry
const & g
= bv
.coordCache().getArrays().geometry(&c
);
281 int x1
= g
.pos
.x_
+ c
.pos2x(pi
.base
.bv
, s1
.pos());
282 int y1
= g
.pos
.y_
- g
.dim
.ascent();
283 int x2
= g
.pos
.x_
+ c
.pos2x(pi
.base
.bv
, s2
.pos());
284 int y2
= g
.pos
.y_
+ g
.dim
.descent();
285 pi
.pain
.fillRectangle(x1
, y1
, x2
- x1
, y2
- y1
, Color_selection
);
286 //lyxerr << "InsetMathNest::drawing selection 3: "
287 // << " x1: " << x1 << " x2: " << x2
288 // << " y1: " << y1 << " y2: " << y2 << endl;
290 for (idx_type i
= 0; i
< nargs(); ++i
) {
291 if (idxBetween(i
, s1
.idx(), s2
.idx())) {
292 MathData
const & c
= cell(i
);
293 Geometry
const & g
= bv
.coordCache().getArrays().geometry(&c
);
295 int y1
= g
.pos
.y_
- g
.dim
.ascent();
296 int x2
= g
.pos
.x_
+ g
.dim
.width();
297 int y2
= g
.pos
.y_
+ g
.dim
.descent();
298 pi
.pain
.fillRectangle(x1
, y1
, x2
- x1
, y2
- y1
, Color_selection
);
305 void InsetMathNest::validate(LaTeXFeatures
& features
) const
307 for (idx_type i
= 0; i
< nargs(); ++i
)
308 cell(i
).validate(features
);
312 void InsetMathNest::replace(ReplaceData
& rep
)
314 for (idx_type i
= 0; i
< nargs(); ++i
)
315 cell(i
).replace(rep
);
319 bool InsetMathNest::contains(MathData
const & ar
) const
321 for (idx_type i
= 0; i
< nargs(); ++i
)
322 if (cell(i
).contains(ar
))
328 bool InsetMathNest::lock() const
334 void InsetMathNest::lock(bool l
)
340 bool InsetMathNest::isActive() const
346 MathData
InsetMathNest::glue() const
349 for (size_t i
= 0; i
< nargs(); ++i
)
355 void InsetMathNest::write(WriteStream
& os
) const
357 ModeSpecifier
specifier(os
, currentMode(), lockedMode());
358 docstring
const latex_name
= name();
359 os
<< '\\' << latex_name
;
360 for (size_t i
= 0; i
< nargs(); ++i
)
361 os
<< '{' << cell(i
) << '}';
363 os
.pendingSpace(true);
364 if (lock_
&& !os
.latex()) {
366 os
.pendingSpace(true);
371 void InsetMathNest::normalize(NormalStream
& os
) const
374 for (size_t i
= 0; i
< nargs(); ++i
)
375 os
<< ' ' << cell(i
);
380 int InsetMathNest::latex(odocstream
& os
, OutputParams
const & runparams
) const
382 WriteStream
wi(os
, runparams
.moving_arg
, true,
383 runparams
.dryrun
? WriteStream::wsDryrun
: WriteStream::wsDefault
,
390 bool InsetMathNest::setMouseHover(bool mouse_hover
)
392 mouse_hover_
= mouse_hover
;
397 bool InsetMathNest::notifyCursorLeaves(Cursor
const & /*old*/, Cursor
& /*cur*/)
401 MathData
& ar
= cur
.cell();
402 // remove base-only "scripts"
403 for (pos_type i
= 0; i
+ 1 < ar
.size(); ++i
) {
404 InsetMathScript
* p
= operator[](i
).nucleus()->asScriptInset();
405 if (p
&& p
->nargs() == 1) {
406 MathData ar
= p
->nuc();
409 cur
.adjust(i
, ar
.size() - 1);
413 // glue adjacent font insets of the same kind
414 for (pos_type i
= 0; i
+ 1 < size(); ++i
) {
415 InsetMathFont
* p
= operator[](i
).nucleus()->asFontInset();
416 InsetMathFont
const * q
= operator[](i
+ 1)->asFontInset();
417 if (p
&& q
&& p
->name() == q
->name()) {
418 p
->cell(0).append(q
->cell(0));
428 void InsetMathNest::handleFont
429 (Cursor
& cur
, docstring
const & arg
, char const * const font
)
431 handleFont(cur
, arg
, from_ascii(font
));
435 void InsetMathNest::handleFont(Cursor
& cur
, docstring
const & arg
,
436 docstring
const & font
)
438 cur
.recordUndoSelection();
440 // this whole function is a hack and won't work for incremental font
442 if (cur
.inset().asInsetMath()->name() == font
)
443 cur
.handleFont(to_utf8(font
));
445 handleNest(cur
, createInsetMath(font
, cur
.buffer()), arg
);
449 void InsetMathNest::handleNest(Cursor
& cur
, MathAtom
const & nest
)
451 handleNest(cur
, nest
, docstring());
455 void InsetMathNest::handleNest(Cursor
& cur
, MathAtom
const & nest
,
456 docstring
const & arg
)
458 CursorSlice i1
= cur
.selBegin();
459 CursorSlice i2
= cur
.selEnd();
460 if (!i1
.inset().asInsetMath())
462 if (i1
.idx() == i2
.idx()) {
463 // the easy case where only one cell is selected
464 cur
.handleNest(nest
);
469 // multiple selected cells in a simple non-grid inset
470 if (i1
.asInsetMath()->nrows() == 0 || i1
.asInsetMath()->ncols() == 0) {
471 for (idx_type i
= i1
.idx(); i
<= i2
.idx(); ++i
) {
476 cur
.pos() = cur
.lastpos();
479 // change font of cell
480 cur
.handleNest(nest
);
483 // cur is in the font inset now. If the loop continues,
484 // we need to get outside again for the next cell
485 if (i
+ 1 <= i2
.idx())
491 // the complicated case with multiple selected cells in a grid
494 cap::region(i1
, i2
, r1
, r2
, c1
, c2
);
495 for (row_type row
= r1
; row
<= r2
; ++row
) {
496 for (col_type col
= c1
; col
<= c2
; ++col
) {
498 cur
.idx() = i1
.asInsetMath()->index(row
, col
);
501 cur
.pos() = cur
.lastpos();
505 cur
.handleNest(nest
);
508 // cur is in the font inset now. If the loop continues,
509 // we need to get outside again for the next cell
510 if (col
+ 1 <= c2
|| row
+ 1 <= r2
)
517 void InsetMathNest::handleFont2(Cursor
& cur
, docstring
const & arg
)
519 cur
.recordUndoSelection();
522 font
.fromString(to_utf8(arg
), b
);
523 if (font
.fontInfo().color() != Color_inherit
&&
524 font
.fontInfo().color() != Color_ignore
)
525 handleNest(cur
, MathAtom(new InsetMathColor(buffer_
, true, font
.fontInfo().color())));
527 // FIXME: support other font changes here as well?
531 void InsetMathNest::doDispatch(Cursor
& cur
, FuncRequest
& cmd
)
533 //lyxerr << "InsetMathNest: request: " << cmd << endl;
535 Parse::flags parseflg
= Parse::QUIET
| Parse::USETEXT
;
537 switch (cmd
.action
) {
539 case LFUN_CLIPBOARD_PASTE
:
540 parseflg
|= Parse::VERBATIM
;
543 if (cur
.currentMode() <= TEXT_MODE
)
544 parseflg
|= Parse::TEXTMODE
;
545 cur
.recordUndoSelection();
546 cur
.message(_("Paste"));
547 replaceSelection(cur
);
549 if (cmd
.argument().empty() && !theClipboard().isInternal())
550 topaste
= theClipboard().getAsText();
553 idocstringstream
is(cmd
.argument());
555 topaste
= cap::selection(n
);
557 cur
.niceInsert(topaste
, parseflg
);
558 cur
.clearSelection(); // bug 393
565 cutSelection(cur
, true, true);
566 cur
.message(_("Cut"));
567 // Prevent stale position >= size crash
568 // Probably not necessary anymore, see eraseSelection (gb 2005-10-09)
574 cur
.message(_("Copy"));
577 case LFUN_MOUSE_PRESS
:
578 lfunMousePress(cur
, cmd
);
581 case LFUN_MOUSE_MOTION
:
582 lfunMouseMotion(cur
, cmd
);
585 case LFUN_MOUSE_RELEASE
:
586 lfunMouseRelease(cur
, cmd
);
589 case LFUN_FINISHED_LEFT
: // in math, left is backwards
590 case LFUN_FINISHED_BACKWARD
:
591 cur
.bv().cursor() = cur
;
594 case LFUN_FINISHED_RIGHT
: // in math, right is forward
595 case LFUN_FINISHED_FORWARD
:
597 cur
.bv().cursor() = cur
;
600 case LFUN_CHAR_RIGHT
:
602 case LFUN_CHAR_BACKWARD
:
603 case LFUN_CHAR_FORWARD
:
604 cur
.updateFlags(Update::Decoration
| Update::FitCursor
);
605 case LFUN_CHAR_RIGHT_SELECT
:
606 case LFUN_CHAR_LEFT_SELECT
:
607 case LFUN_CHAR_BACKWARD_SELECT
:
608 case LFUN_CHAR_FORWARD_SELECT
: {
609 // are we in a selection?
610 bool select
= (cmd
.action
== LFUN_CHAR_RIGHT_SELECT
611 || cmd
.action
== LFUN_CHAR_LEFT_SELECT
612 || cmd
.action
== LFUN_CHAR_BACKWARD_SELECT
613 || cmd
.action
== LFUN_CHAR_FORWARD_SELECT
);
614 // are we moving forward or backwards?
615 // If the command was RIGHT or LEFT, then whether we're moving forward
616 // or backwards depends on the cursor movement mode (logical or visual):
617 // * in visual mode, since math is always LTR, right -> forward,
619 // * in logical mode, the mapping is determined by the
620 // reverseDirectionNeeded() function
623 FuncCode finish_lfun
;
625 if (cmd
.action
== LFUN_CHAR_FORWARD
626 || cmd
.action
== LFUN_CHAR_FORWARD_SELECT
) {
628 finish_lfun
= LFUN_FINISHED_FORWARD
;
630 else if (cmd
.action
== LFUN_CHAR_BACKWARD
631 || cmd
.action
== LFUN_CHAR_BACKWARD_SELECT
) {
633 finish_lfun
= LFUN_FINISHED_BACKWARD
;
636 bool right
= (cmd
.action
== LFUN_CHAR_RIGHT_SELECT
637 || cmd
.action
== LFUN_CHAR_RIGHT
);
638 if (lyxrc
.visual_cursor
|| !reverseDirectionNeeded(cur
))
644 finish_lfun
= LFUN_FINISHED_RIGHT
;
646 finish_lfun
= LFUN_FINISHED_LEFT
;
648 // Now that we know exactly what we want to do, let's do it!
649 cur
.selHandle(select
);
651 cur
.macroModeClose();
652 // try moving forward or backwards as necessary...
653 if (!(forward
? cursorMathForward(cur
) : cursorMathBackward(cur
))) {
654 // ... and if movement failed, then finish forward or backwards
656 cmd
= FuncRequest(finish_lfun
);
664 cur
.updateFlags(Update::Decoration
| Update::FitCursor
);
665 case LFUN_DOWN_SELECT
:
666 case LFUN_UP_SELECT
: {
667 // close active macro
668 if (cur
.inMacroMode()) {
669 cur
.macroModeClose();
673 // stop/start the selection
674 bool select
= cmd
.action
== LFUN_DOWN_SELECT
||
675 cmd
.action
== LFUN_UP_SELECT
;
676 cur
.selHandle(select
);
679 bool up
= cmd
.action
== LFUN_UP
|| cmd
.action
== LFUN_UP_SELECT
;
680 bool successful
= cur
.upDownInMath(up
);
684 if (cur
.fixIfBroken())
685 // FIXME: Something bad happened. We pass the corrected Cursor
686 // instead of letting things go worse.
689 // We did not manage to move the cursor.
694 case LFUN_MOUSE_DOUBLE
:
695 case LFUN_MOUSE_TRIPLE
:
696 case LFUN_WORD_SELECT
:
700 cur
.setSelection(true);
701 cur
.pos() = cur
.lastpos();
702 cur
.idx() = cur
.lastidx();
705 case LFUN_PARAGRAPH_UP
:
706 case LFUN_PARAGRAPH_DOWN
:
707 cur
.updateFlags(Update::Decoration
| Update::FitCursor
);
708 case LFUN_PARAGRAPH_UP_SELECT
:
709 case LFUN_PARAGRAPH_DOWN_SELECT
:
712 case LFUN_LINE_BEGIN
:
713 case LFUN_WORD_BACKWARD
:
715 cur
.updateFlags(Update::Decoration
| Update::FitCursor
);
716 case LFUN_LINE_BEGIN_SELECT
:
717 case LFUN_WORD_BACKWARD_SELECT
:
718 case LFUN_WORD_LEFT_SELECT
:
719 cur
.selHandle(cmd
.action
== LFUN_WORD_BACKWARD_SELECT
||
720 cmd
.action
== LFUN_WORD_LEFT_SELECT
||
721 cmd
.action
== LFUN_LINE_BEGIN_SELECT
);
722 cur
.macroModeClose();
723 if (cur
.pos() != 0) {
725 } else if (cur
.col() != 0) {
726 cur
.idx() -= cur
.col();
728 } else if (cur
.idx() != 0) {
732 cmd
= FuncRequest(LFUN_FINISHED_BACKWARD
);
737 case LFUN_WORD_FORWARD
:
738 case LFUN_WORD_RIGHT
:
740 cur
.updateFlags(Update::Decoration
| Update::FitCursor
);
741 case LFUN_WORD_FORWARD_SELECT
:
742 case LFUN_WORD_RIGHT_SELECT
:
743 case LFUN_LINE_END_SELECT
:
744 cur
.selHandle(cmd
.action
== LFUN_WORD_FORWARD_SELECT
||
745 cmd
.action
== LFUN_WORD_RIGHT_SELECT
||
746 cmd
.action
== LFUN_LINE_END_SELECT
);
747 cur
.macroModeClose();
749 if (cur
.pos() != cur
.lastpos()) {
750 cur
.pos() = cur
.lastpos();
751 } else if (ncols() && (cur
.col() != cur
.lastcol())) {
752 cur
.idx() = cur
.idx() - cur
.col() + cur
.lastcol();
753 cur
.pos() = cur
.lastpos();
754 } else if (cur
.idx() != cur
.lastidx()) {
755 cur
.idx() = cur
.lastidx();
756 cur
.pos() = cur
.lastpos();
758 cmd
= FuncRequest(LFUN_FINISHED_FORWARD
);
763 case LFUN_CELL_FORWARD
:
764 cur
.updateFlags(Update::Decoration
| Update::FitCursor
);
765 cur
.inset().idxNext(cur
);
768 case LFUN_CELL_BACKWARD
:
769 cur
.updateFlags(Update::Decoration
| Update::FitCursor
);
770 cur
.inset().idxPrev(cur
);
773 case LFUN_WORD_DELETE_BACKWARD
:
774 case LFUN_CHAR_DELETE_BACKWARD
:
776 // May affect external cell:
777 cur
.recordUndoInset();
779 cur
.recordUndoSelection();
780 // if the inset can not be removed from within, delete it
781 if (!cur
.backspace()) {
782 FuncRequest cmd
= FuncRequest(LFUN_CHAR_DELETE_FORWARD
);
783 cur
.innerText()->dispatch(cur
, cmd
);
787 case LFUN_WORD_DELETE_FORWARD
:
788 case LFUN_CHAR_DELETE_FORWARD
:
789 if (cur
.pos() == cur
.lastpos())
790 // May affect external cell:
791 cur
.recordUndoInset();
793 cur
.recordUndoSelection();
794 // if the inset can not be removed from within, delete it
796 FuncRequest cmd
= FuncRequest(LFUN_CHAR_DELETE_FORWARD
);
797 cur
.innerText()->dispatch(cur
, cmd
);
803 cur
.clearSelection();
805 cmd
= FuncRequest(LFUN_FINISHED_FORWARD
);
810 // 'Locks' the math inset. A 'locked' math inset behaves as a unit
811 // that is traversed by a single <CursorLeft>/<CursorRight>.
812 case LFUN_INSET_TOGGLE
:
818 case LFUN_SELF_INSERT
:
819 if (cmd
.argument().size() != 1) {
820 cur
.recordUndoSelection();
821 docstring
const arg
= cmd
.argument();
822 if (!interpretString(cur
, arg
))
826 // Don't record undo steps if we are in macro mode and thus
827 // cmd.argument is the next character of the macro name.
828 // Otherwise we'll get an invalid cursor if we undo after
829 // the macro was finished and the macro is a known command,
830 // e.g. sqrt. Cursor::macroModeClose replaces in this case
831 // the InsetMathUnknown with name "frac" by an empty
832 // InsetMathFrac -> a pos value > 0 is invalid.
833 // A side effect is that an undo before the macro is finished
834 // undoes the complete macro, not only the last character.
835 // At the time we hit '\' we are not in macro mode, still.
836 if (!cur
.inMacroMode())
837 cur
.recordUndoSelection();
839 // spacial handling of space. If we insert an inset
840 // via macro mode, we want to put the cursor inside it
841 // if relevant. Think typing "\frac<space>".
842 if (cmd
.argument()[0] == ' '
843 && cur
.inMacroMode() && cur
.macroName() != "\\"
844 && cur
.macroModeClose()) {
845 MathAtom
const atom
= cur
.prevAtom();
846 if (atom
->asNestInset() && atom
->isActive()) {
848 cur
.pushBackward(*cur
.nextInset());
850 } else if (!interpretChar(cur
, cmd
.argument()[0])) {
851 cmd
= FuncRequest(LFUN_FINISHED_FORWARD
);
856 //case LFUN_SERVER_GET_XY:
859 case LFUN_SERVER_SET_XY
: {
860 lyxerr
<< "LFUN_SERVER_SET_XY broken!" << endl
;
863 istringstream
is(to_utf8(cmd
.argument()));
865 cur
.setScreenPos(x
, y
);
869 // Special casing for superscript in case of LyX handling
871 case LFUN_ACCENT_CIRCUMFLEX
:
872 if (cmd
.argument().empty()) {
873 // do superscript if LyX handles
875 cur
.recordUndoSelection();
876 script(cur
, true, grabAndEraseSelection(cur
));
880 case LFUN_ACCENT_UMLAUT
:
881 case LFUN_ACCENT_ACUTE
:
882 case LFUN_ACCENT_GRAVE
:
883 case LFUN_ACCENT_BREVE
:
884 case LFUN_ACCENT_DOT
:
885 case LFUN_ACCENT_MACRON
:
886 case LFUN_ACCENT_CARON
:
887 case LFUN_ACCENT_TILDE
:
888 case LFUN_ACCENT_CEDILLA
:
889 case LFUN_ACCENT_CIRCLE
:
890 case LFUN_ACCENT_UNDERDOT
:
891 case LFUN_ACCENT_TIE
:
892 case LFUN_ACCENT_OGONEK
:
893 case LFUN_ACCENT_HUNGARIAN_UMLAUT
:
897 case LFUN_TEXTSTYLE_APPLY
:
898 case LFUN_TEXTSTYLE_UPDATE
:
899 handleFont2(cur
, cmd
.argument());
903 if (currentMode() <= TEXT_MODE
)
904 handleFont(cur
, cmd
.argument(), "textbf");
906 handleFont(cur
, cmd
.argument(), "mathbf");
908 case LFUN_FONT_BOLDSYMBOL
:
909 if (currentMode() <= TEXT_MODE
)
910 handleFont(cur
, cmd
.argument(), "textbf");
912 handleFont(cur
, cmd
.argument(), "boldsymbol");
915 if (currentMode() <= TEXT_MODE
)
916 handleFont(cur
, cmd
.argument(), "textsf");
918 handleFont(cur
, cmd
.argument(), "mathsf");
921 if (currentMode() <= TEXT_MODE
)
922 handleFont(cur
, cmd
.argument(), "emph");
924 handleFont(cur
, cmd
.argument(), "mathcal");
926 case LFUN_FONT_ROMAN
:
927 if (currentMode() <= TEXT_MODE
)
928 handleFont(cur
, cmd
.argument(), "textrm");
930 handleFont(cur
, cmd
.argument(), "mathrm");
932 case LFUN_FONT_TYPEWRITER
:
933 if (currentMode() <= TEXT_MODE
)
934 handleFont(cur
, cmd
.argument(), "texttt");
936 handleFont(cur
, cmd
.argument(), "mathtt");
939 handleFont(cur
, cmd
.argument(), "mathfrak");
942 if (currentMode() <= TEXT_MODE
)
943 handleFont(cur
, cmd
.argument(), "textit");
945 handleFont(cur
, cmd
.argument(), "mathit");
948 if (currentMode() <= TEXT_MODE
)
949 // FIXME: should be "noun"
950 handleFont(cur
, cmd
.argument(), "textsc");
952 handleFont(cur
, cmd
.argument(), "mathbb");
954 case LFUN_FONT_DEFAULT
:
955 handleFont(cur
, cmd
.argument(), "textnormal");
958 case LFUN_FONT_UNDERLINE
:
960 cur
.handleNest(createInsetMath("underline", cur
.buffer()));
962 case LFUN_MATH_MODE
: {
964 // ignore math-mode on when already in math mode
965 if (currentMode() == Inset::MATH_MODE
&& cmd
.argument() == "on")
967 cur
.recordUndoSelection();
968 cur
.macroModeClose();
969 docstring
const save_selection
= grabAndEraseSelection(cur
);
971 //cur.plainInsert(MathAtom(new InsetMathMBox(cur.bv())));
972 if (currentMode() <= Inset::TEXT_MODE
)
973 cur
.plainInsert(MathAtom(new InsetMathEnsureMath(buffer_
)));
975 cur
.plainInsert(MathAtom(new InsetMathBox(buffer_
, from_ascii("mbox"))));
977 cur
.pushBackward(*cur
.nextInset());
978 cur
.niceInsert(save_selection
);
980 if (currentMode() == Inset::TEXT_MODE
) {
981 cur
.recordUndoSelection();
982 cur
.niceInsert(MathAtom(new InsetMathHull("simple", cur
.buffer())));
983 cur
.message(_("create new math text environment ($...$)"));
985 handleFont(cur
, cmd
.argument(), "textrm");
986 cur
.message(_("entered math text mode (textrm)"));
992 case LFUN_REGEXP_MODE
: {
993 InsetMathHull
* i
= dynamic_cast<InsetMathHull
*>(cur
.inset().asInsetMath());
994 if (i
&& i
->getType() == hullRegexp
) {
995 cur
.message(_("Already in regexp mode"));
998 cur
.macroModeClose();
999 docstring
const save_selection
= grabAndEraseSelection(cur
);
1001 cur
.plainInsert(MathAtom(new InsetMathHull(buffer_
, hullRegexp
)));
1003 cur
.pushBackward(*cur
.nextInset());
1004 cur
.niceInsert(save_selection
);
1005 cur
.message(_("Regexp editor mode"));
1009 case LFUN_MATH_FONT_STYLE
: {
1010 FuncRequest fr
= FuncRequest(LFUN_MATH_INSERT
, '\\' + cmd
.argument());
1011 doDispatch(cur
, fr
);
1015 case LFUN_MATH_SIZE
: {
1016 FuncRequest fr
= FuncRequest(LFUN_MATH_INSERT
, cmd
.argument());
1017 doDispatch(cur
, fr
);
1021 case LFUN_MATH_MATRIX
: {
1027 idocstringstream
is(cmd
.argument());
1028 is
>> m
>> n
>> v_align
>> h_align
;
1034 cur
.niceInsert(MathAtom(new InsetMathArray(buffer_
,
1035 from_ascii("array"), m
, n
, (char)v_align
[0], h_align
)));
1039 case LFUN_MATH_AMS_MATRIX
: {
1044 idocstringstream
is(cmd
.argument());
1045 is
>> m
>> n
>> name
;
1051 MathAtom(new InsetMathAMSArray(buffer_
, name
, m
, n
)));
1055 case LFUN_MATH_DELIM
: {
1057 docstring rs
= split(cmd
.argument(), ls
, ' ');
1058 // Reasonable default values
1064 cur
.handleNest(MathAtom(new InsetMathDelim(buffer_
, ls
, rs
)));
1068 case LFUN_MATH_BIGDELIM
: {
1069 docstring
const lname
= from_utf8(cmd
.getArg(0));
1070 docstring
const ldelim
= from_utf8(cmd
.getArg(1));
1071 docstring
const rname
= from_utf8(cmd
.getArg(2));
1072 docstring
const rdelim
= from_utf8(cmd
.getArg(3));
1073 latexkeys
const * l
= in_word_set(lname
);
1074 bool const have_l
= l
&& l
->inset
== "big" &&
1075 InsetMathBig::isBigInsetDelim(ldelim
);
1076 l
= in_word_set(rname
);
1077 bool const have_r
= l
&& l
->inset
== "big" &&
1078 InsetMathBig::isBigInsetDelim(rdelim
);
1079 // We mimic LFUN_MATH_DELIM in case we have an empty left
1080 // or right delimiter.
1081 if (have_l
|| have_r
) {
1083 docstring
const selection
= grabAndEraseSelection(cur
);
1086 cur
.insert(MathAtom(new InsetMathBig(lname
,
1088 cur
.niceInsert(selection
);
1090 cur
.insert(MathAtom(new InsetMathBig(rname
,
1093 // Don't call cur.undispatched() if we did nothing, this would
1094 // lead to infinite recursion via Text::dispatch().
1098 case LFUN_SPACE_INSERT
:
1099 cur
.recordUndoSelection();
1100 cur
.insert(MathAtom(new InsetMathSpace
));
1103 case LFUN_MATH_SPACE
:
1104 cur
.recordUndoSelection();
1105 if (cmd
.argument().empty())
1106 cur
.insert(MathAtom(new InsetMathSpace
));
1108 string
const name
= cmd
.getArg(0);
1109 string
const len
= cmd
.getArg(1);
1110 cur
.insert(MathAtom(new InsetMathSpace(name
, len
)));
1114 case LFUN_ERT_INSERT
:
1115 // interpret this as if a backslash was typed
1117 interpretChar(cur
, '\\');
1120 case LFUN_MATH_SUBSCRIPT
:
1121 // interpret this as if a _ was typed
1122 cur
.recordUndoSelection();
1123 interpretChar(cur
, '_');
1126 case LFUN_MATH_SUPERSCRIPT
:
1127 // interpret this as if a ^ was typed
1128 cur
.recordUndoSelection();
1129 interpretChar(cur
, '^');
1132 case LFUN_MATH_MACRO_FOLD
:
1133 case LFUN_MATH_MACRO_UNFOLD
: {
1135 bool fold
= cmd
.action
== LFUN_MATH_MACRO_FOLD
;
1136 bool found
= findMacroToFoldUnfold(it
, fold
);
1138 MathMacro
* macro
= it
.nextInset()->asInsetMath()->asMacro();
1139 cur
.recordUndoInset();
1148 case LFUN_QUOTE_INSERT
:
1149 // interpret this as if a straight " was typed
1150 cur
.recordUndoSelection();
1151 interpretChar(cur
, '\"');
1154 // FIXME: We probably should swap parts of "math-insert" and "self-insert"
1155 // handling such that "self-insert" works on "arbitrary stuff" too, and
1156 // math-insert only handles special math things like "matrix".
1157 case LFUN_MATH_INSERT
: {
1158 cur
.recordUndoSelection();
1159 if (cmd
.argument() == "^" || cmd
.argument() == "_")
1160 interpretChar(cur
, cmd
.argument()[0]);
1163 asArray(cmd
.argument(), ar
);
1164 if (cur
.selection() && ar
.size() == 1
1165 && ar
[0]->asNestInset()
1166 && ar
[0]->asNestInset()->nargs() > 1)
1167 handleNest(cur
, ar
[0]);
1169 cur
.niceInsert(cmd
.argument());
1174 case LFUN_DIALOG_SHOW_NEW_INSET
: {
1175 docstring
const & name
= cmd
.argument();
1177 if (name
== "ref") {
1178 InsetMathRef
tmp(buffer_
, name
);
1179 data
= tmp
.createDialogStr(to_utf8(name
));
1180 } else if (name
== "mathspace") {
1182 data
= tmp
.createDialogStr();
1184 cur
.bv().showDialog(to_utf8(name
), data
);
1188 case LFUN_INSET_INSERT
: {
1190 if (createInsetMath_fromDialogStr(cmd
.argument(), ar
)) {
1191 cur
.recordUndoSelection();
1197 case LFUN_INSET_DISSOLVE
:
1198 if (!asHullInset()) {
1199 cur
.recordUndoInset();
1205 InsetMath::doDispatch(cur
, cmd
);
1211 bool InsetMathNest::findMacroToFoldUnfold(Cursor
& it
, bool fold
) const {
1212 // look for macro to open/close, but stay in mathed
1213 for (; !it
.empty(); it
.pop_back()) {
1215 // go backward through the current cell
1216 Inset
* inset
= it
.nextInset();
1217 while (inset
&& inset
->asInsetMath()) {
1218 MathMacro
* macro
= inset
->asInsetMath()->asMacro();
1220 // found the an macro to open/close?
1221 if (macro
->folded() != fold
)
1224 // Wrong folding state.
1225 // If this was the first we see in this slice, look further left,
1227 if (inset
!= it
.nextInset())
1231 // go up if this was the left most position
1237 inset
= it
.nextInset();
1245 bool InsetMathNest::getStatus(Cursor
& cur
, FuncRequest
const & cmd
,
1246 FuncStatus
& flag
) const
1248 // the font related toggles
1249 //string tc = "mathnormal";
1251 string
const arg
= to_utf8(cmd
.argument());
1252 switch (cmd
.action
) {
1253 case LFUN_TABULAR_FEATURE
:
1254 flag
.setEnabled(false);
1257 case LFUN_TABULAR_FEATURE
:
1258 // FIXME: check temporarily disabled
1260 char align
= mathcursor::valign();
1261 if (align
== '\0') {
1265 if (cmd
.argument().empty()) {
1269 if (!contains("tcb", cmd
.argument()[0])) {
1273 flag
.setOnOff(cmd
.argument()[0] == align
);
1276 /// We have to handle them since 1.4 blocks all unhandled actions
1277 case LFUN_FONT_ITAL
:
1278 case LFUN_FONT_BOLD
:
1279 case LFUN_FONT_BOLDSYMBOL
:
1280 case LFUN_FONT_SANS
:
1281 case LFUN_FONT_EMPH
:
1282 case LFUN_FONT_TYPEWRITER
:
1283 case LFUN_FONT_NOUN
:
1284 case LFUN_FONT_ROMAN
:
1285 case LFUN_FONT_DEFAULT
:
1286 flag
.setEnabled(true);
1289 // we just need to be in math mode to enable that
1290 case LFUN_MATH_SIZE
:
1291 case LFUN_MATH_SPACE
:
1292 case LFUN_MATH_LIMITS
:
1293 case LFUN_MATH_EXTERN
:
1294 flag
.setEnabled(true);
1297 case LFUN_FONT_UNDERLINE
:
1298 case LFUN_FONT_FRAK
:
1299 flag
.setEnabled(currentMode() != TEXT_MODE
);
1302 case LFUN_MATH_FONT_STYLE
: {
1303 bool const textarg
=
1304 arg
== "textbf" || arg
== "textsf" ||
1305 arg
== "textrm" || arg
== "textmd" ||
1306 arg
== "textit" || arg
== "textsc" ||
1307 arg
== "textsl" || arg
== "textup" ||
1308 arg
== "texttt" || arg
== "textbb" ||
1309 arg
== "textnormal";
1310 flag
.setEnabled(currentMode() != TEXT_MODE
|| textarg
);
1314 case LFUN_MATH_INSERT
:
1315 flag
.setEnabled(currentMode() != TEXT_MODE
);
1318 case LFUN_MATH_AMS_MATRIX
:
1319 case LFUN_MATH_MATRIX
:
1320 flag
.setEnabled(currentMode() == MATH_MODE
);
1323 case LFUN_INSET_INSERT
: {
1324 // Don't test createMathInset_fromDialogStr(), since
1325 // getStatus is not called with a valid reference and the
1326 // dialog would not be applyable.
1327 string
const name
= cmd
.getArg(0);
1328 flag
.setEnabled(name
== "ref" || name
== "mathspace");
1332 case LFUN_MATH_DELIM
:
1333 case LFUN_MATH_BIGDELIM
:
1334 // Don't do this with multi-cell selections
1335 flag
.setEnabled(cur
.selBegin().idx() == cur
.selEnd().idx());
1338 case LFUN_MATH_MACRO_FOLD
:
1339 case LFUN_MATH_MACRO_UNFOLD
: {
1341 bool found
= findMacroToFoldUnfold(it
, cmd
.action
== LFUN_MATH_MACRO_FOLD
);
1342 flag
.setEnabled(found
);
1346 case LFUN_SPECIALCHAR_INSERT
:
1347 // FIXME: These would probably make sense in math-text mode
1348 flag
.setEnabled(false);
1351 case LFUN_INSET_DISSOLVE
:
1352 flag
.setEnabled(!asHullInset());
1363 void InsetMathNest::edit(Cursor
& cur
, bool front
, EntryDirection entry_from
)
1366 bool enter_front
= (entry_from
== Inset::ENTRY_DIRECTION_RIGHT
||
1367 (entry_from
== Inset::ENTRY_DIRECTION_IGNORE
&& front
));
1368 cur
.idx() = enter_front
? 0 : cur
.lastidx();
1369 cur
.pos() = enter_front
? 0 : cur
.lastpos();
1371 //lyxerr << "InsetMathNest::edit, cur:\n" << cur << endl;
1375 Inset
* InsetMathNest::editXY(Cursor
& cur
, int x
, int y
)
1378 int dist_min
= 1000000;
1379 for (idx_type i
= 0, n
= nargs(); i
!= n
; ++i
) {
1380 int const d
= cell(i
).dist(cur
.bv(), x
, y
);
1386 MathData
& ar
= cell(idx_min
);
1388 cur
.idx() = idx_min
;
1389 cur
.pos() = ar
.x2pos(&cur
.bv(), x
- ar
.xo(cur
.bv()));
1391 //lyxerr << "found cell : " << idx_min << " pos: " << cur.pos() << endl;
1392 if (dist_min
== 0) {
1394 for (pos_type i
= 0, n
= ar
.size(); i
< n
; ++i
)
1395 if (ar
[i
]->covers(cur
.bv(), x
, y
))
1396 return ar
[i
].nucleus()->editXY(cur
, x
, y
);
1402 void InsetMathNest::lfunMousePress(Cursor
& cur
, FuncRequest
& cmd
)
1404 //lyxerr << "## lfunMousePress: buttons: " << cmd.button() << endl;
1405 BufferView
& bv
= cur
.bv();
1406 bool do_selection
= cmd
.button() == mouse_button::button1
1407 && cmd
.argument() == "region-select";
1408 bv
.mouseSetCursor(cur
, do_selection
);
1409 if (cmd
.button() == mouse_button::button1
) {
1410 //lyxerr << "## lfunMousePress: setting cursor to: " << cur << endl;
1411 // Update the cursor update flags as needed:
1413 // Update::Decoration: tells to update the decoration
1414 // (visual box corners that define
1416 // Update::FitCursor: adjust the screen to the cursor
1417 // position if needed
1418 // cur.result().update(): don't overwrite previously set flags.
1419 cur
.updateFlags(Update::Decoration
| Update::FitCursor
1420 | cur
.result().update());
1421 } else if (cmd
.button() == mouse_button::button2
) {
1422 if (cap::selection()) {
1423 // See comment in Text::dispatch why we do this
1424 cap::copySelectionToStack();
1425 cmd
= FuncRequest(LFUN_PASTE
, "0");
1426 doDispatch(bv
.cursor(), cmd
);
1429 asArray(theSelection().get(), ar
);
1430 bv
.cursor().insert(ar
);
1436 void InsetMathNest::lfunMouseMotion(Cursor
& cur
, FuncRequest
& cmd
)
1438 // only select with button 1
1439 if (cmd
.button() == mouse_button::button1
) {
1440 Cursor
& bvcur
= cur
.bv().cursor();
1441 if (bvcur
.anchor_
.hasPart(cur
)) {
1442 //lyxerr << "## lfunMouseMotion: cursor: " << cur << endl;
1443 bvcur
.setCursor(cur
);
1444 bvcur
.setSelection(true);
1445 //lyxerr << "MOTION " << bvcur << endl;
1452 void InsetMathNest::lfunMouseRelease(Cursor
& cur
, FuncRequest
& cmd
)
1454 //lyxerr << "## lfunMouseRelease: buttons: " << cmd.button() << endl;
1456 if (cmd
.button() == mouse_button::button1
) {
1457 if (!cur
.selection())
1460 Cursor
& bvcur
= cur
.bv().cursor();
1461 bvcur
.setSelection(true);
1470 bool InsetMathNest::interpretChar(Cursor
& cur
, char_type
const c
)
1472 //lyxerr << "interpret 2: '" << c << "'" << endl;
1473 docstring save_selection
;
1474 if (c
== '^' || c
== '_')
1475 save_selection
= grabAndEraseSelection(cur
);
1478 Buffer
* buf
= cur
.buffer();
1481 if (cur
.inMacroMode()) {
1482 docstring name
= cur
.macroName();
1484 /// are we currently typing '#1' or '#2' or...?
1485 if (name
== "\\#") {
1488 if (n
>= 1 && n
<= 9)
1489 cur
.insert(new MathMacroArgument(n
));
1493 // do not finish macro for known * commands
1494 MathWordList
const & mwl
= mathedWordList();
1495 bool star_macro
= c
== '*'
1496 && (mwl
.find(name
.substr(1) + "*") != mwl
.end()
1497 || cur
.buffer()->getMacro(name
.substr(1) + "*", cur
, true));
1498 if (isAlphaASCII(c
) || star_macro
) {
1499 cur
.activeMacro()->setName(name
+ docstring(1, c
));
1503 // handle 'special char' macros
1508 if (currentMode() <= InsetMath::TEXT_MODE
)
1509 cur
.niceInsert(createInsetMath("textbackslash", buf
));
1511 cur
.niceInsert(createInsetMath("backslash", buf
));
1512 } else if (c
== '^' && currentMode() == InsetMath::MATH_MODE
) {
1514 cur
.niceInsert(createInsetMath("mathcircumflex", buf
));
1515 } else if (c
== '{') {
1517 cur
.niceInsert(MathAtom(new InsetMathBrace(buf
)));
1518 } else if (c
== '%') {
1520 cur
.niceInsert(MathAtom(new InsetMathComment(buf
)));
1521 } else if (c
== '#') {
1522 LASSERT(cur
.activeMacro(), /**/);
1523 cur
.activeMacro()->setName(name
+ docstring(1, c
));
1526 cur
.niceInsert(createInsetMath(docstring(1, c
), buf
));
1531 // One character big delimiters. The others are handled in
1532 // interpretString().
1533 latexkeys
const * l
= in_word_set(name
.substr(1));
1534 if (name
[0] == '\\' && l
&& l
->inset
== "big") {
1538 delim
= from_ascii("\\{");
1541 delim
= from_ascii("\\}");
1544 delim
= docstring(1, c
);
1547 if (InsetMathBig::isBigInsetDelim(delim
)) {
1548 // name + delim ared a valid InsetMathBig.
1549 // We can't use cur.macroModeClose() because
1550 // it does not handle delim.
1551 InsetMathUnknown
* p
= cur
.activeMacro();
1554 cur
.cell().erase(cur
.pos());
1555 cur
.plainInsert(MathAtom(
1556 new InsetMathBig(name
.substr(1), delim
)));
1561 // leave macro mode and try again if necessary
1562 if (cur
.macroModeClose()) {
1563 MathAtom
const atom
= cur
.prevAtom();
1564 if (atom
->asNestInset() && atom
->isActive()) {
1566 cur
.pushBackward(*cur
.nextInset());
1570 cur
.niceInsert(MathAtom(new InsetMathBrace(buf
)));
1572 interpretChar(cur
, c
);
1577 // leave autocorrect mode if necessary
1578 if (lyxrc
.autocorrection_math
&& c
== ' ' && cur
.autocorrect()) {
1579 cur
.autocorrect() = false;
1580 cur
.message(_("Autocorrect Off ('!' to enter)"));
1583 if (lyxrc
.autocorrection_math
&& c
== '!' && !cur
.autocorrect()) {
1584 cur
.autocorrect() = true;
1585 cur
.message(_("Autocorrect On (<space> to exit)"));
1589 // just clear selection on pressing the space bar
1590 if (cur
.selection() && c
== ' ') {
1591 cur
.setSelection(false);
1596 //lyxerr << "starting with macro" << endl;
1597 bool reduced
= cap::reduceSelectionToOneCell(cur
);
1598 if (reduced
|| !cur
.selection()) {
1599 docstring
const safe
= cap::grabAndEraseSelection(cur
);
1600 cur
.insert(MathAtom(new InsetMathUnknown(from_ascii("\\"), safe
, false)));
1608 if (currentMode() <= InsetMath::TEXT_MODE
)
1614 if (currentMode() <= InsetMath::TEXT_MODE
) {
1615 // insert spaces in text or undecided mode,
1616 // but suppress direct insertion of two spaces in a row
1617 // the still allows typing '<space>a<space>' and deleting the 'a', but
1618 // it is better than nothing...
1619 if (!cur
.pos() != 0 || cur
.prevAtom()->getChar() != ' ') {
1621 // FIXME: we have to enable full redraw here because of the
1622 // visual box corners that define the inset. If we know for
1623 // sure that we stay within the same cell we can optimize for
1625 //cur.updateFlags(Update::SinglePar | Update::FitCursor);
1629 if (cur
.pos() != 0 && cur
.prevAtom()->asSpaceInset()) {
1630 cur
.prevAtom().nucleus()->asSpaceInset()->incSpace();
1631 // FIXME: we have to enable full redraw here because of the
1632 // visual box corners that define the inset. If we know for
1633 // sure that we stay within the same cell we can optimize for
1635 //cur.updateFlags(Update::SinglePar | Update::FitCursor);
1639 if (cur
.popForward()) {
1640 // FIXME: we have to enable full redraw here because of the
1641 // visual box corners that define the inset. If we know for
1642 // sure that we stay within the same cell we can optimize for
1644 //cur.updateFlags(Update::FitCursor);
1648 // if we are at the very end, leave the formula
1649 return cur
.pos() != cur
.lastpos();
1652 // These should be treated differently when not in text mode:
1653 if (currentMode() != InsetMath::TEXT_MODE
) {
1655 script(cur
, false, save_selection
);
1659 script(cur
, true, save_selection
);
1663 cur
.niceInsert(createInsetMath("sim", buf
));
1666 if (currentMode() == InsetMath::MATH_MODE
&& !isAsciiOrMathAlpha(c
)) {
1667 MathAtom at
= createInsetMath("text", buf
);
1668 at
.nucleus()->cell(0).push_back(MathAtom(new InsetMathChar(c
)));
1675 cur
.niceInsert(createInsetMath("textasciicircum", buf
));
1679 cur
.niceInsert(createInsetMath("textasciitilde", buf
));
1684 if (c
== '{' || c
== '}' || c
== '&' || c
== '$' || c
== '#' ||
1685 c
== '%' || c
== '_') {
1686 cur
.niceInsert(createInsetMath(docstring(1, c
), buf
));
1691 // try auto-correction
1692 if (lyxrc
.autocorrection_math
&& cur
.autocorrect() && cur
.pos() != 0
1693 && math_autocorrect(cur
.prevAtom(), c
))
1696 // no special circumstances, so insert the character without any fuss
1698 if (lyxrc
.autocorrection_math
) {
1699 if (!cur
.autocorrect())
1700 cur
.message(_("Autocorrect Off ('!' to enter)"));
1702 cur
.message(_("Autocorrect On (<space> to exit)"));
1708 bool InsetMathNest::interpretString(Cursor
& cur
, docstring
const & str
)
1710 // Create a InsetMathBig from cur.cell()[cur.pos() - 1] and t if
1712 if (!cur
.empty() && cur
.pos() > 0 &&
1713 cur
.cell()[cur
.pos() - 1]->asUnknownInset()) {
1714 if (InsetMathBig::isBigInsetDelim(str
)) {
1715 docstring prev
= asString(cur
.cell()[cur
.pos() - 1]);
1716 if (prev
[0] == '\\') {
1717 prev
= prev
.substr(1);
1718 latexkeys
const * l
= in_word_set(prev
);
1719 if (l
&& l
->inset
== "big") {
1720 cur
.cell()[cur
.pos() - 1] =
1721 MathAtom(new InsetMathBig(prev
, str
));
1731 bool InsetMathNest::script(Cursor
& cur
, bool up
)
1733 return script(cur
, up
, docstring());
1737 bool InsetMathNest::script(Cursor
& cur
, bool up
,
1738 docstring
const & save_selection
)
1740 // Hack to get \^ and \_ working
1741 //lyxerr << "handling script: up: " << up << endl;
1742 if (cur
.inMacroMode() && cur
.macroName() == "\\") {
1744 cur
.niceInsert(createInsetMath("mathcircumflex", cur
.buffer()));
1746 interpretChar(cur
, '_');
1750 cur
.macroModeClose();
1751 if (asScriptInset() && cur
.idx() == 0) {
1752 // we are in a nucleus of a script inset, move to _our_ script
1753 InsetMathScript
* inset
= asScriptInset();
1754 //lyxerr << " going to cell " << inset->idxOfScript(up) << endl;
1756 cur
.idx() = inset
->idxOfScript(up
);
1758 } else if (cur
.pos() != 0 && cur
.prevAtom()->asScriptInset()) {
1760 InsetMathScript
* inset
= cur
.nextAtom().nucleus()->asScriptInset();
1763 cur
.idx() = inset
->idxOfScript(up
);
1764 cur
.pos() = cur
.lastpos();
1766 // convert the thing to our left to a scriptinset or create a new
1767 // one if in the very first position of the array
1768 if (cur
.pos() == 0) {
1769 //lyxerr << "new scriptinset" << endl;
1770 cur
.insert(new InsetMathScript(buffer_
, up
));
1772 //lyxerr << "converting prev atom " << endl;
1773 cur
.prevAtom() = MathAtom(new InsetMathScript(buffer_
, cur
.prevAtom(), up
));
1776 InsetMathScript
* inset
= cur
.nextAtom().nucleus()->asScriptInset();
1777 // See comment in MathParser.cpp for special handling of {}-bases
1783 //lyxerr << "inserting selection 1:\n" << save_selection << endl;
1784 cur
.niceInsert(save_selection
);
1786 //lyxerr << "inserting selection 2:\n" << save_selection << endl;
1791 bool InsetMathNest::completionSupported(Cursor
const & cur
) const
1793 return cur
.inMacroMode();
1797 bool InsetMathNest::inlineCompletionSupported(Cursor
const & cur
) const
1799 return cur
.inMacroMode();
1803 bool InsetMathNest::automaticInlineCompletion() const
1805 return lyxrc
.completion_inline_math
;
1809 bool InsetMathNest::automaticPopupCompletion() const
1811 return lyxrc
.completion_popup_math
;
1815 CompletionList
const *
1816 InsetMathNest::createCompletionList(Cursor
const & cur
) const
1818 if (!cur
.inMacroMode())
1821 return new MathCompletionList(cur
);
1825 docstring
InsetMathNest::completionPrefix(Cursor
const & cur
) const
1827 if (!cur
.inMacroMode())
1830 return cur
.activeMacro()->name();
1834 bool InsetMathNest::insertCompletion(Cursor
& cur
, docstring
const & s
,
1837 if (!cur
.inMacroMode())
1840 // append completion to active macro
1841 InsetMathUnknown
* inset
= cur
.activeMacro();
1842 inset
->setName(inset
->name() + s
);
1847 // FIXME: this creates duplicates in the completion popup
1848 // which looks ugly. Moreover the changes the list lengths
1850 confuse the popup as well
.
1851 MathCompletionList::addToFavorites(inset
->name());
1853 lyx::dispatch(FuncRequest(LFUN_SELF_INSERT
, " "));
1860 void InsetMathNest::completionPosAndDim(Cursor
const & cur
, int & x
, int & y
,
1861 Dimension
& dim
) const
1863 Inset
const * inset
= cur
.activeMacro();
1867 // get inset dimensions
1868 dim
= cur
.bv().coordCache().insets().dim(inset
);
1869 // FIXME: these 3 are no accurate, but should depend on the font.
1870 // Now the popup jumps down if you enter a char with descent > 0.
1876 = cur
.bv().coordCache().insets().xy(inset
);
1882 bool InsetMathNest::cursorMathForward(Cursor
& cur
)
1884 if (cur
.pos() != cur
.lastpos() && cur
.openable(cur
.nextAtom())) {
1885 cur
.pushBackward(*cur
.nextAtom().nucleus());
1886 cur
.inset().idxFirst(cur
);
1889 if (cur
.posForward() || idxForward(cur
))
1891 // try to pop forwards --- but don't pop out of math! leave that to
1893 int s
= cur
.depth() - 2;
1894 if (s
>= 0 && cur
[s
].inset().asInsetMath())
1895 return cur
.popForward();
1900 bool InsetMathNest::cursorMathBackward(Cursor
& cur
)
1902 if (cur
.pos() != 0 && cur
.openable(cur
.prevAtom())) {
1904 cur
.push(*cur
.nextAtom().nucleus());
1905 cur
.inset().idxLast(cur
);
1908 if (cur
.posBackward() || idxBackward(cur
))
1910 // try to pop backwards --- but don't pop out of math! leave that to
1912 int s
= cur
.depth() - 2;
1913 if (s
>= 0 && cur
[s
].inset().asInsetMath())
1914 return cur
.popBackward();
1919 ////////////////////////////////////////////////////////////////////
1921 MathCompletionList::MathCompletionList(Cursor
const & cur
)
1923 // fill it with macros from the buffer
1924 MacroNameSet macros
;
1925 cur
.buffer()->listMacroNames(macros
);
1926 MacroNameSet::const_iterator it
;
1927 for (it
= macros
.begin(); it
!= macros
.end(); ++it
) {
1928 if (cur
.buffer()->getMacro(*it
, cur
, false))
1929 locals
.push_back("\\" + *it
);
1931 sort(locals
.begin(), locals
.end());
1933 if (globals
.size() > 0)
1936 // fill in global macros
1938 MacroTable::globalMacros().getMacroNames(macros
);
1939 //lyxerr << "Globals completion macros: ";
1940 for (it
= macros
.begin(); it
!= macros
.end(); ++it
) {
1941 //lyxerr << "\\" + *it << " ";
1942 globals
.push_back("\\" + *it
);
1944 //lyxerr << std::endl;
1946 // fill in global commands
1947 globals
.push_back(from_ascii("\\boxed"));
1948 globals
.push_back(from_ascii("\\fbox"));
1949 globals
.push_back(from_ascii("\\framebox"));
1950 globals
.push_back(from_ascii("\\makebox"));
1951 globals
.push_back(from_ascii("\\kern"));
1952 globals
.push_back(from_ascii("\\xrightarrow"));
1953 globals
.push_back(from_ascii("\\xleftarrow"));
1954 globals
.push_back(from_ascii("\\split"));
1955 globals
.push_back(from_ascii("\\gathered"));
1956 globals
.push_back(from_ascii("\\aligned"));
1957 globals
.push_back(from_ascii("\\alignedat"));
1958 globals
.push_back(from_ascii("\\cases"));
1959 globals
.push_back(from_ascii("\\substack"));
1960 globals
.push_back(from_ascii("\\xymatrix"));
1961 globals
.push_back(from_ascii("\\subarray"));
1962 globals
.push_back(from_ascii("\\array"));
1963 globals
.push_back(from_ascii("\\sqrt"));
1964 globals
.push_back(from_ascii("\\root"));
1965 globals
.push_back(from_ascii("\\tabular"));
1966 globals
.push_back(from_ascii("\\stackrel"));
1967 globals
.push_back(from_ascii("\\binom"));
1968 globals
.push_back(from_ascii("\\choose"));
1969 globals
.push_back(from_ascii("\\brace"));
1970 globals
.push_back(from_ascii("\\brack"));
1971 globals
.push_back(from_ascii("\\frac"));
1972 globals
.push_back(from_ascii("\\over"));
1973 globals
.push_back(from_ascii("\\nicefrac"));
1974 globals
.push_back(from_ascii("\\unitfrac"));
1975 globals
.push_back(from_ascii("\\unitfracthree"));
1976 globals
.push_back(from_ascii("\\unitone"));
1977 globals
.push_back(from_ascii("\\unittwo"));
1978 globals
.push_back(from_ascii("\\infer"));
1979 globals
.push_back(from_ascii("\\atop"));
1980 globals
.push_back(from_ascii("\\lefteqn"));
1981 globals
.push_back(from_ascii("\\boldsymbol"));
1982 globals
.push_back(from_ascii("\\bm"));
1983 globals
.push_back(from_ascii("\\color"));
1984 globals
.push_back(from_ascii("\\normalcolor"));
1985 globals
.push_back(from_ascii("\\textcolor"));
1986 globals
.push_back(from_ascii("\\cfrac"));
1987 globals
.push_back(from_ascii("\\cfracleft"));
1988 globals
.push_back(from_ascii("\\cfracright"));
1989 globals
.push_back(from_ascii("\\dfrac"));
1990 globals
.push_back(from_ascii("\\tfrac"));
1991 globals
.push_back(from_ascii("\\dbinom"));
1992 globals
.push_back(from_ascii("\\tbinom"));
1993 globals
.push_back(from_ascii("\\hphantom"));
1994 globals
.push_back(from_ascii("\\phantom"));
1995 globals
.push_back(from_ascii("\\vphantom"));
1996 MathWordList
const & words
= mathedWordList();
1997 MathWordList::const_iterator it2
;
1998 //lyxerr << "Globals completion commands: ";
1999 for (it2
= words
.begin(); it2
!= words
.end(); ++it2
) {
2000 globals
.push_back("\\" + (*it2
).first
);
2001 //lyxerr << "\\" + (*it2).first << " ";
2003 //lyxerr << std::endl;
2004 sort(globals
.begin(), globals
.end());
2008 MathCompletionList::~MathCompletionList()
2013 size_type
MathCompletionList::size() const
2015 return locals
.size() + globals
.size();
2019 docstring
const & MathCompletionList::data(size_t idx
) const
2021 size_t lsize
= locals
.size();
2023 return globals
[idx
- lsize
];
2029 std::string
MathCompletionList::icon(size_t idx
) const
2031 // get the latex command
2033 size_t lsize
= locals
.size();
2035 cmd
= globals
[idx
- lsize
];
2039 // get the icon resource name by stripping the backslash
2040 return "images/math/" + to_utf8(cmd
.substr(1)) + ".png";
2043 std::vector
<docstring
> MathCompletionList::globals
;