]> git.lyx.org Git - lyx.git/blob - src/mathed/InsetMathHull.cpp
Reimplement support for numbering of equation previews
[lyx.git] / src / mathed / InsetMathHull.cpp
1 /**
2  * \file InsetMathHull.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 "InsetMathHull.h"
14 #include "InsetMathChar.h"
15 #include "InsetMathColor.h"
16 #include "InsetMathFrac.h"
17 #include "InsetMathNest.h"
18 #include "InsetMathScript.h"
19 #include "MathExtern.h"
20 #include "MathFactory.h"
21 #include "MathStream.h"
22 #include "MathSupport.h"
23
24 #include "Buffer.h"
25 #include "BufferParams.h"
26 #include "BufferView.h"
27 #include "ColorSet.h"
28 #include "Cursor.h"
29 #include "CutAndPaste.h"
30 #include "Encoding.h"
31 #include "Exporter.h"
32 #include "FuncRequest.h"
33 #include "FuncStatus.h"
34 #include "Language.h"
35 #include "LaTeXFeatures.h"
36 #include "LyXRC.h"
37 #include "MacroTable.h"
38 #include "InsetMathMacro.h"
39 #include "InsetMathMacroTemplate.h"
40 #include "MetricsInfo.h"
41 #include "Paragraph.h"
42 #include "ParIterator.h"
43 #include "xml.h"
44 #include "TexRow.h"
45 #include "TextClass.h"
46 #include "TextPainter.h"
47 #include "TocBackend.h"
48
49 #include "insets/InsetLabel.h"
50 #include "insets/InsetRef.h"
51 #include "insets/RenderPreview.h"
52
53 #include "graphics/PreviewImage.h"
54 #include "graphics/PreviewLoader.h"
55
56 #include "frontends/alert.h"
57 #include "frontends/FontMetrics.h"
58 #include "frontends/Painter.h"
59
60 #include "support/convert.h"
61 #include "support/debug.h"
62 #include "support/gettext.h"
63 #include "support/filetools.h"
64 #include "support/lassert.h"
65 #include "support/lstrings.h"
66 #include "support/Changer.h"
67
68 #include <sstream>
69
70 using namespace std;
71 using namespace lyx::support;
72
73 namespace lyx {
74
75 using cap::grabAndEraseSelection;
76 using cap::reduceSelectionToOneCell;
77
78 namespace {
79
80         int getCols(HullType type)
81         {
82                 switch (type) {
83                 case hullEqnArray:
84                         return 3;
85                 case hullAlign:
86                 case hullFlAlign:
87                 case hullAlignAt:
88                 case hullXAlignAt:
89                 case hullXXAlignAt:
90                         return 2;
91                 case hullUnknown:
92                 case hullNone:
93                 case hullSimple:
94                 case hullEquation:
95                 case hullMultline:
96                 case hullGather:
97                 case hullRegexp:
98                         return 1;
99                 }
100                 // avoid warning
101                 return 0;
102         }
103
104
105         // returns position of first relation operator in the array
106         // used for "intelligent splitting"
107         size_t firstRelOp(MathData const & ar)
108         {
109                 for (MathData::const_iterator it = ar.begin(); it != ar.end(); ++it)
110                         if ((*it)->mathClass() == MC_REL)
111                                 return it - ar.begin();
112                 return ar.size();
113         }
114
115
116         char const * star(bool numbered)
117         {
118                 return numbered ? "" : "*";
119         }
120
121
122         // writes a preamble for underlined or struck out math display
123         void writeMathdisplayPreamble(TeXMathStream & os)
124         {
125                 if (os.strikeoutMath())
126                         return;
127
128                 if (os.ulemCmd() == TeXMathStream::UNDERLINE)
129                         os << "\\raisebox{-\\belowdisplayshortskip}{"
130                               "\\parbox[b]{\\linewidth}{";
131                 else if (os.ulemCmd() == TeXMathStream::STRIKEOUT)
132                         os << "\\parbox{\\linewidth}{";
133         }
134
135
136         // writes a postamble for underlined or struck out math display
137         void writeMathdisplayPostamble(TeXMathStream & os)
138         {
139                 if (os.strikeoutMath())
140                         return;
141
142                 if (os.ulemCmd() == TeXMathStream::UNDERLINE)
143                         os << "}}\\\\\n";
144                 else if (os.ulemCmd() == TeXMathStream::STRIKEOUT)
145                         os << "}\\\\\n";
146         }
147
148 } // namespace
149
150
151 static InsetLabel * dummy_pointer = 0;
152
153 InsetMathHull::InsetMathHull(Buffer * buf)
154         : InsetMathGrid(buf, 1, 1), type_(hullNone), numbered_(1, NONUMBER),
155           numbers_(1, empty_docstring()), label_(1, dummy_pointer),
156           preview_(new RenderPreview(this))
157 {
158         //lyxerr << "sizeof InsetMath: " << sizeof(InsetMath) << endl;
159         //lyxerr << "sizeof MetricsInfo: " << sizeof(MetricsInfo) << endl;
160         //lyxerr << "sizeof InsetMathChar: " << sizeof(InsetMathChar) << endl;
161         //lyxerr << "sizeof FontInfo: " << sizeof(FontInfo) << endl;
162         buffer_ = buf;
163         initMath();
164         setDefaults();
165 }
166
167
168 InsetMathHull::InsetMathHull(Buffer * buf, HullType type)
169         : InsetMathGrid(buf, getCols(type), 1), type_(type), numbered_(1, NONUMBER),
170           numbers_(1, empty_docstring()), label_(1, dummy_pointer),
171           preview_(new RenderPreview(this))
172 {
173         buffer_ = buf;
174         initMath();
175         setDefaults();
176 }
177
178
179 InsetMathHull::InsetMathHull(InsetMathHull const & other) : InsetMathGrid(other)
180 {
181         operator=(other);
182 }
183
184
185 InsetMathHull::~InsetMathHull()
186 {
187         for (auto & i : label_)
188                 delete i;
189 }
190
191
192 Inset * InsetMathHull::clone() const
193 {
194         return new InsetMathHull(*this);
195 }
196
197
198 InsetMathHull & InsetMathHull::operator=(InsetMathHull const & other)
199 {
200         if (this == &other)
201                 return *this;
202         InsetMathGrid::operator=(other);
203         type_  = other.type_;
204         numbered_ = other.numbered_;
205         numbers_ = other.numbers_;
206         buffer_ = other.buffer_;
207         for (auto & i : label_)
208                 delete i;
209         label_ = other.label_;
210         for (size_t i = 0; i != label_.size(); ++i) {
211                 if (label_[i])
212                         label_[i] = new InsetLabel(*label_[i]);
213         }
214         preview_.reset(new RenderPreview(*other.preview_, this));
215
216         return *this;
217 }
218
219
220 void InsetMathHull::setBuffer(Buffer & buffer)
221 {
222         InsetMathGrid::setBuffer(buffer);
223
224         for (size_t i = 0; i != label_.size(); ++i) {
225                 if (label_[i])
226                         label_[i]->setBuffer(buffer);
227         }
228 }
229
230
231 // FIXME This should really be controlled by the TOC level, or
232 // something of the sort.
233 namespace {
234         const char * counters_to_save[] = {"section", "chapter"};
235         unsigned int const numcnts = sizeof(counters_to_save)/sizeof(char *);
236 } // namespace
237
238
239 void InsetMathHull::updateBuffer(ParIterator const & it, UpdateType utype, bool const deleted)
240 {
241         if (!buffer_) {
242                 //FIXME: buffer_ should be set at creation for this inset! Problem is
243                 // This inset is created at too many places (see Parser::parse1() in
244                 // MathParser.cpp).
245                 return;
246         }
247
248         // if any of the equations are numbered, then we want to save the values
249         // of some of the counters.
250         if (haveNumbers()) {
251                 BufferParams const & bp = buffer_->params();
252                 string const & lang = it->getParLanguage(bp)->code();
253                 Counters & cnts =
254                         buffer_->masterBuffer()->params().documentClass().counters();
255
256                 // this has to be done separately
257                 docstring const eqstr = from_ascii("equation");
258                 if (cnts.hasCounter(eqstr)) {
259                         for (size_t i = 0; i != label_.size(); ++i) {
260                                 if (numbered(i)) {
261                                         Paragraph const & par = it.paragraph();
262                                         if (!par.isDeleted(it.pos())) {
263                                                 cnts.step(eqstr, utype);
264                                                 numbers_[i] = cnts.theCounter(eqstr, lang);
265                                         } else
266                                                 numbers_[i] = from_ascii("#");
267                                 } else
268                                         numbers_[i] = empty_docstring();
269                         }
270                 }
271         }
272
273         // now the labels
274         for (size_t i = 0; i != label_.size(); ++i) {
275                 if (label_[i])
276                         label_[i]->updateBuffer(it, utype, deleted);
277         }
278         // pass down
279         InsetMathGrid::updateBuffer(it, utype, deleted);
280 }
281
282
283 void InsetMathHull::addToToc(DocIterator const & pit, bool output_active,
284                                                          UpdateType utype, TocBackend & backend) const
285 {
286         if (!buffer_) {
287                 //FIXME: buffer_ should be set at creation for this inset! Problem is
288                 // This inset is created at too many places (see Parser::parse1() in
289                 // MathParser.cpp).
290                 return;
291         }
292
293         TocBuilder & b = backend.builder("equation");
294         // compute first and last item
295         row_type first = nrows();
296         for (row_type row = 0; row != nrows(); ++row)
297                 if (numbered(row)) {
298                         first = row;
299                         break;
300                 }
301         if (first == nrows())
302                 // no equation
303                 return;
304         row_type last = nrows() - 1;
305         for (; last != 0; --last)
306                 if (numbered(last))
307                         break;
308         // add equation numbers
309         b.pushItem(pit, docstring(), output_active);
310         if (first != last)
311                 b.argumentItem(bformat(from_ascii("(%1$s-%2$s)"),
312                                        numbers_[first], numbers_[last]));
313         for (row_type row = 0; row != nrows(); ++row) {
314                 if (!numbered(row))
315                         continue;
316                 if (label_[row])
317                         label_[row]->addToToc(pit, output_active, utype, backend);
318                 docstring label = nicelabel(row);
319                 if (first == last)
320                         // this is the only equation
321                         b.argumentItem(label);
322                 else {
323                         // insert as sub-items
324                         b.pushItem(pit, label, output_active);
325                         b.pop();
326                 }
327         }
328         b.pop();
329 }
330
331
332 Inset * InsetMathHull::editXY(Cursor & cur, int x, int y)
333 {
334         if (previewState(&cur.bv())) {
335                 edit(cur, true);
336                 return this;
337         }
338         return InsetMathNest::editXY(cur, x, y);
339 }
340
341
342 InsetMath::mode_type InsetMathHull::currentMode() const
343 {
344         switch (type_) {
345         case hullNone:
346                 return UNDECIDED_MODE;
347
348         // definitely math mode ...
349         case hullUnknown:
350         case hullSimple:
351         case hullEquation:
352         case hullMultline:
353         case hullGather:
354         case hullEqnArray:
355         case hullAlign:
356         case hullFlAlign:
357         case hullAlignAt:
358         case hullXAlignAt:
359         case hullXXAlignAt:
360         case hullRegexp:
361                 return MATH_MODE;
362         }
363         // avoid warning
364         return MATH_MODE;
365 }
366
367
368 // FIXME: InsetMathGrid should be changed to let the real column alignment be
369 // given by a virtual method like displayColAlign, because the values produced
370 // by defaultColAlign can be invalidated by lfuns such as add-column. For the
371 // moment the values produced by defaultColAlign are not used, notably because
372 // alignment is not implemented in the LyXHTML output.
373 char InsetMathHull::defaultColAlign(col_type col)
374 {
375         return colAlign(type_, col);
376 }
377
378
379 char InsetMathHull::displayColAlign(idx_type idx) const
380 {
381         switch (type_) {
382         case hullMultline: {
383                 row_type const r = row(idx);
384                 if (r == 0)
385                         return 'l';
386                 if (r == nrows() - 1)
387                         return 'r';
388                 return 'c';
389         }
390         case hullEqnArray:
391         case hullGather:
392         case hullAlign:
393         case hullAlignAt:
394         case hullXAlignAt:
395         case hullXXAlignAt:
396         case hullFlAlign:
397                 return colAlign(type_, col(idx));
398         default:
399                 break;
400         }
401         return InsetMathGrid::displayColAlign(idx);
402 }
403
404
405 int InsetMathHull::displayColSpace(col_type col) const
406 {
407         return colSpace(type_, col);
408 }
409
410
411 // FIXME: same comment as for defaultColAlign applies.
412 int InsetMathHull::defaultColSpace(col_type col)
413 {
414         return colSpace(type_, col);
415 }
416
417
418 string InsetMathHull::standardFont() const
419 {
420         switch (type_) {
421         case hullRegexp:
422                 return "texttt";
423         case hullNone:
424                 return "lyxnochange";
425         default:
426                 return "mathnormal";
427         }
428 }
429
430
431 ColorCode InsetMathHull::standardColor() const
432 {
433         switch (type_) {
434         case hullRegexp:
435         case hullNone:
436                 return Color_foreground;
437
438         default:
439                 return Color_math;
440         }
441 }
442
443
444 bool InsetMathHull::previewState(const BufferView *const bv) const
445 {
446         if (!editing(bv) && RenderPreview::previewMath()
447             && type_ != hullRegexp)
448         {
449                 graphics::PreviewImage const * pimage =
450                         preview_->getPreviewImage(bv->buffer());
451                 return pimage && pimage->image();
452         }
453         return false;
454 }
455
456
457 namespace {
458 const int ERROR_FRAME_WIDTH = 2;
459
460 bool previewTooSmall(Dimension const & dim)
461 {
462         return dim.width() <= 10 && dim.height() <= 10;
463 }
464 }
465
466
467 void InsetMathHull::metrics(MetricsInfo & mi, Dimension & dim) const
468 {
469         /* Compute \(above|below)displayskip
470            true value in LaTeX is 10pt plus 2pt minus 5pt (in normal size at 10pt)
471            But we use a fixed number of pixels and scale them with zoom.
472         */
473         int const bottom_display_margin = mi.base.bv->zoomedPixels(6);
474         int top_display_margin = bottom_display_margin;
475         // at start of paragraph, add an empty line
476         if (mi.vmode)
477                 top_display_margin += theFontMetrics(mi.base.font).maxHeight() + 2;
478
479         if (previewState(mi.base.bv)) {
480                 preview_->metrics(mi, dim);
481                 if (previewTooSmall(dim)) {
482                         // preview image is too small
483                         dim.wid += 2 * ERROR_FRAME_WIDTH;
484                         dim.asc += 2 * ERROR_FRAME_WIDTH;
485                 } else {
486                         // insert a gap in front of the formula
487                         // value was hardcoded to 1 pixel
488                         dim.wid += mi.base.bv->zoomedPixels(1) ;
489                         if (display()) {
490                                 dim.asc += top_display_margin;
491                                 dim.des += bottom_display_margin;
492                         }
493                 }
494                 return;
495         }
496
497         {
498                 Changer dummy1 = mi.base.changeFontSet(standardFont());
499                 Changer dummy2 = mi.base.font.changeStyle(display() ? DISPLAY_STYLE
500                                                                                                   : TEXT_STYLE);
501
502                 // let the cells adjust themselves
503                 InsetMathGrid::metrics(mi, dim);
504         }
505
506         // Check whether the numbering interferes with the equations
507         if (numberedType()) {
508                 BufferParams::MathNumber const math_number = buffer().params().getMathNumber();
509                 int extra_offset = 0;
510                 for (row_type row = 0; row < nrows(); ++row) {
511                         rowinfo(row).offset[mi.base.bv] += extra_offset;
512                         docstring const nl = nicelabel(row);
513                         if (nl.empty())
514                                 continue;
515                         Dimension dimnl;
516                         mathed_string_dim(mi.base.font, nl, dimnl);
517                         int const ind = indent(*mi.base.bv);
518                         int const x = ind ? ind : (mi.base.textwidth - dim.wid) / 2;
519                         // for some reason metrics does not trigger at the
520                         // same point as draw, and therefore we use >= instead of >
521                         if ((math_number == BufferParams::LEFT && dimnl.wid >= x)
522                             || (math_number == BufferParams::RIGHT
523                                 && dimnl.wid >= mi.base.textwidth - x - dim.wid)) {
524                                 extra_offset += dimnl.height();
525                         }
526                 }
527                 dim.des += extra_offset;
528         }
529
530
531         if (display()) {
532                 dim.asc += top_display_margin;
533                 dim.des += bottom_display_margin;
534         }
535
536         // reserve some space for marker.
537         dim.wid += 2;
538 }
539
540
541 ColorCode InsetMathHull::backgroundColor(PainterInfo const & pi) const
542 {
543         BufferView const * const bv = pi.base.bv;
544         if (previewState(bv)) {
545                 Dimension const dim = dimension(*pi.base.bv);
546                 if (previewTooSmall(dim))
547                         return Color_error;
548                 return graphics::PreviewLoader::backgroundColor();
549         }
550         return Color_mathbg;
551 }
552
553
554 void InsetMathHull::drawMarkers(PainterInfo & pi, int x, int y) const
555 {
556         ColorCode pen_color = mouseHovered(pi.base.bv) || editing(pi.base.bv)?
557                 Color_mathframe : Color_mathcorners;
558         // If the corners have the same color as the background, do not paint them.
559         if (lcolor.getX11HexName(Color_mathbg) == lcolor.getX11HexName(pen_color))
560                 return;
561
562         Inset::drawMarkers(pi, x, y);
563         Dimension const dim = dimension(*pi.base.bv);
564         int const t = x + dim.width() - 1;
565         int const a = y - dim.ascent();
566         pi.pain.line(x, a + 3, x, a, pen_color);
567         pi.pain.line(t, a + 3, t, a, pen_color);
568         pi.pain.line(x, a, x + 3, a, pen_color);
569         pi.pain.line(t - 3, a, t, a, pen_color);
570 }
571
572
573 void InsetMathHull::drawBackground(PainterInfo & pi, int x, int y) const
574 {
575         Dimension const dim = dimension(*pi.base.bv);
576         if (previewTooSmall(dim)) {
577                 pi.pain.fillRectangle(x, y - 2 * ERROR_FRAME_WIDTH,
578                     dim.wid, dim.asc + dim.des, backgroundColor(pi));
579                 return;
580         }
581
582         pi.pain.fillRectangle(x + 1, y - dim.asc + 1, dim.wid - 2,
583                               dim.height() - 1, pi.backgroundColor(this));
584 }
585
586
587 void InsetMathHull::draw(PainterInfo & pi, int x, int y) const
588 {
589         BufferView const * const bv = pi.base.bv;
590         Dimension const dim = dimension(*bv);
591
592         if (type_ == hullRegexp)
593                 pi.pain.rectangle(x + 2, y - dim.ascent() + 1,
594                                   dim.width() - 3, dim.height() - 2, Color_regexpframe);
595
596         if (previewState(bv)) {
597                 // Do not draw change tracking cue if taken care of by RowPainter
598                 // already.
599                 Changer dummy = !canPaintChange(*bv) ? changeVar(pi.change, Change())
600                         : noChange();
601                 if (previewTooSmall(dim)) {
602                         // we have an extra frame
603                         preview_->draw(pi, x + ERROR_FRAME_WIDTH, y);
604                 } else {
605                         // one pixel gap in front
606                         // value was hardcoded to 1 pixel
607                         int const gap = pi.base.bv->zoomedPixels(1) ;
608                         preview_->draw(pi, x + gap, y);
609                 }
610                 return;
611         }
612
613         // First draw the numbers
614         if (numberedType()) {
615                 BufferParams::MathNumber const math_number = buffer().params().getMathNumber();
616                 for (row_type row = 0; row < nrows(); ++row) {
617                         int yy = y + rowinfo(row).offset[bv];
618                         docstring const nl = nicelabel(row);
619                         Dimension dimnl;
620                         mathed_string_dim(pi.base.font, nl, dimnl);
621                         if (math_number == BufferParams::LEFT) {
622                                 ColorCode const col = pi.selected_left
623                                         ? Color_selectiontext
624                                         : pi.base.font.color();
625                                 Changer dummy0 = pi.base.font.changeColor(col);
626                                 if (dimnl.wid > x - pi.leftx)
627                                         yy += rowinfo(row).descent + dimnl.asc;
628                                 pi.pain.fillRectangle(pi.leftx, yy - dimnl.asc,
629                                         dimnl.width(), dimnl.height(),
630                                         pi.selected_left ? Color_selection : pi.background_color);
631                                 pi.draw(pi.leftx, yy, nl);
632                         } else {
633                                 ColorCode const col = pi.selected_right
634                                         ? Color_selectiontext
635                                         : pi.base.font.color();
636                                 Changer dummy0 = pi.base.font.changeColor(col);
637                                 if (dimnl.wid > pi.rightx - x - dim.wid)
638                                         yy += rowinfo(row).descent + dimnl.asc;
639                                 pi.pain.fillRectangle(pi.rightx - dimnl.wid, yy - dimnl.asc,
640                                         dimnl.width(), dimnl.height(),
641                                         pi.selected_right ? Color_selection : pi.background_color);
642                                 pi.draw(pi.rightx - dimnl.wid, yy, nl);
643                         }
644                 }
645         }
646
647         // Then the equations
648         ColorCode color = pi.selected && lyxrc.use_system_colors
649                 ? Color_selectiontext : standardColor();
650         bool const really_change_color = pi.base.font.color() == Color_none;
651         Changer dummy0 = really_change_color ? pi.base.font.changeColor(color)
652                 : noChange();
653         Changer dummy1 = pi.base.changeFontSet(standardFont());
654         Changer dummy2 = pi.base.font.changeStyle(display() ? DISPLAY_STYLE
655                                                             : TEXT_STYLE);
656         InsetMathGrid::draw(pi, x + 1, y);
657         drawMarkers(pi, x, y);
658
659         // drawing change line
660         if (canPaintChange(*bv)) {
661                 // like in metrics()
662                 int const display_margin = display() ? pi.base.inPixels(Length(12, Length::PT)) : 0;
663                 pi.change.paintCue(pi, x + 1, y + 1 - dim.asc + display_margin,
664                                     x + dim.wid, y + dim.des - display_margin);
665         }
666 }
667
668
669 void InsetMathHull::metricsT(TextMetricsInfo const & mi, Dimension & dim) const
670 {
671         if (display()) {
672                 InsetMathGrid::metricsT(mi, dim);
673         } else {
674                 odocstringstream os;
675                 otexrowstream ots(os);
676                 TeXMathStream wi(ots, false, true, TeXMathStream::wsDefault);
677                 write(wi);
678                 dim.wid = os.str().size();
679                 dim.asc = 1;
680                 dim.des = 0;
681         }
682 }
683
684
685 void InsetMathHull::drawT(TextPainter & pain, int x, int y) const
686 {
687         if (display()) {
688                 InsetMathGrid::drawT(pain, x, y);
689         } else {
690                 odocstringstream os;
691                 otexrowstream ots(os);
692                 TeXMathStream wi(ots, false, true, TeXMathStream::wsDefault);
693                 write(wi);
694                 pain.draw(x, y, os.str().c_str());
695         }
696 }
697
698
699 static docstring latexString(InsetMathHull const & inset)
700 {
701         odocstringstream ls;
702         // This has to be static, because a preview snippet or a math
703         // macro containing math in text mode (such as $\text{$\phi$}$ or
704         // \newcommand{\xxx}{\text{$\phi$}}) gets processed twice. The
705         // first time as a whole, and the second time only the inner math.
706         // In this last case inset.buffer() would be invalid.
707         static Encoding const * encoding = 0;
708         if (inset.isBufferValid())
709                 encoding = &(inset.buffer().params().encoding());
710         otexrowstream ots(ls);
711         TeXMathStream wi(ots, false, true, TeXMathStream::wsPreview, encoding);
712         inset.write(wi);
713         return ls.str();
714 }
715
716
717 void InsetMathHull::initUnicodeMath() const
718 {
719         // Trigger classification of the unicode symbols in this inset
720         docstring const dummy = latexString(*this);
721 }
722
723
724 void InsetMathHull::addPreview(DocIterator const & inset_pos,
725         graphics::PreviewLoader & /*ploader*/) const
726 {
727         if (RenderPreview::previewMath()) {
728                 preparePreview(inset_pos);
729         }
730 }
731
732
733 void InsetMathHull::usedMacros(MathData const & md, DocIterator const & pos,
734                                MacroNameSet & macros, MacroNameSet & defs) const
735 {
736         MacroNameSet::iterator const end = macros.end();
737
738         for (size_t i = 0; i < md.size(); ++i) {
739                 InsetMathMacro const * mi = md[i].nucleus()->asMacro();
740                 InsetMathMacroTemplate const * mt = md[i].nucleus()->asMacroTemplate();
741                 InsetMathScript const * si = md[i].nucleus()->asScriptInset();
742                 InsetMathFracBase const * fi = md[i].nucleus()->asFracBaseInset();
743                 InsetMathGrid const * gi = md[i].nucleus()->asGridInset();
744                 InsetMathNest const * ni = md[i].nucleus()->asNestInset();
745                 if (mi) {
746                         // Look for macros in the arguments of this macro.
747                         for (idx_type idx = 0; idx < mi->nargs(); ++idx)
748                                 usedMacros(mi->cell(idx), pos, macros, defs);
749                         // Make sure this is a macro defined in the document
750                         // (as we also spot the macros in the symbols file)
751                         // or that we have not already accounted for it.
752                         docstring const name = mi->name();
753                         if (macros.find(name) == end)
754                                 continue;
755                         macros.erase(name);
756                         // Look for macros in the definition of this macro.
757                         MathData ar(pos.buffer());
758                         MacroData const * data =
759                                 pos.buffer()->getMacro(name, pos, true);
760                         if (data) {
761                                 odocstringstream macro_def;
762                                 data->write(macro_def, true);
763                                 macro_def << endl;
764                                 defs.insert(macro_def.str());
765                                 asArray(data->definition(), ar);
766                         }
767                         usedMacros(ar, pos, macros, defs);
768                 } else if (mt) {
769                         MathData ar(pos.buffer());
770                         asArray(mt->definition(), ar);
771                         usedMacros(ar, pos, macros, defs);
772                 } else if (si) {
773                         if (!si->nuc().empty())
774                                 usedMacros(si->nuc(), pos, macros, defs);
775                         if (si->hasDown())
776                                 usedMacros(si->down(), pos, macros, defs);
777                         if (si->hasUp())
778                                 usedMacros(si->up(), pos, macros, defs);
779                 } else if (fi || gi) {
780                         idx_type nidx = fi ? fi->nargs() : gi->nargs();
781                         for (idx_type idx = 0; idx < nidx; ++idx)
782                                 usedMacros(fi ? fi->cell(idx) : gi->cell(idx),
783                                            pos, macros, defs);
784                 } else if (ni) {
785                         usedMacros(ni->cell(0), pos, macros, defs);
786                 }
787         }
788 }
789
790
791 void InsetMathHull::preparePreview(DocIterator const & pos,
792                                    bool forexport) const
793 {
794         // there is no need to do all the macro stuff if we're not
795         // actually going to generate the preview.
796         if (!RenderPreview::previewMath() && !forexport)
797                 return;
798
799         Buffer const * buffer = pos.buffer();
800
801         // collect macros at this position
802         MacroNameSet macros;
803         buffer->listMacroNames(macros);
804
805         // collect definitions only for the macros used in this inset
806         MacroNameSet defs;
807         for (idx_type idx = 0; idx < nargs(); ++idx)
808                 usedMacros(cell(idx), pos, macros, defs);
809
810         docstring macro_preamble;
811         for (auto const & defvar : defs)
812                 macro_preamble.append(defvar);
813
814         // set the font series and size for this snippet
815         DocIterator dit = pos.getInnerText();
816         Paragraph const & par = dit.paragraph();
817         Font font = par.getFontSettings(buffer->params(), dit.pos());
818         font.fontInfo().realize(par.layout().font);
819         string const lsize = font.latexSize();
820         docstring setfont;
821         docstring endfont;
822         if (font.fontInfo().series() == BOLD_SERIES) {
823                 setfont += from_ascii("\\textbf{");
824                 endfont += '}';
825         }
826         if (lsize != "normalsize" && !prefixIs(lsize, "error"))
827                 setfont += from_ascii("\\" + lsize + '\n');
828
829         docstring const snippet = macro_preamble + setfont + latexString(*this) + endfont;
830         LYXERR(Debug::MACROS, "Preview snippet: " << snippet);
831         preview_->addPreview(snippet, *buffer, forexport);
832 }
833
834
835 void InsetMathHull::reloadPreview(DocIterator const & pos) const
836 {
837         preparePreview(pos);
838         preview_->startLoading(*pos.buffer());
839 }
840
841
842 void InsetMathHull::loadPreview(DocIterator const & pos) const
843 {
844         bool const forexport = true;
845         preparePreview(pos, forexport);
846         preview_->startLoading(*pos.buffer(), forexport);
847 }
848
849
850 bool InsetMathHull::notifyCursorLeaves(Cursor const & old, Cursor & cur)
851 {
852         if (RenderPreview::previewMath()) {
853                 reloadPreview(old);
854                 cur.screenUpdateFlags(Update::Force);
855         }
856         return false;
857 }
858
859
860 docstring InsetMathHull::label(row_type row) const
861 {
862         LASSERT(row < nrows(), return docstring());
863         if (InsetLabel * il = label_[row])
864                 return il->screenLabel();
865         return docstring();
866 }
867
868
869 void InsetMathHull::label(row_type row, docstring const & label)
870 {
871         //lyxerr << "setting label '" << label << "' for row " << row << endl;
872         if (label_[row]) {
873                 if (label.empty()) {
874                         delete label_[row];
875                         label_[row] = dummy_pointer;
876                 } else {
877                         if (buffer_)
878                                 label_[row]->updateLabelAndRefs(label);
879                         else
880                                 label_[row]->setParam("name", label);
881                 }
882                 return;
883         }
884         InsetCommandParams p(LABEL_CODE);
885         p["name"] = label;
886         label_[row] = new InsetLabel(buffer_, p);
887         if (buffer_)
888                 label_[row]->setBuffer(buffer());
889 }
890
891
892 void InsetMathHull::numbered(row_type row, Numbered num)
893 {
894         numbered_[row] = num;
895 }
896
897
898 bool InsetMathHull::numbered(row_type row) const
899 {
900         return numbered_[row] == NUMBER;
901 }
902
903
904 bool InsetMathHull::ams() const
905 {
906         switch (type_) {
907         case hullAlign:
908         case hullFlAlign:
909         case hullMultline:
910         case hullGather:
911         case hullAlignAt:
912         case hullXAlignAt:
913         case hullXXAlignAt:
914                 return true;
915         case hullUnknown:
916         case hullRegexp:
917                 return false;
918         case hullNone:
919         case hullSimple:
920         case hullEquation:
921         case hullEqnArray:
922                 break;
923         }
924         for (auto const & row : numbered_)
925                 if (row == NOTAG)
926                         return true;
927         return false;
928 }
929
930
931 bool InsetMathHull::outerDisplay() const
932 {
933         switch (type_) {
934         case hullEquation:
935         case hullEqnArray:
936         case hullAlign:
937         case hullFlAlign:
938         case hullGather:
939         case hullMultline:
940                 return true;
941         case hullNone:
942         case hullSimple:
943         case hullAlignAt:
944         case hullXAlignAt:
945         case hullXXAlignAt:
946         case hullUnknown:
947         case hullRegexp:
948                 break;
949         }
950         return false;
951 }
952
953
954 int InsetMathHull::rowFlags() const
955 {
956         switch (type_) {
957         case hullUnknown:
958         case hullSimple:
959         case hullNone:
960         case hullRegexp:
961                 return Inline;
962         case hullEqnArray:
963         case hullAlign:
964         case hullFlAlign:
965         case hullAlignAt:
966         case hullXAlignAt:
967         case hullXXAlignAt:
968         case hullEquation:
969         case hullMultline:
970         case hullGather:
971                 if (buffer().params().is_math_indent)
972                         return Display | AlignLeft;
973                 else
974                         return Display;
975         }
976         // avoid warning
977         return Display;
978 }
979
980
981 int InsetMathHull::indent(BufferView const & bv) const
982 {
983         // FIXME: set this in the textclass. This value is what the article class uses.
984         static Length default_indent(2.5, Length::EM);
985         if (display() && buffer().params().is_math_indent) {
986                 Length const & len = buffer().params().getMathIndent();
987                 if (len.empty())
988                         return bv.inPixels(default_indent);
989                 else
990                         return bv.inPixels(len);
991         } else
992                 return Inset::indent(bv);
993 }
994
995
996 bool InsetMathHull::numberedType() const
997 {
998         switch (type_) {
999         case hullUnknown:
1000         case hullNone:
1001         case hullSimple:
1002         case hullXXAlignAt:
1003         case hullRegexp:
1004                 return false;
1005         case hullEqnArray:
1006         case hullAlign:
1007         case hullFlAlign:
1008         case hullAlignAt:
1009         case hullXAlignAt:
1010         case hullEquation:
1011         case hullMultline:
1012         case hullGather:
1013                 break;
1014         }
1015         for (row_type row = 0; row < nrows(); ++row)
1016                 if (numbered(row))
1017                         return true;
1018         return false;
1019 }
1020
1021
1022 void InsetMathHull::validate(LaTeXFeatures & features) const
1023 {
1024         if (features.runparams().isLaTeX()) {
1025                 if (ams())
1026                         features.require("amsmath");
1027
1028                 if (type_ == hullRegexp) {
1029                         features.require("color");
1030                         docstring frcol = from_utf8(lcolor.getLaTeXName(Color_regexpframe));
1031                         docstring bgcol = from_ascii("white");
1032                         features.addPreambleSnippet(
1033                                 "\\newcommand{\\regexp}[1]{\\fcolorbox{"
1034                                 + frcol + "}{"
1035                                 + bgcol + "}{\\ensuremath{\\mathtt{#1}}}}");
1036                         features.addPreambleSnippet(
1037                                 from_ascii("\\newcommand{\\endregexp}{}"));
1038                 } else if (outerDisplay() && features.inDeletedInset()) {
1039                                 features.require("tikz");
1040                                 features.require("ct-tikz-object-sout");
1041                 }
1042
1043                 // Validation is necessary only if not using AMS math.
1044                 // To be safe, we will always run mathedvalidate.
1045                 //if (features.amsstyle)
1046                 //  return;
1047
1048                 //features.binom      = true;
1049         } else if (features.runparams().math_flavor == OutputParams::MathAsHTML) {
1050                 // it would be better to do this elsewhere, but we can't validate in
1051                 // InsetMathMatrix and we have no way, outside MathExtern, to know if
1052                 // we even have any matrices.
1053                                 features.addCSSSnippet(
1054                                         "table.matrix{display: inline-block; vertical-align: middle; text-align:center;}\n"
1055                                         "table.matrix td{padding: 0.25px;}\n"
1056                                         "td.ldelim{width: 0.5ex; border: thin solid black; border-right: none;}\n"
1057                                         "td.rdelim{width: 0.5ex; border: thin solid black; border-left: none;}");
1058         }
1059         InsetMathGrid::validate(features);
1060 }
1061
1062
1063 CtObject InsetMathHull::getCtObject(OutputParams const & runparams) const
1064 {
1065         CtObject res = CtObject::Normal;
1066         switch(type_) {
1067         case hullNone:
1068         case hullSimple:
1069         case hullAlignAt:
1070         case hullXAlignAt:
1071         case hullXXAlignAt:
1072         case hullRegexp:
1073         case hullUnknown:
1074                 break;
1075
1076         case hullEquation:
1077         case hullEqnArray:
1078         case hullAlign:
1079         case hullFlAlign:
1080         case hullGather:
1081         case hullMultline: {
1082                 if (runparams.inulemcmd
1083                     && (!runparams.local_font || runparams.local_font->fontInfo().strikeout() != FONT_ON))
1084                         res = CtObject::UDisplayObject;
1085                 else
1086                         res = CtObject::DisplayObject;
1087                 break;
1088                 }
1089         }
1090         return res;
1091 }
1092
1093
1094 void InsetMathHull::header_write(TeXMathStream & os) const
1095 {
1096         bool n = numberedType();
1097
1098         switch(type_) {
1099         case hullNone:
1100                 break;
1101
1102         case hullSimple:
1103                 if (os.ulemCmd())
1104                         os << "\\mbox{";
1105                 os << '$';
1106                 os.startOuterRow();
1107                 if (cell(0).empty())
1108                         os << ' ';
1109                 break;
1110
1111         case hullEquation:
1112                 writeMathdisplayPreamble(os);
1113                 os << "\n";
1114                 os.startOuterRow();
1115                 if (n)
1116                         os << "\\begin{equation" << star(n) << "}\n";
1117                 else
1118                         os << "\\[\n";
1119                 break;
1120
1121         case hullEqnArray:
1122         case hullAlign:
1123         case hullFlAlign:
1124         case hullGather:
1125         case hullMultline:
1126                 writeMathdisplayPreamble(os);
1127                 os << "\n";
1128                 os.startOuterRow();
1129                 os << "\\begin{" << hullName(type_) << star(n) << "}\n";
1130                 break;
1131
1132         case hullAlignAt:
1133         case hullXAlignAt:
1134                 os << "\n";
1135                 os.startOuterRow();
1136                 os << "\\begin{" << hullName(type_) << star(n) << '}'
1137                   << '{' << static_cast<unsigned int>((ncols() + 1)/2) << "}\n";
1138                 break;
1139
1140         case hullXXAlignAt:
1141                 os << "\n";
1142                 os.startOuterRow();
1143                 os << "\\begin{" << hullName(type_) << '}'
1144                   << '{' << static_cast<unsigned int>((ncols() + 1)/2) << "}\n";
1145                 break;
1146
1147         case hullRegexp:
1148                 os << "\\regexp{";
1149                 break;
1150
1151         case hullUnknown:
1152                 os << "\n";
1153                 os.startOuterRow();
1154                 os << "\\begin{unknown" << star(n) << "}\n";
1155                 break;
1156         }
1157 }
1158
1159
1160 void InsetMathHull::footer_write(TeXMathStream & os) const
1161 {
1162         bool n = numberedType();
1163
1164         switch(type_) {
1165         case hullNone:
1166                 os << "\n";
1167                 break;
1168
1169         case hullSimple:
1170                 os << '$';
1171                 if (os.ulemCmd())
1172                         os << "}";
1173                 break;
1174
1175         case hullEquation:
1176                 os << "\n";
1177                 os.startOuterRow();
1178                 if (n)
1179                         os << "\\end{equation" << star(n) << "}\n";
1180                 else
1181                         os << "\\]\n";
1182                 writeMathdisplayPostamble(os);
1183                 break;
1184
1185         case hullEqnArray:
1186         case hullAlign:
1187         case hullFlAlign:
1188         case hullGather:
1189         case hullMultline:
1190                 os << "\n";
1191                 os.startOuterRow();
1192                 os << "\\end{" << hullName(type_) << star(n) << "}\n";
1193                 writeMathdisplayPostamble(os);
1194                 break;
1195
1196         case hullAlignAt:
1197         case hullXAlignAt:
1198                 os << "\n";
1199                 os.startOuterRow();
1200                 os << "\\end{" << hullName(type_) << star(n) << "}\n";
1201                 break;
1202
1203         case hullXXAlignAt:
1204                 os << "\n";
1205                 os.startOuterRow();
1206                 os << "\\end{" << hullName(type_) << "}\n";
1207                 break;
1208
1209         case hullRegexp:
1210                 // Only used as a heuristic to find the regexp termination, when searching in ignore-format mode
1211                 os << "\\endregexp{}}";
1212                 break;
1213
1214         case hullUnknown:
1215                 os << "\n";
1216                 os.startOuterRow();
1217                 os << "\\end{unknown" << star(n) << "}\n";
1218                 break;
1219         }
1220 }
1221
1222
1223 bool InsetMathHull::allowsTabularFeatures() const
1224 {
1225         switch (type_) {
1226         case hullEqnArray:
1227         case hullAlign:
1228         case hullAlignAt:
1229         case hullXAlignAt:
1230         case hullXXAlignAt:
1231         case hullFlAlign:
1232         case hullMultline:
1233         case hullGather:
1234                 return true;
1235         case hullNone:
1236         case hullSimple:
1237         case hullEquation:
1238         case hullRegexp:
1239         case hullUnknown:
1240                 break;
1241         }
1242         return false;
1243 }
1244
1245
1246 bool InsetMathHull::rowChangeOK() const
1247 {
1248         return
1249                 type_ == hullEqnArray || type_ == hullAlign ||
1250                 type_ == hullFlAlign || type_ == hullAlignAt ||
1251                 type_ == hullXAlignAt || type_ == hullXXAlignAt ||
1252                 type_ == hullGather || type_ == hullMultline;
1253 }
1254
1255
1256 bool InsetMathHull::colChangeOK() const
1257 {
1258         return
1259                 type_ == hullAlign || type_ == hullFlAlign ||type_ == hullAlignAt ||
1260                 type_ == hullXAlignAt || type_ == hullXXAlignAt;
1261 }
1262
1263
1264 void InsetMathHull::addRow(row_type row)
1265 {
1266         if (!rowChangeOK())
1267                 return;
1268
1269         bool numbered = numberedType();
1270         // Move the number and raw pointer, do not call label() (bug 7511)
1271         InsetLabel * label = dummy_pointer;
1272         docstring number = empty_docstring();
1273         if (type_ == hullMultline) {
1274                 if (row + 1 == nrows())  {
1275                         numbered_[row] = NONUMBER;
1276                         swap(label, label_[row]);
1277                         swap(number, numbers_[row]);
1278                 } else
1279                         numbered = false;
1280         }
1281
1282         numbered_.insert(numbered_.begin() + row + 1, numbered ? NUMBER : NONUMBER);
1283         numbers_.insert(numbers_.begin() + row + 1, number);
1284         label_.insert(label_.begin() + row + 1, label);
1285         InsetMathGrid::addRow(row);
1286 }
1287
1288
1289 void InsetMathHull::swapRow(row_type row)
1290 {
1291         if (nrows() <= 1)
1292                 return;
1293         if (row + 1 == nrows())
1294                 --row;
1295         swap(numbered_[row], numbered_[row + 1]);
1296         swap(numbers_[row], numbers_[row + 1]);
1297         swap(label_[row], label_[row + 1]);
1298         InsetMathGrid::swapRow(row);
1299 }
1300
1301
1302 void InsetMathHull::delRow(row_type row)
1303 {
1304         if (nrows() <= 1 || !rowChangeOK())
1305                 return;
1306         if (row + 1 == nrows() && type_ == hullMultline) {
1307                 swap(numbered_[row - 1], numbered_[row]);
1308                 swap(numbers_[row - 1], numbers_[row]);
1309                 swap(label_[row - 1], label_[row]);
1310                 InsetMathGrid::delRow(row);
1311                 return;
1312         }
1313         InsetMathGrid::delRow(row);
1314         // The last dummy row has no number info nor a label.
1315         // Test nrows() + 1 because we have already erased the row.
1316         if (row == nrows() + 1)
1317                 row--;
1318         numbered_.erase(numbered_.begin() + row);
1319         numbers_.erase(numbers_.begin() + row);
1320         delete label_[row];
1321         label_.erase(label_.begin() + row);
1322 }
1323
1324
1325 void InsetMathHull::addCol(col_type col)
1326 {
1327         if (!colChangeOK())
1328                 return;
1329         InsetMathGrid::addCol(col);
1330 }
1331
1332
1333 void InsetMathHull::delCol(col_type col)
1334 {
1335         if (ncols() <= 1 || !colChangeOK())
1336                 return;
1337         InsetMathGrid::delCol(col);
1338 }
1339
1340
1341 docstring InsetMathHull::nicelabel(row_type row) const
1342 {
1343         if (!numbered(row)) {
1344                 if (!label_[row])
1345                         return docstring();
1346                 return '[' + label_[row]->screenLabel() + ']';
1347         }
1348         docstring const & val = numbers_[row];
1349         if (!label_[row])
1350                 return '(' + val + ')';
1351         return '(' + val + ',' + label_[row]->screenLabel() + ')';
1352 }
1353
1354
1355 void InsetMathHull::glueall(HullType type)
1356 {
1357         MathData ar;
1358         for (idx_type i = 0; i < nargs(); ++i)
1359                 ar.append(cell(i));
1360         InsetLabel * label = 0;
1361         if (type == hullEquation) {
1362                 // preserve first non-empty label
1363                 for (row_type row = 0; row < nrows(); ++row) {
1364                         if (label_[row]) {
1365                                 label = label_[row];
1366                                 label_[row] = 0;
1367                                 break;
1368                         }
1369                 }
1370         }
1371         *this = InsetMathHull(buffer_, hullSimple);
1372         label_[0] = label;
1373         cell(0) = ar;
1374         setDefaults();
1375 }
1376
1377
1378 void InsetMathHull::splitTo2Cols()
1379 {
1380         LASSERT(ncols() == 1, return);
1381         InsetMathGrid::addCol(1);
1382         for (row_type row = 0; row < nrows(); ++row) {
1383                 idx_type const i = 2 * row;
1384                 pos_type pos = firstRelOp(cell(i));
1385                 cell(i + 1) = MathData(buffer_, cell(i).begin() + pos, cell(i).end());
1386                 cell(i).erase(pos, cell(i).size());
1387         }
1388 }
1389
1390
1391 void InsetMathHull::splitTo3Cols()
1392 {
1393         LASSERT(ncols() < 3, return);
1394         if (ncols() < 2)
1395                 splitTo2Cols();
1396         InsetMathGrid::addCol(2);
1397         for (row_type row = 0; row < nrows(); ++row) {
1398                 idx_type const i = 3 * row + 1;
1399                 if (!cell(i).empty()) {
1400                         cell(i + 1) = MathData(buffer_, cell(i).begin() + 1, cell(i).end());
1401                         cell(i).erase(1, cell(i).size());
1402                 }
1403         }
1404 }
1405
1406
1407 void InsetMathHull::changeCols(col_type cols)
1408 {
1409         if (ncols() == cols)
1410                 return;
1411         else if (ncols() < cols) {
1412                 // split columns
1413                 if (cols < 3)
1414                         splitTo2Cols();
1415                 else {
1416                         splitTo3Cols();
1417                         while (ncols() < cols)
1418                                 InsetMathGrid::addCol(ncols());
1419                 }
1420                 return;
1421         }
1422
1423         // combine columns
1424         for (row_type row = 0; row < nrows(); ++row) {
1425                 idx_type const i = row * ncols();
1426                 for (col_type col = cols; col < ncols(); ++col) {
1427                         cell(i + cols - 1).append(cell(i + col));
1428                 }
1429         }
1430         // delete columns
1431         while (ncols() > cols) {
1432                 InsetMathGrid::delCol(ncols() - 1);
1433         }
1434 }
1435
1436
1437 HullType InsetMathHull::getType() const
1438 {
1439         return type_;
1440 }
1441
1442
1443 void InsetMathHull::setType(HullType type)
1444 {
1445         type_ = type;
1446         setDefaults();
1447 }
1448
1449
1450 bool InsetMathHull::isMutable(HullType type)
1451 {
1452         switch (type) {
1453         case hullNone:
1454         case hullSimple:
1455         case hullEquation:
1456         case hullEqnArray:
1457         case hullAlign:
1458         case hullFlAlign:
1459         case hullAlignAt:
1460         case hullXAlignAt:
1461         case hullXXAlignAt:
1462         case hullMultline:
1463         case hullGather:
1464                 return true;
1465         case hullUnknown:
1466         case hullRegexp:
1467                 return false;
1468         }
1469         // avoid warning
1470         return false;
1471 }
1472
1473
1474 void InsetMathHull::mutate(HullType newtype)
1475 {
1476         //lyxerr << "mutating from '" << type_ << "' to '" << newtype << "'" << endl;
1477
1478         if (newtype == type_)
1479                 return;
1480
1481         // This guards the algorithm below it, which is designed with certain types
1482         // in mind.
1483         if (!isMutable(newtype) || !isMutable(type_)) {
1484                 lyxerr << "mutation from '" << to_utf8(hullName(type_))
1485                        << "' to '" << to_utf8(hullName(newtype))
1486                        << "' not implemented" << endl;
1487                 return;
1488         }
1489
1490         // we try to move along the chain
1491         // none <-> simple <-> equation <-> eqnarray -> *align* -> multline, gather -+
1492         //                                     ^                                     |
1493         //                                     +-------------------------------------+
1494         // we use eqnarray as intermediate type for mutations that are not
1495         // directly supported because it handles labels and numbering for
1496         // "down mutation".
1497
1498         switch (type_) {
1499         case hullNone:
1500                 setType(hullSimple);
1501                 numbered(0, false);
1502                 mutate(newtype);
1503                 break;
1504
1505         case hullSimple:
1506                 if (newtype == hullNone) {
1507                         setType(hullNone);
1508                         numbered(0, false);
1509                 } else {
1510                         setType(hullEquation);
1511                         numbered(0, label_[0] != nullptr);
1512                         mutate(newtype);
1513                 }
1514                 break;
1515
1516         case hullEquation:
1517                 switch (newtype) {
1518                 case hullNone:
1519                 case hullSimple:
1520                         setType(hullSimple);
1521                         numbered(0, false);
1522                         mutate(newtype);
1523                         break;
1524                 case hullEqnArray:
1525                         // split it "nicely" on the first relop
1526                         splitTo3Cols();
1527                         setType(hullEqnArray);
1528                         break;
1529                 case hullMultline:
1530                 case hullGather:
1531                         setType(newtype);
1532                         break;
1533                 default:
1534                         // *align*
1535                         // split it "nicely"
1536                         splitTo2Cols();
1537                         setType(hullAlign);
1538                         mutate(newtype);
1539                         break;
1540                 }
1541                 break;
1542
1543         case hullEqnArray:
1544                 switch (newtype) {
1545                 case hullNone:
1546                 case hullSimple:
1547                 case hullEquation:
1548                         glueall(newtype);
1549                         mutate(newtype);
1550                         break;
1551                 default:
1552                         // align & Co.
1553                         changeCols(2);
1554                         setType(hullAlign);
1555                         mutate(newtype);
1556                         break;
1557                 }
1558                 break;
1559
1560         case hullAlign:
1561         case hullAlignAt:
1562         case hullXAlignAt:
1563         case hullFlAlign:
1564                 switch (newtype) {
1565                 case hullNone:
1566                 case hullSimple:
1567                 case hullEquation:
1568                 case hullEqnArray:
1569                         changeCols(3);
1570                         setType(hullEqnArray);
1571                         mutate(newtype);
1572                         break;
1573                 case hullGather:
1574                 case hullMultline:
1575                         changeCols(1);
1576                         setType(newtype);
1577                         break;
1578                 case hullXXAlignAt:
1579                         for (row_type row = 0; row < nrows(); ++row)
1580                                 numbered(row, false);
1581                         setType(newtype);
1582                         break;
1583                 default:
1584                         setType(newtype);
1585                         break;
1586                 }
1587                 break;
1588
1589         case hullXXAlignAt:
1590                 for (row_type row = 0; row < nrows(); ++row)
1591                         numbered(row, false);
1592                 switch (newtype) {
1593                 case hullNone:
1594                 case hullSimple:
1595                 case hullEquation:
1596                 case hullEqnArray:
1597                         changeCols(3);
1598                         setType(hullEqnArray);
1599                         mutate(newtype);
1600                         break;
1601                 case hullGather:
1602                 case hullMultline:
1603                         changeCols(1);
1604                         setType(newtype);
1605                         break;
1606                 default:
1607                         setType(newtype);
1608                         break;
1609                 }
1610                 break;
1611
1612         case hullMultline:
1613         case hullGather:
1614                 switch (newtype) {
1615                 case hullGather:
1616                 case hullMultline:
1617                         setType(newtype);
1618                         break;
1619                 case hullAlign:
1620                 case hullFlAlign:
1621                 case hullAlignAt:
1622                 case hullXAlignAt:
1623                         splitTo2Cols();
1624                         setType(newtype);
1625                         break;
1626                 case hullXXAlignAt:
1627                         splitTo2Cols();
1628                         for (row_type row = 0; row < nrows(); ++row)
1629                                 numbered(row, false);
1630                         setType(newtype);
1631                         break;
1632                 default:
1633                         // first we mutate to EqnArray
1634                         splitTo3Cols();
1635                         setType(hullEqnArray);
1636                         mutate(newtype);
1637                         break;
1638                 }
1639                 break;
1640
1641         default:
1642                 // we passed the guard so we should not be here
1643                 LYXERR0("Mutation not implemented, but should have been.");
1644                 LASSERT(false, return);
1645                 break;
1646         }// switch
1647 }
1648
1649
1650 void InsetMathHull::eol(TeXMathStream & os, row_type row, bool fragile, bool latex,
1651                         bool last_eoln) const
1652 {
1653         if (numberedType()) {
1654                 if (label_[row]) {
1655                         docstring const name =
1656                                 latex ? escape(label_[row]->getParam("name"))
1657                                       : label_[row]->getParam("name");
1658                         os << "\\label{" + name + '}';
1659                 }
1660                 if (type_ != hullMultline) {
1661                         if (numbered_[row]  == NONUMBER)
1662                                 os << "\\nonumber ";
1663                         else if (numbered_[row]  == NOTAG)
1664                                 os<< "\\notag ";
1665                 }
1666                 if (os.output() == TeXMathStream::wsPreview && !numbers_[row].empty()) {
1667                         os << "\\global\\def\\theequation{" << numbers_[row] << "}\n";
1668                 }
1669
1670         }
1671         // Never add \\ on the last empty line of eqnarray and friends
1672         last_eoln = false;
1673         InsetMathGrid::eol(os, row, fragile, latex, last_eoln);
1674 }
1675
1676 void InsetMathHull::write(TeXMathStream & os) const
1677 {
1678         ModeSpecifier specifier(os, MATH_MODE);
1679         header_write(os);
1680         InsetMathGrid::write(os);
1681         footer_write(os);
1682 }
1683
1684
1685 void InsetMathHull::normalize(NormalStream & os) const
1686 {
1687         os << "[formula " << hullName(type_) << ' ';
1688         InsetMathGrid::normalize(os);
1689         os << "] ";
1690 }
1691
1692
1693 void InsetMathHull::infoize(odocstream & os) const
1694 {
1695         os << bformat(_("Type: %1$s"), hullName(type_));
1696 }
1697
1698
1699 void InsetMathHull::check() const
1700 {
1701         LATTEST(numbered_.size() == nrows());
1702         LATTEST(numbers_.size() == nrows());
1703         LATTEST(label_.size() == nrows());
1704 }
1705
1706
1707 void InsetMathHull::doExtern(Cursor & cur, FuncRequest & func)
1708 {
1709         //FIXME: sort out whether we want std::string or docstring for those
1710         string const lang = func.getArg(0);
1711         docstring extra = from_utf8(func.getArg(1));
1712         if (extra.empty())
1713                 extra = from_ascii("noextra");
1714
1715         // replace selection with result of computation
1716         if (reduceSelectionToOneCell(cur)) {
1717                 MathData ar;
1718                 asArray(grabAndEraseSelection(cur), ar);
1719                 lyxerr << "use selection: " << ar << endl;
1720                 cur.insert(pipeThroughExtern(lang, extra, ar));
1721                 return;
1722         }
1723
1724         // only inline, display or eqnarray math is allowed
1725         switch (getType()) {
1726         case hullSimple:
1727         case hullEquation:
1728         case hullEqnArray:
1729                 break;
1730         default:
1731                 frontend::Alert::warning(_("Bad math environment"),
1732                                 _("Computation cannot be performed for AMS "
1733                                   "math environments.\nChange the math "
1734                                   "formula type and try again."));
1735                 return;
1736         }
1737
1738         MathData eq;
1739         eq.push_back(MathAtom(new InsetMathChar('=')));
1740
1741         // go to first item in line
1742         cur.idx() -= cur.idx() % ncols();
1743         cur.pos() = 0;
1744
1745         if (getType() == hullSimple) {
1746                 size_type pos = cur.cell().find_last(eq);
1747                 MathData ar;
1748                 if (pos == cur.cell().size()) {
1749                         ar = cur.cell();
1750                         lyxerr << "use whole cell: " << ar << endl;
1751                 } else {
1752                         ar = MathData(buffer_, cur.cell().begin() + pos + 1, cur.cell().end());
1753                         lyxerr << "use partial cell form pos: " << pos << endl;
1754                 }
1755                 cur.cell().append(eq);
1756                 cur.cell().append(pipeThroughExtern(lang, extra, ar));
1757                 cur.pos() = cur.lastpos();
1758                 return;
1759         }
1760
1761         if (getType() == hullEquation) {
1762                 lyxerr << "use equation inset" << endl;
1763                 mutate(hullEqnArray);
1764                 MathData & ar = cur.cell();
1765                 lyxerr << "use cell: " << ar << endl;
1766                 ++cur.idx();
1767                 cur.cell() = eq;
1768                 ++cur.idx();
1769                 cur.cell() = pipeThroughExtern(lang, extra, ar);
1770                 // move to end of line
1771                 cur.pos() = cur.lastpos();
1772                 return;
1773         }
1774
1775         {
1776                 lyxerr << "use eqnarray" << endl;
1777                 cur.idx() += 2 - cur.idx() % ncols();
1778                 cur.pos() = 0;
1779                 MathData ar = cur.cell();
1780                 lyxerr << "use cell: " << ar << endl;
1781                 // FIXME: temporarily disabled
1782                 addRow(cur.row());
1783                 ++cur.idx();
1784                 ++cur.idx();
1785                 cur.cell() = eq;
1786                 ++cur.idx();
1787                 cur.cell() = pipeThroughExtern(lang, extra, ar);
1788                 cur.pos() = cur.lastpos();
1789         }
1790 }
1791
1792
1793 void InsetMathHull::doDispatch(Cursor & cur, FuncRequest & cmd)
1794 {
1795         //lyxerr << "action: " << cmd.action() << endl;
1796         switch (cmd.action()) {
1797
1798         case LFUN_FINISHED_BACKWARD:
1799         case LFUN_FINISHED_FORWARD:
1800         case LFUN_FINISHED_RIGHT:
1801         case LFUN_FINISHED_LEFT:
1802                 //lyxerr << "action: " << cmd.action() << endl;
1803                 InsetMathGrid::doDispatch(cur, cmd);
1804                 break;
1805
1806         case LFUN_PARAGRAPH_BREAK:
1807                 // just swallow this
1808                 break;
1809
1810         case LFUN_NEWLINE_INSERT:
1811                 // some magic for the common case
1812                 if (type_ == hullSimple || type_ == hullEquation) {
1813                         cur.recordUndoInset();
1814                         bool const align =
1815                                 cur.bv().buffer().params().use_package("amsmath") != BufferParams::package_off;
1816                         mutate(align ? hullAlign : hullEqnArray);
1817                         // mutate() may change labels and such.
1818                         cur.forceBufferUpdate();
1819                         cur.idx() = nrows() * ncols() - 1;
1820                         cur.pos() = cur.lastpos();
1821                 }
1822                 InsetMathGrid::doDispatch(cur, cmd);
1823                 break;
1824
1825         case LFUN_MATH_NUMBER_TOGGLE: {
1826                 //lyxerr << "toggling all numbers" << endl;
1827                 cur.recordUndoInset();
1828                 bool old = numberedType();
1829                 if (type_ == hullMultline)
1830                         numbered(nrows() - 1, !old);
1831                 else
1832                         for (row_type row = 0; row < nrows(); ++row)
1833                                 numbered(row, !old);
1834
1835                 cur.message(old ? _("No number") : _("Number"));
1836                 cur.forceBufferUpdate();
1837                 break;
1838         }
1839
1840         case LFUN_MATH_NUMBER_LINE_TOGGLE: {
1841                 cur.recordUndoInset();
1842                 row_type r = (type_ == hullMultline) ? nrows() - 1 : cur.row();
1843                 bool old = numbered(r);
1844                 cur.message(old ? _("No number") : _("Number"));
1845                 numbered(r, !old);
1846                 cur.forceBufferUpdate();
1847                 break;
1848         }
1849
1850         case LFUN_LABEL_INSERT: {
1851                 row_type r = (type_ == hullMultline) ? nrows() - 1 : cur.row();
1852                 docstring old_label = label(r);
1853                 docstring const default_label = from_ascii("eq:");
1854                 if (old_label.empty())
1855                         old_label = default_label;
1856
1857                 InsetCommandParams p(LABEL_CODE);
1858                 p["name"] = cmd.argument().empty() ? old_label : cmd.argument();
1859                 string const data = InsetCommand::params2string(p);
1860
1861                 if (cmd.argument().empty())
1862                         cur.bv().showDialog("label", data);
1863                 else {
1864                         FuncRequest fr(LFUN_INSET_INSERT, data);
1865                         dispatch(cur, fr);
1866                 }
1867                 break;
1868         }
1869
1870         case LFUN_LABEL_COPY_AS_REFERENCE: {
1871                 row_type row;
1872                 if (cmd.argument().empty() && &cur.inset() == this)
1873                         // if there is no argument and we're inside math, we retrieve
1874                         // the row number from the cursor position.
1875                         row = (type_ == hullMultline) ? nrows() - 1 : cur.row();
1876                 else {
1877                         // if there is an argument, find the corresponding label, else
1878                         // check whether there is at least one label.
1879                         for (row = 0; row != nrows(); ++row)
1880                                 if (label_[row]
1881                                           && (cmd.argument().empty() || label(row) == cmd.argument()))
1882                                         break;
1883                 }
1884
1885                 if (row == nrows())
1886                         break;
1887
1888                 InsetCommandParams p(REF_CODE, "ref");
1889                 p["reference"] = label(row);
1890                 cap::clearSelection();
1891                 cap::copyInset(cur, new InsetRef(buffer_, p), label(row));
1892                 break;
1893         }
1894
1895         case LFUN_WORD_DELETE_FORWARD:
1896         case LFUN_CHAR_DELETE_FORWARD:
1897                 if (col(cur.idx()) + 1 == ncols()
1898                     && cur.pos() == cur.lastpos()
1899                     && !cur.selection()) {
1900                         if (!label(row(cur.idx())).empty()) {
1901                                 cur.recordUndoInset();
1902                                 label(row(cur.idx()), docstring());
1903                         } else if (numbered(row(cur.idx()))) {
1904                                 cur.recordUndoInset();
1905                                 numbered(row(cur.idx()), false);
1906                                 cur.forceBufferUpdate();
1907                         } else {
1908                                 InsetMathGrid::doDispatch(cur, cmd);
1909                                 return;
1910                         }
1911                 } else {
1912                         InsetMathGrid::doDispatch(cur, cmd);
1913                         return;
1914                 }
1915                 break;
1916
1917         case LFUN_INSET_INSERT: {
1918                 //lyxerr << "arg: " << to_utf8(cmd.argument()) << endl;
1919                 // FIXME: this should be cleaned up to use InsetLabel methods directly.
1920                 string const name = cmd.getArg(0);
1921                 if (name == "label") {
1922                         InsetCommandParams p(LABEL_CODE);
1923                         InsetCommand::string2params(to_utf8(cmd.argument()), p);
1924                         docstring str = p["name"];
1925                         cur.recordUndoInset();
1926                         row_type const r = (type_ == hullMultline) ? nrows() - 1 : cur.row();
1927                         str = trim(str);
1928                         if (!str.empty())
1929                                 numbered(r, true);
1930                         docstring old = label(r);
1931                         if (str != old) {
1932                                 if (label_[r])
1933                                         // The label will take care of the reference update.
1934                                         label(r, str);
1935                                 else {
1936                                         label(r, str);
1937                                         // Newly created inset so initialize it.
1938                                         label_[r]->initView();
1939                                 }
1940                         }
1941                         cur.forceBufferUpdate();
1942                         break;
1943                 }
1944                 InsetMathGrid::doDispatch(cur, cmd);
1945                 return;
1946         }
1947
1948         case LFUN_MATH_EXTERN:
1949                 cur.recordUndoInset();
1950                 doExtern(cur, cmd);
1951                 break;
1952
1953         case LFUN_MATH_MUTATE: {
1954                 cur.recordUndoInset();
1955                 row_type row = cur.row();
1956                 col_type col = cur.col();
1957                 mutate(hullType(cmd.argument()));
1958                 cur.idx() = row * ncols() + col;
1959                 if (cur.idx() > cur.lastidx()) {
1960                         cur.idx() = cur.lastidx();
1961                         cur.pos() = cur.lastpos();
1962                 }
1963                 if (cur.pos() > cur.lastpos())
1964                         cur.pos() = cur.lastpos();
1965
1966                 cur.forceBufferUpdate();
1967                 // FIXME: find some more clever handling of the selection,
1968                 // i.e. preserve it.
1969                 cur.clearSelection();
1970                 //cur.dispatched(FINISHED);
1971                 break;
1972         }
1973
1974         case LFUN_MATH_DISPLAY: {
1975                 cur.recordUndoInset();
1976                 mutate(type_ == hullSimple ? hullEquation : hullSimple);
1977                 // if the cursor is in a cell that got merged, move it to
1978                 // start of the hull inset.
1979                 if (cur.idx() > 0) {
1980                         cur.idx() = 0;
1981                         cur.pos() = 0;
1982                 }
1983                 if (cur.pos() > cur.lastpos())
1984                         cur.pos() = cur.lastpos();
1985
1986                 break;
1987         }
1988
1989         case LFUN_TABULAR_FEATURE:
1990                 if (!allowsTabularFeatures())
1991                         cur.undispatched();
1992                 else
1993                         InsetMathGrid::doDispatch(cur, cmd);
1994                 break;
1995
1996         default:
1997                 InsetMathGrid::doDispatch(cur, cmd);
1998                 break;
1999         }
2000 }
2001
2002
2003 namespace {
2004
2005 bool allowDisplayMath(Cursor const & cur)
2006 {
2007         LATTEST(cur.depth() > 1);
2008         Cursor tmpcur = cur;
2009         tmpcur.pop();
2010         FuncStatus status;
2011         FuncRequest cmd(LFUN_MATH_DISPLAY);
2012         return tmpcur.getStatus(cmd, status) && status.enabled();
2013 }
2014
2015 } // namespace
2016
2017
2018 bool InsetMathHull::getStatus(Cursor & cur, FuncRequest const & cmd,
2019                 FuncStatus & status) const
2020 {
2021         switch (cmd.action()) {
2022         case LFUN_FINISHED_BACKWARD:
2023         case LFUN_FINISHED_FORWARD:
2024         case LFUN_FINISHED_RIGHT:
2025         case LFUN_FINISHED_LEFT:
2026         case LFUN_UP:
2027         case LFUN_DOWN:
2028         case LFUN_NEWLINE_INSERT:
2029         case LFUN_MATH_EXTERN:
2030                 // we handle these
2031                 status.setEnabled(true);
2032                 return true;
2033
2034         // we never allow this in math, and we want to bind enter
2035         // to another actions in command-alternatives
2036         case LFUN_PARAGRAPH_BREAK:
2037                 status.setEnabled(false);
2038                 return true;
2039         case LFUN_MATH_MUTATE: {
2040                 HullType const ht = hullType(cmd.argument());
2041                 status.setOnOff(type_ == ht);
2042                 status.setEnabled(isMutable(ht) && isMutable(type_));
2043
2044                 if (ht != hullSimple && status.enabled())
2045                         status.setEnabled(allowDisplayMath(cur));
2046                 return true;
2047         }
2048         case LFUN_MATH_DISPLAY: {
2049                 status.setEnabled(display() || allowDisplayMath(cur));
2050                 status.setOnOff(display());
2051                 return true;
2052         }
2053
2054         case LFUN_MATH_NUMBER_TOGGLE:
2055                 // FIXME: what is the right test, this or the one of
2056                 // LABEL_INSERT?
2057                 status.setEnabled(display());
2058                 status.setOnOff(numberedType());
2059                 return true;
2060
2061         case LFUN_MATH_NUMBER_LINE_TOGGLE: {
2062                 // FIXME: what is the right test, this or the one of
2063                 // LABEL_INSERT?
2064                 bool const enable = (type_ == hullMultline)
2065                         ? (nrows() - 1 == cur.row())
2066                         : display();
2067                 row_type const r = (type_ == hullMultline) ? nrows() - 1 : cur.row();
2068                 status.setEnabled(enable);
2069                 status.setOnOff(enable && numbered(r));
2070                 return true;
2071         }
2072
2073         case LFUN_LABEL_INSERT:
2074                 status.setEnabled(type_ != hullSimple);
2075                 return true;
2076
2077         case LFUN_LABEL_COPY_AS_REFERENCE: {
2078                 bool enabled = false;
2079                 if (cmd.argument().empty() && &cur.inset() == this) {
2080                         // if there is no argument and we're inside math, we retrieve
2081                         // the row number from the cursor position.
2082                         row_type row = (type_ == hullMultline) ? nrows() - 1 : cur.row();
2083                         enabled = numberedType() && label_[row];
2084                 } else {
2085                         // if there is an argument, find the corresponding label, else
2086                         // check whether there is at least one label.
2087                         for (row_type row = 0; row != nrows(); ++row) {
2088                                 if (label_[row] &&
2089                                         (cmd.argument().empty() || label(row) == cmd.argument())) {
2090                                                 enabled = true;
2091                                                 break;
2092                                 }
2093                         }
2094                 }
2095                 status.setEnabled(enabled);
2096                 return true;
2097         }
2098
2099         case LFUN_INSET_INSERT:
2100                 if (cmd.getArg(0) == "label") {
2101                         status.setEnabled(type_ != hullSimple);
2102                         return true;
2103                 }
2104                 return InsetMathGrid::getStatus(cur, cmd, status);
2105
2106         case LFUN_TABULAR_FEATURE: {
2107                 if (!allowsTabularFeatures())
2108                         return false;
2109                 string s = cmd.getArg(0);
2110                 if (!rowChangeOK()
2111                     && (s == "append-row"
2112                         || s == "delete-row"
2113                         || s == "copy-row")) {
2114                         status.message(bformat(
2115                                 from_utf8(N_("Can't change number of rows in '%1$s'")),
2116                                 hullName(type_)));
2117                         status.setEnabled(false);
2118                         return true;
2119                 }
2120                 if (!colChangeOK()
2121                     && (s == "append-column"
2122                         || s == "delete-column"
2123                         || s == "copy-column")) {
2124                         status.message(bformat(
2125                                 from_utf8(N_("Can't change number of columns in '%1$s'")),
2126                                 hullName(type_)));
2127                         status.setEnabled(false);
2128                         return true;
2129                 }
2130                 if (s == "add-vline-left" || s == "add-vline-right") {
2131                         status.message(bformat(
2132                                 from_utf8(N_("Can't add vertical grid lines in '%1$s'")),
2133                                 hullName(type_)));
2134                         status.setEnabled(false);
2135                         return true;
2136                 }
2137                 if (s == "valign-top" || s == "valign-middle"
2138                  || s == "valign-bottom" || s == "align-left"
2139                  || s == "align-center" || s == "align-right") {
2140                         status.setEnabled(false);
2141                         return true;
2142                 }
2143                 return InsetMathGrid::getStatus(cur, cmd, status);
2144         }
2145
2146         default:
2147                 return InsetMathGrid::getStatus(cur, cmd, status);
2148         }
2149 }
2150
2151
2152 int InsetMathHull::leftMargin() const
2153 {
2154         return (getType() == hullSimple) ? 0 : InsetMathGrid::leftMargin();
2155 }
2156
2157
2158 int InsetMathHull::rightMargin() const
2159 {
2160         return (getType() == hullSimple) ? 0 : InsetMathGrid::rightMargin();
2161 }
2162
2163
2164 int InsetMathHull::border() const
2165 {
2166         return (getType() == hullSimple) ? 0 : InsetMathGrid::border();
2167 }
2168
2169
2170 /////////////////////////////////////////////////////////////////////
2171
2172
2173
2174 // simply scrap this function if you want
2175 void InsetMathHull::mutateToText()
2176 {
2177 #if 0
2178         // translate to latex
2179         ostringstream os;
2180         latex(os, false, false);
2181         string str = os.str();
2182
2183         // insert this text
2184         Text * lt = view_->cursor().innerText();
2185         string::const_iterator cit = str.begin();
2186         string::const_iterator end = str.end();
2187         for (; cit != end; ++cit)
2188                 view_->getIntl()->getTransManager().TranslateAndInsert(*cit, lt);
2189
2190         // remove ourselves
2191         //dispatch(LFUN_ESCAPE);
2192 #endif
2193 }
2194
2195
2196 void InsetMathHull::handleFont(Cursor & cur, docstring const & arg,
2197         docstring const & font)
2198 {
2199         // this whole function is a hack and won't work for incremental font
2200         // changes...
2201         cur.recordUndo();
2202         if (cur.inset().asInsetMath()->name() == font)
2203                 cur.handleFont(to_utf8(font));
2204         else {
2205                 cur.handleNest(createInsetMath(font, cur.buffer()));
2206                 cur.insert(arg);
2207         }
2208 }
2209
2210
2211 void InsetMathHull::handleFont2(Cursor & cur, docstring const & arg)
2212 {
2213         cur.recordUndo();
2214         Font font;
2215         bool b;
2216         font.fromString(to_utf8(arg), b);
2217         if (font.fontInfo().color() != Color_inherit) {
2218                 MathAtom at = MathAtom(new InsetMathColor(buffer_, true, font.fontInfo().color()));
2219                 cur.handleNest(at);
2220         }
2221 }
2222
2223
2224 void InsetMathHull::edit(Cursor & cur, bool front, EntryDirection entry_from)
2225 {
2226         cur.push(*this);
2227         bool enter_front = (entry_from == Inset::ENTRY_DIRECTION_LEFT ||
2228                 (entry_from == Inset::ENTRY_DIRECTION_IGNORE && front));
2229         enter_front ? idxFirst(cur) : idxLast(cur);
2230         // The inset formula dimension is not necessarily the same as the
2231         // one of the instant preview image, so we have to indicate to the
2232         // BufferView that a metrics update is needed.
2233         cur.screenUpdateFlags(Update::Force);
2234 }
2235
2236
2237 /////////////////////////////////////////////////////////////////////
2238
2239
2240 #if 0
2241 bool InsetMathHull::searchForward(BufferView * bv, string const & str,
2242                                      bool, bool)
2243 {
2244         // FIXME: completely broken
2245         static InsetMathHull * lastformula = 0;
2246         static CursorBase current = DocIterator(ibegin(nucleus()));
2247         static MathData ar;
2248         static string laststr;
2249
2250         if (lastformula != this || laststr != str) {
2251                 //lyxerr << "reset lastformula to " << this << endl;
2252                 lastformula = this;
2253                 laststr = str;
2254                 current = ibegin(nucleus());
2255                 ar.clear();
2256                 mathed_parse_cell(ar, str, Parse::NORMAL, &buffer());
2257         } else {
2258                 increment(current);
2259         }
2260         //lyxerr << "searching '" << str << "' in " << this << ar << endl;
2261
2262         for (DocIterator it = current; it != iend(nucleus()); increment(it)) {
2263                 CursorSlice & top = it.back();
2264                 MathData const & a = top.asInsetMath()->cell(top.idx_);
2265                 if (a.matchpart(ar, top.pos_)) {
2266                         bv->cursor().setSelection(it, ar.size());
2267                         current = it;
2268                         top.pos_ += ar.size();
2269                         bv->update();
2270                         return true;
2271                 }
2272         }
2273
2274         //lyxerr << "not found!" << endl;
2275         lastformula = 0;
2276         return false;
2277 }
2278 #endif
2279
2280
2281 void InsetMathHull::write(ostream & os) const
2282 {
2283         odocstringstream oss;
2284         otexrowstream ots(oss);
2285         TeXMathStream wi(ots, false, false, TeXMathStream::wsDefault);
2286         oss << "Formula ";
2287         write(wi);
2288         os << to_utf8(oss.str());
2289 }
2290
2291
2292 void InsetMathHull::read(Lexer & lex)
2293 {
2294         MathAtom at;
2295         mathed_parse_normal(buffer_, at, lex, Parse::TRACKMACRO);
2296         operator=(*at->asHullInset());
2297 }
2298
2299
2300 bool InsetMathHull::readQuiet(Lexer & lex)
2301 {
2302         MathAtom at;
2303         bool success = mathed_parse_normal(buffer_, at, lex, Parse::QUIET);
2304         if (success)
2305                 operator=(*at->asHullInset());
2306         return success;
2307 }
2308
2309
2310 int InsetMathHull::plaintext(odocstringstream & os,
2311         OutputParams const & op, size_t max_length) const
2312 {
2313         // Try enabling this now that there is a flag as requested at #2275.
2314         if (buffer().isExporting() && display()) {
2315                 Dimension dim;
2316                 TextMetricsInfo mi;
2317                 metricsT(mi, dim);
2318                 TextPainter tpain(dim.width(), dim.height());
2319                 drawT(tpain, 0, dim.ascent());
2320                 tpain.show(os, 3);
2321                 // reset metrics cache to "real" values
2322                 //metrics();
2323                 return tpain.textheight();
2324         }
2325
2326         odocstringstream oss;
2327         otexrowstream ots(oss);
2328         Encoding const * const enc = encodings.fromLyXName("utf8");
2329
2330         TeXMathStream::OutputType ot;
2331         if (op.find_effective())
2332                 ot = TeXMathStream::wsSearchAdv;
2333         else
2334                 ot = TeXMathStream::wsDefault;
2335         // Fix Bug #6139
2336         if (type_ == hullRegexp) {
2337                 TeXMathStream wi(ots, false, true, ot, enc);
2338                 write(wi);
2339         }
2340         else {
2341                 TeXMathStream wi(ots, false, true, ot, enc);
2342                 for (row_type r = 0; r < nrows(); ++r) {
2343                         for (col_type c = 0; c < ncols(); ++c)
2344                                 wi << (c == 0 ? "" : "\t") << cell(index(r, c));
2345                         // if it's for the TOC, we write just the first line
2346                         // and do not include the newline.
2347                         if (op.for_toc || op.for_tooltip || oss.str().size() >= max_length)
2348                                 break;
2349                         if (r < nrows() - 1)
2350                                 wi << "\n";
2351                 }
2352         }
2353         docstring const str = oss.str();
2354         os << str;
2355         return str.size();
2356 }
2357
2358
2359 void InsetMathHull::docbook(XMLStream & xs, OutputParams const & runparams) const {
2360         // Choose the tag around the MathML equation.
2361         docstring name;
2362         bool doCR = false;
2363         if (getType() == hullSimple)
2364                 name = from_ascii("inlineequation");
2365         else {
2366                 doCR = true; // This is a block equation, always have <informalequation> on its own line.
2367                 name = from_ascii("informalequation");
2368         }
2369
2370         // DocBook also has <equation>, but it comes with a title.
2371         // TODO: recognise \tag from amsmath? This would allow having <equation> with a proper title.
2372
2373         docstring attr;
2374
2375         bool mathmlNamespaceInline = buffer().params().docbook_mathml_prefix == BufferParams::NoPrefix;
2376         if (mathmlNamespaceInline)
2377                 attr += "xmlns=\"http://www.w3.org/1998/Math/MathML\"";
2378
2379         for (row_type i = 0; i < nrows(); ++i) {
2380                 if (!label(i).empty()) {
2381                         if (!attr.empty())
2382                                 attr += " ";
2383
2384                         attr += "xml:id=\"" + xml::cleanID(label(i)) + "\"";
2385                         break;
2386                 }
2387         }
2388
2389         if (doCR)
2390                 if (!xs.isLastTagCR())
2391                         xs << xml::CR();
2392
2393         xs << xml::StartTag(name, attr);
2394         xs << xml::CR();
2395
2396         // With DocBook 5, MathML must be within its own namespace (defined in Buffer.cpp::writeDocBookSource, except when
2397         // it should be inlined).
2398         // Output everything in a separate stream so that this does not interfere with the standard flow of DocBook tags.
2399         std::string mathmlNamespacePrefix;
2400         if (!mathmlNamespaceInline) {
2401                 if (buffer().params().docbook_mathml_prefix == BufferParams::MPrefix)
2402                         mathmlNamespacePrefix = "m";
2403                 else if (buffer().params().docbook_mathml_prefix == BufferParams::MMLPrefix)
2404                         mathmlNamespacePrefix = "mml";
2405         }
2406
2407         odocstringstream osmath;
2408         MathMLStream ms(osmath, mathmlNamespacePrefix, true);
2409
2410         // Output the MathML subtree.
2411         // TeX transcription. Avoid MTag/ETag so that there are no extraneous spaces.
2412         ms << "<" << from_ascii("alt") << " role='tex'" << ">";
2413         // Workaround for db2latex: db2latex always includes equations with
2414         // \ensuremath{} or \begin{display}\end{display}
2415         // so we strip LyX' math environment
2416         odocstringstream ls;
2417         otexstream ols(ls);
2418         TeXMathStream wi(ols, false, false, TeXMathStream::wsDefault, runparams.encoding);
2419         InsetMathGrid::write(wi);
2420         ms << from_utf8(subst(subst(to_utf8(ls.str()), "&", "&amp;"), "<", "&lt;"));
2421         ms << "</" << from_ascii("alt") << ">";
2422
2423         // Actual transformation of the formula into MathML. This translation may fail (for example, due to custom macros).
2424         // The new output stream is required to deal with the errors: first write completely the formula into this
2425         // temporary stream; then, if it is possible without error, then copy it back to the "real" stream. Otherwise,
2426         // some incomplete tags might be put into the real stream.
2427         try {
2428                 // First, generate the MathML expression. If there is an error in the generation, this block is not fully
2429                 // executed, and the formula is not output to the DocBook stream.
2430                 odocstringstream ostmp;
2431                 MathMLStream mstmp(ostmp, ms.xmlns(), ms.xmlMode());
2432                 mathmlize(mstmp);
2433
2434                 // Choose the display style for the formula, to be output as an attribute near the formula root.
2435                 std::string mathmlAttr;
2436                 if (getType() == hullSimple)
2437                         mathmlAttr = "display=\"inline\"";
2438                 else
2439                         mathmlAttr = "display=\"block\"";
2440
2441                 // Then, output the formula.
2442                 ms << MTag("math", mathmlAttr);
2443                 ms.cr();
2444                 osmath << ostmp.str(); // osmath is not a XMLStream, so no need for XMLStream::ESCAPE_NONE.
2445                 ms << ETag("math");
2446         } catch (MathExportException const &) {
2447                 ms.cr();
2448                 osmath << "<mathphrase>MathML export failed. Please report this as a bug to the LyX developers: "
2449                         "https://www.lyx.org/trac.</mathphrase>";
2450         }
2451
2452         // Output the complete formula to the DocBook stream.
2453         xs << XMLStream::ESCAPE_NONE << osmath.str();
2454         xs << xml::CR();
2455         xs << xml::EndTag(name);
2456         if (doCR)
2457                 xs << xml::CR();
2458 }
2459
2460
2461 bool InsetMathHull::haveNumbers() const
2462 {
2463         bool havenumbers = false;
2464         // inline formulas are never numbered (bug 7351 part 3)
2465         if (getType() == hullSimple)
2466                 return havenumbers;
2467         for (size_t i = 0; i != numbered_.size(); ++i) {
2468                 if (numbered(i)) {
2469                         havenumbers = true;
2470                         break;
2471                 }
2472         }
2473         return havenumbers;
2474 }
2475
2476
2477 // FIXME XHTML
2478 // We need to do something about alignment here.
2479 //
2480 // This duplicates code from InsetMathGrid, but
2481 // we need access here to number information,
2482 // and we simply do not have that in InsetMathGrid.
2483 void InsetMathHull::htmlize(HtmlStream & os) const
2484 {
2485         bool const havenumbers = haveNumbers();
2486         bool const havetable = havenumbers || nrows() > 1 || ncols() > 1;
2487
2488         if (!havetable) {
2489                 os << cell(index(0, 0));
2490                 return;
2491         }
2492
2493         os << MTag("table", "class='mathtable'");
2494         for (row_type row = 0; row < nrows(); ++row) {
2495                 os << MTag("tr");
2496                 for (col_type col = 0; col < ncols(); ++col) {
2497                         os << MTag("td");
2498                         os << cell(index(row, col));
2499                         os << ETag("td");
2500                 }
2501                 if (havenumbers) {
2502                         os << MTag("td");
2503                         docstring const & num = numbers_[row];
2504                         if (!num.empty())
2505                                 os << '(' << num << ')';
2506                   os << ETag("td");
2507                 }
2508                 os << ETag("tr");
2509         }
2510         os << ETag("table");
2511 }
2512
2513
2514 // this duplicates code from InsetMathGrid, but
2515 // we need access here to number information,
2516 // and we simply do not have that in InsetMathGrid.
2517 void InsetMathHull::mathmlize(MathMLStream & ms) const
2518 {
2519         bool const havetable = haveNumbers() || nrows() > 1 || ncols() > 1;
2520
2521     if (havetable) {
2522         if (getType() == hullSimple) {
2523                 ms << MTag("mtable");
2524         } else if (getType() >= hullAlign && getType() <= hullXXAlignAt) {
2525             string alignment;
2526             for (col_type col = 0; col < ncols(); ++col) {
2527                 alignment += (col % 2) ? "left " : "right ";
2528             }
2529             ms << MTag("mtable", "displaystyle='true' columnalign='" + alignment + "'");
2530             } else {
2531                 ms << MTag("mtable", "displaystyle='true'");
2532         }
2533     }
2534
2535         char const * const celltag = havetable ? "mtd" : "mrow";
2536         // FIXME There does not seem to be wide support at the moment
2537         // for mlabeledtr, so we have to use just mtr for now.
2538         // char const * const rowtag = haveNumbers() ? "mlabeledtr" : "mtr";
2539         char const * const rowtag = "mtr";
2540         for (row_type row = 0; row < nrows(); ++row) {
2541                 if (havetable)
2542                         ms << MTag(rowtag);
2543                 for (col_type col = 0; col < ncols(); ++col) {
2544                         ms << MTag(celltag)
2545                            << cell(index(row, col))
2546                            << ETag(celltag);
2547                 }
2548                 // fleqn?
2549                 if (haveNumbers()) {
2550                         ms << MTag("mtd");
2551                         docstring const & num = numbers_[row];
2552                         if (!num.empty())
2553                                 ms << MTagInline("mtext") << '(' << num << ')' << ETagInline("mtext");
2554                     ms << ETag("mtd");
2555                 }
2556                 if (havetable)
2557                         ms << ETag(rowtag);
2558         }
2559         if (havetable)
2560                 ms << ETag("mtable");
2561 }
2562
2563
2564 void InsetMathHull::mathAsLatex(TeXMathStream & os) const
2565 {
2566         MathEnsurer ensurer(os, false);
2567         bool havenumbers = haveNumbers();
2568         bool const havetable = havenumbers || nrows() > 1 || ncols() > 1;
2569
2570         if (!havetable) {
2571                 os << cell(index(0, 0));
2572                 return;
2573         }
2574
2575         os << "<table class='mathtable'>";
2576         for (row_type row = 0; row < nrows(); ++row) {
2577                 os << "<tr>";
2578                 for (col_type col = 0; col < ncols(); ++col) {
2579                         os << "<td class='math'>";
2580                         os << cell(index(row, col));
2581                         os << "</td>";
2582                 }
2583                 if (havenumbers) {
2584                         os << "<td>";
2585                         docstring const & num = numbers_[row];
2586                         if (!num.empty())
2587                                 os << '(' << num << ')';
2588                   os << "</td>";
2589                 }
2590                 os << "</tr>";
2591         }
2592         os << "</table>";
2593 }
2594
2595
2596 docstring InsetMathHull::xhtml(XMLStream & xs, OutputParams const & op) const
2597 {
2598         BufferParams::MathOutput const mathtype =
2599                 buffer().masterBuffer()->params().html_math_output;
2600
2601         bool success = false;
2602
2603         // we output all the labels just at the beginning of the equation.
2604         // this should be fine.
2605         for (size_t i = 0; i != label_.size(); ++i) {
2606                 InsetLabel const * const il = label_[i];
2607                 if (!il)
2608                         continue;
2609                 il->xhtml(xs, op);
2610         }
2611
2612         // FIXME Eventually we would like to do this inset by inset.
2613         if (mathtype == BufferParams::MathML) {
2614                 odocstringstream os;
2615                 MathMLStream ms(os);
2616                 try {
2617                         mathmlize(ms);
2618                         success = true;
2619                 } catch (MathExportException const &) {}
2620                 if (success) {
2621                         if (getType() == hullSimple)
2622                                 xs << xml::StartTag("math",
2623                                                         "xmlns=\"http://www.w3.org/1998/Math/MathML\"", true);
2624                         else
2625                                 xs << xml::StartTag("math",
2626                                       "display=\"block\" xmlns=\"http://www.w3.org/1998/Math/MathML\"", true);
2627                         xs << XMLStream::ESCAPE_NONE
2628                                  << os.str()
2629                                  << xml::EndTag("math");
2630                 }
2631         } else if (mathtype == BufferParams::HTML) {
2632                 odocstringstream os;
2633                 HtmlStream ms(os);
2634                 try {
2635                         htmlize(ms);
2636                         success = true;
2637                 } catch (MathExportException const &) {}
2638                 if (success) {
2639                         string const tag = (getType() == hullSimple) ? "span" : "div";
2640                         xs << xml::StartTag(tag, "class='formula'", true)
2641                            << XMLStream::ESCAPE_NONE
2642                            << os.str()
2643                            << xml::EndTag(tag);
2644                 }
2645         }
2646
2647         // what we actually want is this:
2648         // if (
2649         //     ((mathtype == BufferParams::MathML || mathtype == BufferParams::HTML)
2650         //       && !success)
2651         //     || mathtype == BufferParams::Images
2652         //    )
2653         // but what follows is equivalent, since we'll enter only if either (a) we
2654         // tried and failed with MathML or HTML or (b) didn't try yet at all but
2655         // aren't doing LaTeX.
2656         //
2657         // so this is for Images.
2658         if (!success && mathtype != BufferParams::LaTeX) {
2659                 graphics::PreviewImage const * pimage = 0;
2660                 if (!op.dryrun) {
2661                         loadPreview(docit_);
2662                         pimage = preview_->getPreviewImage(buffer());
2663                         // FIXME Do we always have png?
2664                 }
2665
2666                 if (pimage || op.dryrun) {
2667                         string const filename = pimage ? pimage->filename().onlyFileName()
2668                                                        : "previewimage.png";
2669                         if (pimage) {
2670                                 // if we are not in the master buffer, then we need to see that the
2671                                 // generated image is copied there; otherwise, preview fails.
2672                                 Buffer const * mbuf = buffer().masterBuffer();
2673                                 if (mbuf != &buffer()) {
2674                                         string mbtmp = mbuf->temppath();
2675                                         FileName const mbufimg(support::addName(mbtmp, filename));
2676                                         pimage->filename().copyTo(mbufimg);
2677                                 }
2678                                 // add the file to the list of files to be exported
2679                                 op.exportdata->addExternalFile("xhtml", pimage->filename());
2680                         }
2681
2682                         string const tag = (getType() == hullSimple) ? "span" : "div";
2683                         xs << xml::CR()
2684                            << xml::StartTag(tag, "style = \"text-align: center;\"")
2685                            << xml::CompTag("img", "src=\"" + filename + "\" alt=\"Mathematical Equation\"")
2686                            << xml::EndTag(tag)
2687                            << xml::CR();
2688                         success = true;
2689                 }
2690         }
2691
2692         // so we'll pass this test if we've failed everything else, or
2693         // if mathtype was LaTeX, since we won't have entered any of the
2694         // earlier branches
2695         if (!success /* || mathtype != BufferParams::LaTeX */) {
2696                 // Unfortunately, we cannot use latexString() because we do not want
2697                 // $...$ or whatever.
2698                 odocstringstream ls;
2699                 otexrowstream ots(ls);
2700                 TeXMathStream wi(ots, false, true, TeXMathStream::wsPreview);
2701                 ModeSpecifier specifier(wi, MATH_MODE);
2702                 mathAsLatex(wi);
2703                 docstring const latex = ls.str();
2704
2705                 // class='math' allows for use of jsMath
2706                 // http://www.math.union.edu/~dpvc/jsMath/
2707                 // FIXME XHTML
2708                 // probably should allow for some kind of customization here
2709                 string const tag = (getType() == hullSimple) ? "span" : "div";
2710                 xs << xml::StartTag(tag, "class='math'")
2711                    << latex
2712                    << xml::EndTag(tag)
2713                    << xml::CR();
2714         }
2715         return docstring();
2716 }
2717
2718
2719 void InsetMathHull::toString(odocstream & os) const
2720 {
2721         odocstringstream ods;
2722         plaintext(ods, OutputParams(0));
2723         os << ods.str();
2724 }
2725
2726
2727 void InsetMathHull::forOutliner(docstring & os, size_t const, bool const) const
2728 {
2729         odocstringstream ods;
2730         OutputParams op(0);
2731         op.for_toc = true;
2732         // FIXME: this results in spilling TeX into the LyXHTML output since the
2733         // outliner is used to generate the LyXHTML list of figures/etc.
2734         plaintext(ods, op);
2735         os += ods.str();
2736 }
2737
2738
2739 string InsetMathHull::contextMenuName() const
2740 {
2741         return "context-math";
2742 }
2743
2744
2745 void InsetMathHull::recordLocation(DocIterator const & di)
2746 {
2747         docit_ = di;
2748 }
2749
2750
2751 bool InsetMathHull::canPaintChange(BufferView const &) const
2752 {
2753         // We let RowPainter do it seamlessly for inline insets
2754         return display();
2755 }
2756
2757
2758 } // namespace lyx