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