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