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