]> git.lyx.org Git - features.git/blob - src/mathed/InsetMathGrid.cpp
Add FIXME
[features.git] / src / mathed / InsetMathGrid.cpp
1 /**
2  * \file InsetMathGrid.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 #include <algorithm>
13
14 #include "InsetMathGrid.h"
15
16 #include "InsetMathUnknown.h"
17 #include "MathData.h"
18 #include "MathParser.h"
19 #include "MathStream.h"
20 #include "MetricsInfo.h"
21
22 #include "Buffer.h"
23 #include "BufferParams.h"
24 #include "BufferView.h"
25 #include "CoordCache.h"
26 #include "Cursor.h"
27 #include "CutAndPaste.h"
28 #include "FuncRequest.h"
29 #include "FuncStatus.h"
30 #include "LaTeXFeatures.h"
31 #include "TexRow.h"
32
33 #include "frontends/Clipboard.h"
34 #include "frontends/Painter.h"
35
36 #include "support/debug.h"
37 #include "support/docstream.h"
38 #include "support/gettext.h"
39 #include "support/lstrings.h"
40 #include "support/lassert.h"
41
42 #include <sstream>
43
44 using namespace std;
45 using namespace lyx::support;
46
47
48
49 namespace lyx {
50
51 static docstring verboseHLine(int n)
52 {
53         docstring res;
54         for (int i = 0; i < n; ++i)
55                 res += "\\hline";
56         if (n)
57                 res += ' ';
58         return res;
59 }
60
61
62 static int extractInt(istream & is)
63 {
64         int num = 1;
65         is >> num;
66         return (num == 0) ? 1 : num;
67 }
68
69
70 static void resetGrid(InsetMathGrid & grid)
71 {
72         while (grid.ncols() > 1)
73                 grid.delCol(grid.ncols() - 1);
74         while (grid.nrows() > 1)
75                 grid.delRow(grid.nrows() - 1);
76         grid.cell(0).erase(0, grid.cell(0).size());
77         grid.setDefaults();
78 }
79
80
81 //////////////////////////////////////////////////////////////
82
83
84 int InsetMathGrid::RowInfo::skipPixels(MetricsInfo const & mi) const
85 {
86         return mi.base.inPixels(crskip);
87 }
88
89
90 //////////////////////////////////////////////////////////////
91
92
93 InsetMathGrid::InsetMathGrid(Buffer * buf)
94         : InsetMathNest(buf, 1),
95           rowinfo_(1 + 1),
96                 colinfo_(1 + 1),
97                 cellinfo_(1),
98                 v_align_('c')
99 {
100         setDefaults();
101 }
102
103
104 InsetMathGrid::InsetMathGrid(Buffer * buf, col_type m, row_type n)
105         : InsetMathNest(buf, m * n),
106           rowinfo_(n + 1),
107                 colinfo_(m + 1),
108                 cellinfo_(m * n),
109                 v_align_('c')
110 {
111         setDefaults();
112 }
113
114
115 InsetMathGrid::InsetMathGrid(Buffer * buf, col_type m, row_type n, char v,
116         docstring const & h)
117         : InsetMathNest(buf, m * n),
118           rowinfo_(n + 1),
119           colinfo_(m + 1),
120                 cellinfo_(m * n),
121                 v_align_(v)
122 {
123         setDefaults();
124         setVerticalAlignment(v);
125         setHorizontalAlignments(h);
126 }
127
128
129 Inset * InsetMathGrid::clone() const
130 {
131         return new InsetMathGrid(*this);
132 }
133
134
135 idx_type InsetMathGrid::index(row_type row, col_type col) const
136 {
137         return col + ncols() * row;
138 }
139
140
141 void InsetMathGrid::setDefaults()
142 {
143         if (ncols() <= 0)
144                 lyxerr << "positive number of columns expected" << endl;
145         //if (nrows() <= 0)
146         //      lyxerr << "positive number of rows expected" << endl;
147         for (col_type col = 0; col < ncols(); ++col) {
148                 colinfo_[col].align = defaultColAlign(col);
149                 colinfo_[col].skip  = defaultColSpace(col);
150                 colinfo_[col].special.clear();
151         }
152         for (idx_type idx = 0; idx < nargs(); ++idx)
153                 cellinfo_[idx].multi = CELL_NORMAL;
154 }
155
156
157 bool InsetMathGrid::interpretString(Cursor & cur, docstring const & str)
158 {
159         if (str == "\\hline") {
160                 FuncRequest fr = FuncRequest(LFUN_TABULAR_FEATURE, "add-hline-above");
161                 FuncStatus status;
162                 if (getStatus(cur, fr, status)) {
163                         if (status.enabled()) {
164                                 rowinfo_[cur.row()].lines++;
165                                 return true;
166                         }
167                 }
168         }
169         return InsetMathNest::interpretString(cur, str);
170 }
171
172
173 void InsetMathGrid::setHorizontalAlignments(docstring const & hh)
174 {
175         col_type col = 0;
176         for (docstring::const_iterator it = hh.begin(); it != hh.end(); ++it) {
177                 char_type c = *it;
178                 if (c == '|') {
179                         colinfo_[col].lines++;
180                 } else if ((c == 'p' || c == 'm' || c == 'b'||
181                             c == '!' || c == '@' || c == '>' || c == '<') &&
182                            it + 1 != hh.end() && *(it + 1) == '{') {
183                         // @{decl.} and p{width} are standard LaTeX, the
184                         // others are extensions by array.sty
185                         bool const newcolumn = c == 'p' || c == 'm' || c == 'b';
186                         if (newcolumn) {
187                                 // this declares a new column
188                                 if (col >= ncols())
189                                         // Only intercolumn stuff is allowed
190                                         // in the last dummy column
191                                         break;
192                                 colinfo_[col].align = 'l';
193                         } else {
194                                 // this is intercolumn stuff
195                                 if (colinfo_[col].special.empty())
196                                         // Overtake possible lines
197                                         colinfo_[col].special = docstring(colinfo_[col].lines, '|');
198                         }
199                         int brace_open = 0;
200                         int brace_close = 0;
201                         while (it != hh.end()) {
202                                 c = *it;
203                                 colinfo_[col].special += c;
204                                 if (c == '{')
205                                         ++brace_open;
206                                 else if (c == '}')
207                                         ++brace_close;
208                                 ++it;
209                                 if (brace_open > 0 && brace_open == brace_close)
210                                         break;
211                         }
212                         --it;
213                         if (newcolumn) {
214                                 colinfo_[col].lines = count(
215                                         colinfo_[col].special.begin(),
216                                         colinfo_[col].special.end(), '|');
217                                 LYXERR(Debug::MATHED, "special column separator: `"
218                                         << to_utf8(colinfo_[col].special) << '\'');
219                                 ++col;
220                                 colinfo_[col].lines = 0;
221                                 colinfo_[col].special.clear();
222                         }
223                 } else if (col >= ncols()) {
224                         // Only intercolumn stuff is allowed in the last
225                         // dummy column
226                         break;
227                 } else if (c == 'c' || c == 'l' || c == 'r') {
228                         colinfo_[col].align = static_cast<char>(c);
229                         if (!colinfo_[col].special.empty()) {
230                                 colinfo_[col].special += c;
231                                 colinfo_[col].lines = count(
232                                                 colinfo_[col].special.begin(),
233                                                 colinfo_[col].special.end(), '|');
234                                 LYXERR(Debug::MATHED, "special column separator: `"
235                                         << to_utf8(colinfo_[col].special) << '\'');
236                         }
237                         ++col;
238                         colinfo_[col].lines = 0;
239                         colinfo_[col].special.clear();
240                 } else {
241                         lyxerr << "unknown column separator: '" << c << "'" << endl;
242                 }
243         }
244
245 /*
246         col_type n = hh.size();
247         if (n > ncols())
248                 n = ncols();
249         for (col_type col = 0; col < n; ++col)
250                 colinfo_[col].align = hh[col];
251 */
252 }
253
254
255 col_type InsetMathGrid::guessColumns(docstring const & hh)
256 {
257         col_type col = 0;
258         for (char_type const c : hh)
259                 if (c == 'c' || c == 'l' || c == 'r'||
260                     c == 'p' || c == 'm' || c == 'b')
261                         ++col;
262         // let's have at least one column, even if we did not recognize its
263         // alignment
264         if (col == 0)
265                 col = 1;
266         return col;
267 }
268
269
270 void InsetMathGrid::setHorizontalAlignment(char h, col_type col)
271 {
272         colinfo_[col].align = h;
273         if (!colinfo_[col].special.empty()) {
274                 char_type & c = colinfo_[col].special[colinfo_[col].special.size() - 1];
275                 if (c == 'l' || c == 'c' || c == 'r')
276                         c = h;
277         }
278         // FIXME: Change alignment of p, m and b columns, too
279 }
280
281
282 char InsetMathGrid::horizontalAlignment(col_type col) const
283 {
284         return colinfo_[col].align;
285 }
286
287
288 docstring InsetMathGrid::horizontalAlignments() const
289 {
290         docstring res;
291         for (col_type col = 0; col < ncols(); ++col) {
292                 if (colinfo_[col].special.empty()) {
293                         res += docstring(colinfo_[col].lines, '|');
294                         res += colinfo_[col].align;
295                 } else
296                         res += colinfo_[col].special;
297         }
298         if (colinfo_[ncols()].special.empty())
299                 return res + docstring(colinfo_[ncols()].lines, '|');
300         return res + colinfo_[ncols()].special;
301 }
302
303
304 void InsetMathGrid::setVerticalAlignment(char c)
305 {
306         v_align_ = c;
307 }
308
309
310 char InsetMathGrid::verticalAlignment() const
311 {
312         return v_align_;
313 }
314
315
316 col_type InsetMathGrid::ncols() const
317 {
318         return colinfo_.size() - 1;
319 }
320
321
322 row_type InsetMathGrid::nrows() const
323 {
324         return rowinfo_.size() - 1;
325 }
326
327
328 col_type InsetMathGrid::col(idx_type idx) const
329 {
330         return idx % ncols();
331 }
332
333
334 row_type InsetMathGrid::row(idx_type idx) const
335 {
336         return idx / ncols();
337 }
338
339
340 col_type InsetMathGrid::ncellcols(idx_type idx) const
341 {
342         col_type cols = 1;
343         if (cellinfo_[idx].multi == CELL_NORMAL)
344                 return cols;
345         // If the cell at idx is already CELL_PART_OF_MULTICOLUMN we return
346         // the number of remaining columns, not the ones of the complete
347         // multicolumn cell. This makes it possible to always go to the next
348         // cell with idx + ncellcols(idx) - 1.
349         row_type const r = row(idx);
350         while (idx+cols < nargs() && row(idx+cols) == r &&
351                cellinfo_[idx+cols].multi == CELL_PART_OF_MULTICOLUMN)
352                 cols++;
353         return cols;
354 }
355
356
357 void InsetMathGrid::vcrskip(Length const & crskip, row_type row)
358 {
359         rowinfo_[row].crskip = crskip;
360 }
361
362
363 Length InsetMathGrid::vcrskip(row_type row) const
364 {
365         return rowinfo_[row].crskip;
366 }
367
368
369 void InsetMathGrid::metrics(MetricsInfo & mi, Dimension & dim) const
370 {
371         // let the cells adjust themselves
372         for (idx_type i = 0; i < nargs(); ++i) {
373                 if (cellinfo_[i].multi != CELL_PART_OF_MULTICOLUMN) {
374                         Dimension dimc;
375                         // the 'false' is to make sure that the cell is tall enough
376                         cell(i).metrics(mi, dimc, false);
377                 }
378         }
379
380         BufferView & bv = *mi.base.bv;
381
382         // compute absolute sizes of vertical structure
383         for (row_type row = 0; row < nrows(); ++row) {
384                 int asc  = 0;
385                 int desc = 0;
386                 for (col_type col = 0; col < ncols(); ++col) {
387                         idx_type const i = index(row, col);
388                         if (cellinfo_[i].multi != CELL_PART_OF_MULTICOLUMN) {
389                                 Dimension const & dimc = cell(i).dimension(bv);
390                                 asc  = max(asc,  dimc.asc);
391                                 desc = max(desc, dimc.des);
392                         }
393                 }
394                 rowinfo_[row].ascent  = asc;
395                 rowinfo_[row].descent = desc;
396         }
397         rowinfo_[nrows()].ascent  = 0;
398         rowinfo_[nrows()].descent = 0;
399
400         // compute vertical offsets
401         rowinfo_[0].offset[&bv] = 0;
402         for (row_type row = 1; row <= nrows(); ++row) {
403                 rowinfo_[row].offset[&bv] =
404                         rowinfo_[row - 1].offset[&bv] +
405                         rowinfo_[row - 1].descent +
406                         rowinfo_[row - 1].skipPixels(mi) +
407                         rowsep() +
408                         rowinfo_[row].lines * hlinesep() +
409                         rowinfo_[row].ascent;
410         }
411
412         // adjust vertical offset
413         int h = 0;
414         switch (v_align_) {
415                 case 't':
416                         h = 0;
417                         break;
418                 case 'b':
419                         h = rowinfo_[nrows() - 1].offset[&bv];
420                         break;
421                 default:
422                         h = rowinfo_[nrows() - 1].offset[&bv] / 2;
423         }
424         for (row_type row = 0; row <= nrows(); ++row)
425                 rowinfo_[row].offset[&bv] -= h;
426
427
428         // multicolumn cell widths, as a map from first column to width in a
429         // vector of last columns.
430         // This is only used if the grid has more than one row, since for
431         // one-row grids multicolumn cells do not need special handling
432         vector<map<col_type, int> > mcolwidths(ncols());
433
434         // compute absolute sizes of horizontal structure
435         for (col_type col = 0; col < ncols(); ++col) {
436                 int wid = 0;
437                 for (row_type row = 0; row < nrows(); ++row) {
438                         idx_type const i = index(row, col);
439                         if (cellinfo_[i].multi != CELL_PART_OF_MULTICOLUMN) {
440                                 int const w = cell(i).dimension(bv).wid;
441                                 col_type const cols = ncellcols(i);
442                                 if (cols > 1 && nrows() > 1) {
443                                         col_type last = col+cols-1;
444                                         LASSERT(last < ncols(), last = ncols()-1);
445                                         map<col_type, int>::iterator it =
446                                                 mcolwidths[last].find(col);
447                                         if (it == mcolwidths[last].end())
448                                                 mcolwidths[last][col] = w;
449                                         else
450                                                 it->second = max(it->second, w);
451                                 } else
452                                         wid = max(wid, w);
453                         }
454                 }
455                 colinfo_[col].width = wid;
456         }
457         colinfo_[ncols()].width  = 0;
458
459         // compute horizontal offsets
460         colinfo_[0].offset = border() + colinfo_[0].lines * vlinesep();
461         for (col_type col = 1; col <= ncols(); ++col) {
462                 colinfo_[col].offset =
463                         colinfo_[col - 1].offset +
464                         colinfo_[col - 1].width +
465                         displayColSpace(col - 1) +
466                         colsep() +
467                         colinfo_[col].lines * vlinesep();
468         }
469
470         // increase column widths for multicolumn cells if needed
471         // FIXME: multicolumn lines are not yet considered
472         for (col_type last = 0; last < ncols(); ++last) {
473                 map<col_type, int> const & widths = mcolwidths[last];
474                 // We increase the width of the last column of the multicol
475                 // cell (some sort of left alignment). Since we iterate through
476                 // the last and the first columns from left to right, we ensure
477                 // that increased widths of previous columns are correctly
478                 // taken into account for later columns, thus preventing
479                 // unneeded width increasing.
480                 for (map<col_type, int>::const_iterator it = widths.begin();
481                      it != widths.end(); ++it) {
482                         int const wid = it->second;
483                         col_type const first = it->first;
484                         int const nextoffset =
485                                 colinfo_[first].offset +
486                                 wid +
487                                 displayColSpace(last) +
488                                 colsep() +
489                                 colinfo_[last+1].lines * vlinesep();
490                         int const dx = nextoffset - colinfo_[last+1].offset;
491                         if (dx > 0) {
492                                 colinfo_[last].width += dx;
493                                 for (col_type col = last + 1; col <= ncols(); ++col)
494                                         colinfo_[col].offset += dx;
495                         }
496                 }
497         }
498
499
500         dim.wid = colinfo_[ncols() - 1].offset
501                 + colinfo_[ncols() - 1].width
502                 + vlinesep() * colinfo_[ncols()].lines
503                 + border();
504
505         dim.asc = - rowinfo_[0].offset[&bv]
506                 + rowinfo_[0].ascent
507                 + hlinesep() * rowinfo_[0].lines
508                 + border();
509
510         dim.des = rowinfo_[nrows() - 1].offset[&bv]
511                 + rowinfo_[nrows() - 1].descent
512                 + hlinesep() * rowinfo_[nrows()].lines
513                 + border() + 1;
514
515
516 /*
517         // Increase ws_[i] for 'R' columns (except the first one)
518         for (int i = 1; i < nc_; ++i)
519                 if (align[i] == 'R')
520                         ws_[i] += 10 * df_width;
521         // Increase ws_[i] for 'C' column
522         if (align[0] == 'C')
523                 if (ws_[0] < 7 * workwidth / 8)
524                         ws_[0] = 7 * workwidth / 8;
525
526         // Adjust local tabs
527         width = colsep();
528         for (cxrow = row_.begin(); cxrow; ++cxrow) {
529                 int rg = COLSEP;
530                 int lf = 0;
531                 for (int i = 0; i < nc_; ++i) {
532                         bool isvoid = false;
533                         if (cxrow->getTab(i) <= 0) {
534                                 cxrow->setTab(i, df_width);
535                                 isvoid = true;
536                         }
537                         switch (align[i]) {
538                         case 'l':
539                                 lf = 0;
540                                 break;
541                         case 'c':
542                                 lf = (ws_[i] - cxrow->getTab(i))/2;
543                                 break;
544                         case 'r':
545                         case 'R':
546                                 lf = ws_[i] - cxrow->getTab(i);
547                                 break;
548                         case 'C':
549                                 if (cxrow == row_.begin())
550                                         lf = 0;
551                                 else if (cxrow.is_last())
552                                         lf = ws_[i] - cxrow->getTab(i);
553                                 else
554                                         lf = (ws_[i] - cxrow->getTab(i))/2;
555                                 break;
556                         }
557                         int const ww = (isvoid) ? lf : lf + cxrow->getTab(i);
558                         cxrow->setTab(i, lf + rg);
559                         rg = ws_[i] - ww + colsep();
560                         if (cxrow == row_.begin())
561                                 width += ws_[i] + colsep();
562                 }
563                 cxrow->setBaseline(cxrow->getBaseline() - ascent);
564         }
565 */
566         dim.wid += leftMargin() + rightMargin();
567 }
568
569
570 int InsetMathGrid::vLineHOffset(col_type col, unsigned int line) const
571 {
572         if (col < ncols())
573                 return leftMargin() + colinfo_[col].offset
574                         - (colinfo_[col].lines - line - 1) * vlinesep()
575                         - vlinesep()/2 - colsep()/2;
576         else {
577                 LASSERT(col == ncols(), return 0);
578                 return leftMargin() + colinfo_[col-1].offset + colinfo_[col-1].width
579                         + line * vlinesep()
580                         + vlinesep()/2 + colsep()/2;
581         }
582 }
583
584
585 int InsetMathGrid::hLineVOffset(BufferView const & bv, row_type row,
586                                 unsigned int line) const
587 {
588         return rowinfo_[row].offset[&bv]
589                 - rowinfo_[row].ascent
590                 - line * hlinesep()
591                 - hlinesep()/2 - rowsep()/2;
592 }
593
594
595 void InsetMathGrid::draw(PainterInfo & pi, int x, int y) const
596 {
597         BufferView const & bv = *pi.base.bv;
598
599         for (idx_type idx = 0; idx < nargs(); ++idx) {
600                 if (cellinfo_[idx].multi != CELL_PART_OF_MULTICOLUMN) {
601                         cell(idx).draw(pi,
602                                        x + leftMargin() + cellXOffset(bv, idx),
603                                        y + cellYOffset(bv, idx));
604
605                         row_type r = row(idx);
606                         int const yy1 = y + hLineVOffset(bv, r, 0);
607                         int const yy2 = y + hLineVOffset(bv, r + 1, rowinfo_[r + 1].lines - 1);
608                         auto draw_left_borders = [&](col_type c) {
609                                 for (unsigned int i = 0; i < colinfo_[c].lines; ++i) {
610                                         int const xx = x + vLineHOffset(c, i);
611                                         pi.pain.line(xx, yy1, xx, yy2, Color_foreground);
612                                 }
613                         };
614                         col_type c = col(idx);
615                         // Draw inner left borders cell-by-cell because of multicolumns
616                         draw_left_borders(c);
617                         // Draw the right border (only once)
618                         if (c == 0)
619                                 draw_left_borders(ncols());
620                 }
621         }
622
623         // Draw horizontal borders
624         for (row_type r = 0; r <= nrows(); ++r) {
625                 int const xx1 = x + vLineHOffset(0, 0);
626                 int const xx2 = x + vLineHOffset(ncols(), colinfo_[ncols()].lines - 1);
627                 for (unsigned int i = 0; i < rowinfo_[r].lines; ++i) {
628                         int const yy = y + hLineVOffset(bv, r, i);
629                         pi.pain.line(xx1, yy, xx2, yy, Color_foreground);
630                 }
631         }
632 }
633
634
635 void InsetMathGrid::metricsT(TextMetricsInfo const & /*mi*/, Dimension & /*dim*/) const
636 {
637 // FIXME: this does not compile anymore with offset being a map
638 // It is not worth fixing it at this point since the code is basically dead.
639 #if 0
640         // let the cells adjust themselves
641         for (idx_type i = 0; i < nargs(); ++i)
642                 if (cellinfo_[i].multi != CELL_PART_OF_MULTICOLUMN)
643                         cell(i).metricsT(mi, dim);
644
645         // compute absolute sizes of vertical structure
646         for (row_type row = 0; row < nrows(); ++row) {
647                 int asc  = 0;
648                 int desc = 0;
649                 for (col_type col = 0; col < ncols(); ++col) {
650                         idx_type const i = index(row, col);
651                         if (cellinfo_[i].multi != CELL_PART_OF_MULTICOLUMN) {
652                                 //MathData const & c = cell(i);
653                                 // FIXME: BROKEN!
654                                 Dimension dimc;
655                                 asc  = max(asc,  dimc.ascent());
656                                 desc = max(desc, dimc.descent());
657                         }
658                 }
659                 rowinfo_[row].ascent  = asc;
660                 rowinfo_[row].descent = desc;
661         }
662         rowinfo_[nrows()].ascent  = 0;
663         rowinfo_[nrows()].descent = 0;
664
665         // compute vertical offsets
666         rowinfo_[0].offset[&bv] = 0;
667         for (row_type row = 1; row <= nrows(); ++row) {
668                 rowinfo_[row].offset[&bv]  =
669                         rowinfo_[row - 1].offset  +
670                         rowinfo_[row - 1].descent +
671                         //rowinfo_[row - 1].skipPixels(mi) +
672                         1 + //rowsep() +
673                         //rowinfo_[row].lines * hlinesep() +
674                         rowinfo_[row].ascent;
675         }
676
677         // adjust vertical offset
678         int h = 0;
679         switch (v_align_) {
680                 case 't':
681                         h = 0;
682                         break;
683                 case 'b':
684                         h = rowinfo_[nrows() - 1].offset;
685                         break;
686                 default:
687                         h = rowinfo_[nrows() - 1].offset / 2;
688         }
689         for (row_type row = 0; row <= nrows(); ++row)
690                 rowinfo_[row].offset -= h;
691
692
693         // compute absolute sizes of horizontal structure
694         for (col_type col = 0; col < ncols(); ++col) {
695                 int wid = 0;
696                 for (row_type row = 0; row < nrows(); ++row) {
697                         // FIXME: BROKEN!
698                         //idx_type const i = index(row, col);
699                         //if (cellinfo_[i].multi != CELL_PART_OF_MULTICOLUMN)
700                         //      wid = max(wid, cell(i).width());
701                 }
702                 colinfo_[col].width = wid;
703         }
704         colinfo_[ncols()].width  = 0;
705
706         // compute horizontal offsets
707         colinfo_[0].offset = border();
708         for (col_type col = 1; col <= ncols(); ++col) {
709                 colinfo_[col].offset =
710                         colinfo_[col - 1].offset +
711                         colinfo_[col - 1].width +
712                         displayColSpace(col - 1) +
713                         1 ; //colsep() +
714                         //colinfo_[col].lines * vlinesep();
715         }
716
717
718         dim.wid  =  colinfo_[ncols() - 1].offset
719                        + colinfo_[ncols() - 1].width
720                  //+ vlinesep() * colinfo_[ncols()].lines
721                        + 2;
722
723         dim.asc  = -rowinfo_[0].offset
724                        + rowinfo_[0].ascent
725                  //+ hlinesep() * rowinfo_[0].lines
726                        + 1;
727
728         dim.des  =  rowinfo_[nrows() - 1].offset
729                        + rowinfo_[nrows() - 1].descent
730                  //+ hlinesep() * rowinfo_[nrows()].lines
731                        + 1;
732 #endif
733 }
734
735
736 void InsetMathGrid::drawT(TextPainter & /*pain*/, int /*x*/, int /*y*/) const
737 {
738 //      for (idx_type idx = 0; idx < nargs(); ++idx)
739 //              if (cellinfo_[idx].multi != CELL_PART_OF_MULTICOLUMN)
740 //                      cell(idx).drawT(pain, x + cellXOffset(idx), y + cellYOffset(idx));
741 }
742
743
744 void InsetMathGrid::updateBuffer(ParIterator const & it, UpdateType utype, bool const deleted)
745 {
746         // pass down
747         for (idx_type idx = 0; idx < nargs(); ++idx)
748                 if (cellinfo_[idx].multi != CELL_PART_OF_MULTICOLUMN)
749                         cell(idx).updateBuffer(it, utype, deleted);
750 }
751
752
753 docstring InsetMathGrid::eolString(row_type row, bool fragile,
754                 bool /*latex*/, bool last_eoln) const
755 {
756         docstring eol;
757
758         if (!rowinfo_[row].crskip.zero())
759                 eol += '[' + from_utf8(rowinfo_[row].crskip.asLatexString()) + ']';
760         else if(!rowinfo_[row].allow_newpage)
761                 eol += '*';
762
763         // make sure an upcoming '[' does not break anything
764         if (row + 1 < nrows()) {
765                 MathData const & c = cell(index(row + 1, 0));
766                 if (!c.empty() && c.front()->getChar() == '[')
767                         //eol += "[0pt]";
768                         eol += "{}";
769         }
770
771         // only add \\ if necessary
772         if (eol.empty() && row + 1 == nrows() && (nrows() == 1 || !last_eoln))
773                 return docstring();
774
775         // FIXME: As of 2018 (with amendment in LaTeX 2021/06),
776         // \\ is a robust command and the following protection
777         // is no longer necessary
778         return (fragile ? "\\protect\\\\" : "\\\\") + eol;
779 }
780
781
782 docstring InsetMathGrid::eocString(col_type col, col_type lastcol) const
783 {
784         if (col + 1 == lastcol)
785                 return docstring();
786         return from_ascii(" & ");
787 }
788
789
790 void InsetMathGrid::addRow(row_type row)
791 {
792         rowinfo_.insert(rowinfo_.begin() + row + 1, RowInfo());
793         cells_.insert
794                 (cells_.begin() + (row + 1) * ncols(), ncols(), MathData(buffer_));
795         cellinfo_.insert
796                 (cellinfo_.begin() + (row + 1) * ncols(), ncols(), CellInfo());
797 }
798
799
800 void InsetMathGrid::delRow(row_type row)
801 {
802         if (nrows() == 1)
803                 return;
804
805         cells_type::iterator it = cells_.begin() + row * ncols();
806         cells_.erase(it, it + ncols());
807
808         vector<CellInfo>::iterator jt = cellinfo_.begin() + row * ncols();
809         cellinfo_.erase(jt, jt + ncols());
810
811         rowinfo_.erase(rowinfo_.begin() + row);
812 }
813
814
815 void InsetMathGrid::copyRow(row_type row)
816 {
817         addRow(row);
818         for (col_type col = 0; col < ncols(); ++col) {
819                 cells_[(row + 1) * ncols() + col] = cells_[row * ncols() + col];
820                 // copying the cell does not set the buffer
821                 cells_[(row + 1) * ncols() + col].setBuffer(*buffer_);
822         }
823 }
824
825
826 void InsetMathGrid::swapRow(row_type row)
827 {
828         if (nrows() == 1)
829                 return;
830         if (row + 1 == nrows())
831                 --row;
832         for (col_type col = 0; col < ncols(); ++col)
833                 swap(cells_[row * ncols() + col], cells_[(row + 1) * ncols() + col]);
834 }
835
836
837 void InsetMathGrid::addCol(col_type newcol)
838 {
839         const col_type nc = ncols();
840         const row_type nr = nrows();
841         cells_type new_cells((nc + 1) * nr);
842         vector<CellInfo> new_cellinfo((nc + 1) * nr);
843
844         for (row_type row = 0; row < nr; ++row)
845                 for (col_type col = 0; col < nc; ++col) {
846                         new_cells[row * (nc + 1) + col + (col >= newcol)]
847                                 = cells_[row * nc + col];
848                         new_cellinfo[row * (nc + 1) + col + (col >= newcol)]
849                                 = cellinfo_[row * nc + col];
850                 }
851         swap(cells_, new_cells);
852         // copying cells loses the buffer reference
853         setBuffer(*buffer_);
854         swap(cellinfo_, new_cellinfo);
855
856         ColInfo inf;
857         inf.skip  = defaultColSpace(newcol);
858         inf.align = defaultColAlign(newcol);
859         colinfo_.insert(colinfo_.begin() + newcol, inf);
860 }
861
862
863 void InsetMathGrid::delCol(col_type col)
864 {
865         if (ncols() == 1)
866                 return;
867
868         cells_type tmpcells;
869         vector<CellInfo> tmpcellinfo;
870         for (col_type i = 0; i < nargs(); ++i)
871                 if (i % ncols() != col) {
872                         tmpcells.push_back(cells_[i]);
873                         tmpcellinfo.push_back(cellinfo_[i]);
874                 }
875         swap(cells_, tmpcells);
876         // copying cells loses the buffer reference
877         setBuffer(*buffer_);
878         swap(cellinfo_, tmpcellinfo);
879
880         colinfo_.erase(colinfo_.begin() + col);
881 }
882
883
884 void InsetMathGrid::copyCol(col_type col)
885 {
886         addCol(col+1);
887         for (row_type row = 0; row < nrows(); ++row) {
888                 cells_[row * ncols() + col + 1] = cells_[row * ncols() + col];
889                 // copying the cell does not set the buffer
890                 cells_[row * ncols() + col + 1].setBuffer(*buffer_);
891         }
892 }
893
894
895 void InsetMathGrid::swapCol(col_type col)
896 {
897         if (ncols() == 1)
898                 return;
899         if (col + 1 == ncols())
900                 --col;
901         for (row_type row = 0; row < nrows(); ++row)
902                 swap(cells_[row * ncols() + col], cells_[row * ncols() + col + 1]);
903 }
904
905
906 int InsetMathGrid::cellXOffset(BufferView const & bv, idx_type idx) const
907 {
908         if (cellinfo_[idx].multi == CELL_PART_OF_MULTICOLUMN)
909                 return 0;
910         col_type c = col(idx);
911         int x = colinfo_[c].offset;
912         char align = displayColAlign(idx);
913         Dimension const & celldim = cell(idx).dimension(bv);
914         if (align == 'r' || align == 'R')
915                 x += cellWidth(idx) - celldim.wid;
916         if (align == 'c' || align == 'C')
917                 x += (cellWidth(idx) - celldim.wid) / 2;
918         return x;
919 }
920
921
922 int InsetMathGrid::cellYOffset(BufferView const & bv, idx_type idx) const
923 {
924         return rowinfo_[row(idx)].offset[&bv];
925 }
926
927
928 int InsetMathGrid::cellWidth(idx_type idx) const
929 {
930         switch (cellinfo_[idx].multi) {
931         case CELL_NORMAL:
932                 return colinfo_[col(idx)].width;
933         case CELL_BEGIN_OF_MULTICOLUMN: {
934                 col_type c1 = col(idx);
935                 col_type c2 = c1 + ncellcols(idx);
936                 return colinfo_[c2].offset
937                         - colinfo_[c1].offset
938                         - displayColSpace(c2)
939                         - colsep()
940                         - colinfo_[c2].lines * vlinesep();
941         }
942         case CELL_PART_OF_MULTICOLUMN:
943                 return 0;
944         }
945         return 0;
946 }
947
948
949 bool InsetMathGrid::idxUpDown(Cursor & cur, bool up) const
950 {
951         if (up) {
952                 if (cur.row() == 0)
953                         return false;
954                 cur.idx() -= ncols();
955         } else {
956                 if (cur.row() + 1 >= nrows())
957                         return false;
958                 cur.idx() += ncols();
959         }
960         // If we are in a multicolumn cell, move to the "real" cell
961         while (cellinfo_[cur.idx()].multi == CELL_PART_OF_MULTICOLUMN) {
962                 LASSERT(cur.idx() > 0, return false);
963                 --cur.idx();
964         }
965         // FIXME: this is only a workaround to avoid a crash if the inset
966         // in not in coord cache. The best would be to force a FitCursor
967         // operation.
968         CoordCache::Arrays const & arraysCache = cur.bv().coordCache().arrays();
969         if (arraysCache.has(&cur.cell()))
970                 cur.pos() = cur.cell().x2pos(&cur.bv(), cur.x_target() - cur.cell().xo(cur.bv()));
971         else
972                 cur.pos() = 0;
973         return true;
974 }
975
976
977 bool InsetMathGrid::idxBackward(Cursor & cur) const
978 {
979         // leave matrix if at the front edge
980         if (cur.col() == 0)
981                 return false;
982         --cur.idx();
983         // If we are in a multicolumn cell, move to the "real" cell
984         while (cellinfo_[cur.idx()].multi == CELL_PART_OF_MULTICOLUMN) {
985                 LASSERT(cur.idx() > 0, return false);
986                 --cur.idx();
987         }
988         cur.pos() = cur.lastpos();
989         return true;
990 }
991
992
993 bool InsetMathGrid::idxForward(Cursor & cur) const
994 {
995         // leave matrix if at the back edge
996         if (cur.col() + 1 == ncols())
997                 return false;
998         ++cur.idx();
999         // If we are in a multicolumn cell, move to the next cell
1000         while (cellinfo_[cur.idx()].multi == CELL_PART_OF_MULTICOLUMN) {
1001                 // leave matrix if at the back edge
1002                 if (cur.col() + 1 == ncols())
1003                         return false;
1004                 ++cur.idx();
1005         }
1006         cur.pos() = 0;
1007         return true;
1008 }
1009
1010
1011 idx_type InsetMathGrid::firstIdx() const
1012 {
1013         size_type idx = 0;
1014         switch (v_align_) {
1015                 case 't':
1016                         //idx = 0;
1017                         break;
1018                 case 'b':
1019                         idx = (nrows() - 1) * ncols();
1020                         break;
1021                 default:
1022                         idx = ((nrows() - 1) / 2) * ncols();
1023         }
1024         // If we are in a multicolumn cell, move to the "real" cell
1025         while (cellinfo_[idx].multi == CELL_PART_OF_MULTICOLUMN) {
1026                 LASSERT(idx > 0, return 0);
1027                 --idx;
1028         }
1029         return idx;
1030 }
1031
1032
1033 idx_type InsetMathGrid::lastIdx() const
1034 {
1035         size_type idx = 0;
1036         switch (v_align_) {
1037                 case 't':
1038                         idx = ncols() - 1;
1039                         break;
1040                 case 'b':
1041                         idx = nargs() - 1;
1042                         break;
1043                 default:
1044                         idx = ((nrows() - 1) / 2 + 1) * ncols() - 1;
1045         }
1046         // If we are in a multicolumn cell, move to the "real" cell
1047         while (cellinfo_[idx].multi == CELL_PART_OF_MULTICOLUMN) {
1048                 LASSERT(idx > 0, return false);
1049                 --idx;
1050         }
1051         return idx;
1052 }
1053
1054
1055 bool InsetMathGrid::idxDelete(idx_type & idx)
1056 {
1057         // nothing to do if we have just one row
1058         if (nrows() == 1)
1059                 return false;
1060
1061         // nothing to do if we are in the middle of the last row of the inset
1062         if (idx + ncols() > nargs())
1063                 return false;
1064
1065         // try to delete entire sequence of ncols() empty cells if possible
1066         for (idx_type i = idx; i < idx + ncols(); ++i)
1067                 if (!cell(i).empty())
1068                         return false;
1069
1070         // move cells if necessary
1071         for (idx_type i = index(row(idx), 0); i < idx; ++i)
1072                 swap(cell(i), cell(i + ncols()));
1073
1074         delRow(row(idx));
1075
1076         if (idx >= nargs())
1077                 idx = nargs() - 1;
1078
1079         // undo effect of Ctrl-Tab (i.e. pull next cell)
1080         //if (idx + 1 != nargs())
1081         //      cell(idx).swap(cell(idx + 1));
1082
1083         // we handled the event..
1084         return true;
1085 }
1086
1087
1088 // reimplement old behaviour when pressing Delete in the last position
1089 // of a cell
1090 void InsetMathGrid::idxGlue(idx_type idx)
1091 {
1092         col_type c = col(idx);
1093         if (c + 1 == ncols()) {
1094                 if (row(idx) + 1 != nrows()) {
1095                         for (col_type cc = 0; cc < ncols(); ++cc)
1096                                 cell(idx).append(cell(idx + cc + 1));
1097                         delRow(row(idx) + 1);
1098                 }
1099         } else {
1100                 idx_type idx_next = idx + 1;
1101                 while (idx_next < nargs() &&
1102                        cellinfo_[idx_next].multi == CELL_PART_OF_MULTICOLUMN)
1103                         ++idx_next;
1104                 if (idx_next < nargs())
1105                         cell(idx).append(cell(idx_next));
1106                 col_type oldcol = c + 1;
1107                 for (col_type cc = c + 2; cc < ncols(); ++cc)
1108                         cell(idx - oldcol + cc) = cell(idx - oldcol + 1 + cc);
1109                 cell(idx - c + ncols() - 1).clear();
1110         }
1111 }
1112
1113
1114 InsetMathGrid::RowInfo const & InsetMathGrid::rowinfo(row_type row) const
1115 {
1116         return rowinfo_[row];
1117 }
1118
1119
1120 InsetMathGrid::RowInfo & InsetMathGrid::rowinfo(row_type row)
1121 {
1122         return rowinfo_[row];
1123 }
1124
1125
1126 bool InsetMathGrid::idxBetween(idx_type idx, idx_type from, idx_type to) const
1127 {
1128         row_type const ri = row(idx);
1129         row_type const r1 = min(row(from), row(to));
1130         row_type const r2 = max(row(from), row(to));
1131         col_type const ci = col(idx);
1132         col_type const c1 = min(col(from), col(to));
1133         col_type const c2 = max(col(from), col(to));
1134         return r1 <= ri && ri <= r2 && c1 <= ci && ci <= c2;
1135 }
1136
1137
1138 void InsetMathGrid::normalize(NormalStream & os) const
1139 {
1140         os << "[grid ";
1141         for (row_type row = 0; row < nrows(); ++row) {
1142                 os << "[row ";
1143                 for (col_type col = 0; col < ncols(); ++col) {
1144                         idx_type const i = index(row, col);
1145                         switch (cellinfo_[i].multi) {
1146                         case CELL_NORMAL:
1147                                 os << "[cell " << cell(i) << ']';
1148                                 break;
1149                         case CELL_BEGIN_OF_MULTICOLUMN:
1150                                 os << "[cell colspan="
1151                                    << static_cast<int>(ncellcols(i)) << ' '
1152                                    << cell(i) << ']';
1153                                 break;
1154                         case CELL_PART_OF_MULTICOLUMN:
1155                                 break;
1156                         }
1157                 }
1158                 os << ']';
1159         }
1160         os << ']';
1161 }
1162
1163
1164 void InsetMathGrid::mathmlize(MathMLStream & ms) const
1165 {
1166         bool const havetable = nrows() > 1 || ncols() > 1;
1167         if (havetable)
1168                 ms << MTag("mtable");
1169         char const * const celltag = havetable ? "mtd" : "mrow";
1170         for (row_type row = 0; row < nrows(); ++row) {
1171                 if (havetable)
1172                         ms << MTag("mtr");
1173                 for (col_type col = 0; col < ncols(); ++col) {
1174                         idx_type const i = index(row, col);
1175                         if (cellinfo_[i].multi != CELL_PART_OF_MULTICOLUMN) {
1176                                 col_type const cellcols = ncellcols(i);
1177                                 ostringstream attr;
1178                                 if (havetable && cellcols > 1)
1179                                         attr << "colspan='" << cellcols << '\'';
1180                                 ms << MTag(celltag, attr.str());
1181                                 ms << cell(index(row, col));
1182                                 ms << ETag(celltag);
1183                         }
1184                 }
1185                 if (havetable)
1186                         ms << ETag("mtr");
1187         }
1188         if (havetable)
1189                 ms << ETag("mtable");
1190 }
1191
1192
1193 // FIXME XHTML
1194 // We need to do something about alignment here.
1195 void InsetMathGrid::htmlize(HtmlStream & os, string const & attrib) const
1196 {
1197         bool const havetable = nrows() > 1 || ncols() > 1;
1198         if (!havetable) {
1199                 os << cell(index(0, 0));
1200                 return;
1201         }
1202         os << MTag("table", attrib);
1203         for (row_type row = 0; row < nrows(); ++row) {
1204                 os << MTag("tr");
1205                 for (col_type col = 0; col < ncols(); ++col) {
1206                         idx_type const i = index(row, col);
1207                         if (cellinfo_[i].multi != CELL_PART_OF_MULTICOLUMN) {
1208                                 col_type const cellcols = ncellcols(i);
1209                                 ostringstream attr;
1210                                 if (cellcols > 1)
1211                                         attr << "colspan='" << cellcols << '\'';
1212                                 os << MTag("td", attr.str());
1213                                 os << cell(index(row, col));
1214                                 os << ETag("td");
1215                         }
1216                 }
1217                 os << ETag("tr");
1218         }
1219         os << ETag("table");
1220 }
1221
1222
1223 void InsetMathGrid::htmlize(HtmlStream & os) const
1224 {
1225         htmlize(os, "class='mathtable'");
1226 }
1227
1228
1229 void InsetMathGrid::validate(LaTeXFeatures & features) const
1230 {
1231         if (features.runparams().math_flavor == OutputParams::MathAsHTML
1232             && (nrows() > 1 || ncols() > 1)) {
1233                 // CSS taken from InsetMathCases
1234                 features.addCSSSnippet(
1235                         "table.mathtable{display: inline-block; text-align: center; border: none;"
1236                         "border-left: thin solid black; vertical-align: middle; padding-left: 0.5ex;}\n"
1237                         "table.mathtable td {text-align: left; border: none;}");
1238         }
1239         InsetMathNest::validate(features);
1240 }
1241
1242
1243 void InsetMathGrid::write(TeXMathStream & os) const
1244 {
1245         write(os, 0, 0, nrows(), ncols());
1246 }
1247
1248 void InsetMathGrid::write(TeXMathStream & os,
1249                           row_type beg_row, col_type beg_col,
1250                           row_type end_row, col_type end_col) const
1251 {
1252         MathEnsurer ensurer(os, false);
1253         docstring eol;
1254         for (row_type row = beg_row; row < end_row; ++row) {
1255                 os << verboseHLine(rowinfo_[row].lines);
1256                 // don't write & and empty cells at end of line,
1257                 // unless there are vertical lines
1258                 col_type lastcol = 0;
1259                 bool emptyline = true;
1260                 bool last_eoln = true;
1261                 for (col_type col = beg_col; col < end_col; ++col) {
1262                         idx_type const idx = index(row, col);
1263                         bool const empty_cell = cell(idx).empty();
1264                         if (last_eoln && (!empty_cell || cellinfo_[idx].multi != CELL_NORMAL))
1265                                 last_eoln = false;
1266                         if (!empty_cell || cellinfo_[idx].multi != CELL_NORMAL ||
1267                             colinfo_[col + 1].lines) {
1268                                 lastcol = col + 1;
1269                                 emptyline = false;
1270                         }
1271                 }
1272                 for (col_type col = beg_col; col < end_col;) {
1273                         int nccols = 1;
1274                         idx_type const idx = index(row, col);
1275                         TexRow::RowEntry const entry = TexRow::mathEntry(id(),idx);
1276                         os.texrow().start(entry);
1277                         if (col >= lastcol) {
1278                                 ++col;
1279                                 continue;
1280                         }
1281                         Changer dummy = os.changeRowEntry(entry);
1282                         if (cellinfo_[idx].multi == CELL_BEGIN_OF_MULTICOLUMN) {
1283                                 size_t s = col + 1;
1284                                 while (s < ncols() &&
1285                                        cellinfo_[index(row, s)].multi == CELL_PART_OF_MULTICOLUMN)
1286                                         s++;
1287                                 nccols = s - col;
1288                                 os << "\\multicolumn{" << nccols
1289                                    << "}{" << cellinfo_[idx].align
1290                                    << "}{";
1291                         }
1292                         os << cell(idx);
1293                         if (os.pendingBrace())
1294                                 ModeSpecifier specifier(os, TEXT_MODE);
1295                         if (cellinfo_[idx].multi == CELL_BEGIN_OF_MULTICOLUMN)
1296                                 os << '}';
1297                         os << eocString(col + nccols - 1, lastcol);
1298                         col += nccols;
1299                 }
1300                 eol = eolString(row, os.fragile(), os.latex(), last_eoln);
1301                 os << eol;
1302                 // append newline only if line wasn't completely empty
1303                 // and the formula is not written on a single line
1304                 bool const empty = emptyline && eol.empty();
1305                 if (!empty && nrows() > 1)
1306                         os << "\n";
1307         }
1308         // @TODO use end_row instead of nrows() ?
1309         docstring const s = verboseHLine(rowinfo_[nrows()].lines);
1310         if (!s.empty()) {
1311                 if (eol.empty()) {
1312                         if (os.fragile())
1313                                 os << "\\protect";
1314                         os << "\\\\";
1315                 }
1316                 os << s;
1317         }
1318 }
1319
1320
1321 int InsetMathGrid::colsep() const
1322 {
1323         return 6;
1324 }
1325
1326
1327 int InsetMathGrid::rowsep() const
1328 {
1329         return 6;
1330 }
1331
1332
1333 int InsetMathGrid::hlinesep() const
1334 {
1335         return 3;
1336 }
1337
1338
1339 int InsetMathGrid::vlinesep() const
1340 {
1341         return 3;
1342 }
1343
1344
1345 int InsetMathGrid::border() const
1346 {
1347         return 1;
1348 }
1349
1350
1351 void InsetMathGrid::splitCell(Cursor & cur)
1352 {
1353         if (cur.idx() == cur.lastidx())
1354                 return;
1355         MathData ar = cur.cell();
1356         ar.erase(0, cur.pos());
1357         cur.cell().erase(cur.pos(), cur.lastpos());
1358         ++cur.idx();
1359         while (cur.idx() << nargs() &&
1360                cellinfo_[cur.idx()].multi == CELL_BEGIN_OF_MULTICOLUMN)
1361                 ++cur.idx();
1362         cur.pos() = 0;
1363         cur.cell().insert(0, ar);
1364 }
1365
1366
1367 char InsetMathGrid::displayColAlign(idx_type idx) const
1368 {
1369         if (cellinfo_[idx].multi == CELL_BEGIN_OF_MULTICOLUMN) {
1370                 // align may also contain lines like "||r|", so this is
1371                 // not complete, but we catch at least the simple cases.
1372                 if (cellinfo_[idx].align == "c")
1373                         return 'c';
1374                 if (cellinfo_[idx].align == "l")
1375                         return 'l';
1376                 if (cellinfo_[idx].align == "r")
1377                         return 'r';
1378         }
1379         return colinfo_[col(idx)].align;
1380 }
1381
1382
1383 int InsetMathGrid::displayColSpace(col_type col) const
1384 {
1385         return colinfo_[col].skip;
1386 }
1387
1388 void InsetMathGrid::doDispatch(Cursor & cur, FuncRequest & cmd)
1389 {
1390         //lyxerr << "*** InsetMathGrid: request: " << cmd << endl;
1391
1392         Parse::flags parseflg = Parse::QUIET | Parse::USETEXT;
1393
1394         FuncCode const act = cmd.action();
1395         switch (act) {
1396
1397         // insert file functions
1398         case LFUN_LINE_DELETE_FORWARD:
1399                 cur.recordUndoInset();
1400                 //autocorrect_ = false;
1401                 //macroModeClose();
1402                 //if (selection_) {
1403                 //      selDel();
1404                 //      break;
1405                 //}
1406                 if (nrows() > 1)
1407                         delRow(cur.row());
1408                 if (cur.idx() > cur.lastidx())
1409                         cur.idx() = cur.lastidx();
1410                 if (cur.pos() > cur.lastpos())
1411                         cur.pos() = cur.lastpos();
1412                 break;
1413
1414         case LFUN_CELL_SPLIT:
1415                 cur.recordUndo();
1416                 splitCell(cur);
1417                 break;
1418
1419         case LFUN_NEWLINE_INSERT: {
1420                 cur.recordUndoInset();
1421                 row_type const r = cur.row();
1422                 addRow(r);
1423
1424                 // split line
1425                 for (col_type c = col(cur.idx()) + 1; c < ncols(); ++c)
1426                         swap(cell(index(r, c)), cell(index(r + 1, c)));
1427
1428                 // split cell
1429                 splitCell(cur);
1430                 if (ncols() > 1)
1431                         swap(cell(cur.idx()), cell(cur.idx() + ncols() - 1));
1432                 if (cur.idx() > 0)
1433                         --cur.idx();
1434                 cur.pos() = cur.lastpos();
1435                 cur.forceBufferUpdate();
1436                 //mathcursor->normalize();
1437                 //cmd = FuncRequest(LFUN_FINISHED_BACKWARD);
1438                 break;
1439         }
1440
1441         case LFUN_TABULAR_FEATURE: {
1442                 cur.recordUndoInset();
1443                 //lyxerr << "handling tabular-feature " << to_utf8(cmd.argument()) << endl;
1444                 istringstream is(to_utf8(cmd.argument()));
1445                 string s;
1446                 is >> s;
1447                 if (s == "valign-top")
1448                         setVerticalAlignment('t');
1449                 else if (s == "valign-middle")
1450                         setVerticalAlignment('c');
1451                 else if (s == "valign-bottom")
1452                         setVerticalAlignment('b');
1453                 else if (s == "align-left")
1454                         setHorizontalAlignment('l', cur.col());
1455                 else if (s == "align-right")
1456                         setHorizontalAlignment('r', cur.col());
1457                 else if (s == "align-center")
1458                         setHorizontalAlignment('c', cur.col());
1459                 else if (s == "append-row")
1460                         for (int i = 0, n = extractInt(is); i < n; ++i)
1461                                 addRow(cur.row());
1462                 else if (s == "delete-row") {
1463                         cur.clearSelection(); // bug 4323
1464                         for (int i = 0, n = extractInt(is); i < n; ++i) {
1465                                 delRow(cur.row());
1466                                 if (cur.idx() >= nargs())
1467                                         cur.idx() -= ncols();
1468                         }
1469                         cur.pos() = 0; // trick, see below
1470                 }
1471                 else if (s == "copy-row") {
1472                         // Here (as later) we save the cursor col/row
1473                         // in order to restore it after operation.
1474                         row_type const r = cur.row();
1475                         col_type const c = cur.col();
1476                         for (int i = 0, n = extractInt(is); i < n; ++i)
1477                                 copyRow(cur.row());
1478                         cur.idx() = index(r, c);
1479                 }
1480                 else if (s == "swap-row") {
1481                         swapRow(cur.row());
1482                         // Trick to suppress same-idx-means-different-cell
1483                         // assertion crash:
1484                         cur.pos() = 0;
1485                 }
1486                 else if (s == "add-hline-above")
1487                         rowinfo_[cur.row()].lines++;
1488                 else if (s == "add-hline-below")
1489                         rowinfo_[cur.row()+1].lines++;
1490                 else if (s == "delete-hline-above")
1491                         rowinfo_[cur.row()].lines--;
1492                 else if (s == "delete-hline-below")
1493                         rowinfo_[cur.row()+1].lines--;
1494                 else if (s == "append-column") {
1495                         row_type const r = cur.row();
1496                         col_type const c = cur.col();
1497                         for (int i = 0, n = extractInt(is); i < n; ++i)
1498                                 addCol(cur.col() + 1);
1499                         cur.idx() = index(r, c);
1500                 }
1501                 else if (s == "delete-column") {
1502                         cur.clearSelection(); // bug 4323
1503                         row_type const r = cur.row();
1504                         col_type const c = cur.col();
1505                         for (int i = 0, n = extractInt(is); i < n; ++i)
1506                                 delCol(col(cur.idx()));
1507                         cur.idx() = index(r, min(c, cur.ncols() - 1));
1508                         cur.pos() = 0; // trick, see above
1509                 }
1510                 else if (s == "copy-column") {
1511                         row_type const r = cur.row();
1512                         col_type const c = cur.col();
1513                         copyCol(cur.col());
1514                         cur.idx() = index(r, c);
1515                 }
1516                 else if (s == "swap-column") {
1517                         swapCol(cur.col());
1518                         cur.pos() = 0; // trick, see above
1519                 }
1520                 else if (s == "add-vline-left") {
1521                         colinfo_[cur.col()].lines++;
1522                         if (!colinfo_[cur.col()].special.empty())
1523                                 colinfo_[cur.col()].special += '|';
1524                 }
1525                 else if (s == "add-vline-right") {
1526                         colinfo_[cur.col()+1].lines++;
1527                         if (!colinfo_[cur.col()+1].special.empty())
1528                                 colinfo_[cur.col()+1].special.insert(0, 1, '|');
1529                 }
1530                 else if (s == "delete-vline-left") {
1531                         colinfo_[cur.col()].lines--;
1532                         docstring & special = colinfo_[cur.col()].special;
1533                         if (!special.empty()) {
1534                                 docstring::size_type i = special.rfind('|');
1535                                 LASSERT(i != docstring::npos, break);
1536                                 special.erase(i, 1);
1537                         }
1538                 }
1539                 else if (s == "delete-vline-right") {
1540                         colinfo_[cur.col()+1].lines--;
1541                         docstring & special = colinfo_[cur.col()+1].special;
1542                         if (!special.empty()) {
1543                                 docstring::size_type i = special.find('|');
1544                                 LASSERT(i != docstring::npos, break);
1545                                 special.erase(i, 1);
1546                         }
1547                 }
1548                 else {
1549                         cur.undispatched();
1550                         break;
1551                 }
1552                 // perhaps this should be FINISHED_BACKWARD -- just for clarity?
1553                 //lyxerr << "returning FINISHED_LEFT" << endl;
1554                 break;
1555         }
1556
1557         case LFUN_CLIPBOARD_PASTE:
1558                 parseflg |= Parse::VERBATIM;
1559                 // fall through
1560         case LFUN_PASTE: {
1561                 if (cur.currentMode() != MATH_MODE)
1562                         parseflg |= Parse::TEXTMODE;
1563                 cur.message(_("Paste"));
1564                 cap::replaceSelection(cur);
1565                 docstring topaste;
1566                 if (cmd.argument().empty() && !theClipboard().isInternal())
1567                         topaste = theClipboard().getAsText(frontend::Clipboard::PlainTextType);
1568                 else {
1569                         idocstringstream is(cmd.argument());
1570                         int n = 0;
1571                         is >> n;
1572                         topaste = cap::selection(n, make_pair(buffer().params().documentClassPtr(),
1573                                                               buffer().params().authors()), true);
1574                 }
1575                 InsetMathGrid grid(buffer_, 1, 1);
1576                 if (!topaste.empty())
1577                         if ((topaste.size() == 1 && isAscii(topaste))
1578                             || !mathed_parse_normal(grid, topaste, parseflg)) {
1579                                 resetGrid(grid);
1580                                 mathed_parse_normal(grid, topaste, parseflg | Parse::VERBATIM);
1581                         }
1582
1583                 bool hline_enabled = false;
1584                 FuncRequest fr = FuncRequest(LFUN_TABULAR_FEATURE, "add-hline-above");
1585                 FuncStatus status;
1586                 if (getStatus(cur, fr, status))
1587                         hline_enabled = status.enabled();
1588                 if (grid.nargs() == 1) {
1589                         // single cell/part of cell
1590                         cur.recordUndoInset();
1591                         cur.cell().insert(cur.pos(), grid.cell(0));
1592                         cur.pos() += grid.cell(0).size();
1593                         if (hline_enabled)
1594                                 rowinfo_[cur.row()].lines += grid.rowinfo_[0].lines;
1595                         else {
1596                                 for (unsigned int l = 0; l < grid.rowinfo_[0].lines; ++l) {
1597                                          cur.cell().insert(0,
1598                                                 MathAtom(new InsetMathUnknown(from_ascii("\\hline"))));
1599                                          cur.pos()++;
1600                                 }
1601                         }
1602                 } else {
1603                         // multiple cells
1604                         cur.recordUndoInset();
1605                         col_type startcol = col(cur.idx());
1606                         row_type startrow = cur.row();
1607                         col_type oldncols = ncols();
1608                         col_type numcols =
1609                                 min(grid.ncols(), ncols() - startcol);
1610                         row_type const numrows =
1611                                 min(grid.nrows(), nrows() - cur.row());
1612                         for (row_type r = 0; r < numrows; ++r) {
1613                                 for (col_type c = 0; c < numcols; ++c) {
1614                                         idx_type i = index(r + startrow, c + startcol);
1615                                         pos_type const ipos = min(cur.pos(), pos_type(cell(i).size()));
1616                                         cell(i).insert(ipos, grid.cell(grid.index(r, c)));
1617                                 }
1618                                 if (hline_enabled)
1619                                         rowinfo_[r].lines += grid.rowinfo_[r].lines;
1620                                 else {
1621                                         for (unsigned int l = 0; l < grid.rowinfo_[r].lines; ++l) {
1622                                                 idx_type i = index(r + startrow, 0);
1623                                                 cell(i).insert(0,
1624                                                         MathAtom(new InsetMathUnknown(from_ascii("\\hline"))));
1625                                         }
1626                                 }
1627                                 // append columns for the left over horizontal cells
1628                                 for (col_type c = numcols; c < grid.ncols(); ++c) {
1629                                         addCol(c + startcol);
1630                                         idx_type i = index(r + startrow, min(c + startcol, ncols() - 1));
1631                                         cell(i).append(grid.cell(grid.index(r, c)));
1632                                         ++numcols;
1633                                 }
1634                         }
1635                         // amend cursor position if cols have been appended
1636                         cur.idx() += startrow * (ncols() - oldncols);
1637                         // append rows for the left over vertical cells
1638                         idx_type i = nargs() - 1;
1639                         for (row_type r = numrows; r < grid.nrows(); ++r) {
1640                                 row_type crow = startrow + r;
1641                                 addRow(crow - 1);
1642                                 for (col_type c = 0; c < grid.ncols(); ++c)
1643                                         cell(index(min(crow, nrows() - 1), min(c + startcol, ncols() - 1))).append(grid.cell(grid.index(r, c)));
1644                                 if (hline_enabled)
1645                                         rowinfo_[crow].lines += grid.rowinfo_[r].lines;
1646                                 else {
1647                                         for (unsigned int l = 0; l < grid.rowinfo_[r].lines; ++l) {
1648                                                 cell(i).insert(0,
1649                                                         MathAtom(new InsetMathUnknown(from_ascii("\\hline"))));
1650                                         }
1651                                 }
1652                         }
1653                 }
1654                 cur.clearSelection(); // bug 393
1655                 // FIXME audit setBuffer calls
1656                 cur.inset().setBuffer(*buffer_);
1657                 cur.forceBufferUpdate();
1658                 cur.finishUndo();
1659                 break;
1660         }
1661
1662         case LFUN_LINE_BEGIN:
1663                 cur.screenUpdateFlags(Update::Decoration | Update::FitCursor);
1664                 // fall through
1665         case LFUN_LINE_BEGIN_SELECT:
1666                 cur.selHandle(act == LFUN_WORD_BACKWARD_SELECT ||
1667                                 act == LFUN_WORD_LEFT_SELECT ||
1668                                 act == LFUN_LINE_BEGIN_SELECT);
1669                 cur.macroModeClose();
1670                 if (cur.pos() != 0) {
1671                         cur.pos() = 0;
1672                 } else if (cur.idx() % cur.ncols() != 0) {
1673                         cur.idx() -= cur.idx() % cur.ncols();
1674                         cur.pos() = 0;
1675                 } else if (cur.idx() != 0) {
1676                         cur.idx() = 0;
1677                         cur.pos() = 0;
1678                 } else {
1679                         cmd = FuncRequest(LFUN_FINISHED_BACKWARD);
1680                         cur.undispatched();
1681                 }
1682                 break;
1683
1684         case LFUN_LINE_END:
1685                 cur.screenUpdateFlags(Update::Decoration | Update::FitCursor);
1686                 // fall through
1687         case LFUN_LINE_END_SELECT:
1688                 cur.selHandle(act == LFUN_WORD_FORWARD_SELECT ||
1689                                 act == LFUN_WORD_RIGHT_SELECT ||
1690                                 act == LFUN_LINE_END_SELECT);
1691                 cur.macroModeClose();
1692                 cur.clearTargetX();
1693                 if (cur.pos() != cur.lastpos()) {
1694                         cur.pos() = cur.lastpos();
1695                 } else if ((cur.idx() + 1) % cur.ncols() != 0) {
1696                         cur.idx() += cur.ncols() - 1 - cur.idx() % cur.ncols();
1697                         cur.pos() = cur.lastpos();
1698                 } else if (cur.idx() != cur.lastidx()) {
1699                         cur.idx() = cur.lastidx();
1700                         cur.pos() = cur.lastpos();
1701                 } else {
1702                         cmd = FuncRequest(LFUN_FINISHED_FORWARD);
1703                         cur.undispatched();
1704                 }
1705                 break;
1706
1707         default:
1708                 InsetMathNest::doDispatch(cur, cmd);
1709         }
1710 }
1711
1712
1713 bool InsetMathGrid::getStatus(Cursor & cur, FuncRequest const & cmd,
1714                 FuncStatus & status) const
1715 {
1716         switch (cmd.action()) {
1717         case LFUN_TABULAR_FEATURE: {
1718                 string s = cmd.getArg(0);
1719                 if (&cur.inset() != this) {
1720                         // Table actions requires that the cursor is _inside_ the
1721                         // table.
1722                         status.setEnabled(false);
1723                         status.message(from_utf8(N_("Cursor not in table")));
1724                         return true;
1725                 }
1726                 if (nrows() <= 1 && (s == "delete-row" || s == "swap-row")) {
1727                         status.setEnabled(false);
1728                         status.message(from_utf8(N_("Only one row")));
1729                         return true;
1730                 }
1731                 if (ncols() <= 1 &&
1732                     (s == "delete-column" || s == "swap-column")) {
1733                         status.setEnabled(false);
1734                         status.message(from_utf8(N_("Only one column")));
1735                         return true;
1736                 }
1737                 if ((rowinfo_[cur.row()].lines == 0 &&
1738                      s == "delete-hline-above") ||
1739                     (rowinfo_[cur.row() + 1].lines == 0 &&
1740                      s == "delete-hline-below")) {
1741                         status.setEnabled(false);
1742                         status.message(from_utf8(N_("No hline to delete")));
1743                         return true;
1744                 }
1745
1746                 if ((colinfo_[cur.col()].lines == 0 &&
1747                      s == "delete-vline-left") ||
1748                     (colinfo_[cur.col() + 1].lines == 0 &&
1749                      s == "delete-vline-right")) {
1750                         status.setEnabled(false);
1751                         status.message(from_utf8(N_("No vline to delete")));
1752                         return true;
1753                 }
1754                 if (s == "valign-top" || s == "valign-middle" ||
1755                     s == "valign-bottom" || s == "align-left" ||
1756                     s == "align-right" || s == "align-center") {
1757                         status.setEnabled(true);
1758                         char const ha = horizontalAlignment(cur.col());
1759                         char const va = verticalAlignment();
1760                         status.setOnOff((s == "align-left" && ha == 'l')
1761                                         || (s == "align-right"   && ha == 'r')
1762                                         || (s == "align-center"  && ha == 'c')
1763                                         || (s == "valign-top"    && va == 't')
1764                                         || (s == "valign-bottom" && va == 'b')
1765                                         || (s == "valign-middle" && va == 'c'));
1766                         return true;
1767                 }
1768                 if (s == "append-row" || s == "delete-row" ||
1769                     s == "copy-row" || s == "swap-row" ||
1770                     s == "add-hline-above" || s == "add-hline-below" ||
1771                     s == "delete-hline-above" || s == "delete-hline-below" ||
1772                     s == "append-column" || s == "delete-column" ||
1773                     s == "copy-column" || s == "swap-column" ||
1774                     s == "add-vline-left" || s == "add-vline-right" ||
1775                     s == "delete-vline-left" || s == "delete-vline-right") {
1776                         status.setEnabled(true);
1777                 } else {
1778                         status.setEnabled(false);
1779                         status.message(bformat(
1780                             from_utf8(N_("Unknown tabular feature '%1$s'")),
1781                             from_utf8(s)));
1782                 }
1783
1784                 return true;
1785         }
1786
1787         case LFUN_CELL_SPLIT:
1788                 status.setEnabled(cur.idx() != cur.lastidx());
1789                 return true;
1790
1791         case LFUN_CELL_BACKWARD:
1792         case LFUN_CELL_FORWARD:
1793                 status.setEnabled(true);
1794                 return true;
1795
1796         default:
1797                 break;
1798         }
1799         return InsetMathNest::getStatus(cur, cmd, status);
1800 }
1801
1802
1803 char InsetMathGrid::colAlign(HullType type, col_type col) const
1804 {
1805         switch (type) {
1806         case hullEqnArray:
1807                 return "rcl"[col % 3];
1808
1809         case hullMultline:
1810                 return 'c';
1811         case hullGather:
1812                 LASSERT(isBufferValid(),
1813                                 LYXERR0("Buffer not set correctly. Please report!");
1814                                 return 'c';);
1815                 if (buffer().params().is_math_indent)
1816                         return 'l';
1817                 else
1818                         return 'c';
1819
1820         case hullAlign:
1821         case hullAlignAt:
1822         case hullXAlignAt:
1823         case hullXXAlignAt:
1824         case hullFlAlign:
1825                 return "rl"[col & 1];
1826
1827         case hullUnknown:
1828         case hullNone:
1829         case hullSimple:
1830         case hullEquation:
1831         case hullRegexp:
1832                 return 'c';
1833         }
1834         // avoid warning
1835         return 'c';
1836 }
1837
1838
1839 //static
1840 int InsetMathGrid::colSpace(HullType type, col_type col)
1841 {
1842         int alignInterSpace = 0;
1843         switch (type) {
1844         case hullUnknown:
1845         case hullNone:
1846         case hullSimple:
1847         case hullEquation:
1848         case hullMultline:
1849         case hullGather:
1850         case hullRegexp:
1851                 return 0;
1852
1853         case hullEqnArray:
1854                 return 5;
1855
1856         case hullAlign:
1857                 alignInterSpace = 20;
1858                 break;
1859         case hullAlignAt:
1860                 alignInterSpace = 0;
1861                 break;
1862         case hullXAlignAt:
1863                 alignInterSpace = 40;
1864                 break;
1865         case hullXXAlignAt:
1866         case hullFlAlign:
1867                 alignInterSpace = 60;
1868                 break;
1869         }
1870         return (col % 2) ? alignInterSpace : 0;
1871 }
1872
1873
1874 } // namespace lyx