]> git.lyx.org Git - lyx.git/blob - src/mathed/InsetMathNest.cpp
Assure correct spacing of colored items in mathed
[lyx.git] / src / mathed / InsetMathNest.cpp
1 /**
2  * \file InsetMathNest.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author André Pönitz
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12
13 #include "InsetMathNest.h"
14
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"
33 #include "MathData.h"
34 #include "MathFactory.h"
35 #include "InsetMathMacro.h"
36 #include "InsetMathMacroArgument.h"
37 #include "MathParser.h"
38 #include "MathStream.h"
39 #include "MathSupport.h"
40
41 #include "Buffer.h"
42 #include "BufferParams.h"
43 #include "BufferView.h"
44 #include "CoordCache.h"
45 #include "Cursor.h"
46 #include "CutAndPaste.h"
47 #include "DispatchResult.h"
48 #include "Encoding.h"
49 #include "FuncRequest.h"
50 #include "FuncStatus.h"
51 #include "LaTeXFeatures.h"
52 #include "LyX.h"
53 #include "LyXRC.h"
54 #include "MetricsInfo.h"
55 #include "OutputParams.h"
56 #include "TexRow.h"
57 #include "Text.h"
58
59 #include "frontends/Application.h"
60 #include "frontends/Clipboard.h"
61 #include "frontends/Painter.h"
62 #include "frontends/Selection.h"
63
64 #include "support/debug.h"
65 #include "support/docstream.h"
66 #include "support/gettext.h"
67 #include "support/lassert.h"
68 #include "support/lstrings.h"
69 #include "support/textutils.h"
70
71 #include <algorithm>
72 #include <sstream>
73
74 using namespace std;
75 using namespace lyx::support;
76
77 namespace lyx {
78
79 using cap::copySelection;
80 using cap::grabAndEraseSelection;
81 using cap::cutSelection;
82 using cap::replaceSelection;
83 using cap::selClearOrDel;
84
85
86 InsetMathNest::InsetMathNest(Buffer * buf, idx_type nargs)
87         : InsetMath(buf), cells_(nargs), lock_(false)
88 {
89         setBuffer(*buf);
90 }
91
92
93 InsetMathNest::InsetMathNest(InsetMathNest const & inset)
94         : InsetMath(inset), cells_(inset.cells_), lock_(inset.lock_)
95 {}
96
97
98 InsetMathNest::~InsetMathNest()
99 {
100         map<BufferView const *, bool>::iterator it = mouse_hover_.begin();
101         map<BufferView const *, bool>::iterator end = mouse_hover_.end();
102         for (; it != end; ++it)
103                 if (it->second)
104                         it->first->clearLastInset(this);
105 }
106
107
108 InsetMathNest & InsetMathNest::operator=(InsetMathNest const & inset)
109 {
110         cells_ = inset.cells_;
111         lock_ = inset.lock_;
112         mouse_hover_.clear();
113         InsetMath::operator=(inset);
114         return *this;
115 }
116
117
118 void InsetMathNest::setBuffer(Buffer & buffer)
119 {
120         InsetMath::setBuffer(buffer);
121         for (MathData & data : cells_)
122                 data.setBuffer(buffer);
123 }
124
125
126 InsetMath::idx_type InsetMathNest::nargs() const
127 {
128         return cells_.size();
129 }
130
131
132 void InsetMathNest::cursorPos(BufferView const & bv,
133                 CursorSlice const & sl, bool /*boundary*/,
134                 int & x, int & y) const
135 {
136 // FIXME: This is a hack. Ideally, the coord cache should not store
137 // absolute positions, but relative ones. This would mean to call
138 // setXY() not in MathData::draw(), but in the parent insets' draw()
139 // with the correctly adjusted x,y values. But this means that we'd have
140 // to touch all (math)inset's draw() methods. Right now, we'll store
141 // absolute value, and make them here relative, only to make them
142 // absolute again when actually drawing the cursor. What a mess.
143         LASSERT(&sl.inset() == this, return);
144         MathData const & ar = sl.cell();
145         CoordCache const & coord_cache = bv.coordCache();
146         if (!coord_cache.getArrays().has(&ar)) {
147                 // this can (semi-)legally happen if we just created this cell
148                 // and it never has been drawn before. So don't ASSERT.
149                 //lyxerr << "no cached data for array " << &ar << endl;
150                 x = 0;
151                 y = 0;
152                 return;
153         }
154         Point const pt = coord_cache.getArrays().xy(&ar);
155         if (!coord_cache.getInsets().has(this)) {
156                 // same as above
157                 //lyxerr << "no cached data for inset " << this << endl;
158                 x = 0;
159                 y = 0;
160                 return;
161         }
162         Point const pt2 = coord_cache.getInsets().xy(this);
163         //lyxerr << "retrieving position cache for MathData "
164         //      << pt.x_ << ' ' << pt.y_ << endl;
165         x = pt.x_ - pt2.x_ + ar.pos2x(&bv, sl.pos());
166         y = pt.y_ - pt2.y_;
167 //      lyxerr << "pt.y_ : " << pt.y_ << " pt2_.y_ : " << pt2.y_
168 //              << " asc: " << ascent() << "  des: " << descent()
169 //              << " ar.asc: " << ar.ascent() << " ar.des: " << ar.descent() << endl;
170         // move cursor visually into empty cells ("blue rectangles");
171         if (ar.empty()) {
172                 Dimension const dim = coord_cache.getArrays().dim(&ar);
173                 x += dim.wid / 3;
174         }
175 }
176
177
178 void InsetMathNest::cellsMetrics(MetricsInfo const & mi) const
179 {
180         MetricsInfo m = mi;
181         for (auto const & cell : cells_) {
182                 Dimension dim;
183                 cell.metrics(m, dim);
184         }
185 }
186
187
188 void InsetMathNest::updateBuffer(ParIterator const & it, UpdateType utype, bool const deleted)
189 {
190         for (idx_type i = 0, n = nargs(); i != n; ++i)
191                 cell(i).updateBuffer(it, utype, deleted);
192 }
193
194
195
196 bool InsetMathNest::idxNext(Cursor & cur) const
197 {
198         LASSERT(&cur.inset() == this, return false);
199         if (cur.idx() == cur.lastidx())
200                 return false;
201         ++cur.idx();
202         cur.pos() = 0;
203         return true;
204 }
205
206
207 bool InsetMathNest::idxForward(Cursor & cur) const
208 {
209         return idxNext(cur);
210 }
211
212
213 bool InsetMathNest::idxPrev(Cursor & cur) const
214 {
215         LASSERT(&cur.inset() == this, return false);
216         if (cur.idx() == 0)
217                 return false;
218         --cur.idx();
219         cur.pos() = lyxrc.mac_like_cursor_movement ? cur.lastpos() : 0;
220         return true;
221 }
222
223
224 bool InsetMathNest::idxBackward(Cursor & cur) const
225 {
226         return idxPrev(cur);
227 }
228
229
230 bool InsetMathNest::idxFirst(Cursor & cur) const
231 {
232         LASSERT(&cur.inset() == this, return false);
233         if (nargs() == 0)
234                 return false;
235         cur.idx() = firstIdx();
236         cur.pos() = 0;
237         return true;
238 }
239
240
241 bool InsetMathNest::idxLast(Cursor & cur) const
242 {
243         LASSERT(&cur.inset() == this, return false);
244         if (nargs() == 0)
245                 return false;
246         cur.idx() = lastIdx();
247         cur.pos() = cur.lastpos();
248         return true;
249 }
250
251
252 void InsetMathNest::dump() const
253 {
254         odocstringstream oss;
255         otexrowstream ots(oss);
256         WriteStream os(ots);
257         os << "---------------------------------------------\n";
258         write(os);
259         os << "\n";
260         for (idx_type i = 0, n = nargs(); i != n; ++i)
261                 os << cell(i) << "\n";
262         os << "---------------------------------------------\n";
263         lyxerr << to_utf8(oss.str());
264 }
265
266
267 void InsetMathNest::draw(PainterInfo &, int, int) const
268 {
269 #if 0
270         if (lock_)
271                 pi.pain.fillRectangle(x, y - ascent(), width(), height(),
272                                         Color_mathlockbg);
273 #endif
274 }
275
276
277 void InsetMathNest::validate(LaTeXFeatures & features) const
278 {
279         for (idx_type i = 0; i < nargs(); ++i)
280                 cell(i).validate(features);
281 }
282
283
284 void InsetMathNest::replace(ReplaceData & rep)
285 {
286         for (idx_type i = 0; i < nargs(); ++i)
287                 cell(i).replace(rep);
288 }
289
290
291 bool InsetMathNest::contains(MathData const & ar) const
292 {
293         for (idx_type i = 0; i < nargs(); ++i)
294                 if (cell(i).contains(ar))
295                         return true;
296         return false;
297 }
298
299
300 bool InsetMathNest::lock() const
301 {
302         return lock_;
303 }
304
305
306 void InsetMathNest::lock(bool l)
307 {
308         lock_ = l;
309 }
310
311
312 bool InsetMathNest::isActive() const
313 {
314         return nargs() > 0;
315 }
316
317
318 MathData InsetMathNest::glue() const
319 {
320         MathData ar;
321         for (size_t i = 0; i < nargs(); ++i)
322                 ar.append(cell(i));
323         return ar;
324 }
325
326
327 void InsetMathNest::write(WriteStream & os) const
328 {
329         MathEnsurer ensurer(os, currentMode() == MATH_MODE);
330         ModeSpecifier specifier(os, currentMode(), lockedMode());
331         docstring const latex_name = name();
332         os << '\\' << latex_name;
333         os.inMathClass(asClassInset());
334         for (size_t i = 0; i < nargs(); ++i) {
335                 Changer dummy = os.changeRowEntry(TexRow::mathEntry(id(),i));
336                 os << '{' << cell(i) << '}';
337         }
338         if (nargs() == 0)
339                 os.pendingSpace(true);
340         if (lock_ && !os.latex()) {
341                 os << "\\lyxlock";
342                 os.pendingSpace(true);
343         }
344         os.inMathClass(false);
345 }
346
347
348 void InsetMathNest::normalize(NormalStream & os) const
349 {
350         os << '[' << name();
351         for (size_t i = 0; i < nargs(); ++i)
352                 os << ' ' << cell(i);
353         os << ']';
354 }
355
356
357 void InsetMathNest::latex(otexstream & os, OutputParams const & runparams) const
358 {
359         WriteStream wi(os, runparams.moving_arg, true,
360                         runparams.dryrun ? WriteStream::wsDryrun : WriteStream::wsDefault,
361                         runparams.encoding);
362         wi.strikeoutMath(runparams.inDeletedInset);
363         if (runparams.inulemcmd) {
364                 wi.ulemCmd(WriteStream::UNDERLINE);
365                 if (runparams.local_font) {
366                         FontInfo f = runparams.local_font->fontInfo();
367                         if (f.strikeout() == FONT_ON)
368                                 wi.ulemCmd(WriteStream::STRIKEOUT);
369                 }
370         }
371         wi.canBreakLine(os.canBreakLine());
372         Changer dummy = wi.changeRowEntry(TexRow::textEntry(runparams.lastid,
373                                                             runparams.lastpos));
374         write(wi);
375         // Reset parbreak and command termination status after a math inset.
376         os.lastChar(0);
377         os.canBreakLine(wi.canBreakLine());
378         os.terminateCommand(false);
379 }
380
381
382 bool InsetMathNest::setMouseHover(BufferView const * bv, bool mouse_hover)
383         const
384 {
385         mouse_hover_[bv] = mouse_hover;
386         return true;
387 }
388
389
390 bool InsetMathNest::notifyCursorLeaves(Cursor const & /*old*/, Cursor & /*cur*/)
391 {
392         // FIXME: look here
393 #if 0
394         MathData & ar = cur.cell();
395         // remove base-only "scripts"
396         for (pos_type i = 0; i + 1 < ar.size(); ++i) {
397                 InsetMathScript * p = operator[](i).nucleus()->asScriptInset();
398                 if (p && p->nargs() == 1) {
399                         MathData ar = p->nuc();
400                         erase(i);
401                         insert(i, ar);
402                         cur.adjust(i, ar.size() - 1);
403                 }
404         }
405
406         // glue adjacent font insets of the same kind
407         for (pos_type i = 0; i + 1 < size(); ++i) {
408                 InsetMathFont * p = operator[](i).nucleus()->asFontInset();
409                 InsetMathFont const * q = operator[](i + 1)->asFontInset();
410                 if (p && q && p->name() == q->name()) {
411                         p->cell(0).append(q->cell(0));
412                         erase(i + 1);
413                         cur.adjust(i, -1);
414                 }
415         }
416 #endif
417         return false;
418 }
419
420
421 void InsetMathNest::handleFont
422         (Cursor & cur, docstring const & arg, char const * const font)
423 {
424         handleFont(cur, arg, from_ascii(font));
425 }
426
427
428 void InsetMathNest::handleFont(Cursor & cur, docstring const & arg,
429         docstring const & font)
430 {
431         cur.recordUndoSelection();
432
433         // this whole function is a hack and won't work for incremental font
434         // changes...
435         if (cur.inset().asInsetMath()->name() == font)
436                 cur.handleFont(to_utf8(font));
437         else
438                 handleNest(cur, createInsetMath(font, cur.buffer()), arg);
439 }
440
441
442 void InsetMathNest::handleNest(Cursor & cur, MathAtom const & nest)
443 {
444         handleNest(cur, nest, docstring());
445 }
446
447
448 void InsetMathNest::handleNest(Cursor & cur, MathAtom const & nest,
449         docstring const & arg)
450 {
451         CursorSlice i1 = cur.selBegin();
452         CursorSlice i2 = cur.selEnd();
453         if (!i1.inset().asInsetMath())
454                 return;
455         if (i1.idx() == i2.idx()) {
456                 // the easy case where only one cell is selected
457                 cur.handleNest(nest);
458                 cur.insert(arg);
459                 return;
460         }
461
462         // multiple selected cells in a simple non-grid inset
463         if (i1.asInsetMath()->nrows() == 0 || i1.asInsetMath()->ncols() == 0) {
464                 for (idx_type i = i1.idx(); i <= i2.idx(); ++i) {
465                         // select cell
466                         cur.idx() = i;
467                         cur.pos() = 0;
468                         cur.resetAnchor();
469                         cur.pos() = cur.lastpos();
470                         cur.setSelection();
471
472                         // change font of cell
473                         cur.handleNest(nest);
474                         cur.insert(arg);
475
476                         // cur is in the font inset now. If the loop continues,
477                         // we need to get outside again for the next cell
478                         if (i + 1 <= i2.idx())
479                                 cur.pop_back();
480                 }
481                 return;
482         }
483
484         // the complicated case with multiple selected cells in a grid
485         row_type r1, r2;
486         col_type c1, c2;
487         cap::region(i1, i2, r1, r2, c1, c2);
488         for (row_type row = r1; row <= r2; ++row) {
489                 for (col_type col = c1; col <= c2; ++col) {
490                         // select cell
491                         cur.idx() = i1.asInsetMath()->index(row, col);
492                         cur.pos() = 0;
493                         cur.resetAnchor();
494                         cur.pos() = cur.lastpos();
495                         cur.setSelection();
496
497                         //
498                         cur.handleNest(nest);
499                         cur.insert(arg);
500
501                         // cur is in the font inset now. If the loop continues,
502                         // we need to get outside again for the next cell
503                         if (col + 1 <= c2 || row + 1 <= r2)
504                                 cur.pop_back();
505                 }
506         }
507 }
508
509
510 void InsetMathNest::handleFont2(Cursor & cur, docstring const & arg)
511 {
512         cur.recordUndoSelection();
513         Font font;
514         bool b;
515         font.fromString(to_utf8(arg), b);
516         if (font.fontInfo().color() != Color_inherit &&
517             font.fontInfo().color() != Color_ignore)
518                 handleNest(cur, MathAtom(new InsetMathColor(buffer_, true, font.fontInfo().color())));
519
520         // FIXME: support other font changes here as well?
521 }
522
523 void InsetMathNest::doDispatch(Cursor & cur, FuncRequest & cmd)
524 {
525         //LYXERR0("InsetMathNest: request: " << cmd);
526
527         Parse::flags parseflg = Parse::QUIET | Parse::USETEXT;
528
529         FuncCode const act = cmd.action();
530         switch (act) {
531
532         case LFUN_CLIPBOARD_PASTE:
533                 parseflg |= Parse::VERBATIM;
534                 // fall through
535         case LFUN_PASTE: {
536                 if (cur.currentMode() != MATH_MODE)
537                         parseflg |= Parse::TEXTMODE;
538                 cur.recordUndoSelection();
539                 cur.message(_("Paste"));
540                 replaceSelection(cur);
541                 docstring topaste;
542                 if (cmd.argument().empty() && !theClipboard().isInternal())
543                         topaste = theClipboard().getAsText(frontend::Clipboard::PlainTextType);
544                 else {
545                         size_t n = 0;
546                         idocstringstream is(cmd.argument());
547                         is >> n;
548                         topaste = cap::selection(n, buffer().params().documentClassPtr());
549                 }
550                 cur.niceInsert(topaste, parseflg, false);
551                 cur.clearSelection(); // bug 393
552                 cur.forceBufferUpdate();
553                 cur.finishUndo();
554                 break;
555         }
556
557         case LFUN_CUT:
558                 cur.recordUndo();
559                 cutSelection(cur, true);
560                 cur.message(_("Cut"));
561                 // Prevent stale position >= size crash
562                 // Probably not necessary anymore, see eraseSelection (gb 2005-10-09)
563                 cur.normalize();
564                 cur.forceBufferUpdate();
565                 break;
566
567         case LFUN_COPY:
568                 copySelection(cur);
569                 cur.message(_("Copy"));
570                 break;
571
572         case LFUN_MOUSE_PRESS:
573                 lfunMousePress(cur, cmd);
574                 break;
575
576         case LFUN_MOUSE_MOTION:
577                 lfunMouseMotion(cur, cmd);
578                 break;
579
580         case LFUN_MOUSE_RELEASE:
581                 lfunMouseRelease(cur, cmd);
582                 break;
583
584         case LFUN_FINISHED_LEFT: // in math, left is backwards
585         case LFUN_FINISHED_BACKWARD:
586                 cur.bv().cursor() = cur;
587                 break;
588
589         case LFUN_FINISHED_RIGHT: // in math, right is forward
590         case LFUN_FINISHED_FORWARD:
591                 ++cur.pos();
592                 cur.bv().cursor() = cur;
593                 break;
594
595         case LFUN_WORD_RIGHT:
596         case LFUN_WORD_LEFT:
597         case LFUN_WORD_BACKWARD:
598         case LFUN_WORD_FORWARD:
599         case LFUN_CHAR_RIGHT:
600         case LFUN_CHAR_LEFT:
601         case LFUN_CHAR_BACKWARD:
602         case LFUN_CHAR_FORWARD:
603                 cur.screenUpdateFlags(Update::Decoration | Update::FitCursor);
604                 // fall through
605         case LFUN_WORD_RIGHT_SELECT:
606         case LFUN_WORD_LEFT_SELECT:
607         case LFUN_WORD_BACKWARD_SELECT:
608         case LFUN_WORD_FORWARD_SELECT:
609         case LFUN_CHAR_RIGHT_SELECT:
610         case LFUN_CHAR_LEFT_SELECT:
611         case LFUN_CHAR_BACKWARD_SELECT:
612         case LFUN_CHAR_FORWARD_SELECT: {
613                 // are we in a selection?
614                 bool select = (act == LFUN_WORD_RIGHT_SELECT
615                                            || act == LFUN_WORD_LEFT_SELECT
616                                            || act == LFUN_WORD_BACKWARD_SELECT
617                                            || act == LFUN_WORD_FORWARD_SELECT
618                                || act == LFUN_CHAR_RIGHT_SELECT
619                                            || act == LFUN_CHAR_LEFT_SELECT
620                                            || act == LFUN_CHAR_BACKWARD_SELECT
621                                            || act == LFUN_CHAR_FORWARD_SELECT);
622                 // select words
623                 bool word = (act == LFUN_WORD_RIGHT_SELECT
624                              || act == LFUN_WORD_LEFT_SELECT
625                              || act == LFUN_WORD_BACKWARD_SELECT
626                              || act == LFUN_WORD_FORWARD_SELECT
627                              || act == LFUN_WORD_RIGHT
628                              || act == LFUN_WORD_LEFT
629                              || act == LFUN_WORD_BACKWARD
630                              || act == LFUN_WORD_FORWARD);
631                 // are we moving forward or backwards?
632                 // If the command was RIGHT or LEFT, then whether we're moving forward
633                 // or backwards depends on the cursor movement mode (logical or visual):
634                 //  * in visual mode, since math is always LTR, right -> forward,
635                 //    left -> backwards
636                 //  * in logical mode, the mapping is determined by the
637                 //    reverseDirectionNeeded() function
638
639                 bool forward;
640                 FuncCode finish_lfun;
641
642                 if (act == LFUN_CHAR_FORWARD
643                     || act == LFUN_CHAR_FORWARD_SELECT
644                     || act == LFUN_WORD_FORWARD
645                     || act == LFUN_WORD_FORWARD_SELECT) {
646                         forward = true;
647                         finish_lfun = LFUN_FINISHED_FORWARD;
648                 }
649                 else if (act == LFUN_CHAR_BACKWARD
650                          || act == LFUN_CHAR_BACKWARD_SELECT
651                          || act == LFUN_WORD_BACKWARD
652                          || act == LFUN_WORD_BACKWARD_SELECT) {
653                         forward = false;
654                         finish_lfun = LFUN_FINISHED_BACKWARD;
655                 }
656                 else {
657                         bool right = (act == LFUN_CHAR_RIGHT_SELECT
658                                                   || act == LFUN_CHAR_RIGHT
659                                       || act == LFUN_WORD_RIGHT_SELECT
660                                       || act == LFUN_WORD_RIGHT);
661                         if (lyxrc.visual_cursor || !cur.reverseDirectionNeeded())
662                                 forward = right;
663                         else
664                                 forward = !right;
665
666                         if (right)
667                                 finish_lfun = LFUN_FINISHED_RIGHT;
668                         else
669                                 finish_lfun = LFUN_FINISHED_LEFT;
670                 }
671                 // Now that we know exactly what we want to do, let's do it!
672                 cur.selHandle(select);
673                 cur.clearTargetX();
674                 cur.macroModeClose();
675                 // try moving forward or backwards as necessary...
676                 if (!(forward ? cur.mathForward(word) : cur.mathBackward(word))) {
677                         // ... and if movement failed, then finish forward or backwards
678                         // as necessary
679                         cmd = FuncRequest(finish_lfun);
680                         cur.undispatched();
681                 }
682                 break;
683         }
684
685         case LFUN_DOWN:
686         case LFUN_UP:
687         case LFUN_PARAGRAPH_UP:
688         case LFUN_PARAGRAPH_DOWN:
689                 cur.screenUpdateFlags(Update::Decoration | Update::FitCursor);
690                 // fall through
691         case LFUN_DOWN_SELECT:
692         case LFUN_UP_SELECT:
693         case LFUN_PARAGRAPH_UP_SELECT:
694         case LFUN_PARAGRAPH_DOWN_SELECT: {
695                 // close active macro
696                 if (cur.inMacroMode()) {
697                         cur.macroModeClose();
698                         break;
699                 }
700
701                 // stop/start the selection
702                 bool select = act == LFUN_DOWN_SELECT
703                         || act == LFUN_UP_SELECT
704                         || act == LFUN_PARAGRAPH_DOWN_SELECT
705                         || act == LFUN_PARAGRAPH_UP_SELECT;
706                 cur.selHandle(select);
707
708                 // go up/down
709                 bool up = act == LFUN_UP || act == LFUN_UP_SELECT
710                         || act == LFUN_PARAGRAPH_UP || act == LFUN_PARAGRAPH_UP_SELECT;
711                 bool successful = cur.upDownInMath(up);
712                 if (successful)
713                         break;
714
715                 if (cur.fixIfBroken())
716                         // FIXME: Something bad happened. We pass the corrected Cursor
717                         // instead of letting things go worse.
718                         break;
719
720                 // We did not manage to move the cursor.
721                 cur.undispatched();
722                 break;
723         }
724
725         case LFUN_MOUSE_DOUBLE:
726         case LFUN_WORD_SELECT:
727                 cur.pos() = 0;
728                 cur.bv().mouseSetCursor(cur);
729                 cur.pos() = cur.lastpos();
730                 cur.bv().mouseSetCursor(cur, true);
731                 break;
732
733         case LFUN_MOUSE_TRIPLE:
734                 cur.idx() = 0;
735                 cur.pos() = 0;
736                 cur.bv().mouseSetCursor(cur);
737                 cur.idx() = cur.lastidx();
738                 cur.pos() = cur.lastpos();
739                 cur.bv().mouseSetCursor(cur, true);
740                 break;
741
742         case LFUN_LINE_BEGIN:
743                 cur.screenUpdateFlags(Update::Decoration | Update::FitCursor);
744                 // fall through
745         case LFUN_LINE_BEGIN_SELECT:
746                 cur.selHandle(act == LFUN_WORD_BACKWARD_SELECT ||
747                                 act == LFUN_WORD_LEFT_SELECT ||
748                                 act == LFUN_LINE_BEGIN_SELECT);
749                 cur.macroModeClose();
750                 if (cur.pos() != 0) {
751                         cur.pos() = 0;
752                 } else if (cur.col() != 0) {
753                         cur.idx() -= cur.col();
754                         cur.pos() = 0;
755                 } else if (cur.idx() != 0) {
756                         cur.idx() = 0;
757                         cur.pos() = 0;
758                 } else {
759                         cmd = FuncRequest(LFUN_FINISHED_BACKWARD);
760                         cur.undispatched();
761                 }
762                 break;
763
764         case LFUN_LINE_END:
765                 cur.screenUpdateFlags(Update::Decoration | Update::FitCursor);
766                 // fall through
767         case LFUN_LINE_END_SELECT:
768                 cur.selHandle(act == LFUN_WORD_FORWARD_SELECT ||
769                                 act == LFUN_WORD_RIGHT_SELECT ||
770                                 act == LFUN_LINE_END_SELECT);
771                 cur.macroModeClose();
772                 cur.clearTargetX();
773                 if (cur.pos() != cur.lastpos()) {
774                         cur.pos() = cur.lastpos();
775                 } else if (ncols() && (cur.col() != cur.lastcol())) {
776                         cur.idx() = cur.idx() - cur.col() + cur.lastcol();
777                         cur.pos() = cur.lastpos();
778                 } else if (cur.idx() != cur.lastidx()) {
779                         cur.idx() = cur.lastidx();
780                         cur.pos() = cur.lastpos();
781                 } else {
782                         cmd = FuncRequest(LFUN_FINISHED_FORWARD);
783                         cur.undispatched();
784                 }
785                 break;
786
787         case LFUN_CELL_FORWARD:
788                 cur.screenUpdateFlags(Update::Decoration | Update::FitCursor);
789                 cur.selHandle(false);
790                 cur.clearTargetX();
791                 cur.macroModeClose();
792                 if (!cur.inset().idxNext(cur)) {
793                         cur.idx() = firstIdx();
794                         cur.pos() = 0;
795                 }
796                 break;
797
798         case LFUN_CELL_BACKWARD:
799                 cur.screenUpdateFlags(Update::Decoration | Update::FitCursor);
800                 cur.selHandle(false);
801                 cur.clearTargetX();
802                 cur.macroModeClose();
803                 if (!cur.inset().idxPrev(cur)) {
804                         cur.idx() = lastIdx();
805                         cur.pos() = lyxrc.mac_like_cursor_movement ? cur.lastpos() : 0;
806                 }
807                 break;
808
809         case LFUN_WORD_DELETE_BACKWARD:
810         case LFUN_CHAR_DELETE_BACKWARD:
811                 if (cur.pos() == 0)
812                         // May affect external cell:
813                         cur.recordUndoInset();
814                 else if (!cur.inMacroMode())
815                         cur.recordUndoSelection();
816                 // if the inset can not be removed from within, delete it
817                 if (!cur.backspace(cmd.getArg(0) == "force")) {
818                         FuncRequest newcmd = FuncRequest(LFUN_CHAR_DELETE_FORWARD, "force");
819                         cur.innerText()->dispatch(cur, newcmd);
820                 }
821                 break;
822
823         case LFUN_WORD_DELETE_FORWARD:
824         case LFUN_CHAR_DELETE_FORWARD:
825                 if (cur.pos() == cur.lastpos())
826                         // May affect external cell:
827                         cur.recordUndoInset();
828                 else
829                         cur.recordUndoSelection();
830                 // if the inset can not be removed from within, delete it
831                 if (!cur.erase(cmd.getArg(0) == "force")) {
832                         FuncRequest newcmd = FuncRequest(LFUN_CHAR_DELETE_FORWARD, "force");
833                         cur.innerText()->dispatch(cur, newcmd);
834                 }
835                 break;
836
837         case LFUN_ESCAPE:
838                 if (cur.selection())
839                         cur.clearSelection();
840                 else  {
841                         if (cur.inMacroMode())
842                                 cur.macroModeClose(true);
843                         else {
844                                 cmd = FuncRequest(LFUN_FINISHED_FORWARD);
845                                 cur.undispatched();
846                         }
847                 }
848                 break;
849
850         // 'Locks' the math inset. A 'locked' math inset behaves as a unit
851         // that is traversed by a single <CursorLeft>/<CursorRight>.
852         case LFUN_INSET_TOGGLE:
853                 cur.recordUndo();
854                 lock(!lock());
855                 cur.popForward();
856                 break;
857
858         case LFUN_SELF_INSERT:
859                 // special case first for big delimiters
860                 if (cmd.argument().size() != 1 && interpretString(cur, cmd.argument()))
861                         break;
862
863                 for (char_type c : cmd.argument()) {
864                         // Don't record undo steps if we are in macro mode and thus
865                         // cmd.argument is the next character of the macro name.
866                         // Otherwise we'll get an invalid cursor if we undo after
867                         // the macro was finished and the macro is a known command,
868                         // e.g. sqrt. Cursor::macroModeClose replaces in this case
869                         // the InsetMathUnknown with name "frac" by an empty
870                         // InsetMathFrac -> a pos value > 0 is invalid.
871                         // A side effect is that an undo before the macro is finished
872                         // undoes the complete macro, not only the last character.
873                         // At the time we hit '\' we are not in macro mode, still.
874                         if (!cur.inMacroMode())
875                                 cur.recordUndoSelection();
876
877                         // special handling of space. If we insert an inset
878                         // via macro mode, we want to put the cursor inside it
879                         // if relevant. Think typing "\frac<space>".
880                         if (c == ' '
881                             && cur.inMacroMode() && cur.macroName() != "\\"
882                             && cur.macroModeClose() && cur.pos() > 0)
883                                 cur.editInsertedInset();
884                         else if (!interpretChar(cur, c)) {
885                                 cmd = FuncRequest(LFUN_FINISHED_FORWARD);
886                                 cur.undispatched();
887                                 // FIXME: can we avoid skipping the end of the string?
888                                 break;
889                         }
890                 }
891                 break;
892
893         //case LFUN_SERVER_GET_XY:
894         //      break;
895
896         case LFUN_SERVER_SET_XY: {
897                 lyxerr << "LFUN_SERVER_SET_XY broken!" << endl;
898                 int x = 0;
899                 int y = 0;
900                 istringstream is(to_utf8(cmd.argument()));
901                 is >> x >> y;
902                 cur.setTargetX(x);
903                 break;
904         }
905
906         // Special casing for superscript in case of LyX handling
907         // dead-keys:
908         case LFUN_ACCENT_CIRCUMFLEX:
909                 if (cmd.argument().empty()) {
910                         // do superscript if LyX handles
911                         // deadkeys
912                         cur.recordUndoSelection();
913                         script(cur, true, grabAndEraseSelection(cur));
914                 }
915                 break;
916
917         case LFUN_ACCENT_UMLAUT:
918         case LFUN_ACCENT_ACUTE:
919         case LFUN_ACCENT_GRAVE:
920         case LFUN_ACCENT_BREVE:
921         case LFUN_ACCENT_DOT:
922         case LFUN_ACCENT_MACRON:
923         case LFUN_ACCENT_CARON:
924         case LFUN_ACCENT_TILDE:
925         case LFUN_ACCENT_CEDILLA:
926         case LFUN_ACCENT_CIRCLE:
927         case LFUN_ACCENT_UNDERDOT:
928         case LFUN_ACCENT_TIE:
929         case LFUN_ACCENT_OGONEK:
930         case LFUN_ACCENT_HUNGARIAN_UMLAUT:
931                 break;
932
933         //  Math fonts
934         case LFUN_TEXTSTYLE_APPLY:
935         case LFUN_TEXTSTYLE_UPDATE:
936                 handleFont2(cur, cmd.argument());
937                 break;
938
939         case LFUN_FONT_BOLD:
940                 if (currentMode() != MATH_MODE)
941                         handleFont(cur, cmd.argument(), "textbf");
942                 else
943                         handleFont(cur, cmd.argument(), "mathbf");
944                 break;
945         case LFUN_FONT_BOLDSYMBOL:
946                 if (currentMode() != MATH_MODE)
947                         handleFont(cur, cmd.argument(), "textbf");
948                 else
949                         handleFont(cur, cmd.argument(), "boldsymbol");
950                 break;
951         case LFUN_FONT_SANS:
952                 if (currentMode() != MATH_MODE)
953                         handleFont(cur, cmd.argument(), "textsf");
954                 else
955                         handleFont(cur, cmd.argument(), "mathsf");
956                 break;
957         case LFUN_FONT_EMPH:
958                 if (currentMode() != MATH_MODE)
959                         handleFont(cur, cmd.argument(), "emph");
960                 else
961                         handleFont(cur, cmd.argument(), "mathcal");
962                 break;
963         case LFUN_FONT_ROMAN:
964                 if (currentMode() != MATH_MODE)
965                         handleFont(cur, cmd.argument(), "textrm");
966                 else
967                         handleFont(cur, cmd.argument(), "mathrm");
968                 break;
969         case LFUN_FONT_TYPEWRITER:
970                 if (currentMode() != MATH_MODE)
971                         handleFont(cur, cmd.argument(), "texttt");
972                 else
973                         handleFont(cur, cmd.argument(), "mathtt");
974                 break;
975         case LFUN_FONT_FRAK:
976                 handleFont(cur, cmd.argument(), "mathfrak");
977                 break;
978         case LFUN_FONT_ITAL:
979                 if (currentMode() != MATH_MODE)
980                         handleFont(cur, cmd.argument(), "textit");
981                 else
982                         handleFont(cur, cmd.argument(), "mathit");
983                 break;
984         case LFUN_FONT_NOUN:
985                 if (currentMode() != MATH_MODE)
986                         // FIXME: should be "noun"
987                         handleFont(cur, cmd.argument(), "textsc");
988                 else
989                         handleFont(cur, cmd.argument(), "mathbb");
990                 break;
991         case LFUN_FONT_DEFAULT:
992                 handleFont(cur, cmd.argument(), "textnormal");
993                 break;
994         case LFUN_FONT_UNDERLINE:
995                 cur.recordUndo();
996                 cur.handleNest(createInsetMath("underline", cur.buffer()));
997                 break;
998
999         case LFUN_MATH_MODE: {
1000 #if 1
1001                 // ignore math-mode on when already in math mode
1002                 if (currentMode() == Inset::MATH_MODE && cmd.argument() == "on")
1003                         break;
1004                 cur.recordUndoSelection();
1005                 cur.macroModeClose();
1006                 docstring const save_selection = grabAndEraseSelection(cur);
1007                 selClearOrDel(cur);
1008                 if (currentMode() != Inset::MATH_MODE)
1009                         cur.plainInsert(MathAtom(new InsetMathEnsureMath(buffer_)));
1010                 else
1011                         cur.plainInsert(createInsetMath("text", buffer_));
1012                 cur.posBackward();
1013                 cur.pushBackward(*cur.nextInset());
1014                 cur.niceInsert(save_selection);
1015                 cur.forceBufferUpdate();
1016 #else
1017                 if (currentMode() == Inset::TEXT_MODE) {
1018                         cur.recordUndoSelection();
1019                         cur.niceInsert(MathAtom(new InsetMathHull("simple", cur.buffer())));
1020                         cur.message(_("create new math text environment ($...$)"));
1021                 } else {
1022                         handleFont(cur, cmd.argument(), "textrm");
1023                         cur.message(_("entered math text mode (textrm)"));
1024                 }
1025 #endif
1026                 break;
1027         }
1028
1029         case LFUN_REGEXP_MODE: {
1030                 InsetMath * im = cur.inset().asInsetMath();
1031                 if (im) {
1032                         InsetMathHull * i = im->asHullInset();
1033                         if (i && i->getType() == hullRegexp) {
1034                                 cur.message(_("Already in regular expression mode"));
1035                                 break;
1036                         }
1037                 }
1038                 cur.macroModeClose();
1039                 docstring const save_selection = grabAndEraseSelection(cur);
1040                 selClearOrDel(cur);
1041                 cur.plainInsert(MathAtom(new InsetMathHull(buffer_, hullRegexp)));
1042                 cur.posBackward();
1043                 cur.pushBackward(*cur.nextInset());
1044                 cur.niceInsert(save_selection);
1045                 cur.message(_("Regular expression editor mode"));
1046                 break;
1047         }
1048
1049         case LFUN_MATH_FONT_STYLE: {
1050                 FuncRequest fr = FuncRequest(LFUN_MATH_INSERT, '\\' + cmd.argument());
1051                 doDispatch(cur, fr);
1052                 break;
1053         }
1054
1055         case LFUN_MATH_SIZE: {
1056                 FuncRequest fr = FuncRequest(LFUN_MATH_INSERT, cmd.argument());
1057                 doDispatch(cur, fr);
1058                 break;
1059         }
1060
1061         case LFUN_MATH_MATRIX: {
1062                 cur.recordUndo();
1063                 unsigned int m = 1;
1064                 unsigned int n = 1;
1065                 docstring v_align;
1066                 docstring h_align;
1067                 idocstringstream is(cmd.argument());
1068                 is >> m >> n >> v_align >> h_align;
1069                 if (m < 1)
1070                         m = 1;
1071                 if (n < 1)
1072                         n = 1;
1073                 v_align += 'c';
1074                 cur.niceInsert(MathAtom(new InsetMathArray(buffer_,
1075                         from_ascii("array"), m, n, (char)v_align[0], h_align)));
1076                 break;
1077         }
1078
1079         case LFUN_MATH_AMS_MATRIX: {
1080                 cur.recordUndo();
1081                 unsigned int m = 1;
1082                 unsigned int n = 1;
1083                 docstring name = from_ascii("matrix");
1084                 idocstringstream is(cmd.argument());
1085                 is >> m >> n >> name;
1086                 if (m < 1)
1087                         m = 1;
1088                 if (n < 1)
1089                         n = 1;
1090                 // check if we have a valid decoration
1091                 if (name != "pmatrix" && name != "bmatrix"
1092                         && name != "Bmatrix" && name != "vmatrix"
1093                         && name != "Vmatrix" && name != "matrix"
1094                         && name != "smallmatrix")
1095                         name = from_ascii("matrix");
1096
1097                 cur.niceInsert(
1098                         MathAtom(new InsetMathAMSArray(buffer_, name, m, n)));
1099                 break;
1100         }
1101
1102         case LFUN_MATH_DELIM: {
1103                 docstring ls;
1104                 docstring rs = split(cmd.argument(), ls, ' ');
1105                 // Reasonable default values
1106                 if (ls.empty())
1107                         ls = '(';
1108                 if (rs.empty())
1109                         rs = ')';
1110                 cur.recordUndo();
1111                 cur.handleNest(MathAtom(new InsetMathDelim(buffer_, ls, rs)));
1112                 break;
1113         }
1114
1115         case LFUN_MATH_BIGDELIM: {
1116                 docstring const lname  = from_utf8(cmd.getArg(0));
1117                 docstring const ldelim = from_utf8(cmd.getArg(1));
1118                 docstring const rname  = from_utf8(cmd.getArg(2));
1119                 docstring const rdelim = from_utf8(cmd.getArg(3));
1120                 latexkeys const * l = in_word_set(lname);
1121                 bool const have_l = l && l->inset == "big" &&
1122                                     InsetMathBig::isBigInsetDelim(ldelim);
1123                 l = in_word_set(rname);
1124                 bool const have_r = l && l->inset == "big" &&
1125                                     InsetMathBig::isBigInsetDelim(rdelim);
1126                 // We mimic LFUN_MATH_DELIM in case we have an empty left
1127                 // or right delimiter.
1128                 if (have_l || have_r) {
1129                         cur.recordUndo();
1130                         docstring const selection = grabAndEraseSelection(cur);
1131                         selClearOrDel(cur);
1132                         if (have_l)
1133                                 cur.insert(MathAtom(new InsetMathBig(lname,
1134                                                                 ldelim)));
1135                         // first insert the right delimiter and then go back
1136                         // and re-insert the selection (bug 7088)
1137                         if (have_r) {
1138                                 cur.insert(MathAtom(new InsetMathBig(rname,
1139                                                                 rdelim)));
1140                                 cur.posBackward();
1141                         }
1142                         cur.niceInsert(selection);
1143                 }
1144                 // Don't call cur.undispatched() if we did nothing, this would
1145                 // lead to infinite recursion via Text::dispatch().
1146                 break;
1147         }
1148
1149         case LFUN_SPACE_INSERT: {
1150                 cur.recordUndoSelection();
1151                 string const name = cmd.getArg(0);
1152                 if (name == "normal")
1153                         cur.insert(MathAtom(new InsetMathSpace(" ", "")));
1154                 else if (name == "protected")
1155                         cur.insert(MathAtom(new InsetMathSpace("~", "")));
1156                 else if (name == "thin" || name == "med" || name == "thick")
1157                         cur.insert(MathAtom(new InsetMathSpace(name + "space", "")));
1158                 else if (name == "hfill*")
1159                         cur.insert(MathAtom(new InsetMathSpace("hspace*{\\fill}", "")));
1160                 else if (name == "quad" || name == "qquad" ||
1161                          name == "enspace" || name == "enskip" ||
1162                          name == "negthinspace" || name == "negmedspace" ||
1163                          name == "negthickspace" || name == "hfill")
1164                         cur.insert(MathAtom(new InsetMathSpace(name, "")));
1165                 else if (name == "hspace" || name == "hspace*") {
1166                         string const len = cmd.getArg(1);
1167                         if (len.empty() || !isValidLength(len)) {
1168                                 lyxerr << "LyX function 'space-insert " << name << "' "
1169                                           "needs a valid length argument." << endl;
1170                                 break;
1171                         }
1172                         cur.insert(MathAtom(new InsetMathSpace(name, len)));
1173                 } else
1174                         cur.insert(MathAtom(new InsetMathSpace));
1175                 break;
1176         }
1177
1178         case LFUN_MATH_SPACE:
1179                 cur.recordUndoSelection();
1180                 if (cmd.argument().empty())
1181                         cur.insert(MathAtom(new InsetMathSpace));
1182                 else {
1183                         string const name = cmd.getArg(0);
1184                         string const len = cmd.getArg(1);
1185                         cur.insert(MathAtom(new InsetMathSpace(name, len)));
1186                 }
1187                 break;
1188
1189         case LFUN_ERT_INSERT:
1190                 // interpret this as if a backslash was typed
1191                 cur.recordUndo();
1192                 interpretChar(cur, '\\');
1193                 break;
1194
1195         case LFUN_MATH_SUBSCRIPT:
1196                 // interpret this as if a _ was typed
1197                 cur.recordUndoSelection();
1198                 interpretChar(cur, '_');
1199                 break;
1200
1201         case LFUN_MATH_SUPERSCRIPT:
1202                 // interpret this as if a ^ was typed
1203                 cur.recordUndoSelection();
1204                 interpretChar(cur, '^');
1205                 break;
1206
1207         case LFUN_MATH_MACRO_FOLD:
1208         case LFUN_MATH_MACRO_UNFOLD: {
1209                 Cursor it = cur;
1210                 bool fold = act == LFUN_MATH_MACRO_FOLD;
1211                 bool found = findMacroToFoldUnfold(it, fold);
1212                 if (found) {
1213                         InsetMathMacro * macro = it.nextInset()->asInsetMath()->asMacro();
1214                         cur.recordUndoInset();
1215                         if (fold)
1216                                 macro->fold(cur);
1217                         else
1218                                 macro->unfold(cur);
1219                 }
1220                 break;
1221         }
1222
1223         case LFUN_QUOTE_INSERT:
1224                 // interpret this as if a straight " was typed
1225                 cur.recordUndoSelection();
1226                 interpretChar(cur, '\"');
1227                 break;
1228
1229 // FIXME: We probably should swap parts of "math-insert" and "self-insert"
1230 // handling such that "self-insert" works on "arbitrary stuff" too, and
1231 // math-insert only handles special math things like "matrix".
1232         case LFUN_MATH_INSERT: {
1233                 cur.recordUndoSelection();
1234                 if (cmd.argument() == "^" || cmd.argument() == "_")
1235                         interpretChar(cur, cmd.argument()[0]);
1236                 else if (!cur.selection())
1237                         cur.niceInsert(cmd.argument());
1238                 else {
1239                         MathData ar(cur.buffer());
1240                         asArray(cmd.argument(), ar);
1241                         if (ar.size() == 1
1242                             && ar[0]->asNestInset()
1243                             && ar[0]->asNestInset()->nargs() > 1)
1244                                 handleNest(cur, ar[0]);
1245                         else
1246                                 cur.niceInsert(cmd.argument());
1247                 }
1248                 break;
1249         }
1250
1251         case LFUN_DIALOG_SHOW_NEW_INSET: {
1252                 docstring const & name = cmd.argument();
1253                 string data;
1254                 if (name == "ref") {
1255                         InsetMathRef tmp(buffer_, name);
1256                         data = tmp.createDialogStr();
1257                         cur.bv().showDialog(to_utf8(name), data);
1258                 } else if (name == "mathspace") {
1259                         cur.bv().showDialog(to_utf8(name));
1260                 }
1261                 break;
1262         }
1263
1264         case LFUN_INSET_INSERT: {
1265                 MathData ar;
1266                 if (createInsetMath_fromDialogStr(cmd.argument(), ar)) {
1267                         cur.recordUndoSelection();
1268                         cur.insert(ar);
1269                         cur.forceBufferUpdate();
1270                 } else
1271                         cur.undispatched();
1272                 break;
1273         }
1274         case LFUN_INSET_DISSOLVE:
1275                 if (!asHullInset()) {
1276                         cur.recordUndoInset();
1277                         cur.pullArg();
1278                 }
1279                 break;
1280
1281         case LFUN_MATH_LIMITS: {
1282                 InsetMath * in = 0;
1283                 if (cur.pos() < cur.lastpos() && cur.nextMath().allowsLimitsChange())
1284                         in = &cur.nextMath();
1285                 else if (cur.pos() > 0 && cur.prevMath().allowsLimitsChange())
1286                         in = &cur.prevMath();
1287                 else if (cur.lastpos() > 0 && cur.cell().back()->allowsLimitsChange())
1288                         in = cur.cell().back().nucleus();
1289                 // only when nucleus allows this
1290                 if (!in)
1291                         return;
1292                 cur.recordUndoInset();
1293                 if (!cmd.argument().empty()) {
1294                         if (cmd.argument() == "limits")
1295                                 in->limits(LIMITS);
1296                         else if (cmd.argument() == "nolimits")
1297                                 in->limits(NO_LIMITS);
1298                         else
1299                                 in->limits(AUTO_LIMITS);
1300                 } else if (in->limits() == AUTO_LIMITS)
1301                         in->limits(in->defaultLimits() == LIMITS ? NO_LIMITS : LIMITS);
1302                 else
1303                         in->limits(AUTO_LIMITS);
1304                 return;
1305         }
1306
1307         default:
1308                 InsetMath::doDispatch(cur, cmd);
1309                 break;
1310         }
1311 }
1312
1313
1314 bool InsetMathNest::findMacroToFoldUnfold(Cursor & it, bool fold) const {
1315         // look for macro to open/close, but stay in mathed
1316         for (; !it.empty(); it.pop_back()) {
1317
1318                 // go backward through the current cell
1319                 Inset * inset = it.nextInset();
1320                 while (inset && inset->asInsetMath()) {
1321                         InsetMathMacro * macro = inset->asInsetMath()->asMacro();
1322                         if (macro) {
1323                                 // found the an macro to open/close?
1324                                 if (macro->folded() != fold)
1325                                         return true;
1326
1327                                 // Wrong folding state.
1328                                 // If this was the first we see in this slice, look further left,
1329                                 // otherwise go up.
1330                                 if (inset != it.nextInset())
1331                                         break;
1332                         }
1333
1334                         // go up if this was the left most position
1335                         if (it.pos() == 0)
1336                                 break;
1337
1338                         // go left
1339                         it.pos()--;
1340                         inset = it.nextInset();
1341                 }
1342         }
1343
1344         return false;
1345 }
1346
1347
1348 bool InsetMathNest::getStatus(Cursor & cur, FuncRequest const & cmd,
1349                 FuncStatus & flag) const
1350 {
1351         // the font related toggles
1352         //string tc = "mathnormal";
1353         bool ret = true;
1354         string const arg = to_utf8(cmd.argument());
1355         switch (cmd.action()) {
1356 #if 0
1357         case LFUN_INSET_MODIFY:
1358                 // FIXME: check temporarily disabled
1359                 // valign code
1360                 char align = mathcursor::valign();
1361                 if (align == '\0') {
1362                         enable = false;
1363                         break;
1364                 }
1365                 if (cmd.argument().empty()) {
1366                         flag.clear();
1367                         break;
1368                 }
1369                 if (!contains("tcb", cmd.argument()[0])) {
1370                         enable = false;
1371                         break;
1372                 }
1373                 flag.setOnOff(cmd.argument()[0] == align);
1374                 break;
1375 #endif
1376         /// We have to handle them since 1.4 blocks all unhandled actions
1377         case LFUN_FONT_ITAL:
1378         case LFUN_FONT_BOLD:
1379         case LFUN_FONT_BOLDSYMBOL:
1380         case LFUN_FONT_SANS:
1381         case LFUN_FONT_EMPH:
1382         case LFUN_FONT_TYPEWRITER:
1383         case LFUN_FONT_NOUN:
1384         case LFUN_FONT_ROMAN:
1385         case LFUN_FONT_DEFAULT:
1386                 flag.setEnabled(true);
1387                 break;
1388
1389         // we just need to be in math mode to enable that
1390         case LFUN_MATH_SIZE:
1391         case LFUN_MATH_SPACE:
1392         case LFUN_MATH_EXTERN:
1393                 flag.setEnabled(true);
1394                 break;
1395
1396         case LFUN_FONT_UNDERLINE:
1397         case LFUN_FONT_FRAK:
1398                 flag.setEnabled(currentMode() != TEXT_MODE);
1399                 break;
1400
1401         case LFUN_MATH_FONT_STYLE: {
1402                 bool const textarg =
1403                         arg == "textbf"   || arg == "textsf" ||
1404                         arg == "textrm"   || arg == "textmd" ||
1405                         arg == "textit"   || arg == "textsc" ||
1406                         arg == "textsl"   || arg == "textup" ||
1407                         arg == "texttt"   || arg == "textbb" ||
1408                         arg == "textnormal";
1409                 flag.setEnabled(currentMode() != TEXT_MODE || textarg);
1410                 break;
1411         }
1412
1413         case LFUN_MATH_MODE:
1414                 // forbid "math-mode on" in math mode to prevent irritating
1415                 // behaviour of menu entries (bug 6709)
1416                 flag.setEnabled(currentMode() == TEXT_MODE || arg != "on");
1417                 break;
1418
1419         case LFUN_MATH_INSERT:
1420                 flag.setEnabled(currentMode() != TEXT_MODE);
1421                 break;
1422
1423         case LFUN_MATH_AMS_MATRIX:
1424         case LFUN_MATH_MATRIX:
1425                 flag.setEnabled(currentMode() == MATH_MODE);
1426                 break;
1427
1428         case LFUN_INSET_INSERT: {
1429                 // Don't test createMathInset_fromDialogStr(), since
1430                 // getStatus is not called with a valid reference and the
1431                 // dialog would not be applicable.
1432                 string const name = cmd.getArg(0);
1433                 flag.setEnabled(name == "ref" || name == "mathspace");
1434                 break;
1435         }
1436
1437         case LFUN_DIALOG_SHOW_NEW_INSET: {
1438                 docstring const & name = cmd.argument();
1439                 if (name == "space")
1440                         flag.setEnabled(false);
1441                 break;
1442         }
1443
1444
1445         case LFUN_MATH_DELIM:
1446         case LFUN_MATH_BIGDELIM:
1447                 // Don't do this with multi-cell selections
1448                 flag.setEnabled(cur.selBegin().idx() == cur.selEnd().idx());
1449                 break;
1450
1451         case LFUN_MATH_MACRO_FOLD:
1452         case LFUN_MATH_MACRO_UNFOLD: {
1453                 Cursor it = cur;
1454                 bool found = findMacroToFoldUnfold(it, cmd.action() == LFUN_MATH_MACRO_FOLD);
1455                 flag.setEnabled(found);
1456                 break;
1457         }
1458
1459         case LFUN_SPECIALCHAR_INSERT:
1460         case LFUN_SCRIPT_INSERT:
1461                 // FIXME: These would probably make sense in math-text mode
1462                 flag.setEnabled(false);
1463                 break;
1464
1465         case LFUN_CAPTION_INSERT:
1466                 flag.setEnabled(false);
1467                 break;
1468
1469         case LFUN_SPACE_INSERT: {
1470                 docstring const & name = cmd.argument();
1471                 if (name == "visible")
1472                         flag.setEnabled(false);
1473                 break;
1474         }
1475
1476         case LFUN_INSET_DISSOLVE:
1477                 flag.setEnabled(!asHullInset());
1478                 break;
1479
1480         case LFUN_PASTE: {
1481                 docstring const & name = cmd.argument();
1482                 if (name == "html" || name == "latex")
1483                         flag.setEnabled(false);
1484                 break;
1485         }
1486
1487         case LFUN_MATH_LIMITS: {
1488                 InsetMath * in = 0;
1489                 if (cur.pos() < cur.lastpos() && cur.nextMath().allowsLimitsChange())
1490                         in = &cur.nextMath();
1491                 else if (cur.pos() > 0 && cur.prevMath().allowsLimitsChange())
1492                         in = &cur.prevMath();
1493                 else if (cur.lastpos() > 0 && cur.cell().back()->allowsLimitsChange())
1494                         in = cur.cell().back().nucleus();
1495                 if (in) {
1496                         if (!cmd.argument().empty()) {
1497                                 if (cmd.argument() == "limits")
1498                                         flag.setOnOff(in->limits() == LIMITS);
1499                                 else if (cmd.argument() == "nolimits")
1500                                         flag.setOnOff(in->limits() == NO_LIMITS);
1501                                 else
1502                                         flag.setOnOff(in->limits() == AUTO_LIMITS);
1503                         }
1504                         flag.setEnabled(true);
1505                 } else
1506                         flag.setEnabled(false);
1507                 return true;
1508         }
1509
1510         default:
1511                 ret = false;
1512                 break;
1513         }
1514         return ret;
1515 }
1516
1517
1518 void InsetMathNest::edit(Cursor & cur, bool front, EntryDirection entry_from)
1519 {
1520         cur.push(*this);
1521         bool enter_front = (entry_from == Inset::ENTRY_DIRECTION_LEFT ||
1522                 (entry_from == Inset::ENTRY_DIRECTION_IGNORE && front));
1523         enter_front ? idxFirst(cur) : idxLast(cur);
1524         cur.resetAnchor();
1525         //lyxerr << "InsetMathNest::edit, cur:\n" << cur << endl;
1526 }
1527
1528
1529 Inset * InsetMathNest::editXY(Cursor & cur, int x, int y)
1530 {
1531         int idx_min = -1;
1532         int dist_min = 1000000;
1533         for (idx_type i = 0, n = nargs(); i != n; ++i) {
1534                 int const d = cell(i).dist(cur.bv(), x, y);
1535                 if (d < dist_min) {
1536                         dist_min = d;
1537                         idx_min = i;
1538                 }
1539         }
1540         if (idx_min == -1)
1541                 return this;
1542
1543         MathData & ar = cell(idx_min);
1544         cur.push(*this);
1545         cur.idx() = idx_min;
1546         cur.pos() = ar.x2pos(&cur.bv(), x - ar.xo(cur.bv()));
1547
1548         //lyxerr << "found cell : " << idx_min << " pos: " << cur.pos() << endl;
1549         if (dist_min == 0) {
1550                 // hit inside cell
1551                 for (pos_type i = 0, n = ar.size(); i < n; ++i)
1552                         if (ar[i]->covers(cur.bv(), x, y))
1553                                 return ar[i].nucleus()->editXY(cur, x, y);
1554         }
1555         return this;
1556 }
1557
1558
1559 void InsetMathNest::lfunMousePress(Cursor & cur, FuncRequest & cmd)
1560 {
1561         //lyxerr << "## lfunMousePress: buttons: " << cmd.button() << endl;
1562         BufferView & bv = cur.bv();
1563         if (cmd.button() == mouse_button::button3) {
1564                 // Don't do anything if we right-click a
1565                 // selection, a context menu will popup.
1566                 if (bv.cursor().selection() && cur >= bv.cursor().selectionBegin()
1567                       && cur < bv.cursor().selectionEnd()) {
1568                         cur.noScreenUpdate();
1569                         return;
1570                 }
1571         }
1572
1573         // set cursor after the inset if x is nearer to that position (bug 9748)
1574         cur.moveToClosestEdge(cmd.x(), true);
1575
1576         bool do_selection = cmd.button() == mouse_button::button1
1577                 && cmd.modifier() == ShiftModifier;
1578         bv.mouseSetCursor(cur, do_selection);
1579         if (cmd.button() == mouse_button::button1) {
1580                 //lyxerr << "## lfunMousePress: setting cursor to: " << cur << endl;
1581                 // Update the cursor update flags as needed:
1582                 //
1583                 // Update::Decoration: tells to update the decoration
1584                 //                     (visual box corners that define
1585                 //                     the inset)/
1586                 // Update::FitCursor: adjust the screen to the cursor
1587                 //                    position if needed
1588                 // cur.result().update(): don't overwrite previously set flags.
1589                 cur.screenUpdateFlags(Update::Decoration | Update::FitCursor
1590                                 | cur.result().screenUpdate());
1591         } else if (cmd.button() == mouse_button::button2 && lyxrc.mouse_middlebutton_paste) {
1592                 if (cap::selection()) {
1593                         // See comment in Text::dispatch why we do this
1594                         cap::copySelectionToStack();
1595                         cmd = FuncRequest(LFUN_PASTE, "0");
1596                         doDispatch(bv.cursor(), cmd);
1597                 } else {
1598                         MathData ar;
1599                         asArray(theSelection().get(), ar);
1600                         bv.cursor().insert(ar);
1601                 }
1602         }
1603 }
1604
1605
1606 void InsetMathNest::lfunMouseMotion(Cursor & cur, FuncRequest & cmd)
1607 {
1608         // only select with button 1
1609         if (cmd.button() != mouse_button::button1)
1610                 return;
1611
1612         Cursor & bvcur = cur.bv().cursor();
1613
1614         // ignore motions deeper nested than the real anchor
1615         if (!bvcur.realAnchor().hasPart(cur)) {
1616                 cur.undispatched();
1617                 return;
1618         }
1619
1620         // set cursor after the inset if x is nearer to that position (bug 9748)
1621         cur.moveToClosestEdge(cmd.x());
1622
1623         CursorSlice old = bvcur.top();
1624
1625         // We continue with our existing selection or start a new one, so don't
1626         // reset the anchor.
1627         bvcur.setCursor(cur);
1628         // Did we actually move?
1629         if (cur.top() == old)
1630                 // We didn't move one iota, so no need to change selection status
1631                 // or update the screen.
1632                 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
1633         else
1634                 bvcur.setSelection();
1635 }
1636
1637
1638 void InsetMathNest::lfunMouseRelease(Cursor & cur, FuncRequest & cmd)
1639 {
1640         //lyxerr << "## lfunMouseRelease: buttons: " << cmd.button() << endl;
1641
1642         if (cmd.button() == mouse_button::button1) {
1643                 if (!cur.selection())
1644                         cur.noScreenUpdate();
1645                 else {
1646                         Cursor & bvcur = cur.bv().cursor();
1647                         bvcur.selection(true);
1648                 }
1649                 return;
1650         }
1651
1652         cur.undispatched();
1653 }
1654
1655
1656 bool InsetMathNest::interpretChar(Cursor & cur, char_type const c)
1657 {
1658         // try auto-correction
1659         if (lyxrc.autocorrection_math && cur.pos() != 0
1660                   && math_autocorrect(cur, c))
1661                 return true;
1662
1663         //lyxerr << "interpret 2: '" << c << "'" << endl;
1664         docstring save_selection;
1665         if (c == '^' || c == '_')
1666                 save_selection = grabAndEraseSelection(cur);
1667
1668         cur.clearTargetX();
1669         Buffer * buf = cur.buffer();
1670
1671         // handle macroMode
1672         if (cur.inMacroMode()) {
1673                 docstring name = cur.macroName();
1674
1675                 /// are we currently typing '#1' or '#2' or...?
1676                 if (name == "\\#") {
1677                         cur.backspace();
1678                         int n = c - '0';
1679                         if (n >= 1 && n <= 9)
1680                                 cur.insert(new InsetMathMacroArgument(n));
1681                         return true;
1682                 }
1683
1684                 // do not finish macro for known * commands
1685                 bool star_macro = c == '*'
1686                         && (in_word_set(name.substr(1) + '*')
1687                             || cur.buffer()->getMacro(name.substr(1) + "*", cur, true));
1688                 if (isAlphaASCII(c) || star_macro) {
1689                         cur.activeMacro()->setName(name + docstring(1, c));
1690                         return true;
1691                 }
1692
1693                 // handle 'special char' macros
1694                 if (name == "\\") {
1695                         // remove the '\\'
1696                         if (c == '\\') {
1697                                 cur.backspace();
1698                                 if (currentMode() != InsetMath::MATH_MODE)
1699                                         cur.niceInsert(createInsetMath("textbackslash", buf));
1700                                 else
1701                                         cur.niceInsert(createInsetMath("backslash", buf));
1702                         } else if (c == '^' && currentMode() == InsetMath::MATH_MODE) {
1703                                 cur.backspace();
1704                                 cur.niceInsert(createInsetMath("mathcircumflex", buf));
1705                         } else if (c == '{' || c == '%') {
1706                                 //using the saved selection as argument
1707                                 InsetMathUnknown * p = cur.activeMacro();
1708                                 p->finalize();
1709                                 MathData sel(cur.buffer());
1710                                 asArray(p->selection(), sel);
1711                                 cur.backspace();
1712                                 if (c == '{')
1713                                         cur.niceInsert(MathAtom(new InsetMathBrace(sel)));
1714                                 else
1715                                         cur.niceInsert(MathAtom(new InsetMathComment(sel)));
1716                         } else if (c == '#') {
1717                                 LASSERT(cur.activeMacro(), return false);
1718                                 cur.activeMacro()->setName(name + docstring(1, c));
1719                         } else {
1720                                 cur.backspace();
1721                                 cur.niceInsert(createInsetMath(docstring(1, c), buf));
1722                         }
1723                         return true;
1724                 }
1725
1726                 // One character big delimiters. The others are handled in
1727                 // interpretString().
1728                 latexkeys const * l = in_word_set(name.substr(1));
1729                 if (name[0] == '\\' && l && l->inset == "big") {
1730                         docstring delim;
1731                         switch (c) {
1732                         case '{':
1733                                 delim = from_ascii("\\{");
1734                                 break;
1735                         case '}':
1736                                 delim = from_ascii("\\}");
1737                                 break;
1738                         default:
1739                                 delim = docstring(1, c);
1740                                 break;
1741                         }
1742                         if (InsetMathBig::isBigInsetDelim(delim)) {
1743                                 // name + delim ared a valid InsetMathBig.
1744                                 // We can't use cur.macroModeClose() because
1745                                 // it does not handle delim.
1746                                 InsetMathUnknown * p = cur.activeMacro();
1747                                 p->finalize();
1748                                 --cur.pos();
1749                                 cur.cell().erase(cur.pos());
1750                                 cur.plainInsert(MathAtom(
1751                                         new InsetMathBig(name.substr(1), delim)));
1752                                 return true;
1753                         }
1754                 } else if (name == "\\smash" && c == '[') {
1755                         // We can't use cur.macroModeClose() because
1756                         // it would create an InsetMathPhantom
1757                         InsetMathUnknown * p = cur.activeMacro();
1758                         p->finalize();
1759                         interpretChar(cur, c);
1760                         return true;
1761                 }
1762
1763                 // leave macro mode and try again if necessary
1764                 if (cur.macroModeClose()) {
1765                         MathAtom const atom = cur.prevAtom();
1766                         if (atom->asNestInset() && atom->isActive()) {
1767                                 cur.posBackward();
1768                                 cur.nextInset()->edit(cur, true);
1769                         }
1770                 }
1771                 if (c == '{')
1772                         cur.niceInsert(MathAtom(new InsetMathBrace(buf)));
1773                 else if (c != ' ')
1774                         interpretChar(cur, c);
1775                 return true;
1776         }
1777
1778
1779         // just clear selection on pressing the space bar
1780         if (cur.selection() && c == ' ') {
1781                 cur.selection(false);
1782                 return true;
1783         }
1784
1785         if (c == '\\') {
1786                 //lyxerr << "starting with macro" << endl;
1787                 bool reduced = cap::reduceSelectionToOneCell(cur);
1788                 if (reduced || !cur.selection()) {
1789                         cur.recordUndoInset();
1790                         docstring const safe = cap::grabAndEraseSelection(cur);
1791                         if (!cur.inRegexped())
1792                                 cur.insert(MathAtom(new InsetMathUnknown(from_ascii("\\"), safe, false)));
1793                         else
1794                                 cur.niceInsert(createInsetMath("backslash", buf));
1795                 }
1796                 return true;
1797         }
1798
1799         selClearOrDel(cur);
1800
1801         if (c == '\n') {
1802                 if (currentMode() != InsetMath::MATH_MODE)
1803                         cur.insert(c);
1804                 return true;
1805         }
1806
1807         if (c == ' ') {
1808                 if (currentMode() != InsetMath::MATH_MODE) {
1809                         // insert spaces in text or undecided mode,
1810                         // but suppress direct insertion of two spaces in a row
1811                         // the still allows typing  '<space>a<space>' and deleting the 'a', but
1812                         // it is better than nothing...
1813                         if (cur.pos() == 0 || cur.prevAtom()->getChar() != ' ') {
1814                                 cur.insert(c);
1815                                 // FIXME: we have to enable full redraw here because of the
1816                                 // visual box corners that define the inset. If we know for
1817                                 // sure that we stay within the same cell we can optimize for
1818                                 // that using:
1819                                 //cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
1820                         }
1821                         return true;
1822                 }
1823                 if (cur.pos() != 0 && cur.prevAtom()->asSpaceInset()) {
1824                         cur.prevAtom().nucleus()->asSpaceInset()->incSpace();
1825                         // FIXME: we have to enable full redraw here because of the
1826                         // visual box corners that define the inset. If we know for
1827                         // sure that we stay within the same cell we can optimize for
1828                         // that using:
1829                         //cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
1830                         return true;
1831                 }
1832
1833                 if (cur.popForward()) {
1834                         // FIXME: we have to enable full redraw here because of the
1835                         // visual box corners that define the inset. If we know for
1836                         // sure that we stay within the same cell we can optimize for
1837                         // that using:
1838                         //cur.screenUpdateFlags(Update::FitCursor);
1839                         return true;
1840                 }
1841
1842                 // if we are at the very end, leave the formula
1843                 return cur.pos() != cur.lastpos();
1844         }
1845
1846         // These should be treated differently when not in text mode:
1847         if (cur.inRegexped()) {
1848                 switch (c) {
1849                 case '^':
1850                         cur.niceInsert(createInsetMath("mathcircumflex", buf));
1851                         break;
1852                 case '{':
1853                 case '}':
1854                 case '#':
1855                 case '%':
1856                 case '_':
1857                         cur.niceInsert(createInsetMath(docstring(1, c), buf));
1858                         break;
1859                 case '~':
1860                         cur.niceInsert(createInsetMath("sim", buf));
1861                         break;
1862                 default:
1863                         cur.insert(c);
1864                 }
1865                 return true;
1866         } else if (currentMode() != InsetMath::TEXT_MODE) {
1867                 if (c == '_') {
1868                         script(cur, false, save_selection);
1869                         return true;
1870                 }
1871                 if (c == '^') {
1872                         script(cur, true, save_selection);
1873                         return true;
1874                 }
1875                 if (c == '~') {
1876                         cur.niceInsert(createInsetMath("sim", buf));
1877                         return true;
1878                 }
1879                 if (currentMode() == InsetMath::MATH_MODE && Encodings::isUnicodeTextOnly(c)) {
1880                         MathAtom at = createInsetMath("text", buf);
1881                         at.nucleus()->cell(0).push_back(MathAtom(new InsetMathChar(c)));
1882                         cur.insert(at);
1883                         cur.posForward();
1884                         return true;
1885                 }
1886         } else {
1887                 if (c == '^') {
1888                         cur.niceInsert(createInsetMath("textasciicircum", buf));
1889                         return true;
1890                 }
1891                 if (c == '~') {
1892                         cur.niceInsert(createInsetMath("textasciitilde", buf));
1893                         return true;
1894                 }
1895         }
1896
1897         if (c == '{' || c == '}' || c == '&' || c == '$' || c == '#' ||
1898             c == '%' || c == '_') {
1899                 cur.niceInsert(createInsetMath(docstring(1, c), buf));
1900                 return true;
1901         }
1902
1903         // no special circumstances, so insert the character without any fuss
1904         cur.insert(c);
1905         return true;
1906 }
1907
1908
1909 bool InsetMathNest::interpretString(Cursor & cur, docstring const & str)
1910 {
1911         if (str == "\\limits" || str == "\\nolimits") {
1912                 if (cur.pos() > 0 && cur.prevMath().allowsLimitsChange()) {
1913                         cur.prevMath().limits(str == "\\limits" ? LIMITS : NO_LIMITS);
1914                         return true;
1915                 } else {
1916                         cur.message(bformat(_("Cannot apply %1$s here."), str));
1917                         return false;
1918                 }
1919         }
1920         // Create a InsetMathBig from cur.cell()[cur.pos() - 1] and t if
1921         // possible
1922         if (!cur.empty() && cur.pos() > 0 &&
1923             cur.cell()[cur.pos() - 1]->asUnknownInset()) {
1924                 if (InsetMathBig::isBigInsetDelim(str)) {
1925                         docstring prev = asString(cur.cell()[cur.pos() - 1]);
1926                         if (prev[0] == '\\') {
1927                                 prev = prev.substr(1);
1928                                 latexkeys const * l = in_word_set(prev);
1929                                 if (l && l->inset == "big") {
1930                                         cur.recordUndoSelection();
1931                                         cur.cell()[cur.pos() - 1] =
1932                                                 MathAtom(new InsetMathBig(prev, str));
1933                                         return true;
1934                                 }
1935                         }
1936                 }
1937         }
1938         return false;
1939 }
1940
1941
1942 bool InsetMathNest::script(Cursor & cur, bool up)
1943 {
1944         return script(cur, up, docstring());
1945 }
1946
1947
1948 bool InsetMathNest::script(Cursor & cur, bool up,
1949                 docstring const & save_selection)
1950 {
1951         // Hack to get \^ and \_ working
1952         //lyxerr << "handling script: up: " << up << endl;
1953         if (cur.inMacroMode() && cur.macroName() == "\\") {
1954                 if (up)
1955                         cur.niceInsert(createInsetMath("mathcircumflex", cur.buffer()));
1956                 else
1957                         interpretChar(cur, '_');
1958                 return true;
1959         }
1960
1961         cur.macroModeClose();
1962         if (asScriptInset() && cur.idx() == 0) {
1963                 // we are in a nucleus of a script inset, move to _our_ script
1964                 InsetMathScript * inset = asScriptInset();
1965                 //lyxerr << " going to cell " << inset->idxOfScript(up) << endl;
1966                 inset->ensure(up);
1967                 cur.idx() = inset->idxOfScript(up);
1968                 cur.pos() = 0;
1969         } else if (cur.pos() != 0 && cur.prevAtom()->asScriptInset()) {
1970                 --cur.pos();
1971                 InsetMathScript * inset = cur.nextAtom().nucleus()->asScriptInset();
1972                 cur.push(*inset);
1973                 inset->ensure(up);
1974                 cur.idx() = inset->idxOfScript(up);
1975                 cur.pos() = cur.lastpos();
1976         } else {
1977                 // convert the thing to our left to a scriptinset or create a new
1978                 // one if in the very first position of the array
1979                 if (cur.pos() == 0) {
1980                         //lyxerr << "new scriptinset" << endl;
1981                         cur.insert(new InsetMathScript(buffer_, up));
1982                 } else {
1983                         //lyxerr << "converting prev atom " << endl;
1984                         cur.prevAtom() = MathAtom(new InsetMathScript(buffer_, cur.prevAtom(), up));
1985                 }
1986                 --cur.pos();
1987                 InsetMathScript * inset = cur.nextAtom().nucleus()->asScriptInset();
1988                 // See comment in MathParser.cpp for special handling of {}-bases
1989
1990                 cur.push(*inset);
1991                 cur.idx() = 1;
1992                 cur.pos() = 0;
1993         }
1994         //lyxerr << "inserting selection 1:\n" << save_selection << endl;
1995         cur.niceInsert(save_selection);
1996         cur.resetAnchor();
1997         //lyxerr << "inserting selection 2:\n" << save_selection << endl;
1998         return true;
1999 }
2000
2001
2002 bool InsetMathNest::completionSupported(Cursor const & cur) const
2003 {
2004         return cur.inMacroMode();
2005 }
2006
2007
2008 bool InsetMathNest::inlineCompletionSupported(Cursor const & cur) const
2009 {
2010         return cur.inMacroMode();
2011 }
2012
2013
2014 bool InsetMathNest::automaticInlineCompletion() const
2015 {
2016         return lyxrc.completion_inline_math;
2017 }
2018
2019
2020 bool InsetMathNest::automaticPopupCompletion() const
2021 {
2022         return lyxrc.completion_popup_math;
2023 }
2024
2025
2026 CompletionList const *
2027 InsetMathNest::createCompletionList(Cursor const & cur) const
2028 {
2029         if (!cur.inMacroMode())
2030                 return 0;
2031
2032         return new MathCompletionList(cur);
2033 }
2034
2035
2036 docstring InsetMathNest::completionPrefix(Cursor const & cur) const
2037 {
2038         if (!cur.inMacroMode())
2039                 return docstring();
2040
2041         return cur.activeMacro()->name();
2042 }
2043
2044
2045 bool InsetMathNest::insertCompletion(Cursor & cur, docstring const & s,
2046                                      bool finished)
2047 {
2048         if (!cur.inMacroMode())
2049                 return false;
2050
2051         // append completion to active macro
2052         InsetMathUnknown * inset = cur.activeMacro();
2053         inset->setName(inset->name() + s);
2054
2055         // finish macro
2056         if (finished) {
2057 #if 0
2058                 // FIXME: this creates duplicates in the completion popup
2059                 // which looks ugly. Moreover the changes the list lengths
2060                 // which seems to confuse the popup as well.
2061                 MathCompletionList::addToFavorites(inset->name());
2062 #endif
2063                 lyx::dispatch(FuncRequest(LFUN_SELF_INSERT, " "));
2064         }
2065
2066         return true;
2067 }
2068
2069
2070 void InsetMathNest::completionPosAndDim(Cursor const & cur, int & x, int & y,
2071                                         Dimension & dim) const
2072 {
2073         Inset const * inset = cur.activeMacro();
2074         if (!inset)
2075                 return;
2076
2077         // get inset dimensions
2078         dim = cur.bv().coordCache().insets().dim(inset);
2079         // FIXME: these 3 are no accurate, but should depend on the font.
2080         // Now the popup jumps down if you enter a char with descent > 0.
2081         dim.des += 3;
2082         dim.asc += 3;
2083
2084         // and position
2085         Point xy = cur.bv().coordCache().insets().xy(inset);
2086         x = xy.x_;
2087         y = xy.y_;
2088 }
2089
2090
2091 ////////////////////////////////////////////////////////////////////
2092
2093 MathCompletionList::MathCompletionList(Cursor const & cur)
2094 {
2095         // fill it with macros from the buffer
2096         MacroNameSet macros;
2097         cur.buffer()->listMacroNames(macros);
2098         MacroNameSet::const_iterator it;
2099         for (it = macros.begin(); it != macros.end(); ++it) {
2100                 if (cur.buffer()->getMacro(*it, cur, false))
2101                         locals.push_back("\\" + *it);
2102         }
2103         sort(locals.begin(), locals.end());
2104
2105         if (!globals.empty())
2106                 return;
2107
2108         // fill in global macros
2109         macros.clear();
2110         MacroTable::globalMacros().getMacroNames(macros, false);
2111         //lyxerr << "Globals completion macros: ";
2112         for (it = macros.begin(); it != macros.end(); ++it) {
2113                 //lyxerr << "\\" + *it << " ";
2114                 globals.push_back("\\" + *it);
2115         }
2116         //lyxerr << std::endl;
2117
2118         // fill in global commands
2119         globals.push_back(from_ascii("\\boxed"));
2120         globals.push_back(from_ascii("\\fbox"));
2121         globals.push_back(from_ascii("\\framebox"));
2122         globals.push_back(from_ascii("\\makebox"));
2123         globals.push_back(from_ascii("\\kern"));
2124         globals.push_back(from_ascii("\\xhookrightarrow"));
2125         globals.push_back(from_ascii("\\xhookleftarrow"));
2126         globals.push_back(from_ascii("\\xrightarrow"));
2127         globals.push_back(from_ascii("\\xRightarrow"));
2128         globals.push_back(from_ascii("\\xrightharpoondown"));
2129         globals.push_back(from_ascii("\\xrightharpoonup"));
2130         globals.push_back(from_ascii("\\xrightleftharpoons"));
2131         globals.push_back(from_ascii("\\xleftarrow"));
2132         globals.push_back(from_ascii("\\xLeftarrow"));
2133         globals.push_back(from_ascii("\\xleftharpoondown"));
2134         globals.push_back(from_ascii("\\xleftharpoonup"));
2135         globals.push_back(from_ascii("\\xleftrightarrow"));
2136         globals.push_back(from_ascii("\\xLeftrightarrow"));
2137         globals.push_back(from_ascii("\\xleftrightharpoons"));
2138         globals.push_back(from_ascii("\\xmapsto"));
2139         globals.push_back(from_ascii("\\split"));
2140         globals.push_back(from_ascii("\\gathered"));
2141         globals.push_back(from_ascii("\\aligned"));
2142         globals.push_back(from_ascii("\\alignedat"));
2143         globals.push_back(from_ascii("\\cases"));
2144         globals.push_back(from_ascii("\\substack"));
2145         globals.push_back(from_ascii("\\xymatrix"));
2146         globals.push_back(from_ascii("\\Diagram"));
2147         globals.push_back(from_ascii("\\subarray"));
2148         globals.push_back(from_ascii("\\array"));
2149         globals.push_back(from_ascii("\\sqrt"));
2150         globals.push_back(from_ascii("\\root"));
2151         globals.push_back(from_ascii("\\tabular"));
2152         globals.push_back(from_ascii("\\sideset"));
2153         globals.push_back(from_ascii("\\stackrel"));
2154         globals.push_back(from_ascii("\\stackrelthree"));
2155         globals.push_back(from_ascii("\\binom"));
2156         globals.push_back(from_ascii("\\choose"));
2157         globals.push_back(from_ascii("\\brace"));
2158         globals.push_back(from_ascii("\\brack"));
2159         globals.push_back(from_ascii("\\frac"));
2160         globals.push_back(from_ascii("\\over"));
2161         globals.push_back(from_ascii("\\nicefrac"));
2162         globals.push_back(from_ascii("\\unitfrac"));
2163         globals.push_back(from_ascii("\\unitfracthree"));
2164         globals.push_back(from_ascii("\\unitone"));
2165         globals.push_back(from_ascii("\\unittwo"));
2166         globals.push_back(from_ascii("\\infer"));
2167         globals.push_back(from_ascii("\\atop"));
2168         globals.push_back(from_ascii("\\lefteqn"));
2169         globals.push_back(from_ascii("\\boldsymbol"));
2170         globals.push_back(from_ascii("\\bm"));
2171         globals.push_back(from_ascii("\\color"));
2172         globals.push_back(from_ascii("\\normalcolor"));
2173         globals.push_back(from_ascii("\\textcolor"));
2174         globals.push_back(from_ascii("\\cfrac"));
2175         globals.push_back(from_ascii("\\cfracleft"));
2176         globals.push_back(from_ascii("\\cfracright"));
2177         globals.push_back(from_ascii("\\dfrac"));
2178         globals.push_back(from_ascii("\\tfrac"));
2179         globals.push_back(from_ascii("\\dbinom"));
2180         globals.push_back(from_ascii("\\tbinom"));
2181         globals.push_back(from_ascii("\\hphantom"));
2182         globals.push_back(from_ascii("\\phantom"));
2183         globals.push_back(from_ascii("\\vphantom"));
2184         globals.push_back(from_ascii("\\cancel"));
2185         globals.push_back(from_ascii("\\bcancel"));
2186         globals.push_back(from_ascii("\\xcancel"));
2187         globals.push_back(from_ascii("\\cancelto"));
2188         globals.push_back(from_ascii("\\smash"));
2189         globals.push_back(from_ascii("\\mathclap"));
2190         globals.push_back(from_ascii("\\mathllap"));
2191         globals.push_back(from_ascii("\\mathrlap"));
2192         globals.push_back(from_ascii("\\ensuremath"));
2193         MathWordList const & words = mathedWordList();
2194         MathWordList::const_iterator it2;
2195         //lyxerr << "Globals completion commands: ";
2196         for (it2 = words.begin(); it2 != words.end(); ++it2) {
2197                 if (it2->second.inset != "macro" && !it2->second.hidden) {
2198                         // macros are already read from MacroTable::globalMacros()
2199                         globals.push_back('\\' + it2->first);
2200                         //lyxerr << '\\' + it2->first << ' ';
2201                 }
2202         }
2203         //lyxerr << std::endl;
2204         sort(globals.begin(), globals.end());
2205 }
2206
2207
2208 MathCompletionList::~MathCompletionList()
2209 {
2210 }
2211
2212
2213 size_type MathCompletionList::size() const
2214 {
2215         return locals.size() + globals.size();
2216 }
2217
2218
2219 docstring const & MathCompletionList::data(size_t idx) const
2220 {
2221         size_t lsize = locals.size();
2222         if (idx >= lsize)
2223                 return globals[idx - lsize];
2224         else
2225                 return locals[idx];
2226 }
2227
2228
2229 std::string MathCompletionList::icon(size_t idx) const
2230 {
2231         // get the latex command
2232         docstring cmd;
2233         size_t lsize = locals.size();
2234         if (idx >= lsize)
2235                 cmd = globals[idx - lsize];
2236         else
2237                 cmd = locals[idx];
2238
2239         // get the icon name by stripping the backslash
2240         docstring icon_name = frontend::Application::mathIcon(cmd.substr(1));
2241         if (icon_name.empty())
2242                 return std::string();
2243         return "math/" + to_utf8(icon_name);
2244 }
2245
2246 std::vector<docstring> MathCompletionList::globals;
2247
2248 } // namespace lyx