]> git.lyx.org Git - lyx.git/blob - src/mathed/InsetMathGrid.cpp
"fix" bug #3332 (plain text export depends on the menu language)
[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
13 #include "InsetMathGrid.h"
14 #include "MathData.h"
15 #include "MathParser.h"
16 #include "MathStream.h"
17
18 #include "BufferView.h"
19 #include "CutAndPaste.h"
20 #include "FuncStatus.h"
21 #include "Color.h"
22 #include "Cursor.h"
23 #include "debug.h"
24 #include "FuncRequest.h"
25 #include "gettext.h"
26 #include "Undo.h"
27
28 #include "frontends/Clipboard.h"
29 #include "frontends/Painter.h"
30
31 #include "support/lstrings.h"
32
33 #include <sstream>
34
35
36 namespace lyx {
37
38 using support::bformat;
39
40 using std::endl;
41 using std::max;
42 using std::min;
43 using std::swap;
44
45 using std::string;
46 using std::auto_ptr;
47 using std::istream;
48 using std::istringstream;
49 using std::vector;
50
51
52 namespace {
53
54 docstring verboseHLine(int n)
55 {
56         docstring res;
57         for (int i = 0; i < n; ++i)
58                 res += "\\hline";
59         if (n)
60                 res += ' ';
61         return res;
62 }
63
64
65 int extractInt(istream & is)
66 {
67         int num = 1;
68         is >> num;
69         return (num == 0) ? 1 : num;
70 }
71
72 }
73
74
75 //////////////////////////////////////////////////////////////
76
77
78 InsetMathGrid::CellInfo::CellInfo()
79         : dummy_(false)
80 {}
81
82
83
84
85 //////////////////////////////////////////////////////////////
86
87
88 InsetMathGrid::RowInfo::RowInfo()
89         : lines_(0), skip_(0), allow_pagebreak_(true)
90 {}
91
92
93
94 int InsetMathGrid::RowInfo::skipPixels() const
95 {
96         return crskip_.inBP();
97 }
98
99
100
101 //////////////////////////////////////////////////////////////
102
103
104 InsetMathGrid::ColInfo::ColInfo()
105         : align_('c'), lines_(0)
106 {}
107
108
109 //////////////////////////////////////////////////////////////
110
111
112 InsetMathGrid::InsetMathGrid(char v, docstring const & h)
113         : InsetMathNest(guessColumns(h)),
114           rowinfo_(2),
115           colinfo_(guessColumns(h) + 1),
116           cellinfo_(1 * guessColumns(h))
117 {
118         setDefaults();
119         valign(v);
120         halign(h);
121         //lyxerr << "created grid with " << ncols() << " columns" << endl;
122 }
123
124
125 InsetMathGrid::InsetMathGrid()
126         : InsetMathNest(1),
127           rowinfo_(1 + 1),
128                 colinfo_(1 + 1),
129                 cellinfo_(1),
130                 v_align_('c')
131 {
132         setDefaults();
133 }
134
135
136 InsetMathGrid::InsetMathGrid(col_type m, row_type n)
137         : InsetMathNest(m * n),
138           rowinfo_(n + 1),
139                 colinfo_(m + 1),
140                 cellinfo_(m * n),
141                 v_align_('c')
142 {
143         setDefaults();
144 }
145
146
147 InsetMathGrid::InsetMathGrid(col_type m, row_type n, char v, docstring const & h)
148         : InsetMathNest(m * n),
149           rowinfo_(n + 1),
150           colinfo_(m + 1),
151                 cellinfo_(m * n),
152                 v_align_(v)
153 {
154         setDefaults();
155         valign(v);
156         halign(h);
157 }
158
159
160 auto_ptr<Inset> InsetMathGrid::doClone() const
161 {
162         return auto_ptr<Inset>(new InsetMathGrid(*this));
163 }
164
165
166 InsetMath::idx_type InsetMathGrid::index(row_type row, col_type col) const
167 {
168         return col + ncols() * row;
169 }
170
171
172 void InsetMathGrid::setDefaults()
173 {
174         if (ncols() <= 0)
175                 lyxerr << "positive number of columns expected" << endl;
176         //if (nrows() <= 0)
177         //      lyxerr << "positive number of rows expected" << endl;
178         for (col_type col = 0; col < ncols(); ++col) {
179                 colinfo_[col].align_ = defaultColAlign(col);
180                 colinfo_[col].skip_  = defaultColSpace(col);
181         }
182 }
183
184
185 void InsetMathGrid::halign(docstring const & hh)
186 {
187         col_type col = 0;
188         for (docstring::const_iterator it = hh.begin(); it != hh.end(); ++it) {
189                 char_type c = *it;
190                 if (c == '|') {
191                         colinfo_[col].lines_++;
192                 } else if (col >= ncols()) {
193                         // Only '|' is allowed in the last dummy column
194                         break;
195                 } else if (c == 'c' || c == 'l' || c == 'r') {
196                         colinfo_[col].align_ = (char)c;
197                         ++col;
198                         colinfo_[col].lines_ = 0;
199                 } else {
200                         lyxerr << "unknown column separator: '" << c << "'" << endl;
201                 }
202         }
203
204 /*
205         col_type n = hh.size();
206         if (n > ncols())
207                 n = ncols();
208         for (col_type col = 0; col < n; ++col)
209                 colinfo_[col].align_ = hh[col];
210 */
211 }
212
213
214 InsetMathGrid::col_type InsetMathGrid::guessColumns(docstring const & hh) const
215 {
216         col_type col = 0;
217         for (docstring::const_iterator it = hh.begin(); it != hh.end(); ++it)
218                 if (*it == 'c' || *it == 'l' || *it == 'r')
219                         ++col;
220         // let's have at least one column, even if we did not recognize its
221         // alignment
222         if (col == 0)
223                 col = 1;
224         return col;
225 }
226
227
228 void InsetMathGrid::halign(char h, col_type col)
229 {
230         colinfo_[col].align_ = h;
231 }
232
233
234 char InsetMathGrid::halign(col_type col) const
235 {
236         return colinfo_[col].align_;
237 }
238
239
240 docstring InsetMathGrid::halign() const
241 {
242         docstring res;
243         for (col_type col = 0; col < ncols(); ++col) {
244                 res += docstring(colinfo_[col].lines_, '|');
245                 res += colinfo_[col].align_;
246         }
247         return res + docstring(colinfo_[ncols()].lines_, '|');
248 }
249
250
251 void InsetMathGrid::valign(char c)
252 {
253         v_align_ = c;
254 }
255
256
257 char InsetMathGrid::valign() const
258 {
259         return v_align_;
260 }
261
262
263 InsetMathGrid::col_type InsetMathGrid::ncols() const
264 {
265         return colinfo_.size() - 1;
266 }
267
268
269 InsetMathGrid::row_type InsetMathGrid::nrows() const
270 {
271         return rowinfo_.size() - 1;
272 }
273
274
275 InsetMathGrid::col_type InsetMathGrid::col(idx_type idx) const
276 {
277         return idx % ncols();
278 }
279
280
281 InsetMathGrid::row_type InsetMathGrid::row(idx_type idx) const
282 {
283         return idx / ncols();
284 }
285
286
287 void InsetMathGrid::vcrskip(Length const & crskip, row_type row)
288 {
289         rowinfo_[row].crskip_ = crskip;
290 }
291
292
293 Length InsetMathGrid::vcrskip(row_type row) const
294 {
295         return rowinfo_[row].crskip_;
296 }
297
298
299 void InsetMathGrid::metrics(MetricsInfo & mi) const
300 {
301         // let the cells adjust themselves
302         InsetMathNest::metrics(mi);
303
304         // compute absolute sizes of vertical structure
305         for (row_type row = 0; row < nrows(); ++row) {
306                 int asc  = 0;
307                 int desc = 0;
308                 for (col_type col = 0; col < ncols(); ++col) {
309                         MathData const & c = cell(index(row, col));
310                         asc  = max(asc,  c.ascent());
311                         desc = max(desc, c.descent());
312                 }
313                 rowinfo_[row].ascent_  = asc;
314                 rowinfo_[row].descent_ = desc;
315         }
316         rowinfo_[0].ascent_       += hlinesep() * rowinfo_[0].lines_;
317         rowinfo_[nrows()].ascent_  = 0;
318         rowinfo_[nrows()].descent_ = 0;
319
320         // compute vertical offsets
321         rowinfo_[0].offset_ = 0;
322         for (row_type row = 1; row <= nrows(); ++row) {
323                 rowinfo_[row].offset_  =
324                         rowinfo_[row - 1].offset_  +
325                         rowinfo_[row - 1].descent_ +
326                         rowinfo_[row - 1].skipPixels() +
327                         rowsep() +
328                         rowinfo_[row].lines_ * hlinesep() +
329                         rowinfo_[row].ascent_;
330         }
331
332         // adjust vertical offset
333         int h = 0;
334         switch (v_align_) {
335                 case 't':
336                         h = 0;
337                         break;
338                 case 'b':
339                         h = rowinfo_[nrows() - 1].offset_;
340                         break;
341                 default:
342                         h = rowinfo_[nrows() - 1].offset_ / 2;
343         }
344         for (row_type row = 0; row <= nrows(); ++row)
345                 rowinfo_[row].offset_ -= h;
346
347
348         // compute absolute sizes of horizontal structure
349         for (col_type col = 0; col < ncols(); ++col) {
350                 int wid = 0;
351                 for (row_type row = 0; row < nrows(); ++row)
352                         wid = max(wid, cell(index(row, col)).width());
353                 colinfo_[col].width_ = wid;
354         }
355         colinfo_[ncols()].width_  = 0;
356
357         // compute horizontal offsets
358         colinfo_[0].offset_ = border();
359         for (col_type col = 1; col <= ncols(); ++col) {
360                 colinfo_[col].offset_ =
361                         colinfo_[col - 1].offset_ +
362                         colinfo_[col - 1].width_ +
363                         colinfo_[col - 1].skip_ +
364                         colsep() +
365                         colinfo_[col].lines_ * vlinesep();
366         }
367
368
369         dim_.wid   =   colinfo_[ncols() - 1].offset_
370                        + colinfo_[ncols() - 1].width_
371                  + vlinesep() * colinfo_[ncols()].lines_
372                        + border();
373
374         dim_.asc  = - rowinfo_[0].offset_
375                        + rowinfo_[0].ascent_
376                  + hlinesep() * rowinfo_[0].lines_
377                        + border();
378
379         dim_.des =   rowinfo_[nrows() - 1].offset_
380                        + rowinfo_[nrows() - 1].descent_
381                  + hlinesep() * rowinfo_[nrows()].lines_
382                        + border();
383
384
385 /*
386         // Increase ws_[i] for 'R' columns (except the first one)
387         for (int i = 1; i < nc_; ++i)
388                 if (align_[i] == 'R')
389                         ws_[i] += 10 * df_width;
390         // Increase ws_[i] for 'C' column
391         if (align_[0] == 'C')
392                 if (ws_[0] < 7 * workwidth / 8)
393                         ws_[0] = 7 * workwidth / 8;
394
395         // Adjust local tabs
396         width = colsep();
397         for (cxrow = row_.begin(); cxrow; ++cxrow) {
398                 int rg = COLSEP;
399                 int lf = 0;
400                 for (int i = 0; i < nc_; ++i) {
401                         bool isvoid = false;
402                         if (cxrow->getTab(i) <= 0) {
403                                 cxrow->setTab(i, df_width);
404                                 isvoid = true;
405                         }
406                         switch (align_[i]) {
407                         case 'l':
408                                 lf = 0;
409                                 break;
410                         case 'c':
411                                 lf = (ws_[i] - cxrow->getTab(i))/2;
412                                 break;
413                         case 'r':
414                         case 'R':
415                                 lf = ws_[i] - cxrow->getTab(i);
416                                 break;
417                         case 'C':
418                                 if (cxrow == row_.begin())
419                                         lf = 0;
420                                 else if (cxrow.is_last())
421                                         lf = ws_[i] - cxrow->getTab(i);
422                                 else
423                                         lf = (ws_[i] - cxrow->getTab(i))/2;
424                                 break;
425                         }
426                         int const ww = (isvoid) ? lf : lf + cxrow->getTab(i);
427                         cxrow->setTab(i, lf + rg);
428                         rg = ws_[i] - ww + colsep();
429                         if (cxrow == row_.begin())
430                                 width += ws_[i] + colsep();
431                 }
432                 cxrow->setBaseline(cxrow->getBaseline() - ascent);
433         }
434 */
435         metricsMarkers2(dim_);
436 }
437
438
439 bool InsetMathGrid::metrics(MetricsInfo & mi, Dimension & dim) const
440 {
441         dim = dim_;
442         metrics(mi);
443         if (dim_ == dim)
444                 return false;
445         dim = dim_;
446         return true;
447 }
448
449
450 void InsetMathGrid::draw(PainterInfo & pi, int x, int y) const
451 {
452         drawWithMargin(pi, x, y, 0, 0);
453 }
454
455 void InsetMathGrid::drawWithMargin(PainterInfo & pi, int x, int y,
456         int lmargin, int rmargin) const
457 {
458         for (idx_type idx = 0; idx < nargs(); ++idx)
459                 cell(idx).draw(pi, x + lmargin + cellXOffset(idx),
460                         y + cellYOffset(idx));
461
462         for (row_type row = 0; row <= nrows(); ++row)
463                 for (unsigned int i = 0; i < rowinfo_[row].lines_; ++i) {
464                         int yy = y + rowinfo_[row].offset_ - rowinfo_[row].ascent_
465                                 - i * hlinesep() - hlinesep()/2 - rowsep()/2;
466                         pi.pain.line(x + lmargin + 1, yy,
467                                      x + dim_.width() - rmargin - 1, yy,
468                                      Color::foreground);
469                 }
470
471         for (col_type col = 0; col <= ncols(); ++col)
472                 for (unsigned int i = 0; i < colinfo_[col].lines_; ++i) {
473                         int xx = x + lmargin + colinfo_[col].offset_
474                                 - i * vlinesep() - vlinesep()/2 - colsep()/2;
475                         pi.pain.line(xx, y - dim_.ascent() + 1,
476                                      xx, y + dim_.descent() - 1,
477                                      Color::foreground);
478                 }
479         drawMarkers2(pi, x, y);
480 }
481
482
483 void InsetMathGrid::metricsT(TextMetricsInfo const & mi, Dimension & dim) const
484 {
485         // let the cells adjust themselves
486         //InsetMathNest::metrics(mi);
487         for (idx_type i = 0; i < nargs(); ++i)
488                 cell(i).metricsT(mi, dim);
489
490         // compute absolute sizes of vertical structure
491         for (row_type row = 0; row < nrows(); ++row) {
492                 int asc  = 0;
493                 int desc = 0;
494                 for (col_type col = 0; col < ncols(); ++col) {
495                         MathData const & c = cell(index(row, col));
496                         asc  = max(asc,  c.ascent());
497                         desc = max(desc, c.descent());
498                 }
499                 rowinfo_[row].ascent_  = asc;
500                 rowinfo_[row].descent_ = desc;
501         }
502         //rowinfo_[0].ascent_       += hlinesep() * rowinfo_[0].lines_;
503         rowinfo_[nrows()].ascent_  = 0;
504         rowinfo_[nrows()].descent_ = 0;
505
506         // compute vertical offsets
507         rowinfo_[0].offset_ = 0;
508         for (row_type row = 1; row <= nrows(); ++row) {
509                 rowinfo_[row].offset_  =
510                         rowinfo_[row - 1].offset_  +
511                         rowinfo_[row - 1].descent_ +
512                         //rowinfo_[row - 1].skipPixels() +
513                         1 + //rowsep() +
514                         //rowinfo_[row].lines_ * hlinesep() +
515                         rowinfo_[row].ascent_;
516         }
517
518         // adjust vertical offset
519         int h = 0;
520         switch (v_align_) {
521                 case 't':
522                         h = 0;
523                         break;
524                 case 'b':
525                         h = rowinfo_[nrows() - 1].offset_;
526                         break;
527                 default:
528                         h = rowinfo_[nrows() - 1].offset_ / 2;
529         }
530         for (row_type row = 0; row <= nrows(); ++row)
531                 rowinfo_[row].offset_ -= h;
532
533
534         // compute absolute sizes of horizontal structure
535         for (col_type col = 0; col < ncols(); ++col) {
536                 int wid = 0;
537                 for (row_type row = 0; row < nrows(); ++row)
538                         wid = max(wid, cell(index(row, col)).width());
539                 colinfo_[col].width_ = wid;
540         }
541         colinfo_[ncols()].width_  = 0;
542
543         // compute horizontal offsets
544         colinfo_[0].offset_ = border();
545         for (col_type col = 1; col <= ncols(); ++col) {
546                 colinfo_[col].offset_ =
547                         colinfo_[col - 1].offset_ +
548                         colinfo_[col - 1].width_ +
549                         colinfo_[col - 1].skip_ +
550                         1 ; //colsep() +
551                         //colinfo_[col].lines_ * vlinesep();
552         }
553
554
555         dim.wid  =  colinfo_[ncols() - 1].offset_
556                        + colinfo_[ncols() - 1].width_
557                  //+ vlinesep() * colinfo_[ncols()].lines_
558                        + 2;
559
560         dim.asc  = -rowinfo_[0].offset_
561                        + rowinfo_[0].ascent_
562                  //+ hlinesep() * rowinfo_[0].lines_
563                        + 1;
564
565         dim.des  =  rowinfo_[nrows() - 1].offset_
566                        + rowinfo_[nrows() - 1].descent_
567                  //+ hlinesep() * rowinfo_[nrows()].lines_
568                        + 1;
569 }
570
571
572 void InsetMathGrid::drawT(TextPainter & pain, int x, int y) const
573 {
574         for (idx_type idx = 0; idx < nargs(); ++idx)
575                 cell(idx).drawT(pain, x + cellXOffset(idx), y + cellYOffset(idx));
576 }
577
578
579 docstring InsetMathGrid::eolString(row_type row, bool emptyline, bool fragile) const
580 {
581         docstring eol;
582
583         if (!rowinfo_[row].crskip_.zero())
584                 eol += '[' + from_utf8(rowinfo_[row].crskip_.asLatexString()) + ']';
585         else if(!rowinfo_[row].allow_pagebreak_)
586                 eol += '*';
587
588         // make sure an upcoming '[' does not break anything
589         if (row + 1 < nrows()) {
590                 MathData const & c = cell(index(row + 1, 0));
591                 if (c.size() && c.front()->getChar() == '[')
592                         //eol += "[0pt]";
593                         eol += "{}";
594         }
595
596         // only add \\ if necessary
597         if (eol.empty() && row + 1 == nrows() && (nrows() == 1 || !emptyline))
598                 return docstring();
599
600         return (fragile ? "\\protect\\\\" : "\\\\") + eol;
601 }
602
603
604 docstring InsetMathGrid::eocString(col_type col, col_type lastcol) const
605 {
606         if (col + 1 == lastcol)
607                 return docstring();
608         return from_ascii(" & ");
609 }
610
611
612 void InsetMathGrid::addRow(row_type row)
613 {
614         rowinfo_.insert(rowinfo_.begin() + row + 1, RowInfo());
615         cells_.insert
616                 (cells_.begin() + (row + 1) * ncols(), ncols(), MathData());
617         cellinfo_.insert
618                 (cellinfo_.begin() + (row + 1) * ncols(), ncols(), CellInfo());
619 }
620
621
622 void InsetMathGrid::appendRow()
623 {
624         rowinfo_.push_back(RowInfo());
625         //cells_.insert(cells_.end(), ncols(), MathData());
626         for (col_type col = 0; col < ncols(); ++col) {
627                 cells_.push_back(cells_type::value_type());
628                 cellinfo_.push_back(CellInfo());
629         }
630 }
631
632
633 void InsetMathGrid::delRow(row_type row)
634 {
635         if (nrows() == 1)
636                 return;
637
638         cells_type::iterator it = cells_.begin() + row * ncols();
639         cells_.erase(it, it + ncols());
640
641         vector<CellInfo>::iterator jt = cellinfo_.begin() + row * ncols();
642         cellinfo_.erase(jt, jt + ncols());
643
644         rowinfo_.erase(rowinfo_.begin() + row);
645 }
646
647
648 void InsetMathGrid::copyRow(row_type row)
649 {
650         addRow(row);
651         for (col_type col = 0; col < ncols(); ++col)
652                 cells_[(row + 1) * ncols() + col] = cells_[row * ncols() + col];
653 }
654
655
656 void InsetMathGrid::swapRow(row_type row)
657 {
658         if (nrows() == 1)
659                 return;
660         if (row + 1 == nrows())
661                 --row;
662         for (col_type col = 0; col < ncols(); ++col)
663                 swap(cells_[row * ncols() + col], cells_[(row + 1) * ncols() + col]);
664 }
665
666
667 void InsetMathGrid::addCol(col_type newcol)
668 {
669         const col_type nc = ncols();
670         const row_type nr = nrows();
671         cells_type new_cells((nc + 1) * nr);
672         vector<CellInfo> new_cellinfo((nc + 1) * nr);
673
674         for (row_type row = 0; row < nr; ++row)
675                 for (col_type col = 0; col < nc; ++col) {
676                         new_cells[row * (nc + 1) + col + (col > newcol)]
677                                 = cells_[row * nc + col];
678                         new_cellinfo[row * (nc + 1) + col + (col > newcol)]
679                                 = cellinfo_[row * nc + col];
680                 }
681         swap(cells_, new_cells);
682         swap(cellinfo_, new_cellinfo);
683
684         ColInfo inf;
685         inf.skip_  = defaultColSpace(newcol);
686         inf.align_ = defaultColAlign(newcol);
687         colinfo_.insert(colinfo_.begin() + newcol, inf);
688 }
689
690
691 void InsetMathGrid::delCol(col_type col)
692 {
693         if (ncols() == 1)
694                 return;
695
696         cells_type tmpcells;
697         vector<CellInfo> tmpcellinfo;
698         for (col_type i = 0; i < nargs(); ++i)
699                 if (i % ncols() != col) {
700                         tmpcells.push_back(cells_[i]);
701                         tmpcellinfo.push_back(cellinfo_[i]);
702                 }
703         swap(cells_, tmpcells);
704         swap(cellinfo_, tmpcellinfo);
705
706         colinfo_.erase(colinfo_.begin() + col);
707 }
708
709
710 void InsetMathGrid::copyCol(col_type col)
711 {
712         addCol(col);
713         for (row_type row = 0; row < nrows(); ++row)
714                 cells_[row * ncols() + col + 1] = cells_[row * ncols() + col];
715 }
716
717
718 void InsetMathGrid::swapCol(col_type col)
719 {
720         if (ncols() == 1)
721                 return;
722         if (col + 1 == ncols())
723                 --col;
724         for (row_type row = 0; row < nrows(); ++row)
725                 swap(cells_[row * ncols() + col], cells_[row * ncols() + col + 1]);
726 }
727
728
729 int InsetMathGrid::cellXOffset(idx_type idx) const
730 {
731         col_type c = col(idx);
732         int x = colinfo_[c].offset_;
733         char align = colinfo_[c].align_;
734         if (align == 'r' || align == 'R')
735                 x += colinfo_[c].width_ - cell(idx).width();
736         if (align == 'c' || align == 'C')
737                 x += (colinfo_[c].width_ - cell(idx).width()) / 2;
738         return x;
739 }
740
741
742 int InsetMathGrid::cellYOffset(idx_type idx) const
743 {
744         return rowinfo_[row(idx)].offset_;
745 }
746
747
748 bool InsetMathGrid::idxUpDown(Cursor & cur, bool up) const
749 {
750         if (up) {
751                 if (cur.row() == 0)
752                         return false;
753                 cur.idx() -= ncols();
754         } else {
755                 if (cur.row() + 1 >= nrows())
756                         return false;
757                 cur.idx() += ncols();
758         }
759         cur.pos() = cur.cell().x2pos(cur.x_target() - cur.cell().xo(cur.bv()));
760         return true;
761 }
762
763
764 bool InsetMathGrid::idxLeft(Cursor & cur) const
765 {
766         // leave matrix if on the left hand edge
767         if (cur.col() == 0)
768                 return false;
769         --cur.idx();
770         cur.pos() = cur.lastpos();
771         return true;
772 }
773
774
775 bool InsetMathGrid::idxRight(Cursor & cur) const
776 {
777         // leave matrix if on the right hand edge
778         if (cur.col() + 1 == ncols())
779                 return false;
780         ++cur.idx();
781         cur.pos() = 0;
782         return true;
783 }
784
785
786 bool InsetMathGrid::idxFirst(Cursor & cur) const
787 {
788         switch (v_align_) {
789                 case 't':
790                         cur.idx() = 0;
791                         break;
792                 case 'b':
793                         cur.idx() = (nrows() - 1) * ncols();
794                         break;
795                 default:
796                         cur.idx() = ((nrows() - 1) / 2) * ncols();
797         }
798         cur.pos() = 0;
799         return true;
800 }
801
802
803 bool InsetMathGrid::idxLast(Cursor & cur) const
804 {
805         switch (v_align_) {
806                 case 't':
807                         cur.idx() = ncols() - 1;
808                         break;
809                 case 'b':
810                         cur.idx() = nargs() - 1;
811                         break;
812                 default:
813                         cur.idx() = ((nrows() - 1) / 2 + 1) * ncols() - 1;
814         }
815         cur.pos() = cur.lastpos();
816         return true;
817 }
818
819
820 bool InsetMathGrid::idxDelete(idx_type & idx)
821 {
822         // nothing to do if we have just one row
823         if (nrows() == 1)
824                 return false;
825
826         // nothing to do if we are in the middle of the last row of the inset
827         if (idx + ncols() > nargs())
828                 return false;
829
830         // try to delete entire sequence of ncols() empty cells if possible
831         for (idx_type i = idx; i < idx + ncols(); ++i)
832                 if (cell(i).size())
833                         return false;
834
835         // move cells if necessary
836         for (idx_type i = index(row(idx), 0); i < idx; ++i)
837                 swap(cell(i), cell(i + ncols()));
838
839         delRow(row(idx));
840
841         if (idx >= nargs())
842                 idx = nargs() - 1;
843
844         // undo effect of Ctrl-Tab (i.e. pull next cell)
845         //if (idx + 1 != nargs())
846         //      cell(idx).swap(cell(idx + 1));
847
848         // we handled the event..
849         return true;
850 }
851
852
853 // reimplement old behaviour when pressing Delete in the last position
854 // of a cell
855 void InsetMathGrid::idxGlue(idx_type idx)
856 {
857         col_type c = col(idx);
858         if (c + 1 == ncols()) {
859                 if (row(idx) + 1 != nrows()) {
860                         for (col_type cc = 0; cc < ncols(); ++cc)
861                                 cell(idx).append(cell(idx + cc + 1));
862                         delRow(row(idx) + 1);
863                 }
864         } else {
865                 cell(idx).append(cell(idx + 1));
866                 for (col_type cc = c + 2; cc < ncols(); ++cc)
867                         cell(idx - c + cc - 1) = cell(idx - c + cc);
868                 cell(idx - c + ncols() - 1).clear();
869         }
870 }
871
872
873 InsetMathGrid::RowInfo const & InsetMathGrid::rowinfo(row_type row) const
874 {
875         return rowinfo_[row];
876 }
877
878
879 InsetMathGrid::RowInfo & InsetMathGrid::rowinfo(row_type row)
880 {
881         return rowinfo_[row];
882 }
883
884
885 bool InsetMathGrid::idxBetween(idx_type idx, idx_type from, idx_type to) const
886 {
887         row_type const ri = row(idx);
888         row_type const r1 = min(row(from), row(to));
889         row_type const r2 = max(row(from), row(to));
890         col_type const ci = col(idx);
891         col_type const c1 = min(col(from), col(to));
892         col_type const c2 = max(col(from), col(to));
893         return r1 <= ri && ri <= r2 && c1 <= ci && ci <= c2;
894 }
895
896
897
898 void InsetMathGrid::normalize(NormalStream & os) const
899 {
900         os << "[grid ";
901         for (row_type row = 0; row < nrows(); ++row) {
902                 os << "[row ";
903                 for (col_type col = 0; col < ncols(); ++col)
904                         os << "[cell " << cell(index(row, col)) << ']';
905                 os << ']';
906         }
907         os << ']';
908 }
909
910
911 void InsetMathGrid::mathmlize(MathStream & os) const
912 {
913         os << MTag("mtable");
914         for (row_type row = 0; row < nrows(); ++row) {
915                 os << MTag("mtr");
916                 for (col_type col = 0; col < ncols(); ++col)
917                         os << cell(index(row, col));
918                 os << ETag("mtr");
919         }
920         os << ETag("mtable");
921 }
922
923
924 void InsetMathGrid::write(WriteStream & os) const
925 {
926         docstring eol;
927         for (row_type row = 0; row < nrows(); ++row) {
928                 os << verboseHLine(rowinfo_[row].lines_);
929                 // don't write & and empty cells at end of line
930                 col_type lastcol = 0;
931                 bool emptyline = true;
932                 for (col_type col = 0; col < ncols(); ++col)
933                         if (!cell(index(row, col)).empty()) {
934                                 lastcol = col + 1;
935                                 emptyline = false;
936                         }
937                 for (col_type col = 0; col < lastcol; ++col)
938                         os << cell(index(row, col)) << eocString(col, lastcol);
939                 eol = eolString(row, emptyline, os.fragile());
940                 os << eol;
941                 // append newline only if line wasn't completely empty
942                 // and this was not the last line in the grid
943                 if (!emptyline && row + 1 < nrows())
944                         os << "\n";
945         }
946         docstring const s = verboseHLine(rowinfo_[nrows()].lines_);
947         if (!s.empty()) {
948                 if (eol.empty()) {
949                         if (os.fragile())
950                                 os << "\\protect";
951                         os << "\\\\";
952                 }
953                 os << s;
954         }
955 }
956
957
958 int InsetMathGrid::colsep() const
959 {
960         return 6;
961 }
962
963
964 int InsetMathGrid::rowsep() const
965 {
966         return 6;
967 }
968
969
970 int InsetMathGrid::hlinesep() const
971 {
972         return 3;
973 }
974
975
976 int InsetMathGrid::vlinesep() const
977 {
978         return 3;
979 }
980
981
982 int InsetMathGrid::border() const
983 {
984         return 1;
985 }
986
987
988 void InsetMathGrid::splitCell(Cursor & cur)
989 {
990         if (cur.idx() == cur.lastidx())
991                 return;
992         MathData ar = cur.cell();
993         ar.erase(0, cur.pos());
994         cur.cell().erase(cur.pos(), cur.lastpos());
995         ++cur.idx();
996         cur.pos() = 0;
997         cur.cell().insert(0, ar);
998 }
999
1000
1001 void InsetMathGrid::doDispatch(Cursor & cur, FuncRequest & cmd)
1002 {
1003         //lyxerr << "*** InsetMathGrid: request: " << cmd << endl;
1004         switch (cmd.action) {
1005
1006         // insert file functions
1007         case LFUN_LINE_DELETE:
1008                 // FIXME: We use recordUndoInset when a change reflects more
1009                 // than one cell, because recordUndo does not work for
1010                 // multiple cells. Unfortunately this puts the cursor in front
1011                 // of the inset after undo. This is (especilally for large
1012                 // grids) annoying.
1013                 recordUndoInset(cur);
1014                 //autocorrect_ = false;
1015                 //macroModeClose();
1016                 //if (selection_) {
1017                 //      selDel();
1018                 //      break;
1019                 //}
1020                 if (nrows() > 1)
1021                         delRow(cur.row());
1022                 if (cur.idx() > cur.lastidx())
1023                         cur.idx() = cur.lastidx();
1024                 if (cur.pos() > cur.lastpos())
1025                         cur.pos() = cur.lastpos();
1026                 break;
1027
1028         case LFUN_CELL_SPLIT:
1029                 recordUndo(cur);
1030                 splitCell(cur);
1031                 break;
1032
1033         case LFUN_CELL_BACKWARD:
1034                 // See below.
1035                 cur.selection() = false;
1036                 if (!idxPrev(cur)) {
1037                         cmd = FuncRequest(LFUN_FINISHED_LEFT);
1038                         cur.undispatched();
1039                 }
1040                 break;
1041
1042         case LFUN_CELL_FORWARD:
1043                 // Can't handle selection by additional 'shift' as this is
1044                 // hard bound to LFUN_CELL_BACKWARD
1045                 cur.selection() = false;
1046                 if (!idxNext(cur)) {
1047                         cmd = FuncRequest(LFUN_FINISHED_RIGHT);
1048                         cur.undispatched();
1049                 }
1050                 break;
1051
1052         case LFUN_BREAK_LINE: {
1053                 recordUndoInset(cur);
1054                 row_type const r = cur.row();
1055                 addRow(r);
1056
1057                 // split line
1058                 for (col_type c = col(cur.idx()) + 1; c < ncols(); ++c)
1059                         swap(cell(index(r, c)), cell(index(r + 1, c)));
1060
1061                 // split cell
1062                 splitCell(cur);
1063                 swap(cell(cur.idx()), cell(cur.idx() + ncols() - 1));
1064                 if (cur.idx() > 0)
1065                         --cur.idx();
1066                 cur.pos() = cur.lastpos();
1067
1068                 //mathcursor->normalize();
1069                 //cmd = FuncRequest(LFUN_FINISHED_LEFT);
1070                 break;
1071         }
1072
1073         case LFUN_TABULAR_FEATURE: {
1074                 recordUndoInset(cur);
1075                 //lyxerr << "handling tabular-feature " << to_utf8(cmd.argument()) << endl;
1076                 istringstream is(to_utf8(cmd.argument()));
1077                 string s;
1078                 is >> s;
1079                 if (s == "valign-top")
1080                         valign('t');
1081                 else if (s == "valign-middle")
1082                         valign('c');
1083                 else if (s == "valign-bottom")
1084                         valign('b');
1085                 else if (s == "align-left")
1086                         halign('l', cur.col());
1087                 else if (s == "align-right")
1088                         halign('r', cur.col());
1089                 else if (s == "align-center")
1090                         halign('c', cur.col());
1091                 else if (s == "append-row")
1092                         for (int i = 0, n = extractInt(is); i < n; ++i)
1093                                 addRow(cur.row());
1094                 else if (s == "delete-row") {
1095                         for (int i = 0, n = extractInt(is); i < n; ++i) {
1096                                 delRow(cur.row());
1097                                 if (cur.idx() >= nargs())
1098                                         cur.idx() -= ncols();
1099                         }
1100                         cur.pos() = 0; // trick, see below
1101                 }
1102                 else if (s == "copy-row") {
1103                         // Here (as later) we save the cursor col/row
1104                         // in order to restore it after operation.
1105                         row_type const r = cur.row();
1106                         col_type const c = cur.col();
1107                         for (int i = 0, n = extractInt(is); i < n; ++i)
1108                                 copyRow(cur.row());
1109                         cur.idx() = index(r, c);
1110                 }
1111                 else if (s == "swap-row") {
1112                         swapRow(cur.row());
1113                         // Trick to suppress same-idx-means-different-cell
1114                         // assertion crash:
1115                         cur.pos() = 0;
1116                 }
1117                 else if (s == "add-hline-above")
1118                         rowinfo_[cur.row()].lines_++;
1119                 else if (s == "add-hline-below")
1120                         rowinfo_[cur.row()+1].lines_++;
1121                 else if (s == "delete-hline-above")
1122                         rowinfo_[cur.row()].lines_--;
1123                 else if (s == "delete-hline-below")
1124                         rowinfo_[cur.row()+1].lines_--;
1125                 else if (s == "append-column") {
1126                         row_type const r = cur.row();
1127                         col_type const c = cur.col();
1128                         for (int i = 0, n = extractInt(is); i < n; ++i)
1129                                 addCol(cur.col());
1130                         cur.idx() = index(r, c);
1131                 }
1132                 else if (s == "delete-column") {
1133                         row_type const r = cur.row();
1134                         col_type const c = cur.col();
1135                         for (int i = 0, n = extractInt(is); i < n; ++i)
1136                                 delCol(col(cur.idx()));
1137                         cur.idx() = index(r, min(c, cur.ncols() - 1));
1138                         cur.pos() = 0; // trick, see above
1139                 }
1140                 else if (s == "copy-column") {
1141                         row_type const r = cur.row();
1142                         col_type const c = cur.col();
1143                         copyCol(cur.col());
1144                         cur.idx() = index(r, c);
1145                 }
1146                 else if (s == "swap-column") {
1147                         swapCol(cur.col());
1148                         cur.pos() = 0; // trick, see above
1149                 }
1150                 else if (s == "add-vline-left")
1151                         colinfo_[cur.col()].lines_++;
1152                 else if (s == "add-vline-right")
1153                         colinfo_[cur.col()+1].lines_++;
1154                 else if (s == "delete-vline-left")
1155                         colinfo_[cur.col()].lines_--;
1156                 else if (s == "delete-vline-right")
1157                         colinfo_[cur.col()+1].lines_--;
1158                 else {
1159                         cur.undispatched();
1160                         break;
1161                 }
1162                 lyxerr << "returning FINISHED_LEFT" << endl;
1163                 break;
1164         }
1165
1166         case LFUN_PASTE: {
1167                 cur.message(_("Paste"));
1168                 cap::replaceSelection(cur);
1169                 docstring topaste;
1170                 if (cmd.argument().empty() && !theClipboard().isInternal())
1171                         topaste = theClipboard().getAsText();
1172                 else {
1173                         idocstringstream is(cmd.argument());
1174                         int n = 0;
1175                         is >> n;
1176                         topaste = cap::getSelection(cur.buffer(), n);
1177                 }
1178                 InsetMathGrid grid(1, 1);
1179                 if (!topaste.empty())
1180                         mathed_parse_normal(grid, topaste);
1181
1182                 if (grid.nargs() == 1) {
1183                         // single cell/part of cell
1184                         recordUndo(cur);
1185                         cur.cell().insert(cur.pos(), grid.cell(0));
1186                         cur.pos() += grid.cell(0).size();
1187                 } else {
1188                         // multiple cells
1189                         recordUndoInset(cur);
1190                         col_type const numcols =
1191                                 min(grid.ncols(), ncols() - col(cur.idx()));
1192                         row_type const numrows =
1193                                 min(grid.nrows(), nrows() - cur.row());
1194                         for (row_type r = 0; r < numrows; ++r) {
1195                                 for (col_type c = 0; c < numcols; ++c) {
1196                                         idx_type i = index(r + cur.row(), c + col(cur.idx()));
1197                                         cell(i).insert(0, grid.cell(grid.index(r, c)));
1198                                 }
1199                                 // append the left over horizontal cells to the last column
1200                                 idx_type i = index(r + cur.row(), ncols() - 1);
1201                                 for (InsetMath::col_type c = numcols; c < grid.ncols(); ++c)
1202                                         cell(i).append(grid.cell(grid.index(r, c)));
1203                         }
1204                         // append the left over vertical cells to the last _cell_
1205                         idx_type i = nargs() - 1;
1206                         for (row_type r = numrows; r < grid.nrows(); ++r)
1207                                 for (col_type c = 0; c < grid.ncols(); ++c)
1208                                         cell(i).append(grid.cell(grid.index(r, c)));
1209                 }
1210                 cur.clearSelection(); // bug 393
1211                 cur.bv().switchKeyMap();
1212                 finishUndo();
1213                 break;
1214         }
1215
1216         case LFUN_LINE_BEGIN_SELECT:
1217         case LFUN_LINE_BEGIN:
1218         case LFUN_WORD_BACKWARD_SELECT:
1219         case LFUN_WORD_BACKWARD:
1220                 cur.selHandle(cmd.action == LFUN_WORD_BACKWARD_SELECT ||
1221                                 cmd.action == LFUN_LINE_BEGIN_SELECT);
1222                 cur.macroModeClose();
1223                 if (cur.pos() != 0) {
1224                         cur.pos() = 0;
1225                 } else if (cur.idx() % cur.ncols() != 0) {
1226                         cur.idx() -= cur.idx() % cur.ncols();
1227                         cur.pos() = 0;
1228                 } else if (cur.idx() != 0) {
1229                         cur.idx() = 0;
1230                         cur.pos() = 0;
1231                 } else {
1232                         cmd = FuncRequest(LFUN_FINISHED_LEFT);
1233                         cur.undispatched();
1234                 }
1235                 break;
1236
1237         case LFUN_WORD_FORWARD_SELECT:
1238         case LFUN_WORD_FORWARD:
1239         case LFUN_LINE_END_SELECT:
1240         case LFUN_LINE_END:
1241                 cur.selHandle(cmd.action == LFUN_WORD_FORWARD_SELECT ||
1242                                 cmd.action == LFUN_LINE_END_SELECT);
1243                 cur.macroModeClose();
1244                 cur.clearTargetX();
1245                 if (cur.pos() != cur.lastpos()) {
1246                         cur.pos() = cur.lastpos();
1247                 } else if ((cur.idx() + 1) % cur.ncols() != 0) {
1248                         cur.idx() += cur.ncols() - 1 - cur.idx() % cur.ncols();
1249                         cur.pos() = cur.lastpos();
1250                 } else if (cur.idx() != cur.lastidx()) {
1251                         cur.idx() = cur.lastidx();
1252                         cur.pos() = cur.lastpos();
1253                 } else {
1254                         cmd = FuncRequest(LFUN_FINISHED_RIGHT);
1255                         cur.undispatched();
1256                 }
1257                 break;
1258
1259         default:
1260                 InsetMathNest::doDispatch(cur, cmd);
1261         }
1262 }
1263
1264
1265 bool InsetMathGrid::getStatus(Cursor & cur, FuncRequest const & cmd,
1266                 FuncStatus & status) const
1267 {
1268         switch (cmd.action) {
1269         case LFUN_TABULAR_FEATURE: {
1270                 string const s = to_utf8(cmd.argument());
1271                 if (nrows() <= 1 && (s == "delete-row" || s == "swap-row")) {
1272                         status.enabled(false);
1273                         status.message(from_utf8(N_("Only one row")));
1274                         return true;
1275                 }
1276                 if (ncols() <= 1 &&
1277                     (s == "delete-column" || s == "swap-column")) {
1278                         status.enabled(false);
1279                         status.message(from_utf8(N_("Only one column")));
1280                         return true;
1281                 }
1282                 if ((rowinfo_[cur.row()].lines_ == 0 &&
1283                      s == "delete-hline-above") ||
1284                     (rowinfo_[cur.row() + 1].lines_ == 0 &&
1285                      s == "delete-hline-below")) {
1286                         status.enabled(false);
1287                         status.message(from_utf8(N_("No hline to delete")));
1288                         return true;
1289                 }
1290
1291                 if ((colinfo_[cur.col()].lines_ == 0 &&
1292                      s == "delete-vline-left") ||
1293                     (colinfo_[cur.col() + 1].lines_ == 0 &&
1294                      s == "delete-vline-right")) {
1295                         status.enabled(false);
1296                         status.message(from_utf8(N_("No vline to delete")));
1297                         return true;
1298                 }
1299                 if (s == "valign-top" || s == "valign-middle" ||
1300                     s == "valign-bottom" || s == "align-left" ||
1301                     s == "align-right" || s == "align-center" ||
1302                     s == "append-row" || s == "delete-row" ||
1303                     s == "copy-row" || s == "swap-row" ||
1304                     s == "add-hline-above" || s == "add-hline-below" ||
1305                     s == "delete-hline-above" || s == "delete-hline-below" ||
1306                     s == "append-column" || s == "delete-column" ||
1307                     s == "copy-column" || s == "swap-column" ||
1308                     s == "add-vline-left" || s == "add-vline-right" ||
1309                     s == "delete-vline-left" || s == "delete-vline-right")
1310                         status.enabled(true);
1311                 else {
1312                         status.enabled(false);
1313                         status.message(bformat(
1314                                 from_utf8(N_("Unknown tabular feature '%1$s'")), lyx::from_ascii(s)));
1315                 }
1316
1317                 status.setOnOff(s == "align-left"    && halign(cur.col()) == 'l'
1318                            || s == "align-right"   && halign(cur.col()) == 'r'
1319                            || s == "align-center"  && halign(cur.col()) == 'c'
1320                            || s == "valign-top"    && valign() == 't'
1321                            || s == "valign-bottom" && valign() == 'b'
1322                            || s == "valign-middle" && valign() == 'm');
1323
1324 #if 0
1325                 // FIXME: What did this code do?
1326                 // Please check whether it is still needed!
1327                 // should be more precise
1328                 if (v_align_ == '\0') {
1329                         status.enable(true);
1330                         break;
1331                 }
1332                 if (cmd.argument().empty()) {
1333                         status.enable(false);
1334                         break;
1335                 }
1336                 if (!support::contains("tcb", cmd.argument()[0])) {
1337                         status.enable(false);
1338                         break;
1339                 }
1340                 status.setOnOff(cmd.argument()[0] == v_align_);
1341                 status.enabled(true);
1342 #endif
1343                 return true;
1344         }
1345
1346         case LFUN_CELL_SPLIT:
1347                 status.enabled(true);
1348                 return true;
1349
1350         case LFUN_CELL_BACKWARD:
1351         case LFUN_CELL_FORWARD:
1352                 status.enabled(true);
1353                 return true;
1354
1355         default:
1356                 return InsetMathNest::getStatus(cur, cmd, status);
1357         }
1358 }
1359
1360
1361 } // namespace lyx