]> git.lyx.org Git - lyx.git/blob - src/mathed/InsetMathGrid.cpp
Allow pasting references to mathed
[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 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         return (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 eol;
1251         for (row_type row = beg_row; row < end_row; ++row) {
1252                 os << verboseHLine(rowinfo_[row].lines);
1253                 // don't write & and empty cells at end of line,
1254                 // unless there are vertical lines
1255                 col_type lastcol = 0;
1256                 bool emptyline = true;
1257                 bool last_eoln = true;
1258                 for (col_type col = beg_col; col < end_col; ++col) {
1259                         idx_type const idx = index(row, col);
1260                         bool const empty_cell = cell(idx).empty();
1261                         if (last_eoln && (!empty_cell || cellinfo_[idx].multi != CELL_NORMAL))
1262                                 last_eoln = false;
1263                         if (!empty_cell || cellinfo_[idx].multi != CELL_NORMAL ||
1264                             colinfo_[col + 1].lines) {
1265                                 lastcol = col + 1;
1266                                 emptyline = false;
1267                         }
1268                 }
1269                 for (col_type col = beg_col; col < end_col;) {
1270                         int nccols = 1;
1271                         idx_type const idx = index(row, col);
1272                         TexRow::RowEntry const entry = TexRow::mathEntry(id(),idx);
1273                         os.texrow().start(entry);
1274                         if (col >= lastcol) {
1275                                 ++col;
1276                                 continue;
1277                         }
1278                         Changer dummy = os.changeRowEntry(entry);
1279                         if (cellinfo_[idx].multi == CELL_BEGIN_OF_MULTICOLUMN) {
1280                                 size_t s = col + 1;
1281                                 while (s < ncols() &&
1282                                        cellinfo_[index(row, s)].multi == CELL_PART_OF_MULTICOLUMN)
1283                                         s++;
1284                                 nccols = s - col;
1285                                 os << "\\multicolumn{" << nccols
1286                                    << "}{" << cellinfo_[idx].align
1287                                    << "}{";
1288                         }
1289                         os << cell(idx);
1290                         if (os.pendingBrace())
1291                                 ModeSpecifier specifier(os, TEXT_MODE);
1292                         if (cellinfo_[idx].multi == CELL_BEGIN_OF_MULTICOLUMN)
1293                                 os << '}';
1294                         os << eocString(col + nccols - 1, lastcol);
1295                         col += nccols;
1296                 }
1297                 eol = eolString(row, os.fragile(), os.latex(), last_eoln);
1298                 os << eol;
1299                 // append newline only if line wasn't completely empty
1300                 // and the formula is not written on a single line
1301                 bool const empty = emptyline && eol.empty();
1302                 if (!empty && nrows() > 1)
1303                         os << "\n";
1304         }
1305         // @TODO use end_row instead of nrows() ?
1306         docstring const s = verboseHLine(rowinfo_[nrows()].lines);
1307         if (!s.empty()) {
1308                 if (eol.empty()) {
1309                         if (os.fragile())
1310                                 os << "\\protect";
1311                         os << "\\\\";
1312                 }
1313                 os << s;
1314         }
1315 }
1316
1317
1318 int InsetMathGrid::colsep() const
1319 {
1320         return 6;
1321 }
1322
1323
1324 int InsetMathGrid::rowsep() const
1325 {
1326         return 6;
1327 }
1328
1329
1330 int InsetMathGrid::hlinesep() const
1331 {
1332         return 3;
1333 }
1334
1335
1336 int InsetMathGrid::vlinesep() const
1337 {
1338         return 3;
1339 }
1340
1341
1342 int InsetMathGrid::border() const
1343 {
1344         return 1;
1345 }
1346
1347
1348 void InsetMathGrid::splitCell(Cursor & cur)
1349 {
1350         if (cur.idx() == cur.lastidx())
1351                 return;
1352         MathData ar = cur.cell();
1353         ar.erase(0, cur.pos());
1354         cur.cell().erase(cur.pos(), cur.lastpos());
1355         ++cur.idx();
1356         while (cur.idx() << nargs() &&
1357                cellinfo_[cur.idx()].multi == CELL_BEGIN_OF_MULTICOLUMN)
1358                 ++cur.idx();
1359         cur.pos() = 0;
1360         cur.cell().insert(0, ar);
1361 }
1362
1363
1364 char InsetMathGrid::displayColAlign(idx_type idx) const
1365 {
1366         if (cellinfo_[idx].multi == CELL_BEGIN_OF_MULTICOLUMN) {
1367                 // align may also contain lines like "||r|", so this is
1368                 // not complete, but we catch at least the simple cases.
1369                 if (cellinfo_[idx].align == "c")
1370                         return 'c';
1371                 if (cellinfo_[idx].align == "l")
1372                         return 'l';
1373                 if (cellinfo_[idx].align == "r")
1374                         return 'r';
1375         }
1376         return colinfo_[col(idx)].align;
1377 }
1378
1379
1380 int InsetMathGrid::displayColSpace(col_type col) const
1381 {
1382         return colinfo_[col].skip;
1383 }
1384
1385 void InsetMathGrid::doDispatch(Cursor & cur, FuncRequest & cmd)
1386 {
1387         //lyxerr << "*** InsetMathGrid: request: " << cmd << endl;
1388
1389         Parse::flags parseflg = Parse::QUIET | Parse::USETEXT;
1390
1391         FuncCode const act = cmd.action();
1392         switch (act) {
1393
1394         // insert file functions
1395         case LFUN_LINE_DELETE_FORWARD:
1396                 cur.recordUndoInset();
1397                 //autocorrect_ = false;
1398                 //macroModeClose();
1399                 //if (selection_) {
1400                 //      selDel();
1401                 //      break;
1402                 //}
1403                 if (nrows() > 1)
1404                         delRow(cur.row());
1405                 if (cur.idx() > cur.lastidx())
1406                         cur.idx() = cur.lastidx();
1407                 if (cur.pos() > cur.lastpos())
1408                         cur.pos() = cur.lastpos();
1409                 break;
1410
1411         case LFUN_CELL_SPLIT:
1412                 cur.recordUndo();
1413                 splitCell(cur);
1414                 break;
1415
1416         case LFUN_NEWLINE_INSERT: {
1417                 cur.recordUndoInset();
1418                 row_type const r = cur.row();
1419                 addRow(r);
1420
1421                 // split line
1422                 for (col_type c = col(cur.idx()) + 1; c < ncols(); ++c)
1423                         swap(cell(index(r, c)), cell(index(r + 1, c)));
1424
1425                 // split cell
1426                 splitCell(cur);
1427                 if (ncols() > 1)
1428                         swap(cell(cur.idx()), cell(cur.idx() + ncols() - 1));
1429                 if (cur.idx() > 0)
1430                         --cur.idx();
1431                 cur.pos() = cur.lastpos();
1432                 cur.forceBufferUpdate();
1433                 //mathcursor->normalize();
1434                 //cmd = FuncRequest(LFUN_FINISHED_BACKWARD);
1435                 break;
1436         }
1437
1438         case LFUN_TABULAR_FEATURE: {
1439                 cur.recordUndoInset();
1440                 //lyxerr << "handling tabular-feature " << to_utf8(cmd.argument()) << endl;
1441                 istringstream is(to_utf8(cmd.argument()));
1442                 string s;
1443                 is >> s;
1444                 if (s == "valign-top")
1445                         setVerticalAlignment('t');
1446                 else if (s == "valign-middle")
1447                         setVerticalAlignment('c');
1448                 else if (s == "valign-bottom")
1449                         setVerticalAlignment('b');
1450                 else if (s == "align-left")
1451                         setHorizontalAlignment('l', cur.col());
1452                 else if (s == "align-right")
1453                         setHorizontalAlignment('r', cur.col());
1454                 else if (s == "align-center")
1455                         setHorizontalAlignment('c', cur.col());
1456                 else if (s == "append-row")
1457                         for (int i = 0, n = extractInt(is); i < n; ++i)
1458                                 addRow(cur.row());
1459                 else if (s == "delete-row") {
1460                         cur.clearSelection(); // bug 4323
1461                         for (int i = 0, n = extractInt(is); i < n; ++i) {
1462                                 delRow(cur.row());
1463                                 if (cur.idx() >= nargs())
1464                                         cur.idx() -= ncols();
1465                         }
1466                         cur.pos() = 0; // trick, see below
1467                 }
1468                 else if (s == "copy-row") {
1469                         // Here (as later) we save the cursor col/row
1470                         // in order to restore it after operation.
1471                         row_type const r = cur.row();
1472                         col_type const c = cur.col();
1473                         for (int i = 0, n = extractInt(is); i < n; ++i)
1474                                 copyRow(cur.row());
1475                         cur.idx() = index(r, c);
1476                 }
1477                 else if (s == "swap-row") {
1478                         swapRow(cur.row());
1479                         // Trick to suppress same-idx-means-different-cell
1480                         // assertion crash:
1481                         cur.pos() = 0;
1482                 }
1483                 else if (s == "add-hline-above")
1484                         rowinfo_[cur.row()].lines++;
1485                 else if (s == "add-hline-below")
1486                         rowinfo_[cur.row()+1].lines++;
1487                 else if (s == "delete-hline-above")
1488                         rowinfo_[cur.row()].lines--;
1489                 else if (s == "delete-hline-below")
1490                         rowinfo_[cur.row()+1].lines--;
1491                 else if (s == "append-column") {
1492                         row_type const r = cur.row();
1493                         col_type const c = cur.col();
1494                         for (int i = 0, n = extractInt(is); i < n; ++i)
1495                                 addCol(cur.col() + 1);
1496                         cur.idx() = index(r, c);
1497                 }
1498                 else if (s == "delete-column") {
1499                         cur.clearSelection(); // bug 4323
1500                         row_type const r = cur.row();
1501                         col_type const c = cur.col();
1502                         for (int i = 0, n = extractInt(is); i < n; ++i)
1503                                 delCol(col(cur.idx()));
1504                         cur.idx() = index(r, min(c, cur.ncols() - 1));
1505                         cur.pos() = 0; // trick, see above
1506                 }
1507                 else if (s == "copy-column") {
1508                         row_type const r = cur.row();
1509                         col_type const c = cur.col();
1510                         copyCol(cur.col());
1511                         cur.idx() = index(r, c);
1512                 }
1513                 else if (s == "swap-column") {
1514                         swapCol(cur.col());
1515                         cur.pos() = 0; // trick, see above
1516                 }
1517                 else if (s == "add-vline-left") {
1518                         colinfo_[cur.col()].lines++;
1519                         if (!colinfo_[cur.col()].special.empty())
1520                                 colinfo_[cur.col()].special += '|';
1521                 }
1522                 else if (s == "add-vline-right") {
1523                         colinfo_[cur.col()+1].lines++;
1524                         if (!colinfo_[cur.col()+1].special.empty())
1525                                 colinfo_[cur.col()+1].special.insert(0, 1, '|');
1526                 }
1527                 else if (s == "delete-vline-left") {
1528                         colinfo_[cur.col()].lines--;
1529                         docstring & special = colinfo_[cur.col()].special;
1530                         if (!special.empty()) {
1531                                 docstring::size_type i = special.rfind('|');
1532                                 LASSERT(i != docstring::npos, break);
1533                                 special.erase(i, 1);
1534                         }
1535                 }
1536                 else if (s == "delete-vline-right") {
1537                         colinfo_[cur.col()+1].lines--;
1538                         docstring & special = colinfo_[cur.col()+1].special;
1539                         if (!special.empty()) {
1540                                 docstring::size_type i = special.find('|');
1541                                 LASSERT(i != docstring::npos, break);
1542                                 special.erase(i, 1);
1543                         }
1544                 }
1545                 else {
1546                         cur.undispatched();
1547                         break;
1548                 }
1549                 // perhaps this should be FINISHED_BACKWARD -- just for clarity?
1550                 //lyxerr << "returning FINISHED_LEFT" << endl;
1551                 break;
1552         }
1553
1554         case LFUN_CLIPBOARD_PASTE:
1555                 parseflg |= Parse::VERBATIM;
1556                 // fall through
1557         case LFUN_PASTE: {
1558                 if (cur.currentMode() != MATH_MODE)
1559                         parseflg |= Parse::TEXTMODE;
1560                 cur.message(_("Paste"));
1561                 cap::replaceSelection(cur);
1562                 docstring topaste;
1563                 if (cmd.argument().empty() && !theClipboard().isInternal())
1564                         topaste = theClipboard().getAsText(frontend::Clipboard::PlainTextType);
1565                 else {
1566                         idocstringstream is(cmd.argument());
1567                         int n = 0;
1568                         is >> n;
1569                         topaste = cap::selection(n, buffer().params().documentClassPtr(), true);
1570                 }
1571                 InsetMathGrid grid(buffer_, 1, 1);
1572                 if (!topaste.empty())
1573                         if ((topaste.size() == 1 && isAscii(topaste))
1574                             || !mathed_parse_normal(grid, topaste, parseflg)) {
1575                                 resetGrid(grid);
1576                                 mathed_parse_normal(grid, topaste, parseflg | Parse::VERBATIM);
1577                         }
1578
1579                 bool hline_enabled = false;
1580                 FuncRequest fr = FuncRequest(LFUN_TABULAR_FEATURE, "add-hline-above");
1581                 FuncStatus status;
1582                 if (getStatus(cur, fr, status))
1583                         hline_enabled = status.enabled();
1584                 if (grid.nargs() == 1) {
1585                         // single cell/part of cell
1586                         cur.recordUndoInset();
1587                         cur.cell().insert(cur.pos(), grid.cell(0));
1588                         cur.pos() += grid.cell(0).size();
1589                         if (hline_enabled)
1590                                 rowinfo_[cur.row()].lines += grid.rowinfo_[0].lines;
1591                         else {
1592                                 for (unsigned int l = 0; l < grid.rowinfo_[0].lines; ++l) {
1593                                          cur.cell().insert(0,
1594                                                 MathAtom(new InsetMathUnknown(from_ascii("\\hline"))));
1595                                          cur.pos()++;
1596                                 }
1597                         }
1598                 } else {
1599                         // multiple cells
1600                         cur.recordUndoInset();
1601                         col_type startcol = col(cur.idx());
1602                         row_type startrow = cur.row();
1603                         col_type oldncols = ncols();
1604                         col_type numcols =
1605                                 min(grid.ncols(), ncols() - startcol);
1606                         row_type const numrows =
1607                                 min(grid.nrows(), nrows() - cur.row());
1608                         for (row_type r = 0; r < numrows; ++r) {
1609                                 for (col_type c = 0; c < numcols; ++c) {
1610                                         idx_type i = index(r + startrow, c + startcol);
1611                                         pos_type const ipos = min(cur.pos(), pos_type(cell(i).size()));
1612                                         cell(i).insert(ipos, grid.cell(grid.index(r, c)));
1613                                 }
1614                                 if (hline_enabled)
1615                                         rowinfo_[r].lines += grid.rowinfo_[r].lines;
1616                                 else {
1617                                         for (unsigned int l = 0; l < grid.rowinfo_[r].lines; ++l) {
1618                                                 idx_type i = index(r + startrow, 0);
1619                                                 cell(i).insert(0,
1620                                                         MathAtom(new InsetMathUnknown(from_ascii("\\hline"))));
1621                                         }
1622                                 }
1623                                 // append columns for the left over horizontal cells
1624                                 for (col_type c = numcols; c < grid.ncols(); ++c) {
1625                                         addCol(c + startcol);
1626                                         idx_type i = index(r + startrow, min(c + startcol, ncols() - 1));
1627                                         cell(i).append(grid.cell(grid.index(r, c)));
1628                                         ++numcols;
1629                                 }
1630                         }
1631                         // amend cursor position if cols have been appended
1632                         cur.idx() += startrow * (ncols() - oldncols);
1633                         // append rows for the left over vertical cells
1634                         idx_type i = nargs() - 1;
1635                         for (row_type r = numrows; r < grid.nrows(); ++r) {
1636                                 row_type crow = startrow + r;
1637                                 addRow(crow - 1);
1638                                 for (col_type c = 0; c < grid.ncols(); ++c)
1639                                         cell(index(min(crow, nrows() - 1), min(c + startcol, ncols() - 1))).append(grid.cell(grid.index(r, c)));
1640                                 if (hline_enabled)
1641                                         rowinfo_[crow].lines += grid.rowinfo_[r].lines;
1642                                 else {
1643                                         for (unsigned int l = 0; l < grid.rowinfo_[r].lines; ++l) {
1644                                                 cell(i).insert(0,
1645                                                         MathAtom(new InsetMathUnknown(from_ascii("\\hline"))));
1646                                         }
1647                                 }
1648                         }
1649                 }
1650                 cur.clearSelection(); // bug 393
1651                 // FIXME audit setBuffer calls
1652                 cur.inset().setBuffer(*buffer_);
1653                 cur.forceBufferUpdate();
1654                 cur.finishUndo();
1655                 break;
1656         }
1657
1658         case LFUN_LINE_BEGIN:
1659                 cur.screenUpdateFlags(Update::Decoration | Update::FitCursor);
1660                 // fall through
1661         case LFUN_LINE_BEGIN_SELECT:
1662                 cur.selHandle(act == LFUN_WORD_BACKWARD_SELECT ||
1663                                 act == LFUN_WORD_LEFT_SELECT ||
1664                                 act == LFUN_LINE_BEGIN_SELECT);
1665                 cur.macroModeClose();
1666                 if (cur.pos() != 0) {
1667                         cur.pos() = 0;
1668                 } else if (cur.idx() % cur.ncols() != 0) {
1669                         cur.idx() -= cur.idx() % cur.ncols();
1670                         cur.pos() = 0;
1671                 } else if (cur.idx() != 0) {
1672                         cur.idx() = 0;
1673                         cur.pos() = 0;
1674                 } else {
1675                         cmd = FuncRequest(LFUN_FINISHED_BACKWARD);
1676                         cur.undispatched();
1677                 }
1678                 break;
1679
1680         case LFUN_LINE_END:
1681                 cur.screenUpdateFlags(Update::Decoration | Update::FitCursor);
1682                 // fall through
1683         case LFUN_LINE_END_SELECT:
1684                 cur.selHandle(act == LFUN_WORD_FORWARD_SELECT ||
1685                                 act == LFUN_WORD_RIGHT_SELECT ||
1686                                 act == LFUN_LINE_END_SELECT);
1687                 cur.macroModeClose();
1688                 cur.clearTargetX();
1689                 if (cur.pos() != cur.lastpos()) {
1690                         cur.pos() = cur.lastpos();
1691                 } else if ((cur.idx() + 1) % cur.ncols() != 0) {
1692                         cur.idx() += cur.ncols() - 1 - cur.idx() % cur.ncols();
1693                         cur.pos() = cur.lastpos();
1694                 } else if (cur.idx() != cur.lastidx()) {
1695                         cur.idx() = cur.lastidx();
1696                         cur.pos() = cur.lastpos();
1697                 } else {
1698                         cmd = FuncRequest(LFUN_FINISHED_FORWARD);
1699                         cur.undispatched();
1700                 }
1701                 break;
1702
1703         default:
1704                 InsetMathNest::doDispatch(cur, cmd);
1705         }
1706 }
1707
1708
1709 bool InsetMathGrid::getStatus(Cursor & cur, FuncRequest const & cmd,
1710                 FuncStatus & status) const
1711 {
1712         switch (cmd.action()) {
1713         case LFUN_TABULAR_FEATURE: {
1714                 string s = cmd.getArg(0);
1715                 if (&cur.inset() != this) {
1716                         // Table actions requires that the cursor is _inside_ the
1717                         // table.
1718                         status.setEnabled(false);
1719                         status.message(from_utf8(N_("Cursor not in table")));
1720                         return true;
1721                 }
1722                 if (nrows() <= 1 && (s == "delete-row" || s == "swap-row")) {
1723                         status.setEnabled(false);
1724                         status.message(from_utf8(N_("Only one row")));
1725                         return true;
1726                 }
1727                 if (ncols() <= 1 &&
1728                     (s == "delete-column" || s == "swap-column")) {
1729                         status.setEnabled(false);
1730                         status.message(from_utf8(N_("Only one column")));
1731                         return true;
1732                 }
1733                 if ((rowinfo_[cur.row()].lines == 0 &&
1734                      s == "delete-hline-above") ||
1735                     (rowinfo_[cur.row() + 1].lines == 0 &&
1736                      s == "delete-hline-below")) {
1737                         status.setEnabled(false);
1738                         status.message(from_utf8(N_("No hline to delete")));
1739                         return true;
1740                 }
1741
1742                 if ((colinfo_[cur.col()].lines == 0 &&
1743                      s == "delete-vline-left") ||
1744                     (colinfo_[cur.col() + 1].lines == 0 &&
1745                      s == "delete-vline-right")) {
1746                         status.setEnabled(false);
1747                         status.message(from_utf8(N_("No vline to delete")));
1748                         return true;
1749                 }
1750                 if (s == "valign-top" || s == "valign-middle" ||
1751                     s == "valign-bottom" || s == "align-left" ||
1752                     s == "align-right" || s == "align-center") {
1753                         status.setEnabled(true);
1754                         char const ha = horizontalAlignment(cur.col());
1755                         char const va = verticalAlignment();
1756                         status.setOnOff((s == "align-left" && ha == 'l')
1757                                         || (s == "align-right"   && ha == 'r')
1758                                         || (s == "align-center"  && ha == 'c')
1759                                         || (s == "valign-top"    && va == 't')
1760                                         || (s == "valign-bottom" && va == 'b')
1761                                         || (s == "valign-middle" && va == 'c'));
1762                         return true;
1763                 }
1764                 if (s == "append-row" || s == "delete-row" ||
1765                     s == "copy-row" || s == "swap-row" ||
1766                     s == "add-hline-above" || s == "add-hline-below" ||
1767                     s == "delete-hline-above" || s == "delete-hline-below" ||
1768                     s == "append-column" || s == "delete-column" ||
1769                     s == "copy-column" || s == "swap-column" ||
1770                     s == "add-vline-left" || s == "add-vline-right" ||
1771                     s == "delete-vline-left" || s == "delete-vline-right") {
1772                         status.setEnabled(true);
1773                 } else {
1774                         status.setEnabled(false);
1775                         status.message(bformat(
1776                             from_utf8(N_("Unknown tabular feature '%1$s'")),
1777                             from_utf8(s)));
1778                 }
1779
1780                 return true;
1781         }
1782
1783         case LFUN_CELL_SPLIT:
1784                 status.setEnabled(cur.idx() != cur.lastidx());
1785                 return true;
1786
1787         case LFUN_CELL_BACKWARD:
1788         case LFUN_CELL_FORWARD:
1789                 status.setEnabled(true);
1790                 return true;
1791
1792         default:
1793                 break;
1794         }
1795         return InsetMathNest::getStatus(cur, cmd, status);
1796 }
1797
1798
1799 char InsetMathGrid::colAlign(HullType type, col_type col) const
1800 {
1801         switch (type) {
1802         case hullEqnArray:
1803                 return "rcl"[col % 3];
1804
1805         case hullMultline:
1806                 return 'c';
1807         case hullGather:
1808                 LASSERT(isBufferValid(),
1809                                 LYXERR0("Buffer not set correctly. Please report!");
1810                                 return 'c';);
1811                 if (buffer().params().is_math_indent)
1812                         return 'l';
1813                 else
1814                         return 'c';
1815
1816         case hullAlign:
1817         case hullAlignAt:
1818         case hullXAlignAt:
1819         case hullXXAlignAt:
1820         case hullFlAlign:
1821                 return "rl"[col & 1];
1822
1823         case hullUnknown:
1824         case hullNone:
1825         case hullSimple:
1826         case hullEquation:
1827         case hullRegexp:
1828                 return 'c';
1829         }
1830         // avoid warning
1831         return 'c';
1832 }
1833
1834
1835 //static
1836 int InsetMathGrid::colSpace(HullType type, col_type col)
1837 {
1838         int alignInterSpace = 0;
1839         switch (type) {
1840         case hullUnknown:
1841         case hullNone:
1842         case hullSimple:
1843         case hullEquation:
1844         case hullMultline:
1845         case hullGather:
1846         case hullRegexp:
1847                 return 0;
1848
1849         case hullEqnArray:
1850                 return 5;
1851
1852         case hullAlign:
1853                 alignInterSpace = 20;
1854                 break;
1855         case hullAlignAt:
1856                 alignInterSpace = 0;
1857                 break;
1858         case hullXAlignAt:
1859                 alignInterSpace = 40;
1860                 break;
1861         case hullXXAlignAt:
1862         case hullFlAlign:
1863                 alignInterSpace = 60;
1864                 break;
1865         }
1866         return (col % 2) ? alignInterSpace : 0;
1867 }
1868
1869
1870 } // namespace lyx