2 * \file InsetMathGrid.cpp
3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
8 * Full author contact details are available in file CREDITS.
14 #include "InsetMathGrid.h"
17 #include "MathParser.h"
18 #include "MathStream.h"
19 #include "MetricsInfo.h"
21 #include "BufferView.h"
22 #include "CutAndPaste.h"
23 #include "FuncStatus.h"
25 #include "FuncRequest.h"
27 #include "frontends/Clipboard.h"
28 #include "frontends/FontMetrics.h"
29 #include "frontends/Painter.h"
31 #include "support/debug.h"
32 #include "support/docstream.h"
33 #include "support/gettext.h"
34 #include "support/lstrings.h"
36 #include "support/lassert.h"
41 using namespace lyx::support
;
46 static docstring
verboseHLine(int n
)
49 for (int i
= 0; i
< n
; ++i
)
57 static int extractInt(istream
& is
)
61 return (num
== 0) ? 1 : num
;
65 static void resetGrid(InsetMathGrid
& grid
)
67 while (grid
.ncols() > 1)
68 grid
.delCol(grid
.ncols());
69 while (grid
.nrows() > 1)
70 grid
.delRow(grid
.nrows());
71 grid
.cell(0).erase(0, grid
.cell(0).size());
77 //////////////////////////////////////////////////////////////
80 InsetMathGrid::CellInfo::CellInfo()
86 //////////////////////////////////////////////////////////////
89 InsetMathGrid::RowInfo::RowInfo()
90 : lines_(0), skip_(0), allow_newpage_(true)
95 int InsetMathGrid::RowInfo::skipPixels(MetricsInfo
const & mi
) const
97 frontend::FontMetrics
const & fm
= theFontMetrics(mi
.base
.font
);
98 return crskip_
.inPixels(mi
.base
.textwidth
,
99 fm
.width(char_type('M')));
104 //////////////////////////////////////////////////////////////
107 InsetMathGrid::ColInfo::ColInfo()
108 : align_('c'), lines_(0)
112 //////////////////////////////////////////////////////////////
115 InsetMathGrid::InsetMathGrid(Buffer
* buf
)
116 : InsetMathNest(buf
, 1),
126 InsetMathGrid::InsetMathGrid(Buffer
* buf
, col_type m
, row_type n
)
127 : InsetMathNest(buf
, m
* n
),
137 InsetMathGrid::InsetMathGrid(Buffer
* buf
, col_type m
, row_type n
, char v
,
139 : InsetMathNest(buf
, m
* n
),
146 setVerticalAlignment(v
);
147 setHorizontalAlignments(h
);
151 Inset
* InsetMathGrid::clone() const
153 return new InsetMathGrid(*this);
157 InsetMath::idx_type
InsetMathGrid::index(row_type row
, col_type col
) const
159 return col
+ ncols() * row
;
163 void InsetMathGrid::setDefaults()
166 lyxerr
<< "positive number of columns expected" << endl
;
168 // lyxerr << "positive number of rows expected" << endl;
169 for (col_type col
= 0; col
< ncols(); ++col
) {
170 colinfo_
[col
].align_
= defaultColAlign(col
);
171 colinfo_
[col
].skip_
= defaultColSpace(col
);
172 colinfo_
[col
].special_
.clear();
177 void InsetMathGrid::setHorizontalAlignments(docstring
const & hh
)
180 for (docstring::const_iterator it
= hh
.begin(); it
!= hh
.end(); ++it
) {
183 colinfo_
[col
].lines_
++;
184 } else if ((c
== 'p' || c
== 'm' || c
== 'b'||
185 c
== '!' || c
== '@' || c
== '>' || c
== '<') &&
186 it
+ 1 != hh
.end() && *(it
+ 1) == '{') {
187 // @{decl.} and p{width} are standard LaTeX, the
188 // others are extensions by array.sty
189 bool const newcolumn
= c
== 'p' || c
== 'm' || c
== 'b';
191 // this declares a new column
193 // Only intercolumn stuff is allowed
194 // in the last dummy column
196 colinfo_
[col
].align_
= 'l';
198 // this is intercolumn stuff
199 if (colinfo_
[col
].special_
.empty())
200 // Overtake possible lines
201 colinfo_
[col
].special_
= docstring(colinfo_
[col
].lines_
, '|');
205 while (it
!= hh
.end()) {
207 colinfo_
[col
].special_
+= c
;
213 if (brace_open
> 0 && brace_open
== brace_close
)
218 colinfo_
[col
].lines_
= count(
219 colinfo_
[col
].special_
.begin(),
220 colinfo_
[col
].special_
.end(), '|');
221 LYXERR(Debug::MATHED
, "special column separator: `"
222 << to_utf8(colinfo_
[col
].special_
) << '\'');
224 colinfo_
[col
].lines_
= 0;
225 colinfo_
[col
].special_
.clear();
227 } else if (col
>= ncols()) {
228 // Only intercolumn stuff is allowed in the last
231 } else if (c
== 'c' || c
== 'l' || c
== 'r') {
232 colinfo_
[col
].align_
= static_cast<char>(c
);
233 if (!colinfo_
[col
].special_
.empty()) {
234 colinfo_
[col
].special_
+= c
;
235 colinfo_
[col
].lines_
= count(
236 colinfo_
[col
].special_
.begin(),
237 colinfo_
[col
].special_
.end(), '|');
238 LYXERR(Debug::MATHED
, "special column separator: `"
239 << to_utf8(colinfo_
[col
].special_
) << '\'');
242 colinfo_
[col
].lines_
= 0;
243 colinfo_
[col
].special_
.clear();
245 lyxerr
<< "unknown column separator: '" << c
<< "'" << endl
;
250 col_type n = hh.size();
253 for (col_type col = 0; col < n; ++col)
254 colinfo_[col].align_ = hh[col];
259 InsetMathGrid::col_type
InsetMathGrid::guessColumns(docstring
const & hh
)
262 for (docstring::const_iterator it
= hh
.begin(); it
!= hh
.end(); ++it
)
263 if (*it
== 'c' || *it
== 'l' || *it
== 'r'||
264 *it
== 'p' || *it
== 'm' || *it
== 'b')
266 // let's have at least one column, even if we did not recognize its
274 void InsetMathGrid::setHorizontalAlignment(char h
, col_type col
)
276 colinfo_
[col
].align_
= h
;
277 if (!colinfo_
[col
].special_
.empty()) {
278 char_type
& c
= colinfo_
[col
].special_
[colinfo_
[col
].special_
.size() - 1];
279 if (c
== 'l' || c
== 'c' || c
== 'r')
282 // FIXME: Change alignment of p, m and b columns, too
286 char InsetMathGrid::horizontalAlignment(col_type col
) const
288 return colinfo_
[col
].align_
;
292 docstring
InsetMathGrid::horizontalAlignments() const
295 for (col_type col
= 0; col
< ncols(); ++col
) {
296 if (colinfo_
[col
].special_
.empty()) {
297 res
+= docstring(colinfo_
[col
].lines_
, '|');
298 res
+= colinfo_
[col
].align_
;
300 res
+= colinfo_
[col
].special_
;
302 if (colinfo_
[ncols()].special_
.empty())
303 return res
+ docstring(colinfo_
[ncols()].lines_
, '|');
304 return res
+ colinfo_
[ncols()].special_
;
308 void InsetMathGrid::setVerticalAlignment(char c
)
314 char InsetMathGrid::verticalAlignment() const
320 InsetMathGrid::col_type
InsetMathGrid::ncols() const
322 return colinfo_
.size() - 1;
326 InsetMathGrid::row_type
InsetMathGrid::nrows() const
328 return rowinfo_
.size() - 1;
332 InsetMathGrid::col_type
InsetMathGrid::col(idx_type idx
) const
334 return idx
% ncols();
338 InsetMathGrid::row_type
InsetMathGrid::row(idx_type idx
) const
340 return idx
/ ncols();
344 void InsetMathGrid::vcrskip(Length
const & crskip
, row_type row
)
346 rowinfo_
[row
].crskip_
= crskip
;
350 Length
InsetMathGrid::vcrskip(row_type row
) const
352 return rowinfo_
[row
].crskip_
;
356 void InsetMathGrid::metrics(MetricsInfo
& mi
, Dimension
& dim
) const
358 // let the cells adjust themselves
359 InsetMathNest::metrics(mi
);
361 BufferView
& bv
= *mi
.base
.bv
;
363 // compute absolute sizes of vertical structure
364 for (row_type row
= 0; row
< nrows(); ++row
) {
367 for (col_type col
= 0; col
< ncols(); ++col
) {
368 Dimension
const & dimc
= cell(index(row
, col
)).dimension(bv
);
369 asc
= max(asc
, dimc
.asc
);
370 desc
= max(desc
, dimc
.des
);
372 rowinfo_
[row
].ascent_
= asc
;
373 rowinfo_
[row
].descent_
= desc
;
375 rowinfo_
[0].ascent_
+= hlinesep() * rowinfo_
[0].lines_
;
376 rowinfo_
[nrows()].ascent_
= 0;
377 rowinfo_
[nrows()].descent_
= 0;
379 // compute vertical offsets
380 rowinfo_
[0].offset_
= 0;
381 for (row_type row
= 1; row
<= nrows(); ++row
) {
382 rowinfo_
[row
].offset_
=
383 rowinfo_
[row
- 1].offset_
+
384 rowinfo_
[row
- 1].descent_
+
385 rowinfo_
[row
- 1].skipPixels(mi
) +
387 rowinfo_
[row
].lines_
* hlinesep() +
388 rowinfo_
[row
].ascent_
;
391 // adjust vertical offset
398 h
= rowinfo_
[nrows() - 1].offset_
;
401 h
= rowinfo_
[nrows() - 1].offset_
/ 2;
403 for (row_type row
= 0; row
<= nrows(); ++row
)
404 rowinfo_
[row
].offset_
-= h
;
407 // compute absolute sizes of horizontal structure
408 for (col_type col
= 0; col
< ncols(); ++col
) {
410 for (row_type row
= 0; row
< nrows(); ++row
)
411 wid
= max(wid
, cell(index(row
, col
)).dimension(bv
).wid
);
412 colinfo_
[col
].width_
= wid
;
414 colinfo_
[ncols()].width_
= 0;
416 // compute horizontal offsets
417 colinfo_
[0].offset_
= border();
418 for (col_type col
= 1; col
<= ncols(); ++col
) {
419 colinfo_
[col
].offset_
=
420 colinfo_
[col
- 1].offset_
+
421 colinfo_
[col
- 1].width_
+
422 colinfo_
[col
- 1].skip_
+
424 colinfo_
[col
].lines_
* vlinesep();
428 dim
.wid
= colinfo_
[ncols() - 1].offset_
429 + colinfo_
[ncols() - 1].width_
430 + vlinesep() * colinfo_
[ncols()].lines_
433 dim
.asc
= - rowinfo_
[0].offset_
434 + rowinfo_
[0].ascent_
435 + hlinesep() * rowinfo_
[0].lines_
438 dim
.des
= rowinfo_
[nrows() - 1].offset_
439 + rowinfo_
[nrows() - 1].descent_
440 + hlinesep() * rowinfo_
[nrows()].lines_
445 // Increase ws_[i] for 'R' columns (except the first one)
446 for (int i = 1; i < nc_; ++i)
447 if (align_[i] == 'R')
448 ws_[i] += 10 * df_width;
449 // Increase ws_[i] for 'C' column
450 if (align_[0] == 'C')
451 if (ws_[0] < 7 * workwidth / 8)
452 ws_[0] = 7 * workwidth / 8;
456 for (cxrow = row_.begin(); cxrow; ++cxrow) {
459 for (int i = 0; i < nc_; ++i) {
461 if (cxrow->getTab(i) <= 0) {
462 cxrow->setTab(i, df_width);
470 lf = (ws_[i] - cxrow->getTab(i))/2;
474 lf = ws_[i] - cxrow->getTab(i);
477 if (cxrow == row_.begin())
479 else if (cxrow.is_last())
480 lf = ws_[i] - cxrow->getTab(i);
482 lf = (ws_[i] - cxrow->getTab(i))/2;
485 int const ww = (isvoid) ? lf : lf + cxrow->getTab(i);
486 cxrow->setTab(i, lf + rg);
487 rg = ws_[i] - ww + colsep();
488 if (cxrow == row_.begin())
489 width += ws_[i] + colsep();
491 cxrow->setBaseline(cxrow->getBaseline() - ascent);
494 metricsMarkers2(dim
);
495 // Cache the inset dimension.
496 setDimCache(mi
, dim
);
500 void InsetMathGrid::draw(PainterInfo
& pi
, int x
, int y
) const
502 drawWithMargin(pi
, x
, y
, 1, 1);
506 void InsetMathGrid::drawWithMargin(PainterInfo
& pi
, int x
, int y
,
507 int lmargin
, int rmargin
) const
509 Dimension
const dim
= dimension(*pi
.base
.bv
);
510 BufferView
const & bv
= *pi
.base
.bv
;
512 for (idx_type idx
= 0; idx
< nargs(); ++idx
)
513 cell(idx
).draw(pi
, x
+ lmargin
+ cellXOffset(bv
, idx
),
514 y
+ cellYOffset(idx
));
516 for (row_type row
= 0; row
<= nrows(); ++row
)
517 for (unsigned int i
= 0; i
< rowinfo_
[row
].lines_
; ++i
) {
518 int yy
= y
+ rowinfo_
[row
].offset_
- rowinfo_
[row
].ascent_
519 - i
* hlinesep() - hlinesep()/2 - rowsep()/2;
520 pi
.pain
.line(x
+ lmargin
+ 1, yy
,
521 x
+ dim
.width() - rmargin
- 1, yy
,
525 for (col_type col
= 0; col
<= ncols(); ++col
)
526 for (unsigned int i
= 0; i
< colinfo_
[col
].lines_
; ++i
) {
527 int xx
= x
+ lmargin
+ colinfo_
[col
].offset_
528 - i
* vlinesep() - vlinesep()/2 - colsep()/2;
529 pi
.pain
.line(xx
, y
- dim
.ascent() + 1,
530 xx
, y
+ dim
.descent() - 1,
533 drawMarkers2(pi
, x
, y
);
537 void InsetMathGrid::metricsT(TextMetricsInfo
const & mi
, Dimension
& dim
) const
539 // let the cells adjust themselves
540 //InsetMathNest::metrics(mi);
541 for (idx_type i
= 0; i
< nargs(); ++i
)
542 cell(i
).metricsT(mi
, dim
);
544 // compute absolute sizes of vertical structure
545 for (row_type row
= 0; row
< nrows(); ++row
) {
548 for (col_type col
= 0; col
< ncols(); ++col
) {
549 //MathData const & c = cell(index(row, col));
552 asc
= max(asc
, dimc
.ascent());
553 desc
= max(desc
, dimc
.descent());
555 rowinfo_
[row
].ascent_
= asc
;
556 rowinfo_
[row
].descent_
= desc
;
558 //rowinfo_[0].ascent_ += hlinesep() * rowinfo_[0].lines_;
559 rowinfo_
[nrows()].ascent_
= 0;
560 rowinfo_
[nrows()].descent_
= 0;
562 // compute vertical offsets
563 rowinfo_
[0].offset_
= 0;
564 for (row_type row
= 1; row
<= nrows(); ++row
) {
565 rowinfo_
[row
].offset_
=
566 rowinfo_
[row
- 1].offset_
+
567 rowinfo_
[row
- 1].descent_
+
568 //rowinfo_[row - 1].skipPixels(mi) +
570 //rowinfo_[row].lines_ * hlinesep() +
571 rowinfo_
[row
].ascent_
;
574 // adjust vertical offset
581 h
= rowinfo_
[nrows() - 1].offset_
;
584 h
= rowinfo_
[nrows() - 1].offset_
/ 2;
586 for (row_type row
= 0; row
<= nrows(); ++row
)
587 rowinfo_
[row
].offset_
-= h
;
590 // compute absolute sizes of horizontal structure
591 for (col_type col
= 0; col
< ncols(); ++col
) {
593 for (row_type row
= 0; row
< nrows(); ++row
) {
595 //wid = max(wid, cell(index(row, col)).width());
597 colinfo_
[col
].width_
= wid
;
599 colinfo_
[ncols()].width_
= 0;
601 // compute horizontal offsets
602 colinfo_
[0].offset_
= border();
603 for (col_type col
= 1; col
<= ncols(); ++col
) {
604 colinfo_
[col
].offset_
=
605 colinfo_
[col
- 1].offset_
+
606 colinfo_
[col
- 1].width_
+
607 colinfo_
[col
- 1].skip_
+
609 //colinfo_[col].lines_ * vlinesep();
613 dim
.wid
= colinfo_
[ncols() - 1].offset_
614 + colinfo_
[ncols() - 1].width_
615 //+ vlinesep() * colinfo_[ncols()].lines_
618 dim
.asc
= -rowinfo_
[0].offset_
619 + rowinfo_
[0].ascent_
620 //+ hlinesep() * rowinfo_[0].lines_
623 dim
.des
= rowinfo_
[nrows() - 1].offset_
624 + rowinfo_
[nrows() - 1].descent_
625 //+ hlinesep() * rowinfo_[nrows()].lines_
630 void InsetMathGrid::drawT(TextPainter
& /*pain*/, int /*x*/, int /*y*/) const
632 // for (idx_type idx = 0; idx < nargs(); ++idx)
633 // cell(idx).drawT(pain, x + cellXOffset(idx), y + cellYOffset(idx));
637 docstring
InsetMathGrid::eolString(row_type row
, bool fragile
) const
641 if (!rowinfo_
[row
].crskip_
.zero())
642 eol
+= '[' + from_utf8(rowinfo_
[row
].crskip_
.asLatexString()) + ']';
643 else if(!rowinfo_
[row
].allow_newpage_
)
646 // make sure an upcoming '[' does not break anything
647 if (row
+ 1 < nrows()) {
648 MathData
const & c
= cell(index(row
+ 1, 0));
649 if (c
.size() && c
.front()->getChar() == '[')
654 // only add \\ if necessary
655 if (eol
.empty() && row
+ 1 == nrows())
658 return (fragile
? "\\protect\\\\" : "\\\\") + eol
;
662 docstring
InsetMathGrid::eocString(col_type col
, col_type lastcol
) const
664 if (col
+ 1 == lastcol
)
666 return from_ascii(" & ");
670 void InsetMathGrid::addRow(row_type row
)
672 rowinfo_
.insert(rowinfo_
.begin() + row
+ 1, RowInfo());
674 (cells_
.begin() + (row
+ 1) * ncols(), ncols(), MathData());
676 (cellinfo_
.begin() + (row
+ 1) * ncols(), ncols(), CellInfo());
680 void InsetMathGrid::appendRow()
682 rowinfo_
.push_back(RowInfo());
683 //cells_.insert(cells_.end(), ncols(), MathData());
684 for (col_type col
= 0; col
< ncols(); ++col
) {
685 cells_
.push_back(cells_type::value_type());
686 cellinfo_
.push_back(CellInfo());
691 void InsetMathGrid::delRow(row_type row
)
696 cells_type::iterator it
= cells_
.begin() + row
* ncols();
697 cells_
.erase(it
, it
+ ncols());
699 vector
<CellInfo
>::iterator jt
= cellinfo_
.begin() + row
* ncols();
700 cellinfo_
.erase(jt
, jt
+ ncols());
702 rowinfo_
.erase(rowinfo_
.begin() + row
);
706 void InsetMathGrid::copyRow(row_type row
)
709 for (col_type col
= 0; col
< ncols(); ++col
)
710 cells_
[(row
+ 1) * ncols() + col
] = cells_
[row
* ncols() + col
];
714 void InsetMathGrid::swapRow(row_type row
)
718 if (row
+ 1 == nrows())
720 for (col_type col
= 0; col
< ncols(); ++col
)
721 swap(cells_
[row
* ncols() + col
], cells_
[(row
+ 1) * ncols() + col
]);
725 void InsetMathGrid::addCol(col_type newcol
)
727 const col_type nc
= ncols();
728 const row_type nr
= nrows();
729 cells_type
new_cells((nc
+ 1) * nr
);
730 vector
<CellInfo
> new_cellinfo((nc
+ 1) * nr
);
732 for (row_type row
= 0; row
< nr
; ++row
)
733 for (col_type col
= 0; col
< nc
; ++col
) {
734 new_cells
[row
* (nc
+ 1) + col
+ (col
>= newcol
)]
735 = cells_
[row
* nc
+ col
];
736 new_cellinfo
[row
* (nc
+ 1) + col
+ (col
>= newcol
)]
737 = cellinfo_
[row
* nc
+ col
];
739 swap(cells_
, new_cells
);
740 swap(cellinfo_
, new_cellinfo
);
743 inf
.skip_
= defaultColSpace(newcol
);
744 inf
.align_
= defaultColAlign(newcol
);
745 colinfo_
.insert(colinfo_
.begin() + newcol
, inf
);
749 void InsetMathGrid::delCol(col_type col
)
755 vector
<CellInfo
> tmpcellinfo
;
756 for (col_type i
= 0; i
< nargs(); ++i
)
757 if (i
% ncols() != col
) {
758 tmpcells
.push_back(cells_
[i
]);
759 tmpcellinfo
.push_back(cellinfo_
[i
]);
761 swap(cells_
, tmpcells
);
762 swap(cellinfo_
, tmpcellinfo
);
764 colinfo_
.erase(colinfo_
.begin() + col
);
768 void InsetMathGrid::copyCol(col_type col
)
771 for (row_type row
= 0; row
< nrows(); ++row
)
772 cells_
[row
* ncols() + col
+ 1] = cells_
[row
* ncols() + col
];
776 void InsetMathGrid::swapCol(col_type col
)
780 if (col
+ 1 == ncols())
782 for (row_type row
= 0; row
< nrows(); ++row
)
783 swap(cells_
[row
* ncols() + col
], cells_
[row
* ncols() + col
+ 1]);
787 int InsetMathGrid::cellXOffset(BufferView
const & bv
, idx_type idx
) const
789 col_type c
= col(idx
);
790 int x
= colinfo_
[c
].offset_
;
791 char align
= colinfo_
[c
].align_
;
792 Dimension
const & celldim
= cell(idx
).dimension(bv
);
793 if (align
== 'r' || align
== 'R')
794 x
+= colinfo_
[c
].width_
- celldim
.wid
;
795 if (align
== 'c' || align
== 'C')
796 x
+= (colinfo_
[c
].width_
- celldim
.wid
) / 2;
801 int InsetMathGrid::cellYOffset(idx_type idx
) const
803 return rowinfo_
[row(idx
)].offset_
;
807 bool InsetMathGrid::idxUpDown(Cursor
& cur
, bool up
) const
812 cur
.idx() -= ncols();
814 if (cur
.row() + 1 >= nrows())
816 cur
.idx() += ncols();
818 cur
.pos() = cur
.cell().x2pos(&cur
.bv(), cur
.x_target() - cur
.cell().xo(cur
.bv()));
823 bool InsetMathGrid::idxBackward(Cursor
& cur
) const
825 // leave matrix if at the front edge
829 cur
.pos() = cur
.lastpos();
834 bool InsetMathGrid::idxForward(Cursor
& cur
) const
836 // leave matrix if at the back edge
837 if (cur
.col() + 1 == ncols())
845 bool InsetMathGrid::idxFirst(Cursor
& cur
) const
852 cur
.idx() = (nrows() - 1) * ncols();
855 cur
.idx() = ((nrows() - 1) / 2) * ncols();
862 bool InsetMathGrid::idxLast(Cursor
& cur
) const
866 cur
.idx() = ncols() - 1;
869 cur
.idx() = nargs() - 1;
872 cur
.idx() = ((nrows() - 1) / 2 + 1) * ncols() - 1;
874 cur
.pos() = cur
.lastpos();
879 bool InsetMathGrid::idxDelete(idx_type
& idx
)
881 // nothing to do if we have just one row
885 // nothing to do if we are in the middle of the last row of the inset
886 if (idx
+ ncols() > nargs())
889 // try to delete entire sequence of ncols() empty cells if possible
890 for (idx_type i
= idx
; i
< idx
+ ncols(); ++i
)
894 // move cells if necessary
895 for (idx_type i
= index(row(idx
), 0); i
< idx
; ++i
)
896 swap(cell(i
), cell(i
+ ncols()));
903 // undo effect of Ctrl-Tab (i.e. pull next cell)
904 //if (idx + 1 != nargs())
905 // cell(idx).swap(cell(idx + 1));
907 // we handled the event..
912 // reimplement old behaviour when pressing Delete in the last position
914 void InsetMathGrid::idxGlue(idx_type idx
)
916 col_type c
= col(idx
);
917 if (c
+ 1 == ncols()) {
918 if (row(idx
) + 1 != nrows()) {
919 for (col_type cc
= 0; cc
< ncols(); ++cc
)
920 cell(idx
).append(cell(idx
+ cc
+ 1));
921 delRow(row(idx
) + 1);
924 cell(idx
).append(cell(idx
+ 1));
925 for (col_type cc
= c
+ 2; cc
< ncols(); ++cc
)
926 cell(idx
- c
+ cc
- 1) = cell(idx
- c
+ cc
);
927 cell(idx
- c
+ ncols() - 1).clear();
932 InsetMathGrid::RowInfo
const & InsetMathGrid::rowinfo(row_type row
) const
934 return rowinfo_
[row
];
938 InsetMathGrid::RowInfo
& InsetMathGrid::rowinfo(row_type row
)
940 return rowinfo_
[row
];
944 bool InsetMathGrid::idxBetween(idx_type idx
, idx_type from
, idx_type to
) const
946 row_type
const ri
= row(idx
);
947 row_type
const r1
= min(row(from
), row(to
));
948 row_type
const r2
= max(row(from
), row(to
));
949 col_type
const ci
= col(idx
);
950 col_type
const c1
= min(col(from
), col(to
));
951 col_type
const c2
= max(col(from
), col(to
));
952 return r1
<= ri
&& ri
<= r2
&& c1
<= ci
&& ci
<= c2
;
957 void InsetMathGrid::normalize(NormalStream
& os
) const
960 for (row_type row
= 0; row
< nrows(); ++row
) {
962 for (col_type col
= 0; col
< ncols(); ++col
)
963 os
<< "[cell " << cell(index(row
, col
)) << ']';
970 void InsetMathGrid::mathmlize(MathStream
& os
) const
972 os
<< MTag("mtable");
973 for (row_type row
= 0; row
< nrows(); ++row
) {
975 for (col_type col
= 0; col
< ncols(); ++col
)
976 os
<< cell(index(row
, col
));
979 os
<< ETag("mtable");
983 void InsetMathGrid::write(WriteStream
& os
) const
985 write(os
, 0, 0, nrows(), ncols());
988 void InsetMathGrid::write(WriteStream
& os
,
989 row_type beg_row
, col_type beg_col
,
990 row_type end_row
, col_type end_col
) const
992 MathEnsurer
ensurer(os
, false);
994 for (row_type row
= beg_row
; row
< end_row
; ++row
) {
995 os
<< verboseHLine(rowinfo_
[row
].lines_
);
996 // don't write & and empty cells at end of line
997 col_type lastcol
= 0;
998 bool emptyline
= true;
999 for (col_type col
= beg_col
; col
< end_col
; ++col
)
1000 if (!cell(index(row
, col
)).empty()) {
1004 for (col_type col
= beg_col
; col
< lastcol
; ++col
) {
1005 os
<< cell(index(row
, col
));
1006 if (os
.pendingBrace())
1007 ModeSpecifier
specifier(os
, TEXT_MODE
);
1008 os
<< eocString(col
, lastcol
);
1010 eol
= eolString(row
, os
.fragile());
1012 // append newline only if line wasn't completely empty
1013 // and the formula is not written on a single line
1014 bool const empty
= emptyline
&& eol
.empty();
1015 if (!empty
&& nrows() > 1)
1018 // @TODO use end_row instead of nrows() ?
1019 docstring
const s
= verboseHLine(rowinfo_
[nrows()].lines_
);
1031 int InsetMathGrid::colsep() const
1037 int InsetMathGrid::rowsep() const
1043 int InsetMathGrid::hlinesep() const
1049 int InsetMathGrid::vlinesep() const
1055 int InsetMathGrid::border() const
1061 void InsetMathGrid::splitCell(Cursor
& cur
)
1063 if (cur
.idx() == cur
.lastidx())
1065 MathData ar
= cur
.cell();
1066 ar
.erase(0, cur
.pos());
1067 cur
.cell().erase(cur
.pos(), cur
.lastpos());
1070 cur
.cell().insert(0, ar
);
1074 void InsetMathGrid::doDispatch(Cursor
& cur
, FuncRequest
& cmd
)
1076 //lyxerr << "*** InsetMathGrid: request: " << cmd << endl;
1078 Parse::flags parseflg
= Parse::QUIET
| Parse::USETEXT
;
1080 switch (cmd
.action
) {
1082 // insert file functions
1083 case LFUN_LINE_DELETE
:
1084 cur
.recordUndoInset();
1085 //autocorrect_ = false;
1093 if (cur
.idx() > cur
.lastidx())
1094 cur
.idx() = cur
.lastidx();
1095 if (cur
.pos() > cur
.lastpos())
1096 cur
.pos() = cur
.lastpos();
1099 case LFUN_CELL_SPLIT
:
1104 case LFUN_CELL_BACKWARD
:
1106 cur
.setSelection(false);
1107 if (!idxPrev(cur
)) {
1108 cmd
= FuncRequest(LFUN_FINISHED_BACKWARD
);
1113 case LFUN_CELL_FORWARD
:
1114 // Can't handle selection by additional 'shift' as this is
1115 // hard bound to LFUN_CELL_BACKWARD
1116 cur
.setSelection(false);
1117 if (!idxNext(cur
)) {
1118 cmd
= FuncRequest(LFUN_FINISHED_FORWARD
);
1123 case LFUN_NEWLINE_INSERT
: {
1124 cur
.recordUndoInset();
1125 row_type
const r
= cur
.row();
1129 for (col_type c
= col(cur
.idx()) + 1; c
< ncols(); ++c
)
1130 swap(cell(index(r
, c
)), cell(index(r
+ 1, c
)));
1134 swap(cell(cur
.idx()), cell(cur
.idx() + ncols() - 1));
1137 cur
.pos() = cur
.lastpos();
1139 //mathcursor->normalize();
1140 //cmd = FuncRequest(LFUN_FINISHED_BACKWARD);
1144 case LFUN_TABULAR_FEATURE
: {
1145 cur
.recordUndoInset();
1146 //lyxerr << "handling tabular-feature " << to_utf8(cmd.argument()) << endl;
1147 istringstream
is(to_utf8(cmd
.argument()));
1150 if (s
== "valign-top")
1151 setVerticalAlignment('t');
1152 else if (s
== "valign-middle")
1153 setVerticalAlignment('c');
1154 else if (s
== "valign-bottom")
1155 setVerticalAlignment('b');
1156 else if (s
== "align-left")
1157 setHorizontalAlignment('l', cur
.col());
1158 else if (s
== "align-right")
1159 setHorizontalAlignment('r', cur
.col());
1160 else if (s
== "align-center")
1161 setHorizontalAlignment('c', cur
.col());
1162 else if (s
== "append-row")
1163 for (int i
= 0, n
= extractInt(is
); i
< n
; ++i
)
1165 else if (s
== "delete-row") {
1166 cur
.clearSelection(); // bug 4323
1167 for (int i
= 0, n
= extractInt(is
); i
< n
; ++i
) {
1169 if (cur
.idx() >= nargs())
1170 cur
.idx() -= ncols();
1172 cur
.pos() = 0; // trick, see below
1174 else if (s
== "copy-row") {
1175 // Here (as later) we save the cursor col/row
1176 // in order to restore it after operation.
1177 row_type
const r
= cur
.row();
1178 col_type
const c
= cur
.col();
1179 for (int i
= 0, n
= extractInt(is
); i
< n
; ++i
)
1181 cur
.idx() = index(r
, c
);
1183 else if (s
== "swap-row") {
1185 // Trick to suppress same-idx-means-different-cell
1189 else if (s
== "add-hline-above")
1190 rowinfo_
[cur
.row()].lines_
++;
1191 else if (s
== "add-hline-below")
1192 rowinfo_
[cur
.row()+1].lines_
++;
1193 else if (s
== "delete-hline-above")
1194 rowinfo_
[cur
.row()].lines_
--;
1195 else if (s
== "delete-hline-below")
1196 rowinfo_
[cur
.row()+1].lines_
--;
1197 else if (s
== "append-column") {
1198 row_type
const r
= cur
.row();
1199 col_type
const c
= cur
.col();
1200 for (int i
= 0, n
= extractInt(is
); i
< n
; ++i
)
1201 addCol(cur
.col() + 1);
1202 cur
.idx() = index(r
, c
);
1204 else if (s
== "delete-column") {
1205 cur
.clearSelection(); // bug 4323
1206 row_type
const r
= cur
.row();
1207 col_type
const c
= cur
.col();
1208 for (int i
= 0, n
= extractInt(is
); i
< n
; ++i
)
1209 delCol(col(cur
.idx()));
1210 cur
.idx() = index(r
, min(c
, cur
.ncols() - 1));
1211 cur
.pos() = 0; // trick, see above
1213 else if (s
== "copy-column") {
1214 row_type
const r
= cur
.row();
1215 col_type
const c
= cur
.col();
1217 cur
.idx() = index(r
, c
);
1219 else if (s
== "swap-column") {
1221 cur
.pos() = 0; // trick, see above
1223 else if (s
== "add-vline-left") {
1224 colinfo_
[cur
.col()].lines_
++;
1225 if (!colinfo_
[cur
.col()].special_
.empty())
1226 colinfo_
[cur
.col()].special_
+= '|';
1228 else if (s
== "add-vline-right") {
1229 colinfo_
[cur
.col()+1].lines_
++;
1230 if (!colinfo_
[cur
.col()+1].special_
.empty())
1231 colinfo_
[cur
.col()+1].special_
.insert(0, 1, '|');
1233 else if (s
== "delete-vline-left") {
1234 colinfo_
[cur
.col()].lines_
--;
1235 docstring
& special
= colinfo_
[cur
.col()].special_
;
1236 if (!special
.empty()) {
1237 docstring::size_type i
= special
.rfind('|');
1238 LASSERT(i
!= docstring::npos
, /**/);
1239 special
.erase(i
, 1);
1242 else if (s
== "delete-vline-right") {
1243 colinfo_
[cur
.col()+1].lines_
--;
1244 docstring
& special
= colinfo_
[cur
.col()+1].special_
;
1245 if (!special
.empty()) {
1246 docstring::size_type i
= special
.find('|');
1247 LASSERT(i
!= docstring::npos
, /**/);
1248 special
.erase(i
, 1);
1255 // perhaps this should be FINISHED_BACKWARD -- just for clarity?
1256 //lyxerr << "returning FINISHED_LEFT" << endl;
1260 case LFUN_CLIPBOARD_PASTE
:
1261 parseflg
|= Parse::VERBATIM
;
1264 if (cur
.currentMode() <= TEXT_MODE
)
1265 parseflg
|= Parse::TEXTMODE
;
1266 cur
.message(_("Paste"));
1267 cap::replaceSelection(cur
);
1269 if (cmd
.argument().empty() && !theClipboard().isInternal())
1270 topaste
= theClipboard().getAsText();
1272 idocstringstream
is(cmd
.argument());
1275 topaste
= cap::selection(n
);
1277 InsetMathGrid
grid(buffer_
, 1, 1);
1278 if (!topaste
.empty())
1279 if ((topaste
.size() == 1 && topaste
.at(0) < 0x80)
1280 || !mathed_parse_normal(grid
, topaste
, parseflg
)) {
1282 mathed_parse_normal(grid
, topaste
, parseflg
| Parse::VERBATIM
);
1285 if (grid
.nargs() == 1) {
1286 // single cell/part of cell
1288 cur
.cell().insert(cur
.pos(), grid
.cell(0));
1289 cur
.pos() += grid
.cell(0).size();
1292 cur
.recordUndoInset();
1293 col_type
const numcols
=
1294 min(grid
.ncols(), ncols() - col(cur
.idx()));
1295 row_type
const numrows
=
1296 min(grid
.nrows(), nrows() - cur
.row());
1297 for (row_type r
= 0; r
< numrows
; ++r
) {
1298 for (col_type c
= 0; c
< numcols
; ++c
) {
1299 idx_type i
= index(r
+ cur
.row(), c
+ col(cur
.idx()));
1300 cell(i
).insert(0, grid
.cell(grid
.index(r
, c
)));
1302 // append the left over horizontal cells to the last column
1303 idx_type i
= index(r
+ cur
.row(), ncols() - 1);
1304 for (InsetMath::col_type c
= numcols
; c
< grid
.ncols(); ++c
)
1305 cell(i
).append(grid
.cell(grid
.index(r
, c
)));
1307 // append the left over vertical cells to the last _cell_
1308 idx_type i
= nargs() - 1;
1309 for (row_type r
= numrows
; r
< grid
.nrows(); ++r
)
1310 for (col_type c
= 0; c
< grid
.ncols(); ++c
)
1311 cell(i
).append(grid
.cell(grid
.index(r
, c
)));
1313 cur
.clearSelection(); // bug 393
1318 case LFUN_LINE_BEGIN_SELECT
:
1319 case LFUN_LINE_BEGIN
:
1320 case LFUN_WORD_BACKWARD_SELECT
:
1321 case LFUN_WORD_BACKWARD
:
1322 case LFUN_WORD_LEFT_SELECT
:
1323 case LFUN_WORD_LEFT
:
1324 cur
.selHandle(cmd
.action
== LFUN_WORD_BACKWARD_SELECT
||
1325 cmd
.action
== LFUN_WORD_LEFT_SELECT
||
1326 cmd
.action
== LFUN_LINE_BEGIN_SELECT
);
1327 cur
.macroModeClose();
1328 if (cur
.pos() != 0) {
1330 } else if (cur
.idx() % cur
.ncols() != 0) {
1331 cur
.idx() -= cur
.idx() % cur
.ncols();
1333 } else if (cur
.idx() != 0) {
1337 cmd
= FuncRequest(LFUN_FINISHED_BACKWARD
);
1342 case LFUN_WORD_FORWARD_SELECT
:
1343 case LFUN_WORD_FORWARD
:
1344 case LFUN_WORD_RIGHT_SELECT
:
1345 case LFUN_WORD_RIGHT
:
1346 case LFUN_LINE_END_SELECT
:
1348 cur
.selHandle(cmd
.action
== LFUN_WORD_FORWARD_SELECT
||
1349 cmd
.action
== LFUN_WORD_RIGHT_SELECT
||
1350 cmd
.action
== LFUN_LINE_END_SELECT
);
1351 cur
.macroModeClose();
1353 if (cur
.pos() != cur
.lastpos()) {
1354 cur
.pos() = cur
.lastpos();
1355 } else if ((cur
.idx() + 1) % cur
.ncols() != 0) {
1356 cur
.idx() += cur
.ncols() - 1 - cur
.idx() % cur
.ncols();
1357 cur
.pos() = cur
.lastpos();
1358 } else if (cur
.idx() != cur
.lastidx()) {
1359 cur
.idx() = cur
.lastidx();
1360 cur
.pos() = cur
.lastpos();
1362 cmd
= FuncRequest(LFUN_FINISHED_FORWARD
);
1368 InsetMathNest::doDispatch(cur
, cmd
);
1373 bool InsetMathGrid::getStatus(Cursor
& cur
, FuncRequest
const & cmd
,
1374 FuncStatus
& status
) const
1376 switch (cmd
.action
) {
1377 case LFUN_TABULAR_FEATURE
: {
1378 string
const s
= cmd
.getArg(0);
1379 if (nrows() <= 1 && (s
== "delete-row" || s
== "swap-row")) {
1380 status
.setEnabled(false);
1381 status
.message(from_utf8(N_("Only one row")));
1385 (s
== "delete-column" || s
== "swap-column")) {
1386 status
.setEnabled(false);
1387 status
.message(from_utf8(N_("Only one column")));
1390 if ((rowinfo_
[cur
.row()].lines_
== 0 &&
1391 s
== "delete-hline-above") ||
1392 (rowinfo_
[cur
.row() + 1].lines_
== 0 &&
1393 s
== "delete-hline-below")) {
1394 status
.setEnabled(false);
1395 status
.message(from_utf8(N_("No hline to delete")));
1399 if ((colinfo_
[cur
.col()].lines_
== 0 &&
1400 s
== "delete-vline-left") ||
1401 (colinfo_
[cur
.col() + 1].lines_
== 0 &&
1402 s
== "delete-vline-right")) {
1403 status
.setEnabled(false);
1404 status
.message(from_utf8(N_("No vline to delete")));
1407 if (s
== "valign-top" || s
== "valign-middle" ||
1408 s
== "valign-bottom" || s
== "align-left" ||
1409 s
== "align-right" || s
== "align-center") {
1410 status
.setEnabled(true);
1411 char const ha
= horizontalAlignment(cur
.col());
1412 char const va
= verticalAlignment();
1413 status
.setOnOff((s
== "align-left" && ha
== 'l')
1414 || (s
== "align-right" && ha
== 'r')
1415 || (s
== "align-center" && ha
== 'c')
1416 || (s
== "valign-top" && va
== 't')
1417 || (s
== "valign-bottom" && va
== 'b')
1418 || (s
== "valign-middle" && va
== 'c'));
1421 if (s
== "append-row" || s
== "delete-row" ||
1422 s
== "copy-row" || s
== "swap-row" ||
1423 s
== "add-hline-above" || s
== "add-hline-below" ||
1424 s
== "delete-hline-above" || s
== "delete-hline-below" ||
1425 s
== "append-column" || s
== "delete-column" ||
1426 s
== "copy-column" || s
== "swap-column" ||
1427 s
== "add-vline-left" || s
== "add-vline-right" ||
1428 s
== "delete-vline-left" || s
== "delete-vline-right") {
1429 status
.setEnabled(true);
1431 status
.setEnabled(false);
1432 status
.message(bformat(
1433 from_utf8(N_("Unknown tabular feature '%1$s'")), lyx::from_ascii(s
)));
1437 // FIXME: What did this code do?
1438 // Please check whether it is still needed!
1439 // should be more precise
1440 if (v_align_
== '\0') {
1441 status
.enable(true);
1444 if (cmd
.argument().empty()) {
1445 status
.enable(false);
1448 if (!contains("tcb", cmd
.argument()[0])) {
1449 status
.enable(false);
1452 status
.setOnOff(cmd
.argument()[0] == v_align_
);
1453 status
.setEnabled(true);
1458 case LFUN_CELL_SPLIT
:
1459 status
.setEnabled(true);
1462 case LFUN_CELL_BACKWARD
:
1463 case LFUN_CELL_FORWARD
:
1464 status
.setEnabled(true);
1468 return InsetMathNest::getStatus(cur
, cmd
, status
);