]> git.lyx.org Git - lyx.git/blob - src/mathed/InsetMathGrid.cpp
Improve LaTeX version checking
[lyx.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 void InsetMathGrid::eol(TeXMathStream & os, 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;
774
775         os << (fragile ? "\\protect\\\\" : "\\\\") << eol;
776 }
777
778
779 docstring InsetMathGrid::eocString(col_type col, col_type lastcol) const
780 {
781         if (col + 1 == lastcol)
782                 return docstring();
783         return from_ascii(" & ");
784 }
785
786
787 void InsetMathGrid::addRow(row_type row)
788 {
789         rowinfo_.insert(rowinfo_.begin() + row + 1, RowInfo());
790         cells_.insert
791                 (cells_.begin() + (row + 1) * ncols(), ncols(), MathData(buffer_));
792         cellinfo_.insert
793                 (cellinfo_.begin() + (row + 1) * ncols(), ncols(), CellInfo());
794 }
795
796
797 void InsetMathGrid::delRow(row_type row)
798 {
799         if (nrows() == 1)
800                 return;
801
802         cells_type::iterator it = cells_.begin() + row * ncols();
803         cells_.erase(it, it + ncols());
804
805         vector<CellInfo>::iterator jt = cellinfo_.begin() + row * ncols();
806         cellinfo_.erase(jt, jt + ncols());
807
808         rowinfo_.erase(rowinfo_.begin() + row);
809 }
810
811
812 void InsetMathGrid::copyRow(row_type row)
813 {
814         addRow(row);
815         for (col_type col = 0; col < ncols(); ++col) {
816                 cells_[(row + 1) * ncols() + col] = cells_[row * ncols() + col];
817                 // copying the cell does not set the buffer
818                 cells_[(row + 1) * ncols() + col].setBuffer(*buffer_);
819         }
820 }
821
822
823 void InsetMathGrid::swapRow(row_type row)
824 {
825         if (nrows() == 1)
826                 return;
827         if (row + 1 == nrows())
828                 --row;
829         for (col_type col = 0; col < ncols(); ++col)
830                 swap(cells_[row * ncols() + col], cells_[(row + 1) * ncols() + col]);
831 }
832
833
834 void InsetMathGrid::addCol(col_type newcol)
835 {
836         const col_type nc = ncols();
837         const row_type nr = nrows();
838         cells_type new_cells((nc + 1) * nr);
839         vector<CellInfo> new_cellinfo((nc + 1) * nr);
840
841         for (row_type row = 0; row < nr; ++row)
842                 for (col_type col = 0; col < nc; ++col) {
843                         new_cells[row * (nc + 1) + col + (col >= newcol)]
844                                 = cells_[row * nc + col];
845                         new_cellinfo[row * (nc + 1) + col + (col >= newcol)]
846                                 = cellinfo_[row * nc + col];
847                 }
848         swap(cells_, new_cells);
849         // copying cells loses the buffer reference
850         setBuffer(*buffer_);
851         swap(cellinfo_, new_cellinfo);
852
853         ColInfo inf;
854         inf.skip  = defaultColSpace(newcol);
855         inf.align = defaultColAlign(newcol);
856         colinfo_.insert(colinfo_.begin() + newcol, inf);
857 }
858
859
860 void InsetMathGrid::delCol(col_type col)
861 {
862         if (ncols() == 1)
863                 return;
864
865         cells_type tmpcells;
866         vector<CellInfo> tmpcellinfo;
867         for (col_type i = 0; i < nargs(); ++i)
868                 if (i % ncols() != col) {
869                         tmpcells.push_back(cells_[i]);
870                         tmpcellinfo.push_back(cellinfo_[i]);
871                 }
872         swap(cells_, tmpcells);
873         // copying cells loses the buffer reference
874         setBuffer(*buffer_);
875         swap(cellinfo_, tmpcellinfo);
876
877         colinfo_.erase(colinfo_.begin() + col);
878 }
879
880
881 void InsetMathGrid::copyCol(col_type col)
882 {
883         addCol(col+1);
884         for (row_type row = 0; row < nrows(); ++row) {
885                 cells_[row * ncols() + col + 1] = cells_[row * ncols() + col];
886                 // copying the cell does not set the buffer
887                 cells_[row * ncols() + col + 1].setBuffer(*buffer_);
888         }
889 }
890
891
892 void InsetMathGrid::swapCol(col_type col)
893 {
894         if (ncols() == 1)
895                 return;
896         if (col + 1 == ncols())
897                 --col;
898         for (row_type row = 0; row < nrows(); ++row)
899                 swap(cells_[row * ncols() + col], cells_[row * ncols() + col + 1]);
900 }
901
902
903 int InsetMathGrid::cellXOffset(BufferView const & bv, idx_type idx) const
904 {
905         if (cellinfo_[idx].multi == CELL_PART_OF_MULTICOLUMN)
906                 return 0;
907         col_type c = col(idx);
908         int x = colinfo_[c].offset;
909         char align = displayColAlign(idx);
910         Dimension const & celldim = cell(idx).dimension(bv);
911         if (align == 'r' || align == 'R')
912                 x += cellWidth(idx) - celldim.wid;
913         if (align == 'c' || align == 'C')
914                 x += (cellWidth(idx) - celldim.wid) / 2;
915         return x;
916 }
917
918
919 int InsetMathGrid::cellYOffset(BufferView const & bv, idx_type idx) const
920 {
921         return rowinfo_[row(idx)].offset[&bv];
922 }
923
924
925 int InsetMathGrid::cellWidth(idx_type idx) const
926 {
927         switch (cellinfo_[idx].multi) {
928         case CELL_NORMAL:
929                 return colinfo_[col(idx)].width;
930         case CELL_BEGIN_OF_MULTICOLUMN: {
931                 col_type c1 = col(idx);
932                 col_type c2 = c1 + ncellcols(idx);
933                 return colinfo_[c2].offset
934                         - colinfo_[c1].offset
935                         - displayColSpace(c2)
936                         - colsep()
937                         - colinfo_[c2].lines * vlinesep();
938         }
939         case CELL_PART_OF_MULTICOLUMN:
940                 return 0;
941         }
942         return 0;
943 }
944
945
946 bool InsetMathGrid::idxUpDown(Cursor & cur, bool up) const
947 {
948         if (up) {
949                 if (cur.row() == 0)
950                         return false;
951                 cur.idx() -= ncols();
952         } else {
953                 if (cur.row() + 1 >= nrows())
954                         return false;
955                 cur.idx() += ncols();
956         }
957         // If we are in a multicolumn cell, move to the "real" cell
958         while (cellinfo_[cur.idx()].multi == CELL_PART_OF_MULTICOLUMN) {
959                 LASSERT(cur.idx() > 0, return false);
960                 --cur.idx();
961         }
962         // FIXME: this is only a workaround to avoid a crash if the inset
963         // in not in coord cache. The best would be to force a FitCursor
964         // operation.
965         CoordCache::Arrays const & arraysCache = cur.bv().coordCache().arrays();
966         if (arraysCache.has(&cur.cell()))
967                 cur.pos() = cur.cell().x2pos(&cur.bv(), cur.x_target() - cur.cell().xo(cur.bv()));
968         else
969                 cur.pos() = 0;
970         return true;
971 }
972
973
974 bool InsetMathGrid::idxBackward(Cursor & cur) const
975 {
976         // leave matrix if at the front edge
977         if (cur.col() == 0)
978                 return false;
979         --cur.idx();
980         // If we are in a multicolumn cell, move to the "real" cell
981         while (cellinfo_[cur.idx()].multi == CELL_PART_OF_MULTICOLUMN) {
982                 LASSERT(cur.idx() > 0, return false);
983                 --cur.idx();
984         }
985         cur.pos() = cur.lastpos();
986         return true;
987 }
988
989
990 bool InsetMathGrid::idxForward(Cursor & cur) const
991 {
992         // leave matrix if at the back edge
993         if (cur.col() + 1 == ncols())
994                 return false;
995         ++cur.idx();
996         // If we are in a multicolumn cell, move to the next cell
997         while (cellinfo_[cur.idx()].multi == CELL_PART_OF_MULTICOLUMN) {
998                 // leave matrix if at the back edge
999                 if (cur.col() + 1 == ncols())
1000                         return false;
1001                 ++cur.idx();
1002         }
1003         cur.pos() = 0;
1004         return true;
1005 }
1006
1007
1008 idx_type InsetMathGrid::firstIdx() const
1009 {
1010         size_type idx = 0;
1011         switch (v_align_) {
1012                 case 't':
1013                         //idx = 0;
1014                         break;
1015                 case 'b':
1016                         idx = (nrows() - 1) * ncols();
1017                         break;
1018                 default:
1019                         idx = ((nrows() - 1) / 2) * ncols();
1020         }
1021         // If we are in a multicolumn cell, move to the "real" cell
1022         while (cellinfo_[idx].multi == CELL_PART_OF_MULTICOLUMN) {
1023                 LASSERT(idx > 0, return 0);
1024                 --idx;
1025         }
1026         return idx;
1027 }
1028
1029
1030 idx_type InsetMathGrid::lastIdx() const
1031 {
1032         size_type idx = 0;
1033         switch (v_align_) {
1034                 case 't':
1035                         idx = ncols() - 1;
1036                         break;
1037                 case 'b':
1038                         idx = nargs() - 1;
1039                         break;
1040                 default:
1041                         idx = ((nrows() - 1) / 2 + 1) * ncols() - 1;
1042         }
1043         // If we are in a multicolumn cell, move to the "real" cell
1044         while (cellinfo_[idx].multi == CELL_PART_OF_MULTICOLUMN) {
1045                 LASSERT(idx > 0, return false);
1046                 --idx;
1047         }
1048         return idx;
1049 }
1050
1051
1052 bool InsetMathGrid::idxDelete(idx_type & idx)
1053 {
1054         // nothing to do if we have just one row
1055         if (nrows() == 1)
1056                 return false;
1057
1058         // nothing to do if we are in the middle of the last row of the inset
1059         if (idx + ncols() > nargs())
1060                 return false;
1061
1062         // try to delete entire sequence of ncols() empty cells if possible
1063         for (idx_type i = idx; i < idx + ncols(); ++i)
1064                 if (!cell(i).empty())
1065                         return false;
1066
1067         // move cells if necessary
1068         for (idx_type i = index(row(idx), 0); i < idx; ++i)
1069                 swap(cell(i), cell(i + ncols()));
1070
1071         delRow(row(idx));
1072
1073         if (idx >= nargs())
1074                 idx = nargs() - 1;
1075
1076         // undo effect of Ctrl-Tab (i.e. pull next cell)
1077         //if (idx + 1 != nargs())
1078         //      cell(idx).swap(cell(idx + 1));
1079
1080         // we handled the event..
1081         return true;
1082 }
1083
1084
1085 // reimplement old behaviour when pressing Delete in the last position
1086 // of a cell
1087 void InsetMathGrid::idxGlue(idx_type idx)
1088 {
1089         col_type c = col(idx);
1090         if (c + 1 == ncols()) {
1091                 if (row(idx) + 1 != nrows()) {
1092                         for (col_type cc = 0; cc < ncols(); ++cc)
1093                                 cell(idx).append(cell(idx + cc + 1));
1094                         delRow(row(idx) + 1);
1095                 }
1096         } else {
1097                 idx_type idx_next = idx + 1;
1098                 while (idx_next < nargs() &&
1099                        cellinfo_[idx_next].multi == CELL_PART_OF_MULTICOLUMN)
1100                         ++idx_next;
1101                 if (idx_next < nargs())
1102                         cell(idx).append(cell(idx_next));
1103                 col_type oldcol = c + 1;
1104                 for (col_type cc = c + 2; cc < ncols(); ++cc)
1105                         cell(idx - oldcol + cc) = cell(idx - oldcol + 1 + cc);
1106                 cell(idx - c + ncols() - 1).clear();
1107         }
1108 }
1109
1110
1111 InsetMathGrid::RowInfo const & InsetMathGrid::rowinfo(row_type row) const
1112 {
1113         return rowinfo_[row];
1114 }
1115
1116
1117 InsetMathGrid::RowInfo & InsetMathGrid::rowinfo(row_type row)
1118 {
1119         return rowinfo_[row];
1120 }
1121
1122
1123 bool InsetMathGrid::idxBetween(idx_type idx, idx_type from, idx_type to) const
1124 {
1125         row_type const ri = row(idx);
1126         row_type const r1 = min(row(from), row(to));
1127         row_type const r2 = max(row(from), row(to));
1128         col_type const ci = col(idx);
1129         col_type const c1 = min(col(from), col(to));
1130         col_type const c2 = max(col(from), col(to));
1131         return r1 <= ri && ri <= r2 && c1 <= ci && ci <= c2;
1132 }
1133
1134
1135 void InsetMathGrid::normalize(NormalStream & os) const
1136 {
1137         os << "[grid ";
1138         for (row_type row = 0; row < nrows(); ++row) {
1139                 os << "[row ";
1140                 for (col_type col = 0; col < ncols(); ++col) {
1141                         idx_type const i = index(row, col);
1142                         switch (cellinfo_[i].multi) {
1143                         case CELL_NORMAL:
1144                                 os << "[cell " << cell(i) << ']';
1145                                 break;
1146                         case CELL_BEGIN_OF_MULTICOLUMN:
1147                                 os << "[cell colspan="
1148                                    << static_cast<int>(ncellcols(i)) << ' '
1149                                    << cell(i) << ']';
1150                                 break;
1151                         case CELL_PART_OF_MULTICOLUMN:
1152                                 break;
1153                         }
1154                 }
1155                 os << ']';
1156         }
1157         os << ']';
1158 }
1159
1160
1161 void InsetMathGrid::mathmlize(MathMLStream & ms) const
1162 {
1163         bool const havetable = nrows() > 1 || ncols() > 1;
1164         if (havetable)
1165                 ms << MTag("mtable");
1166         char const * const celltag = havetable ? "mtd" : "mrow";
1167         for (row_type row = 0; row < nrows(); ++row) {
1168                 if (havetable)
1169                         ms << MTag("mtr");
1170                 for (col_type col = 0; col < ncols(); ++col) {
1171                         idx_type const i = index(row, col);
1172                         if (cellinfo_[i].multi != CELL_PART_OF_MULTICOLUMN) {
1173                                 col_type const cellcols = ncellcols(i);
1174                                 ostringstream attr;
1175                                 if (havetable && cellcols > 1)
1176                                         attr << "colspan='" << cellcols << '\'';
1177                                 ms << MTag(celltag, attr.str());
1178                                 ms << cell(index(row, col));
1179                                 ms << ETag(celltag);
1180                         }
1181                 }
1182                 if (havetable)
1183                         ms << ETag("mtr");
1184         }
1185         if (havetable)
1186                 ms << ETag("mtable");
1187 }
1188
1189
1190 // FIXME XHTML
1191 // We need to do something about alignment here.
1192 void InsetMathGrid::htmlize(HtmlStream & os, string const & attrib) const
1193 {
1194         bool const havetable = nrows() > 1 || ncols() > 1;
1195         if (!havetable) {
1196                 os << cell(index(0, 0));
1197                 return;
1198         }
1199         os << MTag("table", attrib);
1200         for (row_type row = 0; row < nrows(); ++row) {
1201                 os << MTag("tr");
1202                 for (col_type col = 0; col < ncols(); ++col) {
1203                         idx_type const i = index(row, col);
1204                         if (cellinfo_[i].multi != CELL_PART_OF_MULTICOLUMN) {
1205                                 col_type const cellcols = ncellcols(i);
1206                                 ostringstream attr;
1207                                 if (cellcols > 1)
1208                                         attr << "colspan='" << cellcols << '\'';
1209                                 os << MTag("td", attr.str());
1210                                 os << cell(index(row, col));
1211                                 os << ETag("td");
1212                         }
1213                 }
1214                 os << ETag("tr");
1215         }
1216         os << ETag("table");
1217 }
1218
1219
1220 void InsetMathGrid::htmlize(HtmlStream & os) const
1221 {
1222         htmlize(os, "class='mathtable'");
1223 }
1224
1225
1226 void InsetMathGrid::validate(LaTeXFeatures & features) const
1227 {
1228         if (features.runparams().math_flavor == OutputParams::MathAsHTML
1229             && (nrows() > 1 || ncols() > 1)) {
1230                 // CSS taken from InsetMathCases
1231                 features.addCSSSnippet(
1232                         "table.mathtable{display: inline-block; text-align: center; border: none;"
1233                         "border-left: thin solid black; vertical-align: middle; padding-left: 0.5ex;}\n"
1234                         "table.mathtable td {text-align: left; border: none;}");
1235         }
1236         InsetMathNest::validate(features);
1237 }
1238
1239
1240 void InsetMathGrid::write(TeXMathStream & os) const
1241 {
1242         write(os, 0, 0, nrows(), ncols());
1243 }
1244
1245 void InsetMathGrid::write(TeXMathStream & os,
1246                           row_type beg_row, col_type beg_col,
1247                           row_type end_row, col_type end_col) const
1248 {
1249         MathEnsurer ensurer(os, false);
1250         docstring eolstr;
1251         // As of 2018 (with amendment in LaTeX 2021/06),
1252         // \\ is a robust command and its protection
1253         // is no longer necessary
1254         bool const fragile = os.fragile()
1255                         && !LaTeXFeatures::isAvailableAtLeastFrom("LaTeX", 2021, 06);
1256         for (row_type row = beg_row; row < end_row; ++row) {
1257                 os << verboseHLine(rowinfo_[row].lines);
1258                 // don't write & and empty cells at end of line,
1259                 // unless there are vertical lines
1260                 col_type lastcol = 0;
1261                 bool emptyline = true;
1262                 bool last_eoln = true;
1263                 for (col_type col = beg_col; col < end_col; ++col) {
1264                         idx_type const idx = index(row, col);
1265                         bool const empty_cell = cell(idx).empty();
1266                         if (last_eoln && (!empty_cell || cellinfo_[idx].multi != CELL_NORMAL))
1267                                 last_eoln = false;
1268                         if (!empty_cell || cellinfo_[idx].multi != CELL_NORMAL ||
1269                             colinfo_[col + 1].lines) {
1270                                 lastcol = col + 1;
1271                                 emptyline = false;
1272                         }
1273                 }
1274                 for (col_type col = beg_col; col < end_col;) {
1275                         int nccols = 1;
1276                         idx_type const idx = index(row, col);
1277                         TexRow::RowEntry const entry = TexRow::mathEntry(id(),idx);
1278                         os.texrow().start(entry);
1279                         if (col >= lastcol) {
1280                                 ++col;
1281                                 continue;
1282                         }
1283                         Changer dummy = os.changeRowEntry(entry);
1284                         if (cellinfo_[idx].multi == CELL_BEGIN_OF_MULTICOLUMN) {
1285                                 size_t s = col + 1;
1286                                 while (s < ncols() &&
1287                                        cellinfo_[index(row, s)].multi == CELL_PART_OF_MULTICOLUMN)
1288                                         s++;
1289                                 nccols = s - col;
1290                                 os << "\\multicolumn{" << nccols
1291                                    << "}{" << cellinfo_[idx].align
1292                                    << "}{";
1293                         }
1294                         os << cell(idx);
1295                         if (os.pendingBrace())
1296                                 ModeSpecifier specifier(os, TEXT_MODE);
1297                         if (cellinfo_[idx].multi == CELL_BEGIN_OF_MULTICOLUMN)
1298                                 os << '}';
1299                         os << eocString(col + nccols - 1, lastcol);
1300                         col += nccols;
1301                 }
1302                 eol(os, row, fragile, os.latex(), last_eoln);
1303                 // append newline only if line wasn't completely empty
1304                 // and the formula is not written on a single line
1305                 bool const empty = emptyline && eolstr.empty();
1306                 if (!empty && nrows() > 1)
1307                         os << "\n";
1308         }
1309         // @TODO use end_row instead of nrows() ?
1310         docstring const s = verboseHLine(rowinfo_[nrows()].lines);
1311         if (!s.empty()) {
1312                 if (eolstr.empty()) {
1313                         if (fragile)
1314                                 os << "\\protect";
1315                         os << "\\\\";
1316                 }
1317                 os << s;
1318         }
1319 }
1320
1321
1322 int InsetMathGrid::colsep() const
1323 {
1324         return 6;
1325 }
1326
1327
1328 int InsetMathGrid::rowsep() const
1329 {
1330         return 6;
1331 }
1332
1333
1334 int InsetMathGrid::hlinesep() const
1335 {
1336         return 3;
1337 }
1338
1339
1340 int InsetMathGrid::vlinesep() const
1341 {
1342         return 3;
1343 }
1344
1345
1346 int InsetMathGrid::border() const
1347 {
1348         return 1;
1349 }
1350
1351
1352 void InsetMathGrid::splitCell(Cursor & cur)
1353 {
1354         if (cur.idx() == cur.lastidx())
1355                 return;
1356         MathData ar = cur.cell();
1357         ar.erase(0, cur.pos());
1358         cur.cell().erase(cur.pos(), cur.lastpos());
1359         ++cur.idx();
1360         while (cur.idx() << nargs() &&
1361                cellinfo_[cur.idx()].multi == CELL_BEGIN_OF_MULTICOLUMN)
1362                 ++cur.idx();
1363         cur.pos() = 0;
1364         cur.cell().insert(0, ar);
1365 }
1366
1367
1368 char InsetMathGrid::displayColAlign(idx_type idx) const
1369 {
1370         if (cellinfo_[idx].multi == CELL_BEGIN_OF_MULTICOLUMN) {
1371                 // align may also contain lines like "||r|", so this is
1372                 // not complete, but we catch at least the simple cases.
1373                 if (cellinfo_[idx].align == "c")
1374                         return 'c';
1375                 if (cellinfo_[idx].align == "l")
1376                         return 'l';
1377                 if (cellinfo_[idx].align == "r")
1378                         return 'r';
1379         }
1380         return colinfo_[col(idx)].align;
1381 }
1382
1383
1384 int InsetMathGrid::displayColSpace(col_type col) const
1385 {
1386         return colinfo_[col].skip;
1387 }
1388
1389 void InsetMathGrid::doDispatch(Cursor & cur, FuncRequest & cmd)
1390 {
1391         //lyxerr << "*** InsetMathGrid: request: " << cmd << endl;
1392
1393         Parse::flags parseflg = Parse::QUIET | Parse::USETEXT;
1394
1395         FuncCode const act = cmd.action();
1396         switch (act) {
1397
1398         // insert file functions
1399         case LFUN_LINE_DELETE_FORWARD:
1400                 cur.recordUndoInset();
1401                 //autocorrect_ = false;
1402                 //macroModeClose();
1403                 //if (selection_) {
1404                 //      selDel();
1405                 //      break;
1406                 //}
1407                 if (nrows() > 1)
1408                         delRow(cur.row());
1409                 if (cur.idx() > cur.lastidx())
1410                         cur.idx() = cur.lastidx();
1411                 if (cur.pos() > cur.lastpos())
1412                         cur.pos() = cur.lastpos();
1413                 break;
1414
1415         case LFUN_CELL_SPLIT:
1416                 cur.recordUndo();
1417                 splitCell(cur);
1418                 break;
1419
1420         case LFUN_NEWLINE_INSERT: {
1421                 cur.recordUndoInset();
1422                 row_type const r = cur.row();
1423                 addRow(r);
1424
1425                 // split line
1426                 for (col_type c = col(cur.idx()) + 1; c < ncols(); ++c)
1427                         swap(cell(index(r, c)), cell(index(r + 1, c)));
1428
1429                 // split cell
1430                 splitCell(cur);
1431                 if (ncols() > 1)
1432                         swap(cell(cur.idx()), cell(cur.idx() + ncols() - 1));
1433                 if (cur.idx() > 0)
1434                         --cur.idx();
1435                 cur.pos() = cur.lastpos();
1436                 cur.forceBufferUpdate();
1437                 //mathcursor->normalize();
1438                 //cmd = FuncRequest(LFUN_FINISHED_BACKWARD);
1439                 break;
1440         }
1441
1442         case LFUN_TABULAR_FEATURE: {
1443                 cur.recordUndoInset();
1444                 //lyxerr << "handling tabular-feature " << to_utf8(cmd.argument()) << endl;
1445                 istringstream is(to_utf8(cmd.argument()));
1446                 string s;
1447                 is >> s;
1448                 if (s == "valign-top")
1449                         setVerticalAlignment('t');
1450                 else if (s == "valign-middle")
1451                         setVerticalAlignment('c');
1452                 else if (s == "valign-bottom")
1453                         setVerticalAlignment('b');
1454                 else if (s == "align-left")
1455                         setHorizontalAlignment('l', cur.col());
1456                 else if (s == "align-right")
1457                         setHorizontalAlignment('r', cur.col());
1458                 else if (s == "align-center")
1459                         setHorizontalAlignment('c', cur.col());
1460                 else if (s == "append-row")
1461                         for (int i = 0, n = extractInt(is); i < n; ++i)
1462                                 addRow(cur.row());
1463                 else if (s == "delete-row") {
1464                         cur.clearSelection(); // bug 4323
1465                         for (int i = 0, n = extractInt(is); i < n; ++i) {
1466                                 delRow(cur.row());
1467                                 if (cur.idx() >= nargs())
1468                                         cur.idx() -= ncols();
1469                         }
1470                         cur.pos() = 0; // trick, see below
1471                 }
1472                 else if (s == "copy-row") {
1473                         // Here (as later) we save the cursor col/row
1474                         // in order to restore it after operation.
1475                         row_type const r = cur.row();
1476                         col_type const c = cur.col();
1477                         for (int i = 0, n = extractInt(is); i < n; ++i)
1478                                 copyRow(cur.row());
1479                         cur.idx() = index(r, c);
1480                 }
1481                 else if (s == "swap-row") {
1482                         swapRow(cur.row());
1483                         // Trick to suppress same-idx-means-different-cell
1484                         // assertion crash:
1485                         cur.pos() = 0;
1486                 }
1487                 else if (s == "add-hline-above")
1488                         rowinfo_[cur.row()].lines++;
1489                 else if (s == "add-hline-below")
1490                         rowinfo_[cur.row()+1].lines++;
1491                 else if (s == "delete-hline-above")
1492                         rowinfo_[cur.row()].lines--;
1493                 else if (s == "delete-hline-below")
1494                         rowinfo_[cur.row()+1].lines--;
1495                 else if (s == "append-column") {
1496                         row_type const r = cur.row();
1497                         col_type const c = cur.col();
1498                         for (int i = 0, n = extractInt(is); i < n; ++i)
1499                                 addCol(cur.col() + 1);
1500                         cur.idx() = index(r, c);
1501                 }
1502                 else if (s == "delete-column") {
1503                         cur.clearSelection(); // bug 4323
1504                         row_type const r = cur.row();
1505                         col_type const c = cur.col();
1506                         for (int i = 0, n = extractInt(is); i < n; ++i)
1507                                 delCol(col(cur.idx()));
1508                         cur.idx() = index(r, min(c, cur.ncols() - 1));
1509                         cur.pos() = 0; // trick, see above
1510                 }
1511                 else if (s == "copy-column") {
1512                         row_type const r = cur.row();
1513                         col_type const c = cur.col();
1514                         copyCol(cur.col());
1515                         cur.idx() = index(r, c);
1516                 }
1517                 else if (s == "swap-column") {
1518                         swapCol(cur.col());
1519                         cur.pos() = 0; // trick, see above
1520                 }
1521                 else if (s == "add-vline-left") {
1522                         colinfo_[cur.col()].lines++;
1523                         if (!colinfo_[cur.col()].special.empty())
1524                                 colinfo_[cur.col()].special += '|';
1525                 }
1526                 else if (s == "add-vline-right") {
1527                         colinfo_[cur.col()+1].lines++;
1528                         if (!colinfo_[cur.col()+1].special.empty())
1529                                 colinfo_[cur.col()+1].special.insert(0, 1, '|');
1530                 }
1531                 else if (s == "delete-vline-left") {
1532                         colinfo_[cur.col()].lines--;
1533                         docstring & special = colinfo_[cur.col()].special;
1534                         if (!special.empty()) {
1535                                 docstring::size_type i = special.rfind('|');
1536                                 LASSERT(i != docstring::npos, break);
1537                                 special.erase(i, 1);
1538                         }
1539                 }
1540                 else if (s == "delete-vline-right") {
1541                         colinfo_[cur.col()+1].lines--;
1542                         docstring & special = colinfo_[cur.col()+1].special;
1543                         if (!special.empty()) {
1544                                 docstring::size_type i = special.find('|');
1545                                 LASSERT(i != docstring::npos, break);
1546                                 special.erase(i, 1);
1547                         }
1548                 }
1549                 else {
1550                         cur.undispatched();
1551                         break;
1552                 }
1553                 // perhaps this should be FINISHED_BACKWARD -- just for clarity?
1554                 //lyxerr << "returning FINISHED_LEFT" << endl;
1555                 break;
1556         }
1557
1558         case LFUN_CLIPBOARD_PASTE:
1559                 parseflg |= Parse::VERBATIM;
1560                 // fall through
1561         case LFUN_PASTE: {
1562                 if (cur.currentMode() != MATH_MODE)
1563                         parseflg |= Parse::TEXTMODE;
1564                 cur.message(_("Paste"));
1565                 cap::replaceSelection(cur);
1566                 docstring topaste;
1567                 if (cmd.argument().empty() && !theClipboard().isInternal())
1568                         topaste = theClipboard().getAsText(frontend::Clipboard::PlainTextType);
1569                 else {
1570                         idocstringstream is(cmd.argument());
1571                         int n = 0;
1572                         is >> n;
1573                         topaste = cap::selection(n, make_pair(buffer().params().documentClassPtr(),
1574                                                               buffer().params().authors()), true);
1575                 }
1576                 InsetMathGrid grid(buffer_, 1, 1);
1577                 if (!topaste.empty())
1578                         if ((topaste.size() == 1 && isAscii(topaste))
1579                             || !mathed_parse_normal(grid, topaste, parseflg)) {
1580                                 resetGrid(grid);
1581                                 mathed_parse_normal(grid, topaste, parseflg | Parse::VERBATIM);
1582                         }
1583
1584                 bool hline_enabled = false;
1585                 FuncRequest fr = FuncRequest(LFUN_TABULAR_FEATURE, "add-hline-above");
1586                 FuncStatus status;
1587                 if (getStatus(cur, fr, status))
1588                         hline_enabled = status.enabled();
1589                 if (grid.nargs() == 1) {
1590                         // single cell/part of cell
1591                         cur.recordUndoInset();
1592                         cur.cell().insert(cur.pos(), grid.cell(0));
1593                         cur.pos() += grid.cell(0).size();
1594                         if (hline_enabled)
1595                                 rowinfo_[cur.row()].lines += grid.rowinfo_[0].lines;
1596                         else {
1597                                 for (unsigned int l = 0; l < grid.rowinfo_[0].lines; ++l) {
1598                                          cur.cell().insert(0,
1599                                                 MathAtom(new InsetMathUnknown(from_ascii("\\hline"))));
1600                                          cur.pos()++;
1601                                 }
1602                         }
1603                 } else {
1604                         // multiple cells
1605                         cur.recordUndoInset();
1606                         col_type startcol = col(cur.idx());
1607                         row_type startrow = cur.row();
1608                         col_type oldncols = ncols();
1609                         col_type numcols =
1610                                 min(grid.ncols(), ncols() - startcol);
1611                         row_type const numrows =
1612                                 min(grid.nrows(), nrows() - cur.row());
1613                         for (row_type r = 0; r < numrows; ++r) {
1614                                 for (col_type c = 0; c < numcols; ++c) {
1615                                         idx_type i = index(r + startrow, c + startcol);
1616                                         pos_type const ipos = min(cur.pos(), pos_type(cell(i).size()));
1617                                         cell(i).insert(ipos, grid.cell(grid.index(r, c)));
1618                                 }
1619                                 if (hline_enabled)
1620                                         rowinfo_[r].lines += grid.rowinfo_[r].lines;
1621                                 else {
1622                                         for (unsigned int l = 0; l < grid.rowinfo_[r].lines; ++l) {
1623                                                 idx_type i = index(r + startrow, 0);
1624                                                 cell(i).insert(0,
1625                                                         MathAtom(new InsetMathUnknown(from_ascii("\\hline"))));
1626                                         }
1627                                 }
1628                                 // append columns for the left over horizontal cells
1629                                 for (col_type c = numcols; c < grid.ncols(); ++c) {
1630                                         addCol(c + startcol);
1631                                         idx_type i = index(r + startrow, min(c + startcol, ncols() - 1));
1632                                         cell(i).append(grid.cell(grid.index(r, c)));
1633                                         ++numcols;
1634                                 }
1635                         }
1636                         // amend cursor position if cols have been appended
1637                         cur.idx() += startrow * (ncols() - oldncols);
1638                         // append rows for the left over vertical cells
1639                         idx_type i = nargs() - 1;
1640                         for (row_type r = numrows; r < grid.nrows(); ++r) {
1641                                 row_type crow = startrow + r;
1642                                 addRow(crow - 1);
1643                                 for (col_type c = 0; c < grid.ncols(); ++c)
1644                                         cell(index(min(crow, nrows() - 1), min(c + startcol, ncols() - 1))).append(grid.cell(grid.index(r, c)));
1645                                 if (hline_enabled)
1646                                         rowinfo_[crow].lines += grid.rowinfo_[r].lines;
1647                                 else {
1648                                         for (unsigned int l = 0; l < grid.rowinfo_[r].lines; ++l) {
1649                                                 cell(i).insert(0,
1650                                                         MathAtom(new InsetMathUnknown(from_ascii("\\hline"))));
1651                                         }
1652                                 }
1653                         }
1654                 }
1655                 cur.clearSelection(); // bug 393
1656                 // FIXME audit setBuffer calls
1657                 cur.inset().setBuffer(*buffer_);
1658                 cur.forceBufferUpdate();
1659                 cur.finishUndo();
1660                 break;
1661         }
1662
1663         case LFUN_LINE_BEGIN:
1664                 cur.screenUpdateFlags(Update::Decoration | Update::FitCursor);
1665                 // fall through
1666         case LFUN_LINE_BEGIN_SELECT:
1667                 cur.selHandle(act == LFUN_WORD_BACKWARD_SELECT ||
1668                                 act == LFUN_WORD_LEFT_SELECT ||
1669                                 act == LFUN_LINE_BEGIN_SELECT);
1670                 cur.macroModeClose();
1671                 if (cur.pos() != 0) {
1672                         cur.pos() = 0;
1673                 } else if (cur.idx() % cur.ncols() != 0) {
1674                         cur.idx() -= cur.idx() % cur.ncols();
1675                         cur.pos() = 0;
1676                 } else if (cur.idx() != 0) {
1677                         cur.idx() = 0;
1678                         cur.pos() = 0;
1679                 } else {
1680                         cmd = FuncRequest(LFUN_FINISHED_BACKWARD);
1681                         cur.undispatched();
1682                 }
1683                 break;
1684
1685         case LFUN_LINE_END:
1686                 cur.screenUpdateFlags(Update::Decoration | Update::FitCursor);
1687                 // fall through
1688         case LFUN_LINE_END_SELECT:
1689                 cur.selHandle(act == LFUN_WORD_FORWARD_SELECT ||
1690                                 act == LFUN_WORD_RIGHT_SELECT ||
1691                                 act == LFUN_LINE_END_SELECT);
1692                 cur.macroModeClose();
1693                 cur.clearTargetX();
1694                 if (cur.pos() != cur.lastpos()) {
1695                         cur.pos() = cur.lastpos();
1696                 } else if ((cur.idx() + 1) % cur.ncols() != 0) {
1697                         cur.idx() += cur.ncols() - 1 - cur.idx() % cur.ncols();
1698                         cur.pos() = cur.lastpos();
1699                 } else if (cur.idx() != cur.lastidx()) {
1700                         cur.idx() = cur.lastidx();
1701                         cur.pos() = cur.lastpos();
1702                 } else {
1703                         cmd = FuncRequest(LFUN_FINISHED_FORWARD);
1704                         cur.undispatched();
1705                 }
1706                 break;
1707
1708         default:
1709                 InsetMathNest::doDispatch(cur, cmd);
1710         }
1711 }
1712
1713
1714 bool InsetMathGrid::getStatus(Cursor & cur, FuncRequest const & cmd,
1715                 FuncStatus & status) const
1716 {
1717         switch (cmd.action()) {
1718         case LFUN_TABULAR_FEATURE: {
1719                 string s = cmd.getArg(0);
1720                 if (&cur.inset() != this) {
1721                         // Table actions requires that the cursor is _inside_ the
1722                         // table.
1723                         status.setEnabled(false);
1724                         status.message(from_utf8(N_("Cursor not in table")));
1725                         return true;
1726                 }
1727                 if (nrows() <= 1 && (s == "delete-row" || s == "swap-row")) {
1728                         status.setEnabled(false);
1729                         status.message(from_utf8(N_("Only one row")));
1730                         return true;
1731                 }
1732                 if (ncols() <= 1 &&
1733                     (s == "delete-column" || s == "swap-column")) {
1734                         status.setEnabled(false);
1735                         status.message(from_utf8(N_("Only one column")));
1736                         return true;
1737                 }
1738                 if ((rowinfo_[cur.row()].lines == 0 &&
1739                      s == "delete-hline-above") ||
1740                     (rowinfo_[cur.row() + 1].lines == 0 &&
1741                      s == "delete-hline-below")) {
1742                         status.setEnabled(false);
1743                         status.message(from_utf8(N_("No hline to delete")));
1744                         return true;
1745                 }
1746
1747                 if ((colinfo_[cur.col()].lines == 0 &&
1748                      s == "delete-vline-left") ||
1749                     (colinfo_[cur.col() + 1].lines == 0 &&
1750                      s == "delete-vline-right")) {
1751                         status.setEnabled(false);
1752                         status.message(from_utf8(N_("No vline to delete")));
1753                         return true;
1754                 }
1755                 if (s == "valign-top" || s == "valign-middle" ||
1756                     s == "valign-bottom" || s == "align-left" ||
1757                     s == "align-right" || s == "align-center") {
1758                         status.setEnabled(true);
1759                         char const ha = horizontalAlignment(cur.col());
1760                         char const va = verticalAlignment();
1761                         status.setOnOff((s == "align-left" && ha == 'l')
1762                                         || (s == "align-right"   && ha == 'r')
1763                                         || (s == "align-center"  && ha == 'c')
1764                                         || (s == "valign-top"    && va == 't')
1765                                         || (s == "valign-bottom" && va == 'b')
1766                                         || (s == "valign-middle" && va == 'c'));
1767                         return true;
1768                 }
1769                 if (s == "append-row" || s == "delete-row" ||
1770                     s == "copy-row" || s == "swap-row" ||
1771                     s == "add-hline-above" || s == "add-hline-below" ||
1772                     s == "delete-hline-above" || s == "delete-hline-below" ||
1773                     s == "append-column" || s == "delete-column" ||
1774                     s == "copy-column" || s == "swap-column" ||
1775                     s == "add-vline-left" || s == "add-vline-right" ||
1776                     s == "delete-vline-left" || s == "delete-vline-right") {
1777                         status.setEnabled(true);
1778                 } else {
1779                         status.setEnabled(false);
1780                         status.message(bformat(
1781                             from_utf8(N_("Unknown tabular feature '%1$s'")),
1782                             from_utf8(s)));
1783                 }
1784
1785                 return true;
1786         }
1787
1788         case LFUN_CELL_SPLIT:
1789                 status.setEnabled(cur.idx() != cur.lastidx());
1790                 return true;
1791
1792         case LFUN_CELL_BACKWARD:
1793         case LFUN_CELL_FORWARD:
1794                 status.setEnabled(true);
1795                 return true;
1796
1797         default:
1798                 break;
1799         }
1800         return InsetMathNest::getStatus(cur, cmd, status);
1801 }
1802
1803
1804 char InsetMathGrid::colAlign(HullType type, col_type col) const
1805 {
1806         switch (type) {
1807         case hullEqnArray:
1808                 return "rcl"[col % 3];
1809
1810         case hullMultline:
1811                 return 'c';
1812         case hullGather:
1813                 LASSERT(isBufferValid(),
1814                                 LYXERR0("Buffer not set correctly. Please report!");
1815                                 return 'c';);
1816                 if (buffer().params().is_math_indent)
1817                         return 'l';
1818                 else
1819                         return 'c';
1820
1821         case hullAlign:
1822         case hullAlignAt:
1823         case hullXAlignAt:
1824         case hullXXAlignAt:
1825         case hullFlAlign:
1826                 return "rl"[col & 1];
1827
1828         case hullUnknown:
1829         case hullNone:
1830         case hullSimple:
1831         case hullEquation:
1832         case hullRegexp:
1833                 return 'c';
1834         }
1835         // avoid warning
1836         return 'c';
1837 }
1838
1839
1840 //static
1841 int InsetMathGrid::colSpace(HullType type, col_type col)
1842 {
1843         int alignInterSpace = 0;
1844         switch (type) {
1845         case hullUnknown:
1846         case hullNone:
1847         case hullSimple:
1848         case hullEquation:
1849         case hullMultline:
1850         case hullGather:
1851         case hullRegexp:
1852                 return 0;
1853
1854         case hullEqnArray:
1855                 return 5;
1856
1857         case hullAlign:
1858                 alignInterSpace = 20;
1859                 break;
1860         case hullAlignAt:
1861                 alignInterSpace = 0;
1862                 break;
1863         case hullXAlignAt:
1864                 alignInterSpace = 40;
1865                 break;
1866         case hullXXAlignAt:
1867         case hullFlAlign:
1868                 alignInterSpace = 60;
1869                 break;
1870         }
1871         return (col % 2) ? alignInterSpace : 0;
1872 }
1873
1874
1875 } // namespace lyx