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