1 /**
2  * \file InsetMathGrid.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author André Pönitz
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10 
11 #include <config.h>
12 #include <algorithm>
13 
14 #include "InsetMathGrid.h"
15 
16 #include "InsetMathUnknown.h"
17 #include "MathData.h"
18 #include "MathParser.h"
19 #include "MathStream.h"
20 #include "MetricsInfo.h"
21 
22 #include "Buffer.h"
23 #include "BufferParams.h"
24 #include "BufferView.h"
25 #include "CoordCache.h"
26 #include "Cursor.h"
27 #include "CutAndPaste.h"
28 #include "FuncRequest.h"
29 #include "FuncStatus.h"
30 #include "LaTeXFeatures.h"
31 #include "TexRow.h"
32 
33 #include "frontends/Clipboard.h"
34 #include "frontends/Painter.h"
35 
36 #include "support/debug.h"
37 #include "support/docstream.h"
38 #include "support/gettext.h"
39 #include "support/lstrings.h"
40 #include "support/lassert.h"
41 
42 #include <sstream>
43 
44 using namespace std;
45 using namespace lyx::support;
46 
47 
48 
49 namespace lyx {
50 
verboseHLine(int n)51 static docstring verboseHLine(int n)
52 {
53 	docstring res;
54 	for (int i = 0; i < n; ++i)
55 		res += "\\hline";
56 	if (n)
57 		res += ' ';
58 	return res;
59 }
60 
61 
extractInt(istream & is)62 static int extractInt(istream & is)
63 {
64 	int num = 1;
65 	is >> num;
66 	return (num == 0) ? 1 : num;
67 }
68 
69 
resetGrid(InsetMathGrid & grid)70 static void resetGrid(InsetMathGrid & grid)
71 {
72 	while (grid.ncols() > 1)
73 		grid.delCol(grid.ncols() - 1);
74 	while (grid.nrows() > 1)
75 		grid.delRow(grid.nrows() - 1);
76 	grid.cell(0).erase(0, grid.cell(0).size());
77 	grid.setDefaults();
78 }
79 
80 
81 
82 //////////////////////////////////////////////////////////////
83 
84 
CellInfo()85 InsetMathGrid::CellInfo::CellInfo()
86 	: multi_(CELL_NORMAL)
87 {}
88 
89 
90 
91 //////////////////////////////////////////////////////////////
92 
93 
RowInfo()94 InsetMathGrid::RowInfo::RowInfo()
95 	: descent_(0), ascent_(0), offset_(0), lines_(0), skip_(0),
96 	  allow_newpage_(true)
97 {}
98 
99 
100 
skipPixels(MetricsInfo const & mi) const101 int InsetMathGrid::RowInfo::skipPixels(MetricsInfo const & mi) const
102 {
103 	return crskip_.inPixels(mi.base);
104 }
105 
106 
107 
108 //////////////////////////////////////////////////////////////
109 
110 
ColInfo()111 InsetMathGrid::ColInfo::ColInfo()
112 	: align_('c'), width_(0), offset_(0), lines_(0), skip_(0)
113 {}
114 
115 
116 //////////////////////////////////////////////////////////////
117 
118 
InsetMathGrid(Buffer * buf)119 InsetMathGrid::InsetMathGrid(Buffer * buf)
120 	: InsetMathNest(buf, 1),
121 	  rowinfo_(1 + 1),
122 		colinfo_(1 + 1),
123 		cellinfo_(1),
124 		v_align_('c')
125 {
126 	setDefaults();
127 }
128 
129 
InsetMathGrid(Buffer * buf,col_type m,row_type n)130 InsetMathGrid::InsetMathGrid(Buffer * buf, col_type m, row_type n)
131 	: InsetMathNest(buf, m * n),
132 	  rowinfo_(n + 1),
133 		colinfo_(m + 1),
134 		cellinfo_(m * n),
135 		v_align_('c')
136 {
137 	setDefaults();
138 }
139 
140 
InsetMathGrid(Buffer * buf,col_type m,row_type n,char v,docstring const & h)141 InsetMathGrid::InsetMathGrid(Buffer * buf, col_type m, row_type n, char v,
142 	docstring const & h)
143 	: InsetMathNest(buf, m * n),
144 	  rowinfo_(n + 1),
145 	  colinfo_(m + 1),
146 		cellinfo_(m * n),
147 		v_align_(v)
148 {
149 	setDefaults();
150 	setVerticalAlignment(v);
151 	setHorizontalAlignments(h);
152 }
153 
154 
clone() const155 Inset * InsetMathGrid::clone() const
156 {
157 	return new InsetMathGrid(*this);
158 }
159 
160 
index(row_type row,col_type col) const161 InsetMath::idx_type InsetMathGrid::index(row_type row, col_type col) const
162 {
163 	return col + ncols() * row;
164 }
165 
166 
setDefaults()167 void InsetMathGrid::setDefaults()
168 {
169 	if (ncols() <= 0)
170 		lyxerr << "positive number of columns expected" << endl;
171 	//if (nrows() <= 0)
172 	//	lyxerr << "positive number of rows expected" << endl;
173 	for (col_type col = 0; col < ncols(); ++col) {
174 		colinfo_[col].align_ = defaultColAlign(col);
175 		colinfo_[col].skip_  = defaultColSpace(col);
176 		colinfo_[col].special_.clear();
177 	}
178 	for (idx_type idx = 0; idx < nargs(); ++idx)
179 		cellinfo_[idx].multi_ = CELL_NORMAL;
180 }
181 
182 
interpretString(Cursor & cur,docstring const & str)183 bool InsetMathGrid::interpretString(Cursor & cur, docstring const & str)
184 {
185 	if (str == "\\hline") {
186 		FuncRequest fr = FuncRequest(LFUN_TABULAR_FEATURE, "add-hline-above");
187 		FuncStatus status;
188 		if (getStatus(cur, fr, status)) {
189 			if (status.enabled()) {
190 				rowinfo_[cur.row()].lines_++;
191 				return true;
192 			}
193 		}
194 	}
195 	return InsetMathNest::interpretString(cur, str);
196 }
197 
198 
setHorizontalAlignments(docstring const & hh)199 void InsetMathGrid::setHorizontalAlignments(docstring const & hh)
200 {
201 	col_type col = 0;
202 	for (docstring::const_iterator it = hh.begin(); it != hh.end(); ++it) {
203 		char_type c = *it;
204 		if (c == '|') {
205 			colinfo_[col].lines_++;
206 		} else if ((c == 'p' || c == 'm' || c == 'b'||
207 		            c == '!' || c == '@' || c == '>' || c == '<') &&
208 		           it + 1 != hh.end() && *(it + 1) == '{') {
209 			// @{decl.} and p{width} are standard LaTeX, the
210 			// others are extensions by array.sty
211 			bool const newcolumn = c == 'p' || c == 'm' || c == 'b';
212 			if (newcolumn) {
213 				// this declares a new column
214 				if (col >= ncols())
215 					// Only intercolumn stuff is allowed
216 					// in the last dummy column
217 					break;
218 				colinfo_[col].align_ = 'l';
219 			} else {
220 				// this is intercolumn stuff
221 				if (colinfo_[col].special_.empty())
222 					// Overtake possible lines
223 					colinfo_[col].special_ = docstring(colinfo_[col].lines_, '|');
224 			}
225 			int brace_open = 0;
226 			int brace_close = 0;
227 			while (it != hh.end()) {
228 				c = *it;
229 				colinfo_[col].special_ += c;
230 				if (c == '{')
231 					++brace_open;
232 				else if (c == '}')
233 					++brace_close;
234 				++it;
235 				if (brace_open > 0 && brace_open == brace_close)
236 					break;
237 			}
238 			--it;
239 			if (newcolumn) {
240 				colinfo_[col].lines_ = count(
241 					colinfo_[col].special_.begin(),
242 					colinfo_[col].special_.end(), '|');
243 				LYXERR(Debug::MATHED, "special column separator: `"
244 					<< to_utf8(colinfo_[col].special_) << '\'');
245 				++col;
246 				colinfo_[col].lines_ = 0;
247 				colinfo_[col].special_.clear();
248 			}
249 		} else if (col >= ncols()) {
250 			// Only intercolumn stuff is allowed in the last
251 			// dummy column
252 			break;
253 		} else if (c == 'c' || c == 'l' || c == 'r') {
254 			colinfo_[col].align_ = static_cast<char>(c);
255 			if (!colinfo_[col].special_.empty()) {
256 				colinfo_[col].special_ += c;
257 				colinfo_[col].lines_ = count(
258 						colinfo_[col].special_.begin(),
259 						colinfo_[col].special_.end(), '|');
260 				LYXERR(Debug::MATHED, "special column separator: `"
261 					<< to_utf8(colinfo_[col].special_) << '\'');
262 			}
263 			++col;
264 			colinfo_[col].lines_ = 0;
265 			colinfo_[col].special_.clear();
266 		} else {
267 			lyxerr << "unknown column separator: '" << c << "'" << endl;
268 		}
269 	}
270 
271 /*
272 	col_type n = hh.size();
273 	if (n > ncols())
274 		n = ncols();
275 	for (col_type col = 0; col < n; ++col)
276 		colinfo_[col].align_ = hh[col];
277 */
278 }
279 
280 
guessColumns(docstring const & hh)281 InsetMathGrid::col_type InsetMathGrid::guessColumns(docstring const & hh)
282 {
283 	col_type col = 0;
284 	for (docstring::const_iterator it = hh.begin(); it != hh.end(); ++it)
285 		if (*it == 'c' || *it == 'l' || *it == 'r'||
286 		    *it == 'p' || *it == 'm' || *it == 'b')
287 			++col;
288 	// let's have at least one column, even if we did not recognize its
289 	// alignment
290 	if (col == 0)
291 		col = 1;
292 	return col;
293 }
294 
295 
setHorizontalAlignment(char h,col_type col)296 void InsetMathGrid::setHorizontalAlignment(char h, col_type col)
297 {
298 	colinfo_[col].align_ = h;
299 	if (!colinfo_[col].special_.empty()) {
300 		char_type & c = colinfo_[col].special_[colinfo_[col].special_.size() - 1];
301 		if (c == 'l' || c == 'c' || c == 'r')
302 			c = h;
303 	}
304 	// FIXME: Change alignment of p, m and b columns, too
305 }
306 
307 
horizontalAlignment(col_type col) const308 char InsetMathGrid::horizontalAlignment(col_type col) const
309 {
310 	return colinfo_[col].align_;
311 }
312 
313 
horizontalAlignments() const314 docstring InsetMathGrid::horizontalAlignments() const
315 {
316 	docstring res;
317 	for (col_type col = 0; col < ncols(); ++col) {
318 		if (colinfo_[col].special_.empty()) {
319 			res += docstring(colinfo_[col].lines_, '|');
320 			res += colinfo_[col].align_;
321 		} else
322 			res += colinfo_[col].special_;
323 	}
324 	if (colinfo_[ncols()].special_.empty())
325 		return res + docstring(colinfo_[ncols()].lines_, '|');
326 	return res + colinfo_[ncols()].special_;
327 }
328 
329 
setVerticalAlignment(char c)330 void InsetMathGrid::setVerticalAlignment(char c)
331 {
332 	v_align_ = c;
333 }
334 
335 
verticalAlignment() const336 char InsetMathGrid::verticalAlignment() const
337 {
338 	return v_align_;
339 }
340 
341 
ncols() const342 InsetMathGrid::col_type InsetMathGrid::ncols() const
343 {
344 	return colinfo_.size() - 1;
345 }
346 
347 
nrows() const348 InsetMathGrid::row_type InsetMathGrid::nrows() const
349 {
350 	return rowinfo_.size() - 1;
351 }
352 
353 
col(idx_type idx) const354 InsetMathGrid::col_type InsetMathGrid::col(idx_type idx) const
355 {
356 	return idx % ncols();
357 }
358 
359 
row(idx_type idx) const360 InsetMathGrid::row_type InsetMathGrid::row(idx_type idx) const
361 {
362 	return idx / ncols();
363 }
364 
365 
ncellcols(idx_type idx) const366 InsetMathGrid::col_type InsetMathGrid::ncellcols(idx_type idx) const
367 {
368 	col_type cols = 1;
369 	if (cellinfo_[idx].multi_ == CELL_NORMAL)
370 		return cols;
371 	// If the cell at idx is already CELL_PART_OF_MULTICOLUMN we return
372 	// the number of remaining columns, not the ones of the complete
373 	// multicolumn cell. This makes it possible to always go to the next
374 	// cell with idx + ncellcols(idx) - 1.
375 	row_type const r = row(idx);
376 	while (idx+cols < nargs() && row(idx+cols) == r &&
377 	       cellinfo_[idx+cols].multi_ == CELL_PART_OF_MULTICOLUMN)
378 		cols++;
379 	return cols;
380 }
381 
382 
vcrskip(Length const & crskip,row_type row)383 void InsetMathGrid::vcrskip(Length const & crskip, row_type row)
384 {
385 	rowinfo_[row].crskip_ = crskip;
386 }
387 
388 
vcrskip(row_type row) const389 Length InsetMathGrid::vcrskip(row_type row) const
390 {
391 	return rowinfo_[row].crskip_;
392 }
393 
394 
metrics(MetricsInfo & mi,Dimension & dim) const395 void InsetMathGrid::metrics(MetricsInfo & mi, Dimension & dim) const
396 {
397 	// let the cells adjust themselves
398 	for (idx_type i = 0; i < nargs(); ++i) {
399 		if (cellinfo_[i].multi_ != CELL_PART_OF_MULTICOLUMN) {
400 			Dimension dimc;
401 			// the 'false' is to make sure that the cell is tall enough
402 			cell(i).metrics(mi, dimc, false);
403 		}
404 	}
405 
406 	BufferView & bv = *mi.base.bv;
407 
408 	// compute absolute sizes of vertical structure
409 	for (row_type row = 0; row < nrows(); ++row) {
410 		int asc  = 0;
411 		int desc = 0;
412 		for (col_type col = 0; col < ncols(); ++col) {
413 			idx_type const i = index(row, col);
414 			if (cellinfo_[i].multi_ != CELL_PART_OF_MULTICOLUMN) {
415 				Dimension const & dimc = cell(i).dimension(bv);
416 				asc  = max(asc,  dimc.asc);
417 				desc = max(desc, dimc.des);
418 			}
419 		}
420 		rowinfo_[row].ascent_  = asc;
421 		rowinfo_[row].descent_ = desc;
422 	}
423 	rowinfo_[nrows()].ascent_  = 0;
424 	rowinfo_[nrows()].descent_ = 0;
425 
426 	// compute vertical offsets
427 	rowinfo_[0].offset_ = 0;
428 	for (row_type row = 1; row <= nrows(); ++row) {
429 		rowinfo_[row].offset_ =
430 			rowinfo_[row - 1].offset_ +
431 			rowinfo_[row - 1].descent_ +
432 			rowinfo_[row - 1].skipPixels(mi) +
433 			rowsep() +
434 			rowinfo_[row].lines_ * hlinesep() +
435 			rowinfo_[row].ascent_;
436 	}
437 
438 	// adjust vertical offset
439 	int h = 0;
440 	switch (v_align_) {
441 		case 't':
442 			h = 0;
443 			break;
444 		case 'b':
445 			h = rowinfo_[nrows() - 1].offset_;
446 			break;
447 		default:
448 			h = rowinfo_[nrows() - 1].offset_ / 2;
449 	}
450 	for (row_type row = 0; row <= nrows(); ++row)
451 		rowinfo_[row].offset_ -= h;
452 
453 
454 	// multicolumn cell widths, as a map from first column to width in a
455 	// vector of last columns.
456 	// This is only used if the grid has more than one row, since for
457 	// one-row grids multicolumn cells do not need special handling
458 	vector<map<col_type, int> > mcolwidths(ncols());
459 
460 	// compute absolute sizes of horizontal structure
461 	for (col_type col = 0; col < ncols(); ++col) {
462 		int wid = 0;
463 		for (row_type row = 0; row < nrows(); ++row) {
464 			idx_type const i = index(row, col);
465 			if (cellinfo_[i].multi_ != CELL_PART_OF_MULTICOLUMN) {
466 				int const w = cell(i).dimension(bv).wid;
467 				col_type const cols = ncellcols(i);
468 				if (cols > 1 && nrows() > 1) {
469 					col_type last = col+cols-1;
470 					LASSERT(last < ncols(), last = ncols()-1);
471 					map<col_type, int>::iterator it =
472 						mcolwidths[last].find(col);
473 					if (it == mcolwidths[last].end())
474 						mcolwidths[last][col] = w;
475 					else
476 						it->second = max(it->second, w);
477 				} else
478 					wid = max(wid, w);
479 			}
480 		}
481 		colinfo_[col].width_ = wid;
482 	}
483 	colinfo_[ncols()].width_  = 0;
484 
485 	// compute horizontal offsets
486 	colinfo_[0].offset_ = border() + colinfo_[0].lines_ * vlinesep();;
487 	for (col_type col = 1; col <= ncols(); ++col) {
488 		colinfo_[col].offset_ =
489 			colinfo_[col - 1].offset_ +
490 			colinfo_[col - 1].width_ +
491 			displayColSpace(col - 1) +
492 			colsep() +
493 			colinfo_[col].lines_ * vlinesep();
494 	}
495 
496 	// increase column widths for multicolumn cells if needed
497 	// FIXME: multicolumn lines are not yet considered
498 	for (col_type last = 0; last < ncols(); ++last) {
499 		map<col_type, int> const & widths = mcolwidths[last];
500 		// We increase the width of the last column of the multicol
501 		// cell (some sort of left alignment). Since we iterate through
502 		// the last and the first columns from left to right, we ensure
503 		// that increased widths of previous columns are correctly
504 		// taken into account for later columns, thus preventing
505 		// unneeded width increasing.
506 		for (map<col_type, int>::const_iterator it = widths.begin();
507 		     it != widths.end(); ++it) {
508 			int const wid = it->second;
509 			col_type const first = it->first;
510 			int const nextoffset =
511 				colinfo_[first].offset_ +
512 				wid +
513 				displayColSpace(last) +
514 				colsep() +
515 				colinfo_[last+1].lines_ * vlinesep();
516 			int const dx = nextoffset - colinfo_[last+1].offset_;
517 			if (dx > 0) {
518 				colinfo_[last].width_ += dx;
519 				for (col_type col = last + 1; col <= ncols(); ++col)
520 					colinfo_[col].offset_ += dx;
521 			}
522 		}
523 	}
524 
525 
526 	dim.wid = colinfo_[ncols() - 1].offset_
527 		+ colinfo_[ncols() - 1].width_
528 		+ vlinesep() * colinfo_[ncols()].lines_
529 		+ border();
530 
531 	dim.asc = - rowinfo_[0].offset_
532 		+ rowinfo_[0].ascent_
533 		+ hlinesep() * rowinfo_[0].lines_
534 		+ border();
535 
536 	dim.des = rowinfo_[nrows() - 1].offset_
537 		+ rowinfo_[nrows() - 1].descent_
538 		+ hlinesep() * rowinfo_[nrows()].lines_
539 		+ border() + 1;
540 
541 
542 /*
543 	// Increase ws_[i] for 'R' columns (except the first one)
544 	for (int i = 1; i < nc_; ++i)
545 		if (align_[i] == 'R')
546 			ws_[i] += 10 * df_width;
547 	// Increase ws_[i] for 'C' column
548 	if (align_[0] == 'C')
549 		if (ws_[0] < 7 * workwidth / 8)
550 			ws_[0] = 7 * workwidth / 8;
551 
552 	// Adjust local tabs
553 	width = colsep();
554 	for (cxrow = row_.begin(); cxrow; ++cxrow) {
555 		int rg = COLSEP;
556 		int lf = 0;
557 		for (int i = 0; i < nc_; ++i) {
558 			bool isvoid = false;
559 			if (cxrow->getTab(i) <= 0) {
560 				cxrow->setTab(i, df_width);
561 				isvoid = true;
562 			}
563 			switch (align_[i]) {
564 			case 'l':
565 				lf = 0;
566 				break;
567 			case 'c':
568 				lf = (ws_[i] - cxrow->getTab(i))/2;
569 				break;
570 			case 'r':
571 			case 'R':
572 				lf = ws_[i] - cxrow->getTab(i);
573 				break;
574 			case 'C':
575 				if (cxrow == row_.begin())
576 					lf = 0;
577 				else if (cxrow.is_last())
578 					lf = ws_[i] - cxrow->getTab(i);
579 				else
580 					lf = (ws_[i] - cxrow->getTab(i))/2;
581 				break;
582 			}
583 			int const ww = (isvoid) ? lf : lf + cxrow->getTab(i);
584 			cxrow->setTab(i, lf + rg);
585 			rg = ws_[i] - ww + colsep();
586 			if (cxrow == row_.begin())
587 				width += ws_[i] + colsep();
588 		}
589 		cxrow->setBaseline(cxrow->getBaseline() - ascent);
590 	}
591 */
592 	dim.wid += leftMargin() + rightMargin();
593 }
594 
595 
vLineHOffset(col_type col,unsigned int line) const596 int InsetMathGrid::vLineHOffset(col_type col, unsigned int line) const
597 {
598 	if (col < ncols())
599 		return leftMargin() + colinfo_[col].offset_
600 			- (colinfo_[col].lines_ - line - 1) * vlinesep()
601 			- vlinesep()/2 - colsep()/2;
602 	else {
603 		LASSERT(col == ncols(), return 0);
604 		return leftMargin() + colinfo_[col-1].offset_ + colinfo_[col-1].width_
605 			+ line * vlinesep()
606 			+ vlinesep()/2 + colsep()/2;
607 	}
608 }
609 
610 
hLineVOffset(row_type row,unsigned int line) const611 int InsetMathGrid::hLineVOffset(row_type row, unsigned int line) const
612 {
613 	return rowinfo_[row].offset_
614 		- rowinfo_[row].ascent_
615 		- line * hlinesep()
616 		- hlinesep()/2 - rowsep()/2;
617 }
618 
619 
draw(PainterInfo & pi,int x,int y) const620 void InsetMathGrid::draw(PainterInfo & pi, int x, int y) const
621 {
622 	BufferView const & bv = *pi.base.bv;
623 
624 	for (idx_type idx = 0; idx < nargs(); ++idx) {
625 		if (cellinfo_[idx].multi_ != CELL_PART_OF_MULTICOLUMN) {
626 			cell(idx).draw(pi,
627 			               x + leftMargin() + cellXOffset(bv, idx),
628 			               y + cellYOffset(idx));
629 
630 			row_type r = row(idx);
631 			int const yy1 = y + hLineVOffset(r, 0);
632 			int const yy2 = y + hLineVOffset(r + 1, rowinfo_[r + 1].lines_ - 1);
633 			auto draw_left_borders = [&](col_type c) {
634 				for (unsigned int i = 0; i < colinfo_[c].lines_; ++i) {
635 					int const xx = x + vLineHOffset(c, i);
636 					pi.pain.line(xx, yy1, xx, yy2, Color_foreground);
637 				}
638 			};
639 			col_type c = col(idx);
640 			// Draw inner left borders cell-by-cell because of multicolumns
641 			draw_left_borders(c);
642 			// Draw the right border (only once)
643 			if (c == 0)
644 				draw_left_borders(ncols());
645 		}
646 	}
647 
648 	// Draw horizontal borders
649 	for (row_type r = 0; r <= nrows(); ++r) {
650 		int const xx1 = x + vLineHOffset(0, 0);
651 		int const xx2 = x + vLineHOffset(ncols(), colinfo_[ncols()].lines_ - 1);
652 		for (unsigned int i = 0; i < rowinfo_[r].lines_; ++i) {
653 			int const yy = y + hLineVOffset(r, i);
654 			pi.pain.line(xx1, yy, xx2, yy, Color_foreground);
655 		}
656 	}
657 }
658 
659 
metricsT(TextMetricsInfo const & mi,Dimension & dim) const660 void InsetMathGrid::metricsT(TextMetricsInfo const & mi, Dimension & dim) const
661 {
662 	// let the cells adjust themselves
663 	for (idx_type i = 0; i < nargs(); ++i)
664 		if (cellinfo_[i].multi_ != CELL_PART_OF_MULTICOLUMN)
665 			cell(i).metricsT(mi, dim);
666 
667 	// compute absolute sizes of vertical structure
668 	for (row_type row = 0; row < nrows(); ++row) {
669 		int asc  = 0;
670 		int desc = 0;
671 		for (col_type col = 0; col < ncols(); ++col) {
672 			idx_type const i = index(row, col);
673 			if (cellinfo_[i].multi_ != CELL_PART_OF_MULTICOLUMN) {
674 				//MathData const & c = cell(i);
675 				// FIXME: BROKEN!
676 				Dimension dimc;
677 				asc  = max(asc,  dimc.ascent());
678 				desc = max(desc, dimc.descent());
679 			}
680 		}
681 		rowinfo_[row].ascent_  = asc;
682 		rowinfo_[row].descent_ = desc;
683 	}
684 	rowinfo_[nrows()].ascent_  = 0;
685 	rowinfo_[nrows()].descent_ = 0;
686 
687 	// compute vertical offsets
688 	rowinfo_[0].offset_ = 0;
689 	for (row_type row = 1; row <= nrows(); ++row) {
690 		rowinfo_[row].offset_  =
691 			rowinfo_[row - 1].offset_  +
692 			rowinfo_[row - 1].descent_ +
693 			//rowinfo_[row - 1].skipPixels(mi) +
694 			1 + //rowsep() +
695 			//rowinfo_[row].lines_ * hlinesep() +
696 			rowinfo_[row].ascent_;
697 	}
698 
699 	// adjust vertical offset
700 	int h = 0;
701 	switch (v_align_) {
702 		case 't':
703 			h = 0;
704 			break;
705 		case 'b':
706 			h = rowinfo_[nrows() - 1].offset_;
707 			break;
708 		default:
709 			h = rowinfo_[nrows() - 1].offset_ / 2;
710 	}
711 	for (row_type row = 0; row <= nrows(); ++row)
712 		rowinfo_[row].offset_ -= h;
713 
714 
715 	// compute absolute sizes of horizontal structure
716 	for (col_type col = 0; col < ncols(); ++col) {
717 		int wid = 0;
718 		for (row_type row = 0; row < nrows(); ++row) {
719 			// FIXME: BROKEN!
720 			//idx_type const i = index(row, col);
721 			//if (cellinfo_[i].multi_ != CELL_PART_OF_MULTICOLUMN)
722 			//	wid = max(wid, cell(i).width());
723 		}
724 		colinfo_[col].width_ = wid;
725 	}
726 	colinfo_[ncols()].width_  = 0;
727 
728 	// compute horizontal offsets
729 	colinfo_[0].offset_ = border();
730 	for (col_type col = 1; col <= ncols(); ++col) {
731 		colinfo_[col].offset_ =
732 			colinfo_[col - 1].offset_ +
733 			colinfo_[col - 1].width_ +
734 			displayColSpace(col - 1) +
735 			1 ; //colsep() +
736 			//colinfo_[col].lines_ * vlinesep();
737 	}
738 
739 
740 	dim.wid  =  colinfo_[ncols() - 1].offset_
741 		       + colinfo_[ncols() - 1].width_
742 		 //+ vlinesep() * colinfo_[ncols()].lines_
743 		       + 2;
744 
745 	dim.asc  = -rowinfo_[0].offset_
746 		       + rowinfo_[0].ascent_
747 		 //+ hlinesep() * rowinfo_[0].lines_
748 		       + 1;
749 
750 	dim.des  =  rowinfo_[nrows() - 1].offset_
751 		       + rowinfo_[nrows() - 1].descent_
752 		 //+ hlinesep() * rowinfo_[nrows()].lines_
753 		       + 1;
754 }
755 
756 
drawT(TextPainter &,int,int) const757 void InsetMathGrid::drawT(TextPainter & /*pain*/, int /*x*/, int /*y*/) const
758 {
759 //	for (idx_type idx = 0; idx < nargs(); ++idx)
760 //		if (cellinfo_[idx].multi_ != CELL_PART_OF_MULTICOLUMN)
761 //			cell(idx).drawT(pain, x + cellXOffset(idx), y + cellYOffset(idx));
762 }
763 
764 
updateBuffer(ParIterator const & it,UpdateType utype)765 void InsetMathGrid::updateBuffer(ParIterator const & it, UpdateType utype)
766 {
767 	// pass down
768 	for (idx_type idx = 0; idx < nargs(); ++idx)
769 		if (cellinfo_[idx].multi_ != CELL_PART_OF_MULTICOLUMN)
770 			cell(idx).updateBuffer(it, utype);
771 }
772 
773 
eolString(row_type row,bool fragile,bool,bool last_eoln) const774 docstring InsetMathGrid::eolString(row_type row, bool fragile,
775 		bool /*latex*/, bool last_eoln) const
776 {
777 	docstring eol;
778 
779 	if (!rowinfo_[row].crskip_.zero())
780 		eol += '[' + from_utf8(rowinfo_[row].crskip_.asLatexString()) + ']';
781 	else if(!rowinfo_[row].allow_newpage_)
782 		eol += '*';
783 
784 	// make sure an upcoming '[' does not break anything
785 	if (row + 1 < nrows()) {
786 		MathData const & c = cell(index(row + 1, 0));
787 		if (!c.empty() && c.front()->getChar() == '[')
788 			//eol += "[0pt]";
789 			eol += "{}";
790 	}
791 
792 	// only add \\ if necessary
793 	if (eol.empty() && row + 1 == nrows() && (nrows() == 1 || !last_eoln))
794 		return docstring();
795 
796 	return (fragile ? "\\protect\\\\" : "\\\\") + eol;
797 }
798 
799 
eocString(col_type col,col_type lastcol) const800 docstring InsetMathGrid::eocString(col_type col, col_type lastcol) const
801 {
802 	if (col + 1 == lastcol)
803 		return docstring();
804 	return from_ascii(" & ");
805 }
806 
807 
addRow(row_type row)808 void InsetMathGrid::addRow(row_type row)
809 {
810 	rowinfo_.insert(rowinfo_.begin() + row + 1, RowInfo());
811 	cells_.insert
812 		(cells_.begin() + (row + 1) * ncols(), ncols(), MathData(buffer_));
813 	cellinfo_.insert
814 		(cellinfo_.begin() + (row + 1) * ncols(), ncols(), CellInfo());
815 }
816 
817 
delRow(row_type row)818 void InsetMathGrid::delRow(row_type row)
819 {
820 	if (nrows() == 1)
821 		return;
822 
823 	cells_type::iterator it = cells_.begin() + row * ncols();
824 	cells_.erase(it, it + ncols());
825 
826 	vector<CellInfo>::iterator jt = cellinfo_.begin() + row * ncols();
827 	cellinfo_.erase(jt, jt + ncols());
828 
829 	rowinfo_.erase(rowinfo_.begin() + row);
830 }
831 
832 
copyRow(row_type row)833 void InsetMathGrid::copyRow(row_type row)
834 {
835 	addRow(row);
836 	for (col_type col = 0; col < ncols(); ++col) {
837 		cells_[(row + 1) * ncols() + col] = cells_[row * ncols() + col];
838 		// copying the cell does not set the buffer
839 		cells_[(row + 1) * ncols() + col].setBuffer(*buffer_);
840 	}
841 }
842 
843 
swapRow(row_type row)844 void InsetMathGrid::swapRow(row_type row)
845 {
846 	if (nrows() == 1)
847 		return;
848 	if (row + 1 == nrows())
849 		--row;
850 	for (col_type col = 0; col < ncols(); ++col)
851 		swap(cells_[row * ncols() + col], cells_[(row + 1) * ncols() + col]);
852 }
853 
854 
addCol(col_type newcol)855 void InsetMathGrid::addCol(col_type newcol)
856 {
857 	const col_type nc = ncols();
858 	const row_type nr = nrows();
859 	cells_type new_cells((nc + 1) * nr);
860 	vector<CellInfo> new_cellinfo((nc + 1) * nr);
861 
862 	for (row_type row = 0; row < nr; ++row)
863 		for (col_type col = 0; col < nc; ++col) {
864 			new_cells[row * (nc + 1) + col + (col >= newcol)]
865 				= cells_[row * nc + col];
866 			new_cellinfo[row * (nc + 1) + col + (col >= newcol)]
867 				= cellinfo_[row * nc + col];
868 		}
869 	swap(cells_, new_cells);
870 	// copying cells loses the buffer reference
871 	setBuffer(*buffer_);
872 	swap(cellinfo_, new_cellinfo);
873 
874 	ColInfo inf;
875 	inf.skip_  = defaultColSpace(newcol);
876 	inf.align_ = defaultColAlign(newcol);
877 	colinfo_.insert(colinfo_.begin() + newcol, inf);
878 }
879 
880 
delCol(col_type col)881 void InsetMathGrid::delCol(col_type col)
882 {
883 	if (ncols() == 1)
884 		return;
885 
886 	cells_type tmpcells;
887 	vector<CellInfo> tmpcellinfo;
888 	for (col_type i = 0; i < nargs(); ++i)
889 		if (i % ncols() != col) {
890 			tmpcells.push_back(cells_[i]);
891 			tmpcellinfo.push_back(cellinfo_[i]);
892 		}
893 	swap(cells_, tmpcells);
894 	// copying cells loses the buffer reference
895 	setBuffer(*buffer_);
896 	swap(cellinfo_, tmpcellinfo);
897 
898 	colinfo_.erase(colinfo_.begin() + col);
899 }
900 
901 
copyCol(col_type col)902 void InsetMathGrid::copyCol(col_type col)
903 {
904 	addCol(col+1);
905 	for (row_type row = 0; row < nrows(); ++row) {
906 		cells_[row * ncols() + col + 1] = cells_[row * ncols() + col];
907 		// copying the cell does not set the buffer
908 		cells_[row * ncols() + col + 1].setBuffer(*buffer_);
909 	}
910 }
911 
912 
swapCol(col_type col)913 void InsetMathGrid::swapCol(col_type col)
914 {
915 	if (ncols() == 1)
916 		return;
917 	if (col + 1 == ncols())
918 		--col;
919 	for (row_type row = 0; row < nrows(); ++row)
920 		swap(cells_[row * ncols() + col], cells_[row * ncols() + col + 1]);
921 }
922 
923 
cellXOffset(BufferView const & bv,idx_type idx) const924 int InsetMathGrid::cellXOffset(BufferView const & bv, idx_type idx) const
925 {
926 	if (cellinfo_[idx].multi_ == CELL_PART_OF_MULTICOLUMN)
927 		return 0;
928 	col_type c = col(idx);
929 	int x = colinfo_[c].offset_;
930 	char align = displayColAlign(idx);
931 	Dimension const & celldim = cell(idx).dimension(bv);
932 	if (align == 'r' || align == 'R')
933 		x += cellWidth(idx) - celldim.wid;
934 	if (align == 'c' || align == 'C')
935 		x += (cellWidth(idx) - celldim.wid) / 2;
936 	return x;
937 }
938 
939 
cellYOffset(idx_type idx) const940 int InsetMathGrid::cellYOffset(idx_type idx) const
941 {
942 	return rowinfo_[row(idx)].offset_;
943 }
944 
945 
cellWidth(idx_type idx) const946 int InsetMathGrid::cellWidth(idx_type idx) const
947 {
948 	switch (cellinfo_[idx].multi_) {
949 	case CELL_NORMAL:
950 		return colinfo_[col(idx)].width_;
951 	case CELL_BEGIN_OF_MULTICOLUMN: {
952 		col_type c1 = col(idx);
953 		col_type c2 = c1 + ncellcols(idx);
954 		return colinfo_[c2].offset_
955 			- colinfo_[c1].offset_
956 			- displayColSpace(c2)
957 			- colsep()
958 			- colinfo_[c2].lines_ * vlinesep();
959 	}
960 	case CELL_PART_OF_MULTICOLUMN:
961 		return 0;
962 	}
963 	return 0;
964 }
965 
966 
idxUpDown(Cursor & cur,bool up) const967 bool InsetMathGrid::idxUpDown(Cursor & cur, bool up) const
968 {
969 	if (up) {
970 		if (cur.row() == 0)
971 			return false;
972 		cur.idx() -= ncols();
973 	} else {
974 		if (cur.row() + 1 >= nrows())
975 			return false;
976 		cur.idx() += ncols();
977 	}
978 	// If we are in a multicolumn cell, move to the "real" cell
979 	while (cellinfo_[cur.idx()].multi_ == CELL_PART_OF_MULTICOLUMN) {
980 		LASSERT(cur.idx() > 0, return false);
981 		--cur.idx();
982 	}
983 	// FIXME: this is only a workaround to avoid a crash if the inset
984 	// in not in coord cache. The best would be to force a FitCursor
985 	// operation.
986 	CoordCache::Arrays const & arraysCache = cur.bv().coordCache().arrays();
987 	if (arraysCache.has(&cur.cell()))
988 		cur.pos() = cur.cell().x2pos(&cur.bv(), cur.x_target() - cur.cell().xo(cur.bv()));
989 	else
990 		cur.pos() = 0;
991 	return true;
992 }
993 
994 
idxBackward(Cursor & cur) const995 bool InsetMathGrid::idxBackward(Cursor & cur) const
996 {
997 	// leave matrix if at the front edge
998 	if (cur.col() == 0)
999 		return false;
1000 	--cur.idx();
1001 	// If we are in a multicolumn cell, move to the "real" cell
1002 	while (cellinfo_[cur.idx()].multi_ == CELL_PART_OF_MULTICOLUMN) {
1003 		LASSERT(cur.idx() > 0, return false);
1004 		--cur.idx();
1005 	}
1006 	cur.pos() = cur.lastpos();
1007 	return true;
1008 }
1009 
1010 
idxForward(Cursor & cur) const1011 bool InsetMathGrid::idxForward(Cursor & cur) const
1012 {
1013 	// leave matrix if at the back edge
1014 	if (cur.col() + 1 == ncols())
1015 		return false;
1016 	++cur.idx();
1017 	// If we are in a multicolumn cell, move to the next cell
1018 	while (cellinfo_[cur.idx()].multi_ == CELL_PART_OF_MULTICOLUMN) {
1019 		// leave matrix if at the back edge
1020 		if (cur.col() + 1 == ncols())
1021 			return false;
1022 		++cur.idx();
1023 	}
1024 	cur.pos() = 0;
1025 	return true;
1026 }
1027 
1028 
idxFirst(Cursor & cur) const1029 bool InsetMathGrid::idxFirst(Cursor & cur) const
1030 {
1031 	switch (v_align_) {
1032 		case 't':
1033 			cur.idx() = 0;
1034 			break;
1035 		case 'b':
1036 			cur.idx() = (nrows() - 1) * ncols();
1037 			break;
1038 		default:
1039 			cur.idx() = ((nrows() - 1) / 2) * ncols();
1040 	}
1041 	// If we are in a multicolumn cell, move to the "real" cell
1042 	while (cellinfo_[cur.idx()].multi_ == CELL_PART_OF_MULTICOLUMN) {
1043 		LASSERT(cur.idx() > 0, return false);
1044 		--cur.idx();
1045 	}
1046 	cur.pos() = 0;
1047 	return true;
1048 }
1049 
1050 
idxLast(Cursor & cur) const1051 bool InsetMathGrid::idxLast(Cursor & cur) const
1052 {
1053 	switch (v_align_) {
1054 		case 't':
1055 			cur.idx() = ncols() - 1;
1056 			break;
1057 		case 'b':
1058 			cur.idx() = nargs() - 1;
1059 			break;
1060 		default:
1061 			cur.idx() = ((nrows() - 1) / 2 + 1) * ncols() - 1;
1062 	}
1063 	// If we are in a multicolumn cell, move to the "real" cell
1064 	while (cellinfo_[cur.idx()].multi_ == CELL_PART_OF_MULTICOLUMN) {
1065 		LASSERT(cur.idx() > 0, return false);
1066 		--cur.idx();
1067 	}
1068 	cur.pos() = cur.lastpos();
1069 	return true;
1070 }
1071 
1072 
idxDelete(idx_type & idx)1073 bool InsetMathGrid::idxDelete(idx_type & idx)
1074 {
1075 	// nothing to do if we have just one row
1076 	if (nrows() == 1)
1077 		return false;
1078 
1079 	// nothing to do if we are in the middle of the last row of the inset
1080 	if (idx + ncols() > nargs())
1081 		return false;
1082 
1083 	// try to delete entire sequence of ncols() empty cells if possible
1084 	for (idx_type i = idx; i < idx + ncols(); ++i)
1085 		if (!cell(i).empty())
1086 			return false;
1087 
1088 	// move cells if necessary
1089 	for (idx_type i = index(row(idx), 0); i < idx; ++i)
1090 		swap(cell(i), cell(i + ncols()));
1091 
1092 	delRow(row(idx));
1093 
1094 	if (idx >= nargs())
1095 		idx = nargs() - 1;
1096 
1097 	// undo effect of Ctrl-Tab (i.e. pull next cell)
1098 	//if (idx + 1 != nargs())
1099 	//	cell(idx).swap(cell(idx + 1));
1100 
1101 	// we handled the event..
1102 	return true;
1103 }
1104 
1105 
1106 // reimplement old behaviour when pressing Delete in the last position
1107 // of a cell
idxGlue(idx_type idx)1108 void InsetMathGrid::idxGlue(idx_type idx)
1109 {
1110 	col_type c = col(idx);
1111 	if (c + 1 == ncols()) {
1112 		if (row(idx) + 1 != nrows()) {
1113 			for (col_type cc = 0; cc < ncols(); ++cc)
1114 				cell(idx).append(cell(idx + cc + 1));
1115 			delRow(row(idx) + 1);
1116 		}
1117 	} else {
1118 		idx_type idx_next = idx + 1;
1119 		while (idx_next < nargs() &&
1120 		       cellinfo_[idx_next].multi_ == CELL_PART_OF_MULTICOLUMN)
1121 			++idx_next;
1122 		if (idx_next < nargs())
1123 			cell(idx).append(cell(idx_next));
1124 		col_type oldcol = c + 1;
1125 		for (col_type cc = c + 2; cc < ncols(); ++cc)
1126 			cell(idx - oldcol + cc) = cell(idx - oldcol + 1 + cc);
1127 		cell(idx - c + ncols() - 1).clear();
1128 	}
1129 }
1130 
1131 
rowinfo(row_type row) const1132 InsetMathGrid::RowInfo const & InsetMathGrid::rowinfo(row_type row) const
1133 {
1134 	return rowinfo_[row];
1135 }
1136 
1137 
rowinfo(row_type row)1138 InsetMathGrid::RowInfo & InsetMathGrid::rowinfo(row_type row)
1139 {
1140 	return rowinfo_[row];
1141 }
1142 
1143 
idxBetween(idx_type idx,idx_type from,idx_type to) const1144 bool InsetMathGrid::idxBetween(idx_type idx, idx_type from, idx_type to) const
1145 {
1146 	row_type const ri = row(idx);
1147 	row_type const r1 = min(row(from), row(to));
1148 	row_type const r2 = max(row(from), row(to));
1149 	col_type const ci = col(idx);
1150 	col_type const c1 = min(col(from), col(to));
1151 	col_type const c2 = max(col(from), col(to));
1152 	return r1 <= ri && ri <= r2 && c1 <= ci && ci <= c2;
1153 }
1154 
1155 
normalize(NormalStream & os) const1156 void InsetMathGrid::normalize(NormalStream & os) const
1157 {
1158 	os << "[grid ";
1159 	for (row_type row = 0; row < nrows(); ++row) {
1160 		os << "[row ";
1161 		for (col_type col = 0; col < ncols(); ++col) {
1162 			idx_type const i = index(row, col);
1163 			switch (cellinfo_[i].multi_) {
1164 			case CELL_NORMAL:
1165 				os << "[cell " << cell(i) << ']';
1166 				break;
1167 			case CELL_BEGIN_OF_MULTICOLUMN:
1168 				os << "[cell colspan="
1169 				   << static_cast<int>(ncellcols(i)) << ' '
1170 				   << cell(i) << ']';
1171 				break;
1172 			case CELL_PART_OF_MULTICOLUMN:
1173 				break;
1174 			}
1175 		}
1176 		os << ']';
1177 	}
1178 	os << ']';
1179 }
1180 
1181 
mathmlize(MathStream & os) const1182 void InsetMathGrid::mathmlize(MathStream & os) const
1183 {
1184 	bool const havetable = nrows() > 1 || ncols() > 1;
1185 	if (havetable)
1186 		os << MTag("mtable");
1187 	char const * const celltag = havetable ? "mtd" : "mrow";
1188 	for (row_type row = 0; row < nrows(); ++row) {
1189 		if (havetable)
1190 			os << MTag("mtr");
1191 		for (col_type col = 0; col < ncols(); ++col) {
1192 			idx_type const i = index(row, col);
1193 			if (cellinfo_[i].multi_ != CELL_PART_OF_MULTICOLUMN) {
1194 				col_type const cellcols = ncellcols(i);
1195 				ostringstream attr;
1196 				if (havetable && cellcols > 1)
1197 					attr << "colspan='" << cellcols << '\'';
1198 				os << MTag(celltag, attr.str());
1199 				os << cell(index(row, col));
1200 				os << ETag(celltag);
1201 			}
1202 		}
1203 		if (havetable)
1204 			os << ETag("mtr");
1205 	}
1206 	if (havetable)
1207 		os << ETag("mtable");
1208 }
1209 
1210 
1211 // FIXME XHTML
1212 // We need to do something about alignment here.
htmlize(HtmlStream & os,string attrib) const1213 void InsetMathGrid::htmlize(HtmlStream & os, string attrib) const
1214 {
1215 	bool const havetable = nrows() > 1 || ncols() > 1;
1216 	if (!havetable) {
1217 		os << cell(index(0, 0));
1218 		return;
1219 	}
1220 	os << MTag("table", attrib);
1221 	for (row_type row = 0; row < nrows(); ++row) {
1222 		os << MTag("tr");
1223 		for (col_type col = 0; col < ncols(); ++col) {
1224 			idx_type const i = index(row, col);
1225 			if (cellinfo_[i].multi_ != CELL_PART_OF_MULTICOLUMN) {
1226 				col_type const cellcols = ncellcols(i);
1227 				ostringstream attr;
1228 				if (cellcols > 1)
1229 					attr << "colspan='" << cellcols << '\'';
1230 				os << MTag("td", attr.str());
1231 				os << cell(index(row, col));
1232 				os << ETag("td");
1233 			}
1234 		}
1235 		os << ETag("tr");
1236 	}
1237 	os << ETag("table");
1238 }
1239 
1240 
htmlize(HtmlStream & os) const1241 void InsetMathGrid::htmlize(HtmlStream & os) const
1242 {
1243 	htmlize(os, "class='mathtable'");
1244 }
1245 
1246 
validate(LaTeXFeatures & features) const1247 void InsetMathGrid::validate(LaTeXFeatures & features) const
1248 {
1249 	if (features.runparams().math_flavor == OutputParams::MathAsHTML
1250 	    && (nrows() > 1 || ncols() > 1)) {
1251 		// CSS taken from InsetMathCases
1252 		features.addCSSSnippet(
1253 			"table.mathtable{display: inline-block; text-align: center; border: none;"
1254 			"border-left: thin solid black; vertical-align: middle; padding-left: 0.5ex;}\n"
1255 			"table.mathtable td {text-align: left; border: none;}");
1256 	}
1257 	InsetMathNest::validate(features);
1258 }
1259 
1260 
write(WriteStream & os) const1261 void InsetMathGrid::write(WriteStream & os) const
1262 {
1263 	write(os, 0, 0, nrows(), ncols());
1264 }
1265 
write(WriteStream & os,row_type beg_row,col_type beg_col,row_type end_row,col_type end_col) const1266 void InsetMathGrid::write(WriteStream & os,
1267 			  row_type beg_row, col_type beg_col,
1268 			  row_type end_row, col_type end_col) const
1269 {
1270 	MathEnsurer ensurer(os, false);
1271 	docstring eol;
1272 	for (row_type row = beg_row; row < end_row; ++row) {
1273 		os << verboseHLine(rowinfo_[row].lines_);
1274 		// don't write & and empty cells at end of line,
1275 		// unless there are vertical lines
1276 		col_type lastcol = 0;
1277 		bool emptyline = true;
1278 		bool last_eoln = true;
1279 		for (col_type col = beg_col; col < end_col; ++col) {
1280 			idx_type const idx = index(row, col);
1281 			bool const empty_cell = cell(idx).empty();
1282 			if (!empty_cell || cellinfo_[idx].multi_ != CELL_NORMAL)
1283 				last_eoln = false;
1284 			if (!empty_cell || cellinfo_[idx].multi_ != CELL_NORMAL ||
1285 			    colinfo_[col + 1].lines_) {
1286 				lastcol = col + 1;
1287 				emptyline = false;
1288 			}
1289 		}
1290 		for (col_type col = beg_col; col < end_col;) {
1291 			int nccols = 1;
1292 			idx_type const idx = index(row, col);
1293 			TexRow::RowEntry entry = TexRow::mathEntry(id(),idx);
1294 			os.texrow().start(entry);
1295 			if (col >= lastcol) {
1296 				++col;
1297 				continue;
1298 			}
1299 			Changer dummy = os.changeRowEntry(entry);
1300 			if (cellinfo_[idx].multi_ == CELL_BEGIN_OF_MULTICOLUMN) {
1301 				size_t s = col + 1;
1302 				while (s < ncols() &&
1303 				       cellinfo_[index(row, s)].multi_ == CELL_PART_OF_MULTICOLUMN)
1304 					s++;
1305 				nccols = s - col;
1306 				os << "\\multicolumn{" << nccols
1307 				   << "}{" << cellinfo_[idx].align_
1308 				   << "}{";
1309 			}
1310 			os << cell(idx);
1311 			if (os.pendingBrace())
1312 				ModeSpecifier specifier(os, TEXT_MODE);
1313 			if (cellinfo_[idx].multi_ == CELL_BEGIN_OF_MULTICOLUMN)
1314 				os << '}';
1315 			os << eocString(col + nccols - 1, lastcol);
1316 			col += nccols;
1317 		}
1318 		eol = eolString(row, os.fragile(), os.latex(), last_eoln);
1319 		os << eol;
1320 		// append newline only if line wasn't completely empty
1321 		// and the formula is not written on a single line
1322 		bool const empty = emptyline && eol.empty();
1323 		if (!empty && nrows() > 1)
1324 			os << "\n";
1325 	}
1326 	// @TODO use end_row instead of nrows() ?
1327 	docstring const s = verboseHLine(rowinfo_[nrows()].lines_);
1328 	if (!s.empty()) {
1329 		if (eol.empty()) {
1330 			if (os.fragile())
1331 				os << "\\protect";
1332 			os << "\\\\";
1333 		}
1334 		os << s;
1335 	}
1336 }
1337 
1338 
colsep() const1339 int InsetMathGrid::colsep() const
1340 {
1341 	return 6;
1342 }
1343 
1344 
rowsep() const1345 int InsetMathGrid::rowsep() const
1346 {
1347 	return 6;
1348 }
1349 
1350 
hlinesep() const1351 int InsetMathGrid::hlinesep() const
1352 {
1353 	return 3;
1354 }
1355 
1356 
vlinesep() const1357 int InsetMathGrid::vlinesep() const
1358 {
1359 	return 3;
1360 }
1361 
1362 
border() const1363 int InsetMathGrid::border() const
1364 {
1365 	return 1;
1366 }
1367 
1368 
splitCell(Cursor & cur)1369 void InsetMathGrid::splitCell(Cursor & cur)
1370 {
1371 	if (cur.idx() == cur.lastidx())
1372 		return;
1373 	MathData ar = cur.cell();
1374 	ar.erase(0, cur.pos());
1375 	cur.cell().erase(cur.pos(), cur.lastpos());
1376 	++cur.idx();
1377 	while (cur.idx() << nargs() &&
1378 	       cellinfo_[cur.idx()].multi_ == CELL_BEGIN_OF_MULTICOLUMN)
1379 		++cur.idx();
1380 	cur.pos() = 0;
1381 	cur.cell().insert(0, ar);
1382 }
1383 
1384 
displayColAlign(idx_type idx) const1385 char InsetMathGrid::displayColAlign(idx_type idx) const
1386 {
1387 	if (cellinfo_[idx].multi_ == CELL_BEGIN_OF_MULTICOLUMN) {
1388 		// align_ may also contain lines like "||r|", so this is
1389 		// not complete, but we catch at least the simple cases.
1390 		if (cellinfo_[idx].align_ == "c")
1391 			return 'c';
1392 		if (cellinfo_[idx].align_ == "l")
1393 			return 'l';
1394 		if (cellinfo_[idx].align_ == "r")
1395 			return 'r';
1396 	}
1397 	return colinfo_[col(idx)].align_;
1398 }
1399 
1400 
displayColSpace(col_type col) const1401 int InsetMathGrid::displayColSpace(col_type col) const
1402 {
1403 	return colinfo_[col].skip_;
1404 }
1405 
doDispatch(Cursor & cur,FuncRequest & cmd)1406 void InsetMathGrid::doDispatch(Cursor & cur, FuncRequest & cmd)
1407 {
1408 	//lyxerr << "*** InsetMathGrid: request: " << cmd << endl;
1409 
1410 	Parse::flags parseflg = Parse::QUIET | Parse::USETEXT;
1411 
1412 	FuncCode const act = cmd.action();
1413 	switch (act) {
1414 
1415 	// insert file functions
1416 	case LFUN_LINE_DELETE_FORWARD:
1417 		cur.recordUndoInset();
1418 		//autocorrect_ = false;
1419 		//macroModeClose();
1420 		//if (selection_) {
1421 		//	selDel();
1422 		//	break;
1423 		//}
1424 		if (nrows() > 1)
1425 			delRow(cur.row());
1426 		if (cur.idx() > cur.lastidx())
1427 			cur.idx() = cur.lastidx();
1428 		if (cur.pos() > cur.lastpos())
1429 			cur.pos() = cur.lastpos();
1430 		break;
1431 
1432 	case LFUN_CELL_SPLIT:
1433 		cur.recordUndo();
1434 		splitCell(cur);
1435 		break;
1436 
1437 	case LFUN_CELL_BACKWARD:
1438 		// See below.
1439 		cur.selection(false);
1440 		if (!idxPrev(cur)) {
1441 			cmd = FuncRequest(LFUN_FINISHED_BACKWARD);
1442 			cur.undispatched();
1443 		}
1444 		break;
1445 
1446 	case LFUN_CELL_FORWARD:
1447 		// Can't handle selection by additional 'shift' as this is
1448 		// hard bound to LFUN_CELL_BACKWARD
1449 		cur.selection(false);
1450 		if (!idxNext(cur)) {
1451 			cmd = FuncRequest(LFUN_FINISHED_FORWARD);
1452 			cur.undispatched();
1453 		}
1454 		break;
1455 
1456 	case LFUN_NEWLINE_INSERT: {
1457 		cur.recordUndoInset();
1458 		row_type const r = cur.row();
1459 		addRow(r);
1460 
1461 		// split line
1462 		for (col_type c = col(cur.idx()) + 1; c < ncols(); ++c)
1463 			swap(cell(index(r, c)), cell(index(r + 1, c)));
1464 
1465 		// split cell
1466 		splitCell(cur);
1467 		if (ncols() > 1)
1468 			swap(cell(cur.idx()), cell(cur.idx() + ncols() - 1));
1469 		if (cur.idx() > 0)
1470 			--cur.idx();
1471 		cur.pos() = cur.lastpos();
1472 		cur.forceBufferUpdate();
1473 		//mathcursor->normalize();
1474 		//cmd = FuncRequest(LFUN_FINISHED_BACKWARD);
1475 		break;
1476 	}
1477 
1478 	case LFUN_TABULAR_FEATURE: {
1479 		cur.recordUndoInset();
1480 		//lyxerr << "handling tabular-feature " << to_utf8(cmd.argument()) << endl;
1481 		istringstream is(to_utf8(cmd.argument()));
1482 		string s;
1483 		is >> s;
1484 		if (s == "valign-top")
1485 			setVerticalAlignment('t');
1486 		else if (s == "valign-middle")
1487 			setVerticalAlignment('c');
1488 		else if (s == "valign-bottom")
1489 			setVerticalAlignment('b');
1490 		else if (s == "align-left")
1491 			setHorizontalAlignment('l', cur.col());
1492 		else if (s == "align-right")
1493 			setHorizontalAlignment('r', cur.col());
1494 		else if (s == "align-center")
1495 			setHorizontalAlignment('c', cur.col());
1496 		else if (s == "append-row")
1497 			for (int i = 0, n = extractInt(is); i < n; ++i)
1498 				addRow(cur.row());
1499 		else if (s == "delete-row") {
1500 			cur.clearSelection(); // bug 4323
1501 			for (int i = 0, n = extractInt(is); i < n; ++i) {
1502 				delRow(cur.row());
1503 				if (cur.idx() >= nargs())
1504 					cur.idx() -= ncols();
1505 			}
1506 			cur.pos() = 0; // trick, see below
1507 		}
1508 		else if (s == "copy-row") {
1509 			// Here (as later) we save the cursor col/row
1510 			// in order to restore it after operation.
1511 			row_type const r = cur.row();
1512 			col_type const c = cur.col();
1513 			for (int i = 0, n = extractInt(is); i < n; ++i)
1514 				copyRow(cur.row());
1515 			cur.idx() = index(r, c);
1516 		}
1517 		else if (s == "swap-row") {
1518 			swapRow(cur.row());
1519 			// Trick to suppress same-idx-means-different-cell
1520 			// assertion crash:
1521 			cur.pos() = 0;
1522 		}
1523 		else if (s == "add-hline-above")
1524 			rowinfo_[cur.row()].lines_++;
1525 		else if (s == "add-hline-below")
1526 			rowinfo_[cur.row()+1].lines_++;
1527 		else if (s == "delete-hline-above")
1528 			rowinfo_[cur.row()].lines_--;
1529 		else if (s == "delete-hline-below")
1530 			rowinfo_[cur.row()+1].lines_--;
1531 		else if (s == "append-column") {
1532 			row_type const r = cur.row();
1533 			col_type const c = cur.col();
1534 			for (int i = 0, n = extractInt(is); i < n; ++i)
1535 				addCol(cur.col() + 1);
1536 			cur.idx() = index(r, c);
1537 		}
1538 		else if (s == "delete-column") {
1539 			cur.clearSelection(); // bug 4323
1540 			row_type const r = cur.row();
1541 			col_type const c = cur.col();
1542 			for (int i = 0, n = extractInt(is); i < n; ++i)
1543 				delCol(col(cur.idx()));
1544 			cur.idx() = index(r, min(c, cur.ncols() - 1));
1545 			cur.pos() = 0; // trick, see above
1546 		}
1547 		else if (s == "copy-column") {
1548 			row_type const r = cur.row();
1549 			col_type const c = cur.col();
1550 			copyCol(cur.col());
1551 			cur.idx() = index(r, c);
1552 		}
1553 		else if (s == "swap-column") {
1554 			swapCol(cur.col());
1555 			cur.pos() = 0; // trick, see above
1556 		}
1557 		else if (s == "add-vline-left") {
1558 			colinfo_[cur.col()].lines_++;
1559 			if (!colinfo_[cur.col()].special_.empty())
1560 				colinfo_[cur.col()].special_ += '|';
1561 		}
1562 		else if (s == "add-vline-right") {
1563 			colinfo_[cur.col()+1].lines_++;
1564 			if (!colinfo_[cur.col()+1].special_.empty())
1565 				colinfo_[cur.col()+1].special_.insert(0, 1, '|');
1566 		}
1567 		else if (s == "delete-vline-left") {
1568 			colinfo_[cur.col()].lines_--;
1569 			docstring & special = colinfo_[cur.col()].special_;
1570 			if (!special.empty()) {
1571 				docstring::size_type i = special.rfind('|');
1572 				LASSERT(i != docstring::npos, break);
1573 				special.erase(i, 1);
1574 			}
1575 		}
1576 		else if (s == "delete-vline-right") {
1577 			colinfo_[cur.col()+1].lines_--;
1578 			docstring & special = colinfo_[cur.col()+1].special_;
1579 			if (!special.empty()) {
1580 				docstring::size_type i = special.find('|');
1581 				LASSERT(i != docstring::npos, break);
1582 				special.erase(i, 1);
1583 			}
1584 		}
1585 		else {
1586 			cur.undispatched();
1587 			break;
1588 		}
1589 		// perhaps this should be FINISHED_BACKWARD -- just for clarity?
1590 		//lyxerr << "returning FINISHED_LEFT" << endl;
1591 		break;
1592 	}
1593 
1594 	case LFUN_CLIPBOARD_PASTE:
1595 		parseflg |= Parse::VERBATIM;
1596 		// fall through
1597 	case LFUN_PASTE: {
1598 		if (cur.currentMode() != MATH_MODE)
1599 			parseflg |= Parse::TEXTMODE;
1600 		cur.message(_("Paste"));
1601 		cap::replaceSelection(cur);
1602 		docstring topaste;
1603 		if (cmd.argument().empty() && !theClipboard().isInternal())
1604 			topaste = theClipboard().getAsText(frontend::Clipboard::PlainTextType);
1605 		else {
1606 			idocstringstream is(cmd.argument());
1607 			int n = 0;
1608 			is >> n;
1609 			topaste = cap::selection(n, buffer().params().documentClassPtr());
1610 		}
1611 		InsetMathGrid grid(buffer_, 1, 1);
1612 		if (!topaste.empty())
1613 			if ((topaste.size() == 1 && isAscii(topaste))
1614 			    || !mathed_parse_normal(grid, topaste, parseflg)) {
1615 				resetGrid(grid);
1616 				mathed_parse_normal(grid, topaste, parseflg | Parse::VERBATIM);
1617 			}
1618 
1619 		bool hline_enabled = false;
1620 		FuncRequest fr = FuncRequest(LFUN_TABULAR_FEATURE, "add-hline-above");
1621 		FuncStatus status;
1622 		if (getStatus(cur, fr, status))
1623 			hline_enabled = status.enabled();
1624 		if (grid.nargs() == 1) {
1625 			// single cell/part of cell
1626 			cur.recordUndoInset();
1627 			cur.cell().insert(cur.pos(), grid.cell(0));
1628 			cur.pos() += grid.cell(0).size();
1629 			if (hline_enabled)
1630 				rowinfo_[cur.row()].lines_ += grid.rowinfo_[0].lines_;
1631 			else {
1632 				for (unsigned int l = 0; l < grid.rowinfo_[0].lines_; ++l) {
1633 					 cur.cell().insert(0,
1634 						MathAtom(new InsetMathUnknown(from_ascii("\\hline"))));
1635 					 cur.pos()++;
1636 				}
1637 			}
1638 		} else {
1639 			// multiple cells
1640 			cur.recordUndoInset();
1641 			col_type const numcols =
1642 				min(grid.ncols(), ncols() - col(cur.idx()));
1643 			row_type const numrows =
1644 				min(grid.nrows(), nrows() - cur.row());
1645 			for (row_type r = 0; r < numrows; ++r) {
1646 				for (col_type c = 0; c < numcols; ++c) {
1647 					idx_type i = index(r + cur.row(), c + col(cur.idx()));
1648 					cell(i).insert(0, grid.cell(grid.index(r, c)));
1649 				}
1650 				if (hline_enabled)
1651 					rowinfo_[r].lines_ += grid.rowinfo_[r].lines_;
1652 				else {
1653 					for (unsigned int l = 0; l < grid.rowinfo_[r].lines_; ++l) {
1654 						idx_type i = index(r + cur.row(), 0);
1655 						cell(i).insert(0,
1656 							MathAtom(new InsetMathUnknown(from_ascii("\\hline"))));
1657 					}
1658 				}
1659 				// append the left over horizontal cells to the last column
1660 				idx_type i = index(r + cur.row(), ncols() - 1);
1661 				for (InsetMath::col_type c = numcols; c < grid.ncols(); ++c)
1662 					cell(i).append(grid.cell(grid.index(r, c)));
1663 			}
1664 			// append the left over vertical cells to the last _cell_
1665 			idx_type i = nargs() - 1;
1666 			for (row_type r = numrows; r < grid.nrows(); ++r) {
1667 				for (col_type c = 0; c < grid.ncols(); ++c)
1668 					cell(i).append(grid.cell(grid.index(r, c)));
1669 				if (hline_enabled)
1670 					rowinfo_[r].lines_ += grid.rowinfo_[r].lines_;
1671 				else {
1672 					for (unsigned int l = 0; l < grid.rowinfo_[r].lines_; ++l) {
1673 						cell(i).insert(0,
1674 							MathAtom(new InsetMathUnknown(from_ascii("\\hline"))));
1675 					}
1676 				}
1677 			}
1678 		}
1679 		cur.clearSelection(); // bug 393
1680 		// FIXME audit setBuffer calls
1681 		cur.inset().setBuffer(*buffer_);
1682 		cur.forceBufferUpdate();
1683 		cur.finishUndo();
1684 		break;
1685 	}
1686 
1687 	case LFUN_LINE_BEGIN:
1688 		cur.screenUpdateFlags(Update::Decoration | Update::FitCursor);
1689 		// fall through
1690 	case LFUN_LINE_BEGIN_SELECT:
1691 		cur.selHandle(act == LFUN_WORD_BACKWARD_SELECT ||
1692 				act == LFUN_WORD_LEFT_SELECT ||
1693 				act == LFUN_LINE_BEGIN_SELECT);
1694 		cur.macroModeClose();
1695 		if (cur.pos() != 0) {
1696 			cur.pos() = 0;
1697 		} else if (cur.idx() % cur.ncols() != 0) {
1698 			cur.idx() -= cur.idx() % cur.ncols();
1699 			cur.pos() = 0;
1700 		} else if (cur.idx() != 0) {
1701 			cur.idx() = 0;
1702 			cur.pos() = 0;
1703 		} else {
1704 			cmd = FuncRequest(LFUN_FINISHED_BACKWARD);
1705 			cur.undispatched();
1706 		}
1707 		break;
1708 
1709 	case LFUN_LINE_END:
1710 		cur.screenUpdateFlags(Update::Decoration | Update::FitCursor);
1711 		// fall through
1712 	case LFUN_LINE_END_SELECT:
1713 		cur.selHandle(act == LFUN_WORD_FORWARD_SELECT ||
1714 				act == LFUN_WORD_RIGHT_SELECT ||
1715 				act == LFUN_LINE_END_SELECT);
1716 		cur.macroModeClose();
1717 		cur.clearTargetX();
1718 		if (cur.pos() != cur.lastpos()) {
1719 			cur.pos() = cur.lastpos();
1720 		} else if ((cur.idx() + 1) % cur.ncols() != 0) {
1721 			cur.idx() += cur.ncols() - 1 - cur.idx() % cur.ncols();
1722 			cur.pos() = cur.lastpos();
1723 		} else if (cur.idx() != cur.lastidx()) {
1724 			cur.idx() = cur.lastidx();
1725 			cur.pos() = cur.lastpos();
1726 		} else {
1727 			cmd = FuncRequest(LFUN_FINISHED_FORWARD);
1728 			cur.undispatched();
1729 		}
1730 		break;
1731 
1732 	default:
1733 		InsetMathNest::doDispatch(cur, cmd);
1734 	}
1735 }
1736 
1737 
getStatus(Cursor & cur,FuncRequest const & cmd,FuncStatus & status) const1738 bool InsetMathGrid::getStatus(Cursor & cur, FuncRequest const & cmd,
1739 		FuncStatus & status) const
1740 {
1741 	switch (cmd.action()) {
1742 	case LFUN_TABULAR_FEATURE: {
1743 		string s = cmd.getArg(0);
1744 		if (&cur.inset() != this) {
1745 			// Table actions requires that the cursor is _inside_ the
1746 			// table.
1747 			status.setEnabled(false);
1748 			status.message(from_utf8(N_("Cursor not in table")));
1749 			return true;
1750 		}
1751 		if (nrows() <= 1 && (s == "delete-row" || s == "swap-row")) {
1752 			status.setEnabled(false);
1753 			status.message(from_utf8(N_("Only one row")));
1754 			return true;
1755 		}
1756 		if (ncols() <= 1 &&
1757 		    (s == "delete-column" || s == "swap-column")) {
1758 			status.setEnabled(false);
1759 			status.message(from_utf8(N_("Only one column")));
1760 			return true;
1761 		}
1762 		if ((rowinfo_[cur.row()].lines_ == 0 &&
1763 		     s == "delete-hline-above") ||
1764 		    (rowinfo_[cur.row() + 1].lines_ == 0 &&
1765 		     s == "delete-hline-below")) {
1766 			status.setEnabled(false);
1767 			status.message(from_utf8(N_("No hline to delete")));
1768 			return true;
1769 		}
1770 
1771 		if ((colinfo_[cur.col()].lines_ == 0 &&
1772 		     s == "delete-vline-left") ||
1773 		    (colinfo_[cur.col() + 1].lines_ == 0 &&
1774 		     s == "delete-vline-right")) {
1775 			status.setEnabled(false);
1776 			status.message(from_utf8(N_("No vline to delete")));
1777 			return true;
1778 		}
1779 		if (s == "valign-top" || s == "valign-middle" ||
1780 		    s == "valign-bottom" || s == "align-left" ||
1781 		    s == "align-right" || s == "align-center") {
1782 			status.setEnabled(true);
1783 			char const ha = horizontalAlignment(cur.col());
1784 			char const va = verticalAlignment();
1785 			status.setOnOff((s == "align-left" && ha == 'l')
1786 					|| (s == "align-right"   && ha == 'r')
1787 					|| (s == "align-center"  && ha == 'c')
1788 					|| (s == "valign-top"    && va == 't')
1789 					|| (s == "valign-bottom" && va == 'b')
1790 					|| (s == "valign-middle" && va == 'c'));
1791 			return true;
1792 		}
1793 		if (s == "append-row" || s == "delete-row" ||
1794 		    s == "copy-row" || s == "swap-row" ||
1795 		    s == "add-hline-above" || s == "add-hline-below" ||
1796 		    s == "delete-hline-above" || s == "delete-hline-below" ||
1797 		    s == "append-column" || s == "delete-column" ||
1798 		    s == "copy-column" || s == "swap-column" ||
1799 		    s == "add-vline-left" || s == "add-vline-right" ||
1800 		    s == "delete-vline-left" || s == "delete-vline-right") {
1801 			status.setEnabled(true);
1802 		} else {
1803 			status.setEnabled(false);
1804 			status.message(bformat(
1805 			    from_utf8(N_("Unknown tabular feature '%1$s'")),
1806 			    from_utf8(s)));
1807 		}
1808 
1809 #if 0
1810 		// FIXME: What did this code do?
1811 		// Please check whether it is still needed!
1812 		// should be more precise
1813 		if (v_align_ == '\0') {
1814 			status.enable(true);
1815 			break;
1816 		}
1817 		if (cmd.argument().empty()) {
1818 			status.enable(false);
1819 			break;
1820 		}
1821 		if (!contains("tcb", cmd.argument()[0])) {
1822 			status.enable(false);
1823 			break;
1824 		}
1825 		status.setOnOff(cmd.argument()[0] == v_align_);
1826 		status.setEnabled(true);
1827 #endif
1828 		return true;
1829 	}
1830 
1831 	case LFUN_CELL_SPLIT:
1832 		status.setEnabled(cur.idx() != cur.lastidx());
1833 		return true;
1834 
1835 	case LFUN_CELL_BACKWARD:
1836 	case LFUN_CELL_FORWARD:
1837 		status.setEnabled(true);
1838 		return true;
1839 
1840 	default:
1841 		break;
1842 	}
1843 	return InsetMathNest::getStatus(cur, cmd, status);
1844 }
1845 
1846 
colAlign(HullType type,col_type col) const1847 char InsetMathGrid::colAlign(HullType type, col_type col) const
1848 {
1849 	switch (type) {
1850 	case hullEqnArray:
1851 		return "rcl"[col % 3];
1852 
1853 	case hullMultline:
1854 		return 'c';
1855 	case hullGather:
1856 		if (!isBufferValid()) {
1857 			LYXERR0("Buffer not set correctly. Please report!");
1858 			return 'c';
1859 		}
1860 		if (buffer().params().is_math_indent)
1861 			return 'l';
1862 		else
1863 			return 'c';
1864 
1865 	case hullAlign:
1866 	case hullAlignAt:
1867 	case hullXAlignAt:
1868 	case hullXXAlignAt:
1869 	case hullFlAlign:
1870 		return "rl"[col & 1];
1871 
1872 	case hullUnknown:
1873 	case hullNone:
1874 	case hullSimple:
1875 	case hullEquation:
1876 	case hullRegexp:
1877 		return 'c';
1878 	}
1879 	// avoid warning
1880 	return 'c';
1881 }
1882 
1883 
1884 //static
colSpace(HullType type,col_type col)1885 int InsetMathGrid::colSpace(HullType type, col_type col)
1886 {
1887 	int alignInterSpace = 0;
1888 	switch (type) {
1889 	case hullUnknown:
1890 	case hullNone:
1891 	case hullSimple:
1892 	case hullEquation:
1893 	case hullMultline:
1894 	case hullGather:
1895 	case hullRegexp:
1896 		return 0;
1897 
1898 	case hullEqnArray:
1899 		return 5;
1900 
1901 	case hullAlign:
1902 		alignInterSpace = 20;
1903 		break;
1904 	case hullAlignAt:
1905 		alignInterSpace = 0;
1906 		break;
1907 	case hullXAlignAt:
1908 		alignInterSpace = 40;
1909 		break;
1910 	case hullXXAlignAt:
1911 	case hullFlAlign:
1912 		alignInterSpace = 60;
1913 		break;
1914 	}
1915 	return (col % 2) ? alignInterSpace : 0;
1916 }
1917 
1918 
1919 } // namespace lyx
1920