]> git.lyx.org Git - lyx.git/blob - src/insets/InsetTabular.cpp
bab4b591934f75709865fa6c861417dcdc46af3f
[lyx.git] / src / insets / InsetTabular.cpp
1 /**
2  * \file InsetTabular.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Lars Gullik Bjønnes
7  * \author Matthias Ettrich
8  * \author José Matos
9  * \author Jean-Marc Lasgouttes
10  * \author Angus Leeming
11  * \author John Levon
12  * \author André Pönitz
13  * \author Jürgen Vigna
14  * \author Uwe Stöhr
15  * \author Edwin Leuven
16  * \author Scott Kostyshak
17  *
18  * Full author contact details are available in file CREDITS.
19  */
20
21 #include <config.h>
22
23 #include "InsetTabular.h"
24
25 #include "buffer_funcs.h"
26 #include "Buffer.h"
27 #include "BufferParams.h"
28 #include "BufferView.h"
29 #include "CoordCache.h"
30 #include "Counters.h"
31 #include "Cursor.h"
32 #include "CutAndPaste.h"
33 #include "DispatchResult.h"
34 #include "FuncRequest.h"
35 #include "FuncStatus.h"
36 #include "InsetIterator.h"
37 #include "InsetList.h"
38 #include "Language.h"
39 #include "LaTeXFeatures.h"
40 #include "Lexer.h"
41 #include "LyX.h"
42 #include "LyXRC.h"
43 #include "MetricsInfo.h"
44 #include "OutputParams.h"
45 #include "output_xhtml.h"
46 #include "Paragraph.h"
47 #include "ParagraphParameters.h"
48 #include "ParIterator.h"
49 #include "TexRow.h"
50 #include "texstream.h"
51 #include "TextClass.h"
52 #include "TextMetrics.h"
53
54 #include "frontends/Application.h"
55 #include "frontends/alert.h"
56 #include "frontends/Clipboard.h"
57 #include "frontends/Painter.h"
58 #include "frontends/Selection.h"
59
60 #include "support/convert.h"
61 #include "support/debug.h"
62 #include "support/docstream.h"
63 #include "support/FileName.h"
64 #include "support/gettext.h"
65 #include "support/lassert.h"
66 #include "support/lstrings.h"
67 #include "support/unique_ptr.h"
68
69 #include <cstring>
70 #include <iomanip>
71 #include <iostream>
72 #include <limits>
73 #include <sstream>
74
75 using namespace std;
76 using namespace lyx::support;
77
78
79
80 namespace lyx {
81
82 using cap::dirtyTabularStack;
83 using cap::tabularStackDirty;
84
85 using graphics::PreviewLoader;
86
87 using frontend::Painter;
88 using frontend::Clipboard;
89
90 namespace Alert = frontend::Alert;
91
92
93 namespace {
94
95 int const ADD_TO_HEIGHT = 2; // in cell
96 int const ADD_TO_TABULAR_WIDTH = 6; // horizontal space before and after the table
97 int const default_line_space = 10; // ?
98 int const WIDTH_OF_LINE = 5; // space between double lines
99
100
101 ///
102 unique_ptr<Tabular> paste_tabular;
103
104
105 struct TabularFeature {
106         Tabular::Feature action;
107         string feature;
108         bool need_value;
109 };
110
111
112 TabularFeature tabularFeature[] =
113 {
114         // the SET/UNSET actions are used by the table dialog,
115         // the TOGGLE actions by the table toolbar buttons
116         // FIXME: these values have been hardcoded in InsetMathGrid and other
117         // math insets.
118         { Tabular::APPEND_ROW, "append-row", false },
119         { Tabular::APPEND_COLUMN, "append-column", false },
120         { Tabular::DELETE_ROW, "delete-row", false },
121         { Tabular::DELETE_COLUMN, "delete-column", false },
122         { Tabular::COPY_ROW, "copy-row", false },
123         { Tabular::COPY_COLUMN, "copy-column", false },
124         { Tabular::MOVE_COLUMN_RIGHT, "move-column-right", false },
125         { Tabular::MOVE_COLUMN_LEFT, "move-column-left", false },
126         { Tabular::MOVE_ROW_DOWN, "move-row-down", false },
127         { Tabular::MOVE_ROW_UP, "move-row-up", false },
128         { Tabular::SET_LINE_TOP, "set-line-top", true },
129         { Tabular::SET_LINE_BOTTOM, "set-line-bottom", true },
130         { Tabular::SET_LINE_LEFT, "set-line-left", true },
131         { Tabular::SET_LINE_RIGHT, "set-line-right", true },
132         { Tabular::TOGGLE_LINE_TOP, "toggle-line-top", false },
133         { Tabular::TOGGLE_LINE_BOTTOM, "toggle-line-bottom", false },
134         { Tabular::TOGGLE_LINE_LEFT, "toggle-line-left", false },
135         { Tabular::TOGGLE_LINE_RIGHT, "toggle-line-right", false },
136         { Tabular::ALIGN_LEFT, "align-left", false },
137         { Tabular::ALIGN_RIGHT, "align-right", false },
138         { Tabular::ALIGN_CENTER, "align-center", false },
139         { Tabular::ALIGN_BLOCK, "align-block", false },
140         { Tabular::ALIGN_DECIMAL, "align-decimal", false },
141         { Tabular::VALIGN_TOP, "valign-top", false },
142         { Tabular::VALIGN_BOTTOM, "valign-bottom", false },
143         { Tabular::VALIGN_MIDDLE, "valign-middle", false },
144         { Tabular::M_ALIGN_LEFT, "m-align-left", false },
145         { Tabular::M_ALIGN_RIGHT, "m-align-right", false },
146         { Tabular::M_ALIGN_CENTER, "m-align-center", false },
147         { Tabular::M_VALIGN_TOP, "m-valign-top", false },
148         { Tabular::M_VALIGN_BOTTOM, "m-valign-bottom", false },
149         { Tabular::M_VALIGN_MIDDLE, "m-valign-middle", false },
150         { Tabular::MULTICOLUMN, "multicolumn", false },
151         { Tabular::SET_MULTICOLUMN, "set-multicolumn", false },
152         { Tabular::UNSET_MULTICOLUMN, "unset-multicolumn", false },
153         { Tabular::MULTIROW, "multirow", false },
154         { Tabular::SET_MULTIROW, "set-multirow", false },
155         { Tabular::UNSET_MULTIROW, "unset-multirow", false },
156         { Tabular::SET_MROFFSET, "set-mroffset", true },
157         { Tabular::SET_ALL_LINES, "set-all-lines", false },
158         { Tabular::UNSET_ALL_LINES, "unset-all-lines", false },
159         { Tabular::TOGGLE_LONGTABULAR, "toggle-longtabular", false },
160         { Tabular::SET_LONGTABULAR, "set-longtabular", false },
161         { Tabular::UNSET_LONGTABULAR, "unset-longtabular", false },
162         { Tabular::SET_PWIDTH, "set-pwidth", true },
163         { Tabular::SET_MPWIDTH, "set-mpwidth", true },
164         { Tabular::TOGGLE_VARWIDTH_COLUMN, "toggle-varwidth-column", true },
165         { Tabular::SET_ROTATE_TABULAR, "set-rotate-tabular", true },
166         { Tabular::UNSET_ROTATE_TABULAR, "unset-rotate-tabular", true },
167         { Tabular::TOGGLE_ROTATE_TABULAR, "toggle-rotate-tabular", true },
168         { Tabular::SET_ROTATE_CELL, "set-rotate-cell", true },
169         { Tabular::UNSET_ROTATE_CELL, "unset-rotate-cell", true },
170         { Tabular::TOGGLE_ROTATE_CELL, "toggle-rotate-cell", true },
171         { Tabular::SET_USEBOX, "set-usebox", true },
172         { Tabular::SET_LTHEAD, "set-lthead", true },
173         { Tabular::UNSET_LTHEAD, "unset-lthead", true },
174         { Tabular::SET_LTFIRSTHEAD, "set-ltfirsthead", true },
175         { Tabular::UNSET_LTFIRSTHEAD, "unset-ltfirsthead", true },
176         { Tabular::SET_LTFOOT, "set-ltfoot", true },
177         { Tabular::UNSET_LTFOOT, "unset-ltfoot", true },
178         { Tabular::SET_LTLASTFOOT, "set-ltlastfoot", true },
179         { Tabular::UNSET_LTLASTFOOT, "unset-ltlastfoot", true },
180         { Tabular::SET_LTNEWPAGE, "set-ltnewpage", false },
181         { Tabular::UNSET_LTNEWPAGE, "unset-ltnewpage", false },
182         { Tabular::TOGGLE_LTCAPTION, "toggle-ltcaption", false },
183         { Tabular::SET_LTCAPTION, "set-ltcaption", false },
184         { Tabular::UNSET_LTCAPTION, "unset-ltcaption", false },
185         { Tabular::SET_SPECIAL_COLUMN, "set-special-column", true },
186         { Tabular::SET_SPECIAL_MULTICOLUMN, "set-special-multicolumn", true },
187         { Tabular::TOGGLE_BOOKTABS, "toggle-booktabs", false },
188         { Tabular::SET_BOOKTABS, "set-booktabs", false },
189         { Tabular::UNSET_BOOKTABS, "unset-booktabs", false },
190         { Tabular::SET_TOP_SPACE, "set-top-space", true },
191         { Tabular::SET_BOTTOM_SPACE, "set-bottom-space", true },
192         { Tabular::SET_INTERLINE_SPACE, "set-interline-space", true },
193         { Tabular::SET_BORDER_LINES, "set-border-lines", false },
194         { Tabular::TABULAR_VALIGN_TOP, "tabular-valign-top", false},
195         { Tabular::TABULAR_VALIGN_MIDDLE, "tabular-valign-middle", false},
196         { Tabular::TABULAR_VALIGN_BOTTOM, "tabular-valign-bottom", false},
197         { Tabular::LONGTABULAR_ALIGN_LEFT, "longtabular-align-left", false },
198         { Tabular::LONGTABULAR_ALIGN_CENTER, "longtabular-align-center", false },
199         { Tabular::LONGTABULAR_ALIGN_RIGHT, "longtabular-align-right", false },
200         { Tabular::SET_DECIMAL_POINT, "set-decimal-point", true },
201         { Tabular::SET_TABULAR_WIDTH, "set-tabular-width", true },
202         { Tabular::LAST_ACTION, "", false }
203 };
204
205
206 string const tostr(LyXAlignment const & num)
207 {
208         switch (num) {
209         case LYX_ALIGN_NONE:
210                 return "none";
211         case LYX_ALIGN_BLOCK:
212                 return "block";
213         case LYX_ALIGN_LEFT:
214                 return "left";
215         case LYX_ALIGN_CENTER:
216                 return "center";
217         case LYX_ALIGN_RIGHT:
218                 return "right";
219         case LYX_ALIGN_LAYOUT:
220                 return "layout";
221         case LYX_ALIGN_SPECIAL:
222                 return "special";
223         case LYX_ALIGN_DECIMAL:
224                 return "decimal";
225         }
226         return string();
227 }
228
229
230 string const tostr(Tabular::HAlignment const & num)
231 {
232         switch (num) {
233         case Tabular::LYX_LONGTABULAR_ALIGN_LEFT:
234                 return "left";
235         case Tabular::LYX_LONGTABULAR_ALIGN_CENTER:
236                 return "center";
237         case Tabular::LYX_LONGTABULAR_ALIGN_RIGHT:
238                 return "right";
239         }
240         return string();
241 }
242
243
244 string const tostr(Tabular::VAlignment const & num)
245 {
246         switch (num) {
247         case Tabular::LYX_VALIGN_TOP:
248                 return "top";
249         case Tabular::LYX_VALIGN_MIDDLE:
250                 return "middle";
251         case Tabular::LYX_VALIGN_BOTTOM:
252                 return "bottom";
253         }
254         return string();
255 }
256
257
258 string const tostr(Tabular::BoxType const & num)
259 {
260         switch (num) {
261         case Tabular::BOX_NONE:
262                 return "none";
263         case Tabular::BOX_PARBOX:
264                 return "parbox";
265         case Tabular::BOX_MINIPAGE:
266                 return "minipage";
267         case Tabular::BOX_VARWIDTH:
268                 return "varwidth";
269         }
270         return string();
271 }
272
273
274 // I would have liked a fromstr template a lot better. (Lgb)
275 bool string2type(string const & str, LyXAlignment & num)
276 {
277         if (str == "none")
278                 num = LYX_ALIGN_NONE;
279         else if (str == "block")
280                 num = LYX_ALIGN_BLOCK;
281         else if (str == "left")
282                 num = LYX_ALIGN_LEFT;
283         else if (str == "center")
284                 num = LYX_ALIGN_CENTER;
285         else if (str == "right")
286                 num = LYX_ALIGN_RIGHT;
287         else if (str == "decimal")
288                 num = LYX_ALIGN_DECIMAL;
289         else
290                 return false;
291         return true;
292 }
293
294
295 bool string2type(string const & str, Tabular::HAlignment & num)
296 {
297         if (str == "left")
298                 num = Tabular::LYX_LONGTABULAR_ALIGN_LEFT;
299         else if (str == "center" )
300                 num = Tabular::LYX_LONGTABULAR_ALIGN_CENTER;
301         else if (str == "right")
302                 num = Tabular::LYX_LONGTABULAR_ALIGN_RIGHT;
303         else
304                 return false;
305         return true;
306 }
307
308
309 bool string2type(string const & str, Tabular::VAlignment & num)
310 {
311         if (str == "top")
312                 num = Tabular::LYX_VALIGN_TOP;
313         else if (str == "middle" )
314                 num = Tabular::LYX_VALIGN_MIDDLE;
315         else if (str == "bottom")
316                 num = Tabular::LYX_VALIGN_BOTTOM;
317         else
318                 return false;
319         return true;
320 }
321
322
323 bool string2type(string const & str, Tabular::BoxType & num)
324 {
325         if (str == "none")
326                 num = Tabular::BOX_NONE;
327         else if (str == "parbox")
328                 num = Tabular::BOX_PARBOX;
329         else if (str == "minipage")
330                 num = Tabular::BOX_MINIPAGE;
331         else if (str == "varwidth")
332                 num = Tabular::BOX_VARWIDTH;
333         else
334                 return false;
335         return true;
336 }
337
338
339 bool string2type(string const & str, bool & num)
340 {
341         if (str == "true")
342                 num = true;
343         else if (str == "false")
344                 num = false;
345         else
346                 return false;
347         return true;
348 }
349
350
351 bool getTokenValue(string const & str, char const * token, string & ret)
352 {
353         ret.erase();
354         size_t token_length = strlen(token);
355         size_t pos = str.find(token);
356
357         if (pos == string::npos || pos + token_length + 1 >= str.length()
358                 || str[pos + token_length] != '=')
359                 return false;
360         pos += token_length + 1;
361         char ch = str[pos];
362         if (ch != '"' && ch != '\'') { // only read till next space
363                 ret += ch;
364                 ch = ' ';
365         }
366         while (pos < str.length() - 1 && str[++pos] != ch)
367                 ret += str[pos];
368
369         return true;
370 }
371
372
373 bool getTokenValue(string const & str, char const * token, docstring & ret)
374 {
375         string tmp;
376         bool const success = getTokenValue(str, token, tmp);
377         ret = from_utf8(tmp);
378         return success;
379 }
380
381
382 bool getTokenValue(string const & str, char const * token, int & num)
383 {
384         string tmp;
385         num = 0;
386         if (!getTokenValue(str, token, tmp))
387                 return false;
388         num = convert<int>(tmp);
389         return true;
390 }
391
392
393 bool getTokenValue(string const & str, char const * token, LyXAlignment & num)
394 {
395         string tmp;
396         return getTokenValue(str, token, tmp) && string2type(tmp, num);
397 }
398
399
400 bool getTokenValue(string const & str, char const * token,
401                                    Tabular::HAlignment & num)
402 {
403         string tmp;
404         return getTokenValue(str, token, tmp) && string2type(tmp, num);
405 }
406
407
408 bool getTokenValue(string const & str, char const * token,
409                                    Tabular::VAlignment & num)
410 {
411         string tmp;
412         return getTokenValue(str, token, tmp) && string2type(tmp, num);
413 }
414
415
416 bool getTokenValue(string const & str, char const * token,
417                                    Tabular::BoxType & num)
418 {
419         string tmp;
420         return getTokenValue(str, token, tmp) && string2type(tmp, num);
421 }
422
423
424 bool getTokenValue(string const & str, char const * token, bool & flag)
425 {
426         // set the flag always to false as this should be the default for bools
427         // not in the file-format.
428         flag = false;
429         string tmp;
430         return getTokenValue(str, token, tmp) && string2type(tmp, flag);
431 }
432
433
434 bool getTokenValue(string const & str, char const * token, Length & len)
435 {
436         // set the length to be zero() as default as this it should be if not
437         // in the file format.
438         len = Length();
439         string tmp;
440         return getTokenValue(str, token, tmp) && isValidLength(tmp, &len);
441 }
442
443
444 bool getTokenValue(string const & str, char const * token, Length & len, bool & flag)
445 {
446         len = Length();
447         flag = false;
448         string tmp;
449         if (!getTokenValue(str, token, tmp))
450                 return false;
451         if (tmp == "default") {
452                 flag = true;
453                 return  true;
454         }
455         return isValidLength(tmp, &len);
456 }
457
458
459 void l_getline(istream & is, string & str)
460 {
461         str.erase();
462         while (str.empty()) {
463                 getline(is, str);
464                 if (!str.empty() && str[str.length() - 1] == '\r')
465                         str.erase(str.length() - 1);
466         }
467 }
468
469 template <class T>
470 string const write_attribute(string const & name, T const & t)
471 {
472         string const s = tostr(t);
473         return s.empty() ? s : " " + name + "=\"" + s + "\"";
474 }
475
476 template <>
477 string const write_attribute(string const & name, string const & t)
478 {
479         return t.empty() ? t : " " + name + "=\"" + t + "\"";
480 }
481
482
483 template <>
484 string const write_attribute(string const & name, docstring const & t)
485 {
486         return t.empty() ? string() : " " + name + "=\"" + to_utf8(t) + "\"";
487 }
488
489
490 template <>
491 string const write_attribute(string const & name, bool const & b)
492 {
493         // we write only true attribute values so we remove a bit of the
494         // file format bloat for tabulars.
495         return b ? write_attribute(name, convert<string>(b)) : string();
496 }
497
498
499 template <>
500 string const write_attribute(string const & name, int const & i)
501 {
502         // we write only true attribute values so we remove a bit of the
503         // file format bloat for tabulars.
504         return i ? write_attribute(name, convert<string>(i)) : string();
505 }
506
507
508 template <>
509 string const write_attribute(string const & name, Tabular::idx_type const & i)
510 {
511         // we write only true attribute values so we remove a bit of the
512         // file format bloat for tabulars.
513         return i ? write_attribute(name, convert<string>(i)) : string();
514 }
515
516
517 template <>
518 string const write_attribute(string const & name, Length const & value)
519 {
520         // we write only the value if we really have one same reson as above.
521         return value.zero() ? string() : write_attribute(name, value.asString());
522 }
523
524 } // namespace
525
526
527 string const featureAsString(Tabular::Feature action)
528 {
529         for (size_t i = 0; i != Tabular::LAST_ACTION; ++i) {
530                 if (tabularFeature[i].action == action)
531                         return tabularFeature[i].feature;
532         }
533         return string();
534 }
535
536
537 DocIterator separatorPos(InsetTableCell const * cell, docstring const & align_d)
538 {
539         DocIterator dit = doc_iterator_begin(&(cell->buffer()), cell);
540         for (; dit; dit.forwardChar())
541                 if (dit.inTexted() && dit.depth() == 1
542                         && dit.paragraph().find(align_d, false, false, dit.pos()))
543                         break;
544
545         return dit;
546 }
547
548
549 InsetTableCell splitCell(InsetTableCell & head, docstring const & align_d, bool & hassep)
550 {
551         InsetTableCell tail = InsetTableCell(head);
552         DocIterator const dit = separatorPos(&head, align_d);
553         hassep = (bool)dit;
554         if (hassep) {
555                 pit_type const psize = head.paragraphs().front().size();
556                 head.paragraphs().front().eraseChars(dit.pos(), psize, false);
557                 tail.paragraphs().front().eraseChars(0,
558                         dit.pos() < psize ? dit.pos() + 1 : psize, false);
559         }
560
561         return tail;
562 }
563
564
565 /////////////////////////////////////////////////////////////////////
566 //
567 // Tabular
568 //
569 /////////////////////////////////////////////////////////////////////
570
571
572 Tabular::CellData::CellData(Buffer * buf)
573         : cellno(0),
574           width(0),
575           multicolumn(Tabular::CELL_NORMAL),
576           multirow(Tabular::CELL_NORMAL),
577           alignment(LYX_ALIGN_CENTER),
578           valignment(LYX_VALIGN_TOP),
579           decimal_hoffset(0),
580           decimal_width(0),
581           voffset(0),
582           top_line(false),
583           bottom_line(false),
584           left_line(false),
585           right_line(false),
586           usebox(BOX_NONE),
587           rotate(0),
588           inset(new InsetTableCell(buf))
589 {
590         inset->setBuffer(*buf);
591 }
592
593
594 Tabular::CellData::CellData(CellData const & cs)
595         : cellno(cs.cellno),
596           width(cs.width),
597           multicolumn(cs.multicolumn),
598           multirow(cs.multirow),
599           mroffset(cs.mroffset),
600           alignment(cs.alignment),
601           valignment(cs.valignment),
602           decimal_hoffset(cs.decimal_hoffset),
603           decimal_width(cs.decimal_width),
604           voffset(cs.voffset),
605           top_line(cs.top_line),
606           bottom_line(cs.bottom_line),
607           left_line(cs.left_line),
608           right_line(cs.right_line),
609           usebox(cs.usebox),
610           rotate(cs.rotate),
611           align_special(cs.align_special),
612           p_width(cs.p_width),
613           inset(static_cast<InsetTableCell *>(cs.inset->clone()))
614 {
615 }
616
617 Tabular::CellData & Tabular::CellData::operator=(CellData const & cs)
618 {
619         if (&cs == this)
620                 return *this;
621         cellno = cs.cellno;
622         width = cs.width;
623         multicolumn = cs.multicolumn;
624         multirow = cs.multirow;
625         mroffset = cs.mroffset;
626         alignment = cs.alignment;
627         valignment = cs.valignment;
628         decimal_hoffset = cs.decimal_hoffset;
629         decimal_width = cs.decimal_width;
630         voffset = cs.voffset;
631         top_line = cs.top_line;
632         bottom_line = cs.bottom_line;
633         left_line = cs.left_line;
634         right_line = cs.right_line;
635         usebox = cs.usebox;
636         rotate = cs.rotate;
637         align_special = cs.align_special;
638         p_width = cs.p_width;
639         inset.reset(static_cast<InsetTableCell *>(cs.inset->clone()));
640         return *this;
641 }
642
643 Tabular::RowData::RowData()
644         : ascent(0),
645           descent(0),
646           top_space_default(false),
647           bottom_space_default(false),
648           interline_space_default(false),
649           endhead(false),
650           endfirsthead(false),
651           endfoot(false),
652           endlastfoot(false),
653           newpage(false),
654           caption(false)
655 {}
656
657
658 Tabular::ColumnData::ColumnData()
659         : alignment(LYX_ALIGN_CENTER),
660           valignment(LYX_VALIGN_TOP),
661           width(0),
662           varwidth(false)
663 {
664 }
665
666
667 Tabular::ltType::ltType()
668         : set(false), topDL(false),
669           bottomDL(false),
670           empty(false)
671 {}
672
673
674 Tabular::Tabular(Buffer * buffer, row_type rows_arg, col_type columns_arg)
675 {
676         init(buffer, rows_arg, columns_arg);
677 }
678
679
680 void Tabular::setBuffer(Buffer & buffer)
681 {
682         buffer_ = &buffer;
683         for (row_type i = 0; i < nrows(); ++i)
684                 for (col_type j = 0; j < ncols(); ++j)
685                         cell_info[i][j].inset->setBuffer(*buffer_);
686 }
687
688
689 // activates all lines and sets all widths to 0
690 void Tabular::init(Buffer * buf, row_type rows_arg,
691                       col_type columns_arg)
692 {
693         buffer_ = buf;
694         row_info = row_vector(rows_arg);
695         column_info = column_vector(columns_arg);
696         cell_info = cell_vvector(rows_arg, cell_vector(columns_arg, CellData(buf)));
697         row_info.reserve(10);
698         column_info.reserve(10);
699         cell_info.reserve(100);
700         updateIndexes();
701         is_long_tabular = false;
702         tabular_valignment = LYX_VALIGN_MIDDLE;
703         tabular_width = Length();
704         longtabular_alignment = LYX_LONGTABULAR_ALIGN_CENTER;
705         rotate = 0;
706         use_booktabs = false;
707         // set silly default lines
708         for (row_type r = 0; r < nrows(); ++r)
709                 for (col_type c = 0; c < ncols(); ++c) {
710                         cell_info[r][c].inset->setBuffer(*buffer_);
711                         cell_info[r][c].top_line = true;
712                         cell_info[r][c].left_line = true;
713                         cell_info[r][c].bottom_line = r == 0 || r == nrows() - 1;
714                         cell_info[r][c].right_line = c == ncols() - 1;
715                 }
716 }
717
718
719 void Tabular::deleteRow(row_type const row)
720 {
721         // Not allowed to delete last row
722         if (nrows() == 1)
723                 return;
724
725         for (col_type c = 0; c < ncols(); ++c) {
726                 // Care about multirow cells
727                 if (row + 1 < nrows() &&
728                     cell_info[row][c].multirow == CELL_BEGIN_OF_MULTIROW &&
729                     cell_info[row + 1][c].multirow == CELL_PART_OF_MULTIROW) {
730                                 cell_info[row + 1][c].multirow = CELL_BEGIN_OF_MULTIROW;
731                 }
732         }
733         row_info.erase(row_info.begin() + row);
734         cell_info.erase(cell_info.begin() + row);
735         updateIndexes();
736 }
737
738
739 void Tabular::copyRow(row_type const row)
740 {
741         insertRow(row, true);
742 }
743
744
745 void Tabular::appendRow(row_type row)
746 {
747         insertRow(row, false);
748 }
749
750
751 void Tabular::insertRow(row_type const row, bool copy)
752 {
753         row_info.insert(row_info.begin() + row + 1, RowData(row_info[row]));
754         cell_info.insert(cell_info.begin() + row + 1,
755                 cell_vector(0, CellData(buffer_)));
756
757         for (col_type c = 0; c < ncols(); ++c) {
758                 cell_info[row + 1].insert(cell_info[row + 1].begin() + c,
759                         copy ? CellData(cell_info[row][c]) : CellData(buffer_));
760                 if (buffer().params().track_changes)
761                         cell_info[row + 1][c].inset->setChange(Change(Change::INSERTED));
762                 if (cell_info[row][c].multirow == CELL_BEGIN_OF_MULTIROW)
763                         cell_info[row + 1][c].multirow = CELL_PART_OF_MULTIROW;
764         }
765
766         updateIndexes();
767         for (col_type c = 0; c < ncols(); ++c) {
768                 if (isPartOfMultiRow(row, c))
769                         continue;
770                 // inherit line settings
771                 idx_type const i = cellIndex(row + 1, c);
772                 idx_type const j = cellIndex(row, c);
773                 setLeftLine(i, leftLine(j));
774                 setRightLine(i, rightLine(j));
775                 setTopLine(i, topLine(j));
776                 if (topLine(j) && bottomLine(j)) {
777                         setBottomLine(i, true);
778                         setBottomLine(j, false);
779                 }
780                 // mark track changes
781                 if (buffer().params().track_changes)
782                         cellInfo(i).inset->setChange(Change(Change::INSERTED));
783         }
784 }
785
786
787 void Tabular::moveColumn(col_type col, ColDirection direction)
788 {
789         if (direction == Tabular::LEFT)
790                 col = col - 1;
791
792         std::swap(column_info[col], column_info[col + 1]);
793
794         for (row_type r = 0; r < nrows(); ++r) {
795                 std::swap(cell_info[r][col], cell_info[r][col + 1]);
796                 std::swap(cell_info[r][col].left_line, cell_info[r][col + 1].left_line);
797                 std::swap(cell_info[r][col].right_line, cell_info[r][col + 1].right_line);
798
799                 // FIXME track changes is broken for tabular features (#8469)
800                 idx_type const i = cellIndex(r, col);
801                 idx_type const j = cellIndex(r, col + 1);
802                 if (buffer().params().track_changes) {
803                         cellInfo(i).inset->setChange(Change(Change::INSERTED));
804                         cellInfo(j).inset->setChange(Change(Change::INSERTED));
805                 }
806         }
807         updateIndexes();
808 }
809
810
811 void Tabular::moveRow(row_type row, RowDirection direction)
812 {
813         if (direction == Tabular::UP)
814                 row = row - 1;
815
816         std::swap(row_info[row], row_info[row + 1]);
817
818         for (col_type c = 0; c < ncols(); ++c) {
819                 std::swap(cell_info[row][c], cell_info[row + 1][c]);
820                 std::swap(cell_info[row][c].top_line, cell_info[row + 1][c].top_line);
821                 std::swap(cell_info[row][c].bottom_line, cell_info[row + 1][c].bottom_line);
822
823                 // FIXME track changes is broken for tabular features (#8469)
824                 idx_type const i = cellIndex(row, c);
825                 idx_type const j = cellIndex(row + 1, c);
826                 if (buffer().params().track_changes) {
827                         cellInfo(i).inset->setChange(Change(Change::INSERTED));
828                         cellInfo(j).inset->setChange(Change(Change::INSERTED));
829                 }
830         }
831         updateIndexes();
832 }
833
834
835 void Tabular::deleteColumn(col_type const col)
836 {
837         // Not allowed to delete last column
838         if (ncols() == 1)
839                 return;
840
841         for (row_type r = 0; r < nrows(); ++r) {
842                 // Care about multicolumn cells
843                 if (col + 1 < ncols() &&
844                     cell_info[r][col].multicolumn == CELL_BEGIN_OF_MULTICOLUMN &&
845                     cell_info[r][col + 1].multicolumn == CELL_PART_OF_MULTICOLUMN) {
846                                 cell_info[r][col + 1].multicolumn = CELL_BEGIN_OF_MULTICOLUMN;
847                 }
848                 cell_info[r].erase(cell_info[r].begin() + col);
849         }
850         column_info.erase(column_info.begin() + col);
851         updateIndexes();
852 }
853
854
855 void Tabular::copyColumn(col_type const col)
856 {
857         insertColumn(col, true);
858 }
859
860
861 void Tabular::appendColumn(col_type col)
862 {
863         insertColumn(col, false);
864 }
865
866
867 void Tabular::insertColumn(col_type const col, bool copy)
868 {
869         BufferParams const & bp = buffer().params();
870         column_info.insert(column_info.begin() + col + 1, ColumnData(column_info[col]));
871
872         for (row_type r = 0; r < nrows(); ++r) {
873                 cell_info[r].insert(cell_info[r].begin() + col + 1,
874                         copy ? CellData(cell_info[r][col]) : CellData(buffer_));
875                 if (bp.track_changes)
876                         cell_info[r][col + 1].inset->setChange(Change(Change::INSERTED));
877                 if (cell_info[r][col].multicolumn == CELL_BEGIN_OF_MULTICOLUMN)
878                         cell_info[r][col + 1].multicolumn = CELL_PART_OF_MULTICOLUMN;
879         }
880         updateIndexes();
881         for (row_type r = 0; r < nrows(); ++r) {
882                 // inherit line settings
883                 idx_type const i = cellIndex(r, col + 1);
884                 idx_type const j = cellIndex(r, col);
885                 setBottomLine(i, bottomLine(j));
886                 setTopLine(i, topLine(j));
887                 setLeftLine(i, leftLine(j));
888                 setRightLine(i, rightLine(j));
889                 if (rightLine(i) && rightLine(j)) {
890                         setRightLine(j, false);
891                 }
892                 if (buffer().params().track_changes)
893                         cellInfo(i).inset->setChange(Change(Change::INSERTED));
894         }
895 }
896
897
898 void Tabular::updateIndexes()
899 {
900         setBuffer(buffer());
901         numberofcells = 0;
902         // reset cell number
903         for (row_type row = 0; row < nrows(); ++row)
904                 for (col_type column = 0; column < ncols(); ++column) {
905                         if (!isPartOfMultiColumn(row, column)
906                                 && !isPartOfMultiRow(row, column))
907                                 ++numberofcells;
908                         if (isPartOfMultiRow(row, column))
909                                 cell_info[row][column].cellno = cell_info[row - 1][column].cellno;
910                         else
911                                 cell_info[row][column].cellno = numberofcells - 1;
912                 }
913
914         rowofcell.resize(numberofcells);
915         columnofcell.resize(numberofcells);
916         idx_type i = 0;
917         // reset column and row of cells and update their width and alignment
918         for (row_type row = 0; row < nrows(); ++row)
919                 for (col_type column = 0; column < ncols(); ++column) {
920                         if (isPartOfMultiColumn(row, column)) {
921                                 cell_info[row][column].inset->toggleMultiCol(true);
922                                 continue;
923                         }
924                         cell_info[row][column].inset->toggleMultiCol(false);
925                         // columnofcell needs to be called before setting width and aligment
926                         // multirow cells inherit the width from the column width
927                         if (!isPartOfMultiRow(row, column)) {
928                                 columnofcell[i] = column;
929                                 rowofcell[i] = row;
930                         }
931                         setFixedWidth(row, column);
932                         if (isPartOfMultiRow(row, column)) {
933                                 cell_info[row][column].inset->toggleMultiRow(true);
934                                 continue;
935                         }
936                         cell_info[row][column].inset->toggleMultiRow(false);
937                         cell_info[row][column].inset->setContentAlignment(
938                                 getAlignment(cellIndex(row, column)));
939                         ++i;
940                 }
941 }
942
943
944 Tabular::idx_type Tabular::numberOfCellsInRow(row_type const row) const
945 {
946         idx_type result = 0;
947         for (col_type c = 0; c < ncols(); ++c)
948                 if (cell_info[row][c].multicolumn != Tabular::CELL_PART_OF_MULTICOLUMN)
949                         ++result;
950         return result;
951 }
952
953
954 bool Tabular::topLine(idx_type const cell) const
955 {
956         return cellInfo(cell).top_line;
957 }
958
959
960 bool Tabular::bottomLine(idx_type const cell) const
961 {
962         return cellInfo(cell).bottom_line;
963 }
964
965
966 bool Tabular::leftLine(idx_type cell) const
967 {
968         if (use_booktabs)
969                 return false;
970         return cellInfo(cell).left_line;
971 }
972
973
974 bool Tabular::rightLine(idx_type cell) const
975 {
976         if (use_booktabs)
977                 return false;
978         return cellInfo(cell).right_line;
979 }
980
981
982 int Tabular::interRowSpace(row_type row) const
983 {
984         if (!row || row >= nrows())
985                 return 0;
986
987         int const interline_space = row_info[row - 1].interline_space_default ?
988                 default_line_space :
989                 row_info[row - 1].interline_space.inPixels(width());
990         if (rowTopLine(row) && rowBottomLine(row - 1))
991                 return interline_space + WIDTH_OF_LINE;
992         return interline_space;
993 }
994
995
996 int Tabular::interColumnSpace(idx_type cell) const
997 {
998         col_type const nextcol = cellColumn(cell) + columnSpan(cell);
999         if (rightLine(cell) && nextcol < ncols()
1000                 && leftLine(cellIndex(cellRow(cell), nextcol)))
1001                 return WIDTH_OF_LINE;
1002         return 0;
1003 }
1004
1005
1006 int Tabular::cellWidth(idx_type cell) const
1007 {
1008         int w = 0;
1009         col_type const span = columnSpan(cell);
1010         col_type const col = cellColumn(cell);
1011         for(col_type c = col; c < col + span ; ++c)
1012                 w += column_info[c].width;
1013         return w;
1014 }
1015
1016
1017 int Tabular::cellHeight(idx_type cell) const
1018 {
1019         row_type const span = rowSpan(cell);
1020         row_type const row = cellRow(cell);
1021         int h = 0;
1022         for(row_type r = row; r < row + span ; ++r) {
1023                 h += rowAscent(r) + rowDescent(r);
1024                 if (r != row + span - 1)
1025                         h += interRowSpace(r + 1);
1026         }
1027
1028         return h;
1029 }
1030
1031
1032 bool Tabular::updateColumnWidths(MetricsInfo & mi)
1033 {
1034         vector<int> max_dwidth(ncols(), 0);
1035         // collect max. fixed width of column
1036         map<col_type, int> max_pwidth;
1037         // collect max. variable width of column
1038         map<col_type, int> max_width;
1039         // collect max. variable width of xcolumn
1040         map<col_type, int> max_xwidth;
1041
1042         for(col_type c = 0; c < ncols(); ++c)
1043                 for(row_type r = 0; r < nrows(); ++r) {
1044                         idx_type const i = cellIndex(r, c);
1045                         if (getAlignment(i) == LYX_ALIGN_DECIMAL)
1046                                 max_dwidth[c] = max(max_dwidth[c], cell_info[r][c].decimal_width);
1047                         if (!getPWidth(i).zero())
1048                                 max_pwidth[c] = max(max_pwidth[c], cell_info[r][c].width);
1049                         else if (column_info[c].varwidth)
1050                                 max_xwidth[c] = max(max_xwidth[c], cell_info[r][c].width);
1051                         else
1052                                 max_width[c] = max(max_width[c], cell_info[r][c].width);
1053                 }
1054
1055         // If we have a fixed tabular width, we take this into account
1056         Length tab_width = tabular_width;
1057         bool const tabularx = hasVarwidthColumn();
1058         if (tabularx && tab_width.zero())
1059                 // If no tabular width is specified with X columns,
1060                 // we use 100% colwidth
1061                 tab_width = Length(100, Length::PCW);
1062         int restwidth = -1;
1063         if (!tab_width.zero()) {
1064                 restwidth = mi.base.inPixels(tab_width);
1065                 // Substract the fixed widths from the table width
1066                 for (auto const w : max_pwidth)
1067                         restwidth -= w.second;
1068         }
1069
1070         // If we have a fixed width, distribute the available table width
1071         // (minus the fixed widths) to the variable-width columns
1072         int vcolwidth = -1;
1073         int restcols = ncols() - max_pwidth.size();
1074         if (restwidth > 0)
1075                 vcolwidth = restwidth / restcols;
1076
1077         // Now consider that some variable width columns exceed the vcolwidth
1078         // FIXME As opposed to tabular*, tabularx with X column do not exceed
1079         //       the overall table width. This is not yet represented, as it
1080         //       needs to include row breaking.
1081         if (vcolwidth > 0) {
1082                 bool changed = false;
1083                 for (auto const w : max_width) {
1084                         if (tabularx || w.second > vcolwidth) {
1085                                 --restcols;
1086                                 restwidth -= w.second;
1087                                 changed = true;
1088                         }
1089                 }
1090                 for (auto const w : max_xwidth) {
1091                         if (w.second > vcolwidth) {
1092                                 --restcols;
1093                                 restwidth -= w.second;
1094                                 changed = true;
1095                         }
1096                 }
1097                 if (changed && restwidth > 0)
1098                         vcolwidth = restwidth / restcols;
1099         }
1100
1101         bool update = false;
1102         // for each col get max of single col cells
1103         for(col_type c = 0; c < ncols(); ++c) {
1104                 int new_width = 0;
1105                 for(row_type r = 0; r < nrows(); ++r) {
1106                         idx_type const i = cellIndex(r, c);
1107                         if (columnSpan(i) == 1) {
1108                                 if (getAlignment(i) == LYX_ALIGN_DECIMAL
1109                                         && cell_info[r][c].decimal_width != 0)
1110                                         new_width = max(new_width, cellInfo(i).width
1111                                                 + max_dwidth[c] - cellInfo(i).decimal_width);
1112                                 else if (getPWidth(i).zero() && vcolwidth > 0) {
1113                                         if (tabularx && !column_info[c].varwidth)
1114                                                 new_width = max(new_width, cellInfo(i).width);
1115                                         else
1116                                                 new_width = max(vcolwidth, max(new_width, cellInfo(i).width));
1117                                 } else
1118                                         new_width = max(new_width, cellInfo(i).width);
1119                         }
1120                 }
1121
1122                 if (column_info[c].width != new_width) {
1123                         column_info[c].width = new_width;
1124                         update = true;
1125                 }
1126         }
1127         // update col widths to fit merged cells
1128         for(col_type c = 0; c < ncols(); ++c)
1129                 for(row_type r = 0; r < nrows(); ++r) {
1130                         idx_type const i = cellIndex(r, c);
1131                         int const span = columnSpan(i);
1132                         if (span == 1 || c > cellColumn(i))
1133                                 continue;
1134
1135                         int old_width = 0;
1136                         for(col_type j = c; j < c + span ; ++j)
1137                                 old_width += column_info[j].width;
1138
1139                         if (cellInfo(i).width > old_width) {
1140                                 column_info[c + span - 1].width += cellInfo(i).width - old_width;
1141                                 update = true;
1142                         }
1143                 }
1144
1145         return update;
1146 }
1147
1148
1149 int Tabular::width() const
1150 {
1151         int width = 0;
1152         for (col_type c = 0; c < ncols(); ++c)
1153                 width += column_info[c].width;
1154         return width;
1155 }
1156
1157
1158 void Tabular::setAlignment(idx_type cell, LyXAlignment align,
1159                               bool has_width)
1160 {
1161         col_type const col = cellColumn(cell);
1162         // set alignment for the whole row if we are not in a multicolumn cell,
1163         // exclude possible multicolumn cells in the row
1164         if (!isMultiColumn(cell)) {
1165                 for (row_type r = 0; r < nrows(); ++r) {
1166                         // only if the column has no width the multirow inherits the
1167                         // alignment of the column, otherwise it is left aligned
1168                         if (!(isMultiRow(cellIndex(r, col)) && has_width)
1169                                 && !isMultiColumn(cellIndex(r, col))) {
1170                                 cell_info[r][col].alignment = align;
1171                                 cell_info[r][col].inset->setContentAlignment(align);
1172                         }
1173                         if ((isMultiRow(cellIndex(r, col)) && has_width)
1174                                 && !isMultiColumn(cellIndex(r, col))) {
1175                                 cell_info[r][col].alignment = LYX_ALIGN_LEFT;
1176                                 cell_info[r][col].inset->setContentAlignment(LYX_ALIGN_LEFT);
1177                         }
1178                 }
1179                 column_info[col].alignment = align;
1180                 docstring & dpoint = column_info[col].decimal_point;
1181                 if (align == LYX_ALIGN_DECIMAL && dpoint.empty())
1182                         dpoint = from_utf8(lyxrc.default_decimal_point);
1183         } else {
1184                 cellInfo(cell).alignment = align;
1185                 cellInset(cell)->setContentAlignment(align);
1186         }
1187 }
1188
1189
1190 void Tabular::setVAlignment(idx_type cell, VAlignment align,
1191                                bool onlycolumn)
1192 {
1193         if (!isMultiColumn(cell) || onlycolumn)
1194                 column_info[cellColumn(cell)].valignment = align;
1195         if (!onlycolumn)
1196                 cellInfo(cell).valignment = align;
1197 }
1198
1199
1200 namespace {
1201
1202 /**
1203  * Allow line and paragraph breaks for fixed width multicol/multirow cells
1204  * or disallow them, merge cell paragraphs and reset layout to standard
1205  * for variable width multicol cells.
1206  */
1207 void toggleFixedWidth(Cursor & cur, InsetTableCell * inset,
1208                       bool const fixedWidth, bool const multicol,
1209                       bool const multirow)
1210 {
1211         inset->toggleFixedWidth(fixedWidth);
1212         if (!multirow && (fixedWidth || !multicol))
1213                 return;
1214
1215         // merge all paragraphs to one
1216         BufferParams const & bp = cur.bv().buffer().params();
1217         while (inset->paragraphs().size() > 1)
1218                 mergeParagraph(bp, inset->paragraphs(), 0);
1219
1220         // This is relevant for multirows
1221         if (fixedWidth)
1222                 return;
1223
1224         // remove newlines
1225         ParagraphList::iterator pit = inset->paragraphs().begin();
1226         for (; pit != inset->paragraphs().end(); ++pit) {
1227                 for (pos_type j = 0; j != pit->size(); ++j) {
1228                         if (pit->isNewline(j))
1229                                 pit->eraseChar(j, bp.track_changes);
1230                 }
1231         }
1232
1233         // reset layout
1234         cur.push(*inset);
1235         // undo information has already been recorded
1236         inset->getText(0)->setLayout(0, cur.lastpit() + 1,
1237                         bp.documentClass().plainLayoutName());
1238         cur.pop();
1239 }
1240
1241 } // namespace
1242
1243
1244 void Tabular::setColumnPWidth(Cursor & cur, idx_type cell,
1245                 Length const & width)
1246 {
1247         col_type const c = cellColumn(cell);
1248
1249         column_info[c].p_width = width;
1250         // reset the vertical alignment to top if the fixed width
1251         // is removed or zero because only fixed width columns can
1252         // have a vertical alignment
1253         if (column_info[c].p_width.zero())
1254                 column_info[c].valignment = LYX_VALIGN_TOP;
1255         for (row_type r = 0; r < nrows(); ++r) {
1256                 idx_type const cidx = cellIndex(r, c);
1257                 // because of multicolumns
1258                 toggleFixedWidth(cur, cellInset(cidx).get(),
1259                                  !getPWidth(cidx).zero(), isMultiColumn(cidx),
1260                                  isMultiRow(cidx));
1261                 if (isMultiRow(cidx))
1262                         setAlignment(cidx, LYX_ALIGN_LEFT, false);
1263         }
1264         // cur can become invalid after paragraphs were merged
1265         cur.fixIfBroken();
1266 }
1267
1268
1269 bool Tabular::setFixedWidth(row_type r, col_type c)
1270 {
1271         bool const multicol = cell_info[r][c].multicolumn != CELL_NORMAL;
1272         bool const fixed_width = (!column_info[c].p_width.zero() && !multicol)
1273               || (multicol && !cell_info[r][c].p_width.zero());
1274         cell_info[r][c].inset->toggleFixedWidth(fixed_width);
1275         return fixed_width;
1276 }
1277
1278
1279 bool Tabular::setMColumnPWidth(Cursor & cur, idx_type cell,
1280                 Length const & width)
1281 {
1282         if (!isMultiColumn(cell))
1283                 return false;
1284
1285         cellInfo(cell).p_width = width;
1286         toggleFixedWidth(cur, cellInset(cell).get(), !width.zero(),
1287                          isMultiColumn(cell), isMultiRow(cell));
1288         // cur can become invalid after paragraphs were merged
1289         cur.fixIfBroken();
1290         return true;
1291 }
1292
1293
1294 bool Tabular::toggleVarwidth(idx_type cell, bool const varwidth)
1295 {
1296         column_info[cellColumn(cell)].varwidth = varwidth;
1297         return true;
1298 }
1299
1300
1301 bool Tabular::setMROffset(Cursor &, idx_type cell, Length const & mroffset)
1302 {
1303         cellInfo(cell).mroffset = mroffset;
1304         return true;
1305 }
1306
1307
1308 void Tabular::setAlignSpecial(idx_type cell, docstring const & special,
1309                                  Tabular::Feature what)
1310 {
1311         if (what == SET_SPECIAL_MULTICOLUMN)
1312                 cellInfo(cell).align_special = special;
1313         else
1314                 column_info[cellColumn(cell)].align_special = special;
1315 }
1316
1317
1318 void Tabular::setTopLine(idx_type i, bool line)
1319 {
1320         cellInfo(i).top_line = line;
1321 }
1322
1323
1324 void Tabular::setBottomLine(idx_type i, bool line)
1325 {
1326         cellInfo(i).bottom_line = line;
1327 }
1328
1329
1330 void Tabular::setLeftLine(idx_type cell, bool line)
1331 {
1332         cellInfo(cell).left_line = line;
1333 }
1334
1335
1336 void Tabular::setRightLine(idx_type cell, bool line)
1337 {
1338         cellInfo(cell).right_line = line;
1339 }
1340
1341 bool Tabular::rowTopLine(row_type r) const
1342 {
1343         bool all_rows_set = true;
1344         for (col_type c = 0; all_rows_set && c < ncols(); ++c)
1345                 all_rows_set = cellInfo(cellIndex(r, c)).top_line;
1346         return all_rows_set;
1347 }
1348
1349
1350 bool Tabular::rowBottomLine(row_type r) const
1351 {
1352         bool all_rows_set = true;
1353         for (col_type c = 0; all_rows_set && c < ncols(); ++c)
1354                 all_rows_set = cellInfo(cellIndex(r, c)).bottom_line;
1355         return all_rows_set;
1356 }
1357
1358
1359 bool Tabular::columnLeftLine(col_type c) const
1360 {
1361         if (use_booktabs)
1362                 return false;
1363
1364         int nrows_left = 0;
1365         int total = 0;
1366         for (row_type r = 0; r < nrows(); ++r) {
1367                 idx_type const i = cellIndex(r, c);
1368                 if (c == cellColumn(i)) {
1369                         ++total;
1370                         bool right = c > 0 && cellInfo(cellIndex(r, c - 1)).right_line;
1371                         if (cellInfo(i).left_line || right)
1372                                 ++nrows_left;
1373                 }
1374         }
1375         return 2 * nrows_left >= total;
1376 }
1377
1378
1379 bool Tabular::columnRightLine(col_type c) const
1380 {
1381         if (use_booktabs)
1382                 return false;
1383
1384         int nrows_right = 0;
1385         int total = 0;
1386         for (row_type r = 0; r < nrows(); ++r) {
1387                 idx_type i = cellIndex(r, c);
1388                 if (c == cellColumn(i) + columnSpan(i) - 1) {
1389                         ++total;
1390                         bool left = (c + 1 < ncols()
1391                                 && cellInfo(cellIndex(r, c + 1)).left_line)
1392                                 || c + 1 == ncols();
1393                         if (cellInfo(i).right_line && left)
1394                                 ++nrows_right;
1395                 }
1396         }
1397         return 2 * nrows_right >= total;
1398 }
1399
1400
1401 LyXAlignment Tabular::getAlignment(idx_type cell, bool onlycolumn) const
1402 {
1403         if (!onlycolumn && (isMultiColumn(cell) || isMultiRow(cell)))
1404                 return cellInfo(cell).alignment;
1405
1406         return column_info[cellColumn(cell)].alignment;
1407 }
1408
1409
1410 Tabular::VAlignment
1411 Tabular::getVAlignment(idx_type cell, bool onlycolumn) const
1412 {
1413         if (!onlycolumn && (isMultiColumn(cell) || isMultiRow(cell)))
1414                 return cellInfo(cell).valignment;
1415         return column_info[cellColumn(cell)].valignment;
1416 }
1417
1418
1419 int Tabular::offsetVAlignment() const
1420 {
1421         // for top-alignment the first horizontal table line must be exactly at
1422         // the position of the base line of the surrounding text line
1423         // for bottom alignment, the same is for the last table line
1424         int offset_valign = 0;
1425         switch (tabular_valignment) {
1426         case Tabular::LYX_VALIGN_BOTTOM:
1427                 offset_valign = rowAscent(0) - height();
1428                 break;
1429         case Tabular::LYX_VALIGN_MIDDLE:
1430                 offset_valign = (- height()) / 2 + rowAscent(0);
1431                 break;
1432         case Tabular::LYX_VALIGN_TOP:
1433                 offset_valign = rowAscent(0);
1434                 break;
1435         }
1436         return offset_valign;
1437 }
1438
1439
1440 Length const Tabular::getPWidth(idx_type cell) const
1441 {
1442         if (isMultiColumn(cell))
1443                 return cellInfo(cell).p_width;
1444         return column_info[cellColumn(cell)].p_width;
1445 }
1446
1447
1448 Length const Tabular::getMROffset(idx_type cell) const
1449 {
1450         return cellInfo(cell).mroffset;
1451 }
1452
1453
1454 int Tabular::textHOffset(idx_type cell) const
1455 {
1456         // the LaTeX Way :-(
1457         int x = WIDTH_OF_LINE;
1458
1459         int const w = cellWidth(cell) - cellInfo(cell).width;
1460
1461         switch (getAlignment(cell)) {
1462         case LYX_ALIGN_CENTER:
1463                 x += w / 2;
1464                 break;
1465         case LYX_ALIGN_RIGHT:
1466                 x += w;
1467                 break;
1468         case LYX_ALIGN_DECIMAL: {
1469                 // we center when no decimal point
1470                 if (cellInfo(cell).decimal_width == 0) {
1471                         x += w / 2;
1472                         break;
1473                 }
1474                 col_type const c = cellColumn(cell);
1475                 int max_dhoffset = 0;
1476                 for(row_type r = 0; r < row_info.size() ; ++r) {
1477                         idx_type const i = cellIndex(r, c);
1478                         if (getAlignment(i) == LYX_ALIGN_DECIMAL
1479                                 && cellInfo(i).decimal_width != 0)
1480                                 max_dhoffset = max(max_dhoffset, cellInfo(i).decimal_hoffset);
1481                 }
1482                 x += max_dhoffset - cellInfo(cell).decimal_hoffset;
1483         }
1484         default:
1485                 // LYX_ALIGN_LEFT: nothing :-)
1486                 break;
1487         }
1488
1489         return x;
1490 }
1491
1492
1493 int Tabular::textVOffset(idx_type cell) const
1494 {
1495         int voffset = cellInfo(cell).voffset;
1496         if (isMultiRow(cell)) {
1497                 row_type const row = cellRow(cell);
1498                 voffset += (cellHeight(cell) - rowAscent(row) - rowDescent(row))/2;
1499         }
1500         return voffset;
1501 }
1502
1503
1504 Tabular::idx_type Tabular::getFirstCellInRow(row_type row) const
1505 {
1506         col_type c = 0;
1507         idx_type const numcells = numberOfCellsInRow(row);
1508         // we check against numcells to make sure we do not crash if all the
1509         // cells are multirow (bug #7535), but in that case our return value
1510         // is really invalid, i.e., it is NOT the first cell in the row. but
1511         // i do not know what to do here. (rgh)
1512         while (c < numcells - 1
1513                && cell_info[row][c].multirow == CELL_PART_OF_MULTIROW)
1514                 ++c;
1515         return cell_info[row][c].cellno;
1516 }
1517
1518
1519 Tabular::idx_type Tabular::getLastCellInRow(row_type row) const
1520 {
1521         col_type c = ncols() - 1;
1522         // of course we check against 0 so we don't crash. but we have the same
1523         // problem as in the previous routine: if all the cells are part of a
1524         // multirow or part of a multi column, then our return value is invalid.
1525         while (c > 0
1526                && (cell_info[row][c].multirow == CELL_PART_OF_MULTIROW
1527                    || cell_info[row][c].multicolumn == CELL_PART_OF_MULTICOLUMN))
1528                 --c;
1529         return cell_info[row][c].cellno;
1530 }
1531
1532
1533 Tabular::row_type Tabular::cellRow(idx_type cell) const
1534 {
1535         if (cell >= numberofcells)
1536                 return nrows() - 1;
1537         if (cell == npos)
1538                 return 0;
1539         return rowofcell[cell];
1540 }
1541
1542
1543 Tabular::col_type Tabular::cellColumn(idx_type cell) const
1544 {
1545         if (cell >= numberofcells)
1546                 return ncols() - 1;
1547         if (cell == npos)
1548                 return 0;
1549         return columnofcell[cell];
1550 }
1551
1552
1553 void Tabular::write(ostream & os) const
1554 {
1555         // header line
1556         os << "<lyxtabular"
1557            << write_attribute("version", 3)
1558            << write_attribute("rows", nrows())
1559            << write_attribute("columns", ncols())
1560            << ">\n";
1561         // global longtable options
1562         os << "<features"
1563            << write_attribute("rotate", rotate)
1564            << write_attribute("booktabs", use_booktabs)
1565            << write_attribute("islongtable", is_long_tabular)
1566            << write_attribute("firstHeadTopDL", endfirsthead.topDL)
1567            << write_attribute("firstHeadBottomDL", endfirsthead.bottomDL)
1568            << write_attribute("firstHeadEmpty", endfirsthead.empty)
1569            << write_attribute("headTopDL", endhead.topDL)
1570            << write_attribute("headBottomDL", endhead.bottomDL)
1571            << write_attribute("footTopDL", endfoot.topDL)
1572            << write_attribute("footBottomDL", endfoot.bottomDL)
1573            << write_attribute("lastFootTopDL", endlastfoot.topDL)
1574            << write_attribute("lastFootBottomDL", endlastfoot.bottomDL)
1575            << write_attribute("lastFootEmpty", endlastfoot.empty);
1576         // longtables cannot be aligned vertically
1577         if (!is_long_tabular)
1578                 os << write_attribute("tabularvalignment", tabular_valignment);
1579         os << write_attribute("tabularwidth", tabular_width);
1580         if (is_long_tabular)
1581                 os << write_attribute("longtabularalignment", longtabular_alignment);
1582         os << ">\n";
1583         for (col_type c = 0; c < ncols(); ++c) {
1584                 os << "<column"
1585                    << write_attribute("alignment", column_info[c].alignment);
1586                 if (column_info[c].alignment == LYX_ALIGN_DECIMAL)
1587                    os << write_attribute("decimal_point", column_info[c].decimal_point);
1588                 os << write_attribute("valignment", column_info[c].valignment)
1589                    << write_attribute("width", column_info[c].p_width.asString())
1590                    << write_attribute("varwidth", column_info[c].varwidth)
1591                    << write_attribute("special", column_info[c].align_special)
1592                    << ">\n";
1593         }
1594         for (row_type r = 0; r < nrows(); ++r) {
1595                 static const string def("default");
1596                 os << "<row";
1597                 if (row_info[r].top_space_default)
1598                         os << write_attribute("topspace", def);
1599                 else
1600                         os << write_attribute("topspace", row_info[r].top_space);
1601                 if (row_info[r].bottom_space_default)
1602                         os << write_attribute("bottomspace", def);
1603                 else
1604                         os << write_attribute("bottomspace", row_info[r].bottom_space);
1605                 if (row_info[r].interline_space_default)
1606                         os << write_attribute("interlinespace", def);
1607                 else
1608                         os << write_attribute("interlinespace", row_info[r].interline_space);
1609                 os << write_attribute("endhead", row_info[r].endhead)
1610                    << write_attribute("endfirsthead", row_info[r].endfirsthead)
1611                    << write_attribute("endfoot", row_info[r].endfoot)
1612                    << write_attribute("endlastfoot", row_info[r].endlastfoot)
1613                    << write_attribute("newpage", row_info[r].newpage)
1614                    << write_attribute("caption", row_info[r].caption)
1615                    << ">\n";
1616                 for (col_type c = 0; c < ncols(); ++c) {
1617                         os << "<cell"
1618                            << write_attribute("multicolumn", cell_info[r][c].multicolumn)
1619                            << write_attribute("multirow", cell_info[r][c].multirow)
1620                            << write_attribute("mroffset", cell_info[r][c].mroffset)
1621                            << write_attribute("alignment", cell_info[r][c].alignment)
1622                            << write_attribute("valignment", cell_info[r][c].valignment)
1623                            << write_attribute("topline", cell_info[r][c].top_line)
1624                            << write_attribute("bottomline", cell_info[r][c].bottom_line)
1625                            << write_attribute("leftline", cell_info[r][c].left_line)
1626                            << write_attribute("rightline", cell_info[r][c].right_line)
1627                            << write_attribute("rotate", cell_info[r][c].rotate)
1628                            << write_attribute("usebox", cell_info[r][c].usebox)
1629                            << write_attribute("width", cell_info[r][c].p_width)
1630                            << write_attribute("special", cell_info[r][c].align_special)
1631                            << ">\n";
1632                         os << "\\begin_inset ";
1633                         cell_info[r][c].inset->write(os);
1634                         os << "\n\\end_inset\n"
1635                            << "</cell>\n";
1636                         // FIXME This can be removed again once the mystery
1637                         // crash has been resolved.
1638                         os << flush;
1639                 }
1640                 os << "</row>\n";
1641         }
1642         os << "</lyxtabular>\n";
1643 }
1644
1645
1646 void Tabular::read(Lexer & lex)
1647 {
1648         string line;
1649         istream & is = lex.getStream();
1650
1651         l_getline(is, line);
1652         if (!prefixIs(line, "<lyxtabular ") && !prefixIs(line, "<Tabular ")) {
1653                 LASSERT(false, return);
1654         }
1655
1656         int version;
1657         if (!getTokenValue(line, "version", version))
1658                 return;
1659         LATTEST(version >= 2);
1660
1661         int rows_arg;
1662         if (!getTokenValue(line, "rows", rows_arg))
1663                 return;
1664         int columns_arg;
1665         if (!getTokenValue(line, "columns", columns_arg))
1666                 return;
1667         init(buffer_, rows_arg, columns_arg);
1668         l_getline(is, line);
1669         if (!prefixIs(line, "<features")) {
1670                 lyxerr << "Wrong tabular format (expected <features ...> got"
1671                        << line << ')' << endl;
1672                 return;
1673         }
1674         getTokenValue(line, "rotate", rotate);
1675         getTokenValue(line, "booktabs", use_booktabs);
1676         getTokenValue(line, "islongtable", is_long_tabular);
1677         getTokenValue(line, "tabularvalignment", tabular_valignment);
1678         getTokenValue(line, "tabularwidth", tabular_width);
1679         getTokenValue(line, "longtabularalignment", longtabular_alignment);
1680         getTokenValue(line, "firstHeadTopDL", endfirsthead.topDL);
1681         getTokenValue(line, "firstHeadBottomDL", endfirsthead.bottomDL);
1682         getTokenValue(line, "firstHeadEmpty", endfirsthead.empty);
1683         getTokenValue(line, "headTopDL", endhead.topDL);
1684         getTokenValue(line, "headBottomDL", endhead.bottomDL);
1685         getTokenValue(line, "footTopDL", endfoot.topDL);
1686         getTokenValue(line, "footBottomDL", endfoot.bottomDL);
1687         getTokenValue(line, "lastFootTopDL", endlastfoot.topDL);
1688         getTokenValue(line, "lastFootBottomDL", endlastfoot.bottomDL);
1689         getTokenValue(line, "lastFootEmpty", endlastfoot.empty);
1690
1691         for (col_type c = 0; c < ncols(); ++c) {
1692                 l_getline(is,line);
1693                 if (!prefixIs(line,"<column")) {
1694                         lyxerr << "Wrong tabular format (expected <column ...> got"
1695                                << line << ')' << endl;
1696                         return;
1697                 }
1698                 getTokenValue(line, "alignment", column_info[c].alignment);
1699                 getTokenValue(line, "decimal_point", column_info[c].decimal_point);
1700                 getTokenValue(line, "valignment", column_info[c].valignment);
1701                 getTokenValue(line, "width", column_info[c].p_width);
1702                 getTokenValue(line, "special", column_info[c].align_special);
1703                 getTokenValue(line, "varwidth", column_info[c].varwidth);
1704         }
1705
1706         for (row_type i = 0; i < nrows(); ++i) {
1707                 l_getline(is, line);
1708                 if (!prefixIs(line, "<row")) {
1709                         lyxerr << "Wrong tabular format (expected <row ...> got"
1710                                << line << ')' << endl;
1711                         return;
1712                 }
1713                 getTokenValue(line, "topspace", row_info[i].top_space,
1714                               row_info[i].top_space_default);
1715                 getTokenValue(line, "bottomspace", row_info[i].bottom_space,
1716                               row_info[i].bottom_space_default);
1717                 getTokenValue(line, "interlinespace", row_info[i].interline_space,
1718                               row_info[i].interline_space_default);
1719                 getTokenValue(line, "endfirsthead", row_info[i].endfirsthead);
1720                 getTokenValue(line, "endhead", row_info[i].endhead);
1721                 getTokenValue(line, "endfoot", row_info[i].endfoot);
1722                 getTokenValue(line, "endlastfoot", row_info[i].endlastfoot);
1723                 getTokenValue(line, "newpage", row_info[i].newpage);
1724                 getTokenValue(line, "caption", row_info[i].caption);
1725                 for (col_type j = 0; j < ncols(); ++j) {
1726                         l_getline(is, line);
1727                         if (!prefixIs(line, "<cell")) {
1728                                 lyxerr << "Wrong tabular format (expected <cell ...> got"
1729                                        << line << ')' << endl;
1730                                 return;
1731                         }
1732                         getTokenValue(line, "multicolumn", cell_info[i][j].multicolumn);
1733                         getTokenValue(line, "multirow", cell_info[i][j].multirow);
1734                         getTokenValue(line, "mroffset", cell_info[i][j].mroffset);
1735                         getTokenValue(line, "alignment", cell_info[i][j].alignment);
1736                         getTokenValue(line, "valignment", cell_info[i][j].valignment);
1737                         getTokenValue(line, "topline", cell_info[i][j].top_line);
1738                         getTokenValue(line, "bottomline", cell_info[i][j].bottom_line);
1739                         getTokenValue(line, "leftline", cell_info[i][j].left_line);
1740                         getTokenValue(line, "rightline", cell_info[i][j].right_line);
1741                         getTokenValue(line, "rotate", cell_info[i][j].rotate);
1742                         getTokenValue(line, "usebox", cell_info[i][j].usebox);
1743                         getTokenValue(line, "width", cell_info[i][j].p_width);
1744                         setFixedWidth(i,j);
1745                         getTokenValue(line, "special", cell_info[i][j].align_special);
1746                         l_getline(is, line);
1747                         if (prefixIs(line, "\\begin_inset")) {
1748                                 cell_info[i][j].inset->setBuffer(*buffer_);
1749                                 cell_info[i][j].inset->read(lex);
1750                                 l_getline(is, line);
1751                         }
1752                         if (!prefixIs(line, "</cell>")) {
1753                                 lyxerr << "Wrong tabular format (expected </cell> got"
1754                                        << line << ')' << endl;
1755                                 return;
1756                         }
1757                 }
1758                 l_getline(is, line);
1759                 if (!prefixIs(line, "</row>")) {
1760                         lyxerr << "Wrong tabular format (expected </row> got"
1761                                << line << ')' << endl;
1762                         return;
1763                 }
1764         }
1765         while (!prefixIs(line, "</lyxtabular>")) {
1766                 l_getline(is, line);
1767         }
1768         updateIndexes();
1769 }
1770
1771
1772 bool Tabular::isMultiColumn(idx_type cell) const
1773 {
1774         return (cellInfo(cell).multicolumn == CELL_BEGIN_OF_MULTICOLUMN
1775                 || cellInfo(cell).multicolumn == CELL_PART_OF_MULTICOLUMN);
1776 }
1777
1778
1779 bool Tabular::hasMultiColumn(col_type c) const
1780 {
1781         for (row_type r = 0; r < nrows(); ++r) {
1782                 if (isMultiColumn(cellIndex(r, c)))
1783                         return true;
1784         }
1785         return false;
1786 }
1787
1788
1789 bool Tabular::hasVarwidthColumn() const
1790 {
1791         for (col_type c = 0; c < ncols(); ++c) {
1792                 if (column_info[c].varwidth)
1793                         return true;
1794         }
1795         return false;
1796 }
1797
1798
1799 bool Tabular::isVTypeColumn(col_type c) const
1800 {
1801         for (row_type r = 0; r < nrows(); ++r) {
1802                 idx_type idx = cellIndex(r, c);
1803                 if (getRotateCell(idx) == 0 && useBox(idx) == BOX_VARWIDTH)
1804                         return true;
1805         }
1806         return false;
1807 }
1808
1809
1810 Tabular::CellData const & Tabular::cellInfo(idx_type cell) const
1811 {
1812         return cell_info[cellRow(cell)][cellColumn(cell)];
1813 }
1814
1815
1816 Tabular::CellData & Tabular::cellInfo(idx_type cell)
1817 {
1818         return cell_info[cellRow(cell)][cellColumn(cell)];
1819 }
1820
1821
1822 Tabular::idx_type Tabular::setMultiColumn(Cursor & cur, idx_type cell, idx_type number,
1823                                           bool const right_border)
1824 {
1825         idx_type const col = cellColumn(cell);
1826         idx_type const row = cellRow(cell);
1827         for (idx_type i = 0; i < number; ++i)
1828                 unsetMultiRow(cellIndex(row, col + i));
1829
1830         // unsetting of multirow may have invalidated cell index
1831         cell = cellIndex(row, col);
1832         CellData & cs = cellInfo(cell);
1833         cs.multicolumn = CELL_BEGIN_OF_MULTICOLUMN;
1834         if (column_info[col].alignment != LYX_ALIGN_DECIMAL)
1835                 cs.alignment = column_info[col].alignment;
1836         setRightLine(cell, right_border);
1837         // non-fixed width multicolumns cannot have multiple paragraphs
1838         if (getPWidth(cell).zero()) {
1839                 toggleFixedWidth(cur, cellInset(cell).get(),
1840                                  !getPWidth(cell).zero(), isMultiColumn(cell),
1841                                  isMultiRow(cell));
1842                 // cur can become invalid after paragraphs were merged
1843                 cur.fixIfBroken();
1844         }
1845
1846         idx_type lastcell = cellIndex(row, col + number - 1);
1847         for (idx_type i = 1; i < lastcell - cell + 1; ++i) {
1848                 CellData & cs1 = cellInfo(cell + i);
1849                 cs1.multicolumn = CELL_PART_OF_MULTICOLUMN;
1850                 cs.inset->appendParagraphs(cs1.inset->paragraphs());
1851                 cs1.inset->clear();
1852         }
1853         updateIndexes();
1854         return cell;
1855 }
1856
1857
1858 bool Tabular::isMultiRow(idx_type cell) const
1859 {
1860         return (cellInfo(cell).multirow == CELL_BEGIN_OF_MULTIROW
1861                 || cellInfo(cell).multirow == CELL_PART_OF_MULTIROW);
1862 }
1863
1864 bool Tabular::hasMultiRow(row_type r) const
1865 {
1866         for (col_type c = 0; c < ncols(); ++c) {
1867                 if (isMultiRow(cellIndex(r, c)))
1868                         return true;
1869         }
1870         return false;
1871 }
1872
1873 Tabular::idx_type Tabular::setMultiRow(Cursor & cur, idx_type cell, idx_type number,
1874                                        bool const bottom_border,
1875                                        LyXAlignment const halign)
1876 {
1877         idx_type const col = cellColumn(cell);
1878         idx_type const row = cellRow(cell);
1879         for (idx_type i = 0; i < number; ++i)
1880                 unsetMultiColumn(cellIndex(row + i, col));
1881
1882         // unsetting of multirow may have invalidated cell index
1883         cell = cellIndex(row, col);
1884         CellData & cs = cellInfo(cell);
1885         cs.multirow = CELL_BEGIN_OF_MULTIROW;
1886         cs.valignment = LYX_VALIGN_MIDDLE;
1887         // the horizontal alignment of multirow cells can only
1888         // be changed for the whole table row,
1889         // support changing this only for the multirow cell can be done via
1890         // \multirowsetup
1891         if (getPWidth(cell).zero())
1892                 cs.alignment = halign;
1893         else
1894                 cs.alignment = LYX_ALIGN_LEFT;
1895
1896         // Multirows cannot have multiple paragraphs
1897         if (getPWidth(cell).zero()) {
1898                 toggleFixedWidth(cur, cellInset(cell).get(),
1899                                  !getPWidth(cell).zero(),
1900                                  isMultiColumn(cell), isMultiRow(cell));
1901                 // cur can become invalid after paragraphs were merged
1902                 cur.fixIfBroken();
1903         }
1904
1905         // set the bottom line of the last selected cell
1906         setBottomLine(cell, bottom_border);
1907
1908         for (idx_type i = 1; i < number; ++i) {
1909                 CellData & cs1 = cell_info[row + i][col];
1910                 cs1.multirow = CELL_PART_OF_MULTIROW;
1911                 cs.inset->appendParagraphs(cs1.inset->paragraphs());
1912                 cs1.inset->clear();
1913         }
1914         updateIndexes();
1915         return cell;
1916 }
1917
1918
1919 Tabular::idx_type Tabular::columnSpan(idx_type cell) const
1920 {
1921         row_type const row = cellRow(cell);
1922         col_type const col = cellColumn(cell);
1923         int span = 1;
1924         while (col + span < ncols() && isPartOfMultiColumn(row, col + span))
1925                 ++span;
1926
1927         return span;
1928 }
1929
1930
1931 Tabular::idx_type Tabular::rowSpan(idx_type cell) const
1932 {
1933         col_type const column = cellColumn(cell);
1934         col_type row = cellRow(cell) + 1;
1935         while (row < nrows() && isPartOfMultiRow(row, column))
1936                 ++row;
1937
1938         return row - cellRow(cell);
1939 }
1940
1941
1942 void Tabular::unsetMultiColumn(idx_type cell)
1943 {
1944         if (!isMultiColumn(cell))
1945                 return;
1946
1947         row_type const row = cellRow(cell);
1948         col_type const col = cellColumn(cell);
1949         row_type const span = columnSpan(cell);
1950         for (col_type c = 0; c < span; ++c) {
1951                 // in the table dialog the lines are set in every case
1952                 // when unsetting a multicolumn this leads to an additional right
1953                 // line for every cell that was part of the former multicolumn cell,
1954                 // except if the cell is in the last column
1955                 // therefore remove this line
1956                 if (cell_info[row][col + c].multicolumn == CELL_BEGIN_OF_MULTICOLUMN
1957                         && (col + c) < (col + span - 1))
1958                         cell_info[row][col + c].right_line = false;
1959                 cell_info[row][col + c].multicolumn = CELL_NORMAL;
1960         }
1961         updateIndexes();
1962 }
1963
1964
1965 void Tabular::unsetMultiRow(idx_type cell)
1966 {
1967         if (!isMultiRow(cell))
1968                 return;
1969
1970         cellInfo(cell).valignment = LYX_VALIGN_TOP;
1971         cellInfo(cell).alignment = LYX_ALIGN_CENTER;
1972         row_type const row = cellRow(cell);
1973         col_type const col = cellColumn(cell);
1974         row_type const span = rowSpan(cell);
1975         for (row_type r = 0; r < span; ++r)
1976                 cell_info[row + r][col].multirow = CELL_NORMAL;
1977         updateIndexes();
1978 }
1979
1980
1981 void Tabular::setRotateCell(idx_type cell, int value)
1982 {
1983         cellInfo(cell).rotate = value;
1984 }
1985
1986
1987 int Tabular::getRotateCell(idx_type cell) const
1988 {
1989         return cellInfo(cell).rotate;
1990 }
1991
1992
1993 bool Tabular::needRotating() const
1994 {
1995         if (rotate && !is_long_tabular)
1996                 return true;
1997         for (row_type r = 0; r < nrows(); ++r)
1998                 for (col_type c = 0; c < ncols(); ++c)
1999                         if (cell_info[r][c].rotate != 0)
2000                                 return true;
2001         return false;
2002 }
2003
2004
2005 bool Tabular::isLastCell(idx_type cell) const
2006 {
2007         if (cell + 1 < numberofcells)
2008                 return false;
2009         return true;
2010 }
2011
2012
2013 Tabular::idx_type Tabular::cellAbove(idx_type cell) const
2014 {
2015         if (cellRow(cell) == 0)
2016                 return cell;
2017
2018         col_type const col = cellColumn(cell);
2019         row_type r = cellRow(cell) - 1;
2020         while (r > 0 && cell_info[r][col].multirow == CELL_PART_OF_MULTIROW)
2021                 --r;
2022
2023         return cell_info[r][col].cellno;
2024 }
2025
2026
2027 Tabular::idx_type Tabular::cellBelow(idx_type cell) const
2028 {
2029         row_type const nextrow = cellRow(cell) + rowSpan(cell);
2030         if (nextrow < nrows())
2031                 return cell_info[nextrow][cellColumn(cell)].cellno;
2032         return cell;
2033 }
2034
2035
2036 Tabular::idx_type Tabular::cellIndex(row_type row, col_type column) const
2037 {
2038         LASSERT(column != npos && column < ncols(), column = 0);
2039         LASSERT(row != npos && row < nrows(), row = 0);
2040         return cell_info[row][column].cellno;
2041 }
2042
2043
2044 void Tabular::setUsebox(idx_type cell, BoxType type)
2045 {
2046         cellInfo(cell).usebox = type;
2047 }
2048
2049
2050 Tabular::BoxType Tabular::getUsebox(idx_type cell) const
2051 {
2052         if (getRotateCell(cell) == 0
2053             && ((!column_info[cellColumn(cell)].p_width.zero() && !isMultiColumn(cell)) ||
2054                 (isMultiColumn(cell) && !cellInfo(cell).p_width.zero())))
2055                 return BOX_NONE;
2056         if (cellInfo(cell).usebox > 1)
2057                 return cellInfo(cell).usebox;
2058         return useBox(cell);
2059 }
2060
2061
2062 ///
2063 //  This are functions used for the longtable support
2064 ///
2065 void Tabular::setLTHead(row_type row, bool flag, ltType const & hd,
2066                            bool first)
2067 {
2068         if (first) {
2069                 endfirsthead = hd;
2070                 if (hd.set)
2071                         row_info[row].endfirsthead = flag;
2072         } else {
2073                 endhead = hd;
2074                 if (hd.set)
2075                         row_info[row].endhead = flag;
2076         }
2077 }
2078
2079
2080 bool Tabular::getRowOfLTHead(row_type row, ltType & hd) const
2081 {
2082         hd = endhead;
2083         hd.set = haveLTHead();
2084         return row_info[row].endhead;
2085 }
2086
2087
2088 bool Tabular::getRowOfLTFirstHead(row_type row, ltType & hd) const
2089 {
2090         hd = endfirsthead;
2091         hd.set = haveLTFirstHead();
2092         return row_info[row].endfirsthead;
2093 }
2094
2095
2096 void Tabular::setLTFoot(row_type row, bool flag, ltType const & fd,
2097                            bool last)
2098 {
2099         if (last) {
2100                 endlastfoot = fd;
2101                 if (fd.set)
2102                         row_info[row].endlastfoot = flag;
2103         } else {
2104                 endfoot = fd;
2105                 if (fd.set)
2106                         row_info[row].endfoot = flag;
2107         }
2108 }
2109
2110
2111 bool Tabular::getRowOfLTFoot(row_type row, ltType & fd) const
2112 {
2113         fd = endfoot;
2114         fd.set = haveLTFoot();
2115         return row_info[row].endfoot;
2116 }
2117
2118
2119 bool Tabular::getRowOfLTLastFoot(row_type row, ltType & fd) const
2120 {
2121         fd = endlastfoot;
2122         fd.set = haveLTLastFoot();
2123         return row_info[row].endlastfoot;
2124 }
2125
2126
2127 void Tabular::setLTNewPage(row_type row, bool what)
2128 {
2129         row_info[row].newpage = what;
2130 }
2131
2132
2133 bool Tabular::getLTNewPage(row_type row) const
2134 {
2135         return row_info[row].newpage;
2136 }
2137
2138
2139 bool Tabular::haveLTHead(bool withcaptions) const
2140 {
2141         if (!is_long_tabular)
2142                 return false;
2143         for (row_type i = 0; i < nrows(); ++i)
2144                 if (row_info[i].endhead &&
2145                     (withcaptions || !row_info[i].caption))
2146                         return true;
2147         return false;
2148 }
2149
2150
2151 bool Tabular::haveLTFirstHead(bool withcaptions) const
2152 {
2153         if (!is_long_tabular || endfirsthead.empty)
2154                 return false;
2155         for (row_type r = 0; r < nrows(); ++r)
2156                 if (row_info[r].endfirsthead &&
2157                     (withcaptions || !row_info[r].caption))
2158                         return true;
2159         return false;
2160 }
2161
2162
2163 bool Tabular::haveLTFoot(bool withcaptions) const
2164 {
2165         if (!is_long_tabular)
2166                 return false;
2167         for (row_type r = 0; r < nrows(); ++r)
2168                 if (row_info[r].endfoot &&
2169                     (withcaptions || !row_info[r].caption))
2170                         return true;
2171         return false;
2172 }
2173
2174
2175 bool Tabular::haveLTLastFoot(bool withcaptions) const
2176 {
2177         if (!is_long_tabular || endlastfoot.empty)
2178                 return false;
2179         for (row_type r = 0; r < nrows(); ++r)
2180                 if (row_info[r].endlastfoot &&
2181                     (withcaptions || !row_info[r].caption))
2182                         return true;
2183         return false;
2184 }
2185
2186
2187 Tabular::idx_type Tabular::setLTCaption(Cursor & cur, row_type row, bool what)
2188 {
2189         idx_type i = getFirstCellInRow(row);
2190         if (what) {
2191                 setMultiColumn(cur, i, numberOfCellsInRow(row), false);
2192                 setTopLine(i, false);
2193                 setBottomLine(i, false);
2194                 setLeftLine(i, false);
2195                 setRightLine(i, false);
2196                 if (!row_info[row].endfirsthead && !row_info[row].endhead &&
2197                     !row_info[row].endfoot && !row_info[row].endlastfoot) {
2198                         setLTHead(row, true, endfirsthead, true);
2199                         row_info[row].endfirsthead = true;
2200                 }
2201         } else {
2202                 unsetMultiColumn(i);
2203                 // When unsetting a caption row, also all existing
2204                 // captions in this row must be dissolved.
2205         }
2206         row_info[row].caption = what;
2207         return i;
2208 }
2209
2210
2211 bool Tabular::ltCaption(row_type row) const
2212 {
2213         return row_info[row].caption;
2214 }
2215
2216
2217 bool Tabular::haveLTCaption(CaptionType captiontype) const
2218 {
2219         if (!is_long_tabular)
2220                 return false;
2221         for (row_type r = 0; r < nrows(); ++r) {
2222                 if (row_info[r].caption) {
2223                         switch (captiontype) {
2224                         case CAPTION_FIRSTHEAD:
2225                                 if (row_info[r].endfirsthead)
2226                                         return true;
2227                                 break;
2228                         case CAPTION_HEAD:
2229                                 if (row_info[r].endhead)
2230                                         return true;
2231                                 break;
2232                         case CAPTION_FOOT:
2233                                 if (row_info[r].endfoot)
2234                                         return true;
2235                                 break;
2236                         case CAPTION_LASTFOOT:
2237                                 if (row_info[r].endlastfoot)
2238                                         return true;
2239                                 break;
2240                         case CAPTION_ANY:
2241                                 return true;
2242                         }
2243                 }
2244         }
2245         return false;
2246 }
2247
2248
2249 // end longtable support functions
2250
2251 void Tabular::setRowAscent(row_type row, int height)
2252 {
2253         if (row >= nrows() || row_info[row].ascent == height)
2254                 return;
2255         row_info[row].ascent = height;
2256 }
2257
2258
2259 void Tabular::setRowDescent(row_type row, int height)
2260 {
2261         if (row >= nrows() || row_info[row].descent == height)
2262                 return;
2263         row_info[row].descent = height;
2264 }
2265
2266
2267 int Tabular::rowAscent(row_type row) const
2268 {
2269         LASSERT(row < nrows(), row = 0);
2270         return row_info[row].ascent;
2271 }
2272
2273
2274 int Tabular::rowDescent(row_type row) const
2275 {
2276         LASSERT(row < nrows(), row = 0);
2277         return row_info[row].descent;
2278 }
2279
2280
2281 int Tabular::height() const
2282 {
2283         int height = 0;
2284         for (row_type row = 0; row < nrows(); ++row)
2285                 height += rowAscent(row) + rowDescent(row) +
2286                         interRowSpace(row);
2287         return height;
2288 }
2289
2290
2291 bool Tabular::isPartOfMultiColumn(row_type row, col_type column) const
2292 {
2293         LASSERT(row < nrows(), return false);
2294         LASSERT(column < ncols(), return false);
2295         return cell_info[row][column].multicolumn == CELL_PART_OF_MULTICOLUMN;
2296 }
2297
2298
2299 bool Tabular::isPartOfMultiRow(row_type row, col_type column) const
2300 {
2301         LASSERT(row < nrows(), return false);
2302         LASSERT(column < ncols(), return false);
2303         return cell_info[row][column].multirow == CELL_PART_OF_MULTIROW;
2304 }
2305
2306
2307 void Tabular::TeXTopHLine(otexstream & os, row_type row, string const & lang,
2308                           list<col_type> columns) const
2309 {
2310         // we only output complete row lines and the 1st row here, the rest
2311         // is done in Tabular::TeXBottomHLine(...)
2312
2313         // get for each column the topline (if any)
2314         map<col_type, bool> topline;
2315         col_type nset = 0;
2316         for (auto const & c : columns) {
2317                 topline[c] = topLine(cellIndex(row, c));
2318                 // If cell is part of a multirow and not the first cell of the
2319                 // multirow, no line must be drawn.
2320                 if (row != 0)
2321                         if (isMultiRow(cellIndex(row, c))
2322                             && cell_info[row][c].multirow != CELL_BEGIN_OF_MULTIROW)
2323                                 topline[c] = false;
2324                 if (topline.find(c) != topline.end() && topline.find(c)->second)
2325                         ++nset;
2326         }
2327
2328         // do nothing if empty first row, or incomplete row line after
2329         if ((row == 0 && nset == 0) || (row > 0 && nset != ncols()))
2330                 return;
2331
2332         // only output complete row lines and the 1st row's clines
2333         if (nset == ncols()) {
2334                 if (use_booktabs) {
2335                         os << (row == 0 ? "\\toprule " : "\\midrule ");
2336                 } else {
2337                         os << "\\hline ";
2338                 }
2339         } else if (row == 0) {
2340                 for (auto & c : columns) {
2341                         if (topline.find(c)->second) {
2342                                 col_type offset = 0;
2343                                 for (col_type j = 0 ; j < c; ++j)
2344                                         if (column_info[j].alignment == LYX_ALIGN_DECIMAL)
2345                                                 ++offset;
2346
2347                                 //babel makes the "-" character an active one, so we have to suppress this here
2348                                 //see http://groups.google.com/group/comp.text.tex/browse_thread/thread/af769424a4a0f289#
2349                                 if (lang == "slovak" || lang == "czech")
2350                                         os << "\\expandafter" << (use_booktabs ? "\\cmidrule" : "\\cline")
2351                                         << "\\expandafter{\\expandafter" << c + 1 + offset << "\\string-";
2352                                 else
2353                                         os << (use_booktabs ? "\\cmidrule{" : "\\cline{") << c + 1 + offset << '-';
2354
2355                                 col_type cstart = c;
2356                                 for ( ; c < ncols() && topline.find(c)->second; ++c) {}
2357
2358                                 for (col_type j = cstart ; j < c ; ++j)
2359                                         if (column_info[j].alignment == LYX_ALIGN_DECIMAL)
2360                                                 ++offset;
2361
2362                                 os << c + offset << "} ";
2363                         }
2364                 }
2365         }
2366         os << "\n";
2367 }
2368
2369
2370 void Tabular::TeXBottomHLine(otexstream & os, row_type row, string const & lang,
2371                              list<col_type> columns) const
2372 {
2373         // we output bottomlines of row r and the toplines of row r+1
2374         // if the latter do not span the whole tabular
2375
2376         // get the bottomlines of row r, and toplines in next row
2377         bool lastrow = row == nrows() - 1;
2378         map<col_type, bool> bottomline, topline;
2379         bool nextrowset = true;
2380         for (auto const & c : columns) {
2381                 bottomline[c] = bottomLine(cellIndex(row, c));
2382                 topline[c] =  !lastrow && topLine(cellIndex(row + 1, c));
2383                 // If cell is part of a multirow and not the last cell of the
2384                 // multirow, no line must be drawn.
2385                 if (!lastrow)
2386                         if (isMultiRow(cellIndex(row, c))
2387                             && isMultiRow(cellIndex(row + 1, c))
2388                             && cell_info[row + 1][c].multirow != CELL_BEGIN_OF_MULTIROW) {
2389                                 bottomline[c] = false;
2390                                 topline[c] = false;
2391                                 }
2392                 nextrowset &= topline.find(c) != topline.end() && topline.find(c)->second;
2393         }
2394
2395         // combine this row's bottom lines and next row's toplines if necessary
2396         col_type nset = 0;
2397         for (auto const & c : columns) {
2398                 if (!nextrowset)
2399                         bottomline[c] = bottomline.find(c)->second || topline.find(c)->second;
2400                 if (bottomline.find(c)->second)
2401                         ++nset;
2402         }
2403
2404         // do nothing if empty, OR incomplete row line with a topline in next row
2405         if (nset == 0 || (nextrowset && nset != ncols()))
2406                 return;
2407
2408         if (nset == ncols()) {
2409                 if (use_booktabs)
2410                         os << (lastrow ? "\\bottomrule" : "\\midrule");
2411                 else
2412                         os << "\\hline ";
2413         } else {
2414                 for (auto & c : columns) {
2415                         if (bottomline.find(c)->second) {
2416                                 col_type offset = 0;
2417                                 for (col_type j = 0 ; j < c; ++j)
2418                                         if (column_info[j].alignment == LYX_ALIGN_DECIMAL)
2419                                                 ++offset;
2420
2421                                 //babel makes the "-" character an active one, so we have to suppress this here
2422                                 //see http://groups.google.com/group/comp.text.tex/browse_thread/thread/af769424a4a0f289#
2423                                 if (lang == "slovak" || lang == "czech")
2424                                         os << "\\expandafter" << (use_booktabs ? "\\cmidrule" : "\\cline")
2425                                         << "\\expandafter{\\expandafter" << c + 1 + offset << "\\string-";
2426                                 else
2427                                         os << (use_booktabs ? "\\cmidrule{" : "\\cline{") << c + 1 + offset << '-';
2428
2429                                 col_type cstart = c;
2430                                 for ( ; c < ncols() && bottomline.find(c)->second; ++c) {}
2431
2432                                 for (col_type j = cstart ; j < c ; ++j)
2433                                         if (column_info[j].alignment == LYX_ALIGN_DECIMAL)
2434                                                 ++offset;
2435
2436                                 os << c + offset << "} ";
2437                         }
2438                 }
2439         }
2440         os << "\n";
2441 }
2442
2443
2444 void Tabular::TeXCellPreamble(otexstream & os, idx_type cell,
2445                               bool & ismulticol, bool & ismultirow,
2446                               bool const bidi) const
2447 {
2448         row_type const r = cellRow(cell);
2449         if (is_long_tabular && row_info[r].caption)
2450                 return;
2451
2452         Tabular::VAlignment valign =  getVAlignment(cell, !isMultiColumn(cell));
2453         LyXAlignment align = getAlignment(cell, !isMultiColumn(cell));
2454         // figure out how to set the lines
2455         // we always set double lines to the right of the cell
2456         // or left in bidi RTL, respectively.
2457         col_type const c = cellColumn(cell);
2458         col_type const nextcol = c + columnSpan(cell);
2459         bool const decimal = column_info[c].alignment == LYX_ALIGN_DECIMAL;
2460         bool colright = columnRightLine(c);
2461         bool colleft = columnLeftLine(c);
2462         bool nextcolleft = nextcol < ncols() && columnLeftLine(nextcol);
2463         bool nextcellleft = nextcol < ncols()
2464                 && leftLine(cellIndex(r, nextcol));
2465         bool coldouble = colright && nextcolleft;
2466         bool celldouble = rightLine(cell) && nextcellleft;
2467
2468         ismulticol = (isMultiColumn(cell)
2469                       || (c == 0 && colleft != leftLine(cell))
2470                       || ((colright || nextcolleft) && !rightLine(cell) && !nextcellleft)
2471                       || (!colright && !nextcolleft && (rightLine(cell) || nextcellleft))
2472                       || (coldouble != celldouble))
2473                      && !decimal;
2474
2475         // we center in multicol when no decimal point
2476         if (decimal) {
2477                 docstring const align_d = column_info[c].decimal_point;
2478                 DocIterator const dit = separatorPos(cellInset(cell), align_d);
2479                 bool const nosep = !dit;
2480                 ismulticol |= nosep;
2481                 celldouble &= nosep;
2482         }
2483
2484         // up counter by 1 for each decimally aligned col since they use 2 latex cols
2485         int latexcolspan = columnSpan(cell);
2486         for (col_type col = c; col < c + columnSpan(cell); ++col)
2487                 if (column_info[col].alignment == LYX_ALIGN_DECIMAL)
2488                         ++latexcolspan;
2489
2490         if (ismulticol) {
2491                 os << "\\multicolumn{" << latexcolspan << "}{";
2492                 if (((bidi && c == getLastCellInRow(cellRow(0)) && rightLine(cell))
2493                      || (!bidi && c == 0 && leftLine(cell))))
2494                         os << '|';
2495                 if (bidi && celldouble)
2496                         // add extra vertical line if we want a double one
2497                         os << '|';
2498                 if (!cellInfo(cell).align_special.empty()) {
2499                         os << cellInfo(cell).align_special;
2500                 } else {
2501                         if (!getPWidth(cell).zero()) {
2502                                 switch (align) {
2503                                 case LYX_ALIGN_LEFT:
2504                                         os << ">{\\raggedright}";
2505                                         break;
2506                                 case LYX_ALIGN_RIGHT:
2507                                         os << ">{\\raggedleft}";
2508                                         break;
2509                                 case LYX_ALIGN_CENTER:
2510                                         os << ">{\\centering}";
2511                                         break;
2512                                 default:
2513                                         break;
2514                                 }
2515                                 switch (valign) {
2516                                 case LYX_VALIGN_TOP:
2517                                         os << 'p';
2518                                         break;
2519                                 case LYX_VALIGN_MIDDLE:
2520                                         os << 'm';
2521                                         break;
2522                                 case LYX_VALIGN_BOTTOM:
2523                                         os << 'b';
2524                                         break;
2525                                 }
2526                                 os << '{'
2527                                    << from_ascii(getPWidth(cell).asLatexString())
2528                                    << '}';
2529                         } else {
2530                                 switch (align) {
2531                                 case LYX_ALIGN_LEFT:
2532                                         os << 'l';
2533                                         break;
2534                                 case LYX_ALIGN_RIGHT:
2535                                         os << 'r';
2536                                         break;
2537                                 default:
2538                                         os << 'c';
2539                                         break;
2540                                 }
2541                         } // end if else !getPWidth
2542                 } // end if else !cellinfo_of_cell
2543                 if ((bidi && leftLine(cell)) || (!bidi && rightLine(cell)) || nextcellleft)
2544                         os << '|';
2545                 if (!bidi && celldouble)
2546                         // add extra vertical line if we want a double one
2547                         os << '|';
2548                 os << "}{";
2549         } // end if ismulticol
2550
2551         // we only need code for the first multirow cell
2552         ismultirow = isMultiRow(cell);
2553         if (ismultirow) {
2554                 os << "\\multirow{" << rowSpan(cell) << "}{";
2555                 if (!getPWidth(cell).zero())
2556                         os << from_ascii(getPWidth(cell).asLatexString());
2557                 else
2558                         // we need to set a default value
2559                         os << "*";
2560                 os << "}";
2561                 if (!getMROffset(cell).zero())
2562                         os << "[" << from_ascii(getMROffset(cell).asLatexString()) << "]";
2563                 os << "{";
2564         } // end if ismultirow
2565
2566         if (getRotateCell(cell) != 0)
2567                 os << "\\begin{turn}{" << convert<string>(getRotateCell(cell)) << "}\n";
2568
2569         if (getUsebox(cell) == BOX_PARBOX) {
2570                 os << "\\parbox[";
2571                 switch (valign) {
2572                 case LYX_VALIGN_TOP:
2573                         os << 't';
2574                         break;
2575                 case LYX_VALIGN_MIDDLE:
2576                         os << 'c';
2577                         break;
2578                 case LYX_VALIGN_BOTTOM:
2579                         os << 'b';
2580                         break;
2581                 }
2582                 os << "]{" << from_ascii(getPWidth(cell).asLatexString())
2583                    << "}{";
2584         } else if (getUsebox(cell) == BOX_MINIPAGE) {
2585                 os << "\\begin{minipage}[";
2586                 switch (valign) {
2587                 case LYX_VALIGN_TOP:
2588                         os << 't';
2589                         break;
2590                 case LYX_VALIGN_MIDDLE:
2591                         os << 'm';
2592                         break;
2593                 case LYX_VALIGN_BOTTOM:
2594                         os << 'b';
2595                         break;
2596                 }
2597                 os << "]{" << from_ascii(getPWidth(cell).asLatexString())
2598                    << "}\n";
2599         } else if (getRotateCell(cell) != 0 && getUsebox(cell) == BOX_VARWIDTH) {
2600                 os << "\\begin{varwidth}[";
2601                 switch (valign) {
2602                 case LYX_VALIGN_TOP:
2603                         os << 't';
2604                         break;
2605                 case LYX_VALIGN_MIDDLE:
2606                         os << 'm';
2607                         break;
2608                 case LYX_VALIGN_BOTTOM:
2609                         os << 'b';
2610                         break;
2611         }
2612         os << "]{\\linewidth}\n";
2613 }
2614 }
2615
2616
2617 void Tabular::TeXCellPostamble(otexstream & os, idx_type cell,
2618                                bool ismulticol, bool ismultirow) const
2619 {
2620         row_type const r = cellRow(cell);
2621         if (is_long_tabular && row_info[r].caption)
2622                 return;
2623
2624         // usual cells
2625         if (getUsebox(cell) == BOX_PARBOX)
2626                 os << '}';
2627         else if (getUsebox(cell) == BOX_MINIPAGE)
2628                 os << breakln << "\\end{minipage}";
2629         else if (getRotateCell(cell) != 0 && getUsebox(cell) == BOX_VARWIDTH)
2630                 os << breakln << "\\end{varwidth}";
2631         if (getRotateCell(cell) != 0)
2632                 os << breakln << "\\end{turn}";
2633         if (ismultirow)
2634                 os << '}';
2635         if (ismulticol)
2636                 os << '}';
2637 }
2638
2639
2640 void Tabular::TeXLongtableHeaderFooter(otexstream & os,
2641                                        OutputParams const & runparams,
2642                                        list<col_type> columns) const
2643 {
2644         if (!is_long_tabular)
2645                 return;
2646
2647         // caption handling
2648         // output caption which is in no header or footer
2649         if (haveLTCaption()) {
2650                 for (row_type r = 0; r < nrows(); ++r) {
2651                         if (row_info[r].caption &&
2652                             !row_info[r].endfirsthead && !row_info[r].endhead &&
2653                             !row_info[r].endfoot && !row_info[r].endlastfoot)
2654                                 TeXRow(os, r, runparams, columns);
2655                 }
2656         }
2657         // output first header info
2658         if (haveLTFirstHead()) {
2659                 if (endfirsthead.topDL)
2660                         os << "\\hline\n";
2661                 for (row_type r = 0; r < nrows(); ++r) {
2662                         if (row_info[r].endfirsthead)
2663                                 TeXRow(os, r, runparams, columns);
2664                 }
2665                 if (endfirsthead.bottomDL)
2666                         os << "\\hline\n";
2667                 os << "\\endfirsthead\n";
2668         }
2669         // output header info
2670         if (haveLTHead()) {
2671                 if (endfirsthead.empty && !haveLTFirstHead())
2672                         os << "\\endfirsthead\n";
2673                 if (endhead.topDL)
2674                         os << "\\hline\n";
2675                 for (row_type r = 0; r < nrows(); ++r) {
2676                         if (row_info[r].endhead)
2677                                 TeXRow(os, r, runparams, columns);
2678                 }
2679                 if (endhead.bottomDL)
2680                         os << "\\hline\n";
2681                 os << "\\endhead\n";
2682         }
2683         // output footer info
2684         if (haveLTFoot()) {
2685                 if (endfoot.topDL)
2686                         os << "\\hline\n";
2687                 for (row_type r = 0; r < nrows(); ++r) {
2688                         if (row_info[r].endfoot)
2689                                 TeXRow(os, r, runparams, columns);
2690                 }
2691                 if (endfoot.bottomDL)
2692                         os << "\\hline\n";
2693                 os << "\\endfoot\n";
2694                 if (endlastfoot.empty && !haveLTLastFoot())
2695                         os << "\\endlastfoot\n";
2696         }
2697         // output lastfooter info
2698         if (haveLTLastFoot()) {
2699                 if (endlastfoot.topDL)
2700                         os << "\\hline\n";
2701                 for (row_type r = 0; r < nrows(); ++r) {
2702                         if (row_info[r].endlastfoot)
2703                                 TeXRow(os, r, runparams, columns);
2704                 }
2705                 if (endlastfoot.bottomDL)
2706                         os << "\\hline\n";
2707                 os << "\\endlastfoot\n";
2708         }
2709 }
2710
2711
2712 bool Tabular::isValidRow(row_type row) const
2713 {
2714         if (!is_long_tabular)
2715                 return true;
2716         return !row_info[row].endhead && !row_info[row].endfirsthead
2717                 && !row_info[row].endfoot && !row_info[row].endlastfoot
2718                 && !row_info[row].caption;
2719 }
2720
2721
2722 void Tabular::TeXRow(otexstream & os, row_type row,
2723                      OutputParams const & runparams,
2724                      list<col_type> columns) const
2725 {
2726         idx_type cell = cellIndex(row, 0);
2727         InsetTableCell const * cinset = cellInset(cell);
2728         Paragraph const & cpar = cinset->paragraphs().front();
2729         string const clang = cpar.getParLanguage(buffer().params())->lang();
2730
2731         //output the top line
2732         TeXTopHLine(os, row, clang, columns);
2733
2734         if (row_info[row].top_space_default) {
2735                 if (use_booktabs)
2736                         os << "\\addlinespace\n";
2737                 else
2738                         os << "\\noalign{\\vskip\\doublerulesep}\n";
2739         } else if(!row_info[row].top_space.zero()) {
2740                 if (use_booktabs)
2741                         os << "\\addlinespace["
2742                            << from_ascii(row_info[row].top_space.asLatexString())
2743                            << "]\n";
2744                 else {
2745                         os << "\\noalign{\\vskip"
2746                            << from_ascii(row_info[row].top_space.asLatexString())
2747                            << "}\n";
2748                 }
2749         }
2750         bool ismulticol = false;
2751         bool ismultirow = false;
2752
2753         // The bidi package (loaded by polyglossia) reverses RTL table columns
2754         bool const bidi_rtl =
2755                 runparams.local_font->isRightToLeft()
2756                 && runparams.use_polyglossia;
2757         idx_type lastcell =
2758                 bidi_rtl ? getFirstCellInRow(row) : getLastCellInRow(row);
2759
2760         for (auto const & c : columns) {
2761                 if (isPartOfMultiColumn(row, c))
2762                         continue;
2763
2764                 cell = cellIndex(row, c);
2765
2766                 if (isPartOfMultiRow(row, c)
2767                     && column_info[c].alignment != LYX_ALIGN_DECIMAL) {
2768                         if (cell != lastcell)
2769                                 os << " & ";
2770                         continue;
2771                 }
2772
2773                 TeXCellPreamble(os, cell, ismulticol, ismultirow, bidi_rtl);
2774                 InsetTableCell const * inset = cellInset(cell);
2775
2776                 Paragraph const & par = inset->paragraphs().front();
2777
2778                 os.texrow().forceStart(par.id(), 0);
2779
2780                 bool rtl = par.isRTL(buffer().params())
2781                         && !par.empty()
2782                         && getPWidth(cell).zero()
2783                         && !runparams.use_polyglossia;
2784
2785                 if (rtl) {
2786                         string const lang =
2787                                 par.getParLanguage(buffer().params())->lang();
2788                         if (lang == "farsi")
2789                                 os << "\\textFR{";
2790                         else if (lang == "arabic_arabi")
2791                                 os << "\\textAR{";
2792                         // currently, remaining RTL languages are
2793                         // arabic_arabtex and hebrew
2794                         else
2795                                 os << "\\R{";
2796                 }
2797                 // pass to the OutputParams that we are in a cell and
2798                 // which alignment we have set.
2799                 // InsetNewline needs this context information.
2800                 OutputParams newrp(runparams);
2801                 newrp.inTableCell = (getAlignment(cell) == LYX_ALIGN_BLOCK)
2802                                     ? OutputParams::PLAIN
2803                                     : OutputParams::ALIGNED;
2804
2805                 if (getAlignment(cell) == LYX_ALIGN_DECIMAL) {
2806                         // copy cell and split in 2
2807                         InsetTableCell head = InsetTableCell(*cellInset(cell));
2808                         head.setBuffer(const_cast<Buffer &>(buffer()));
2809                         DocIterator dit = cellInset(cell)->getText(0)->macrocontextPosition();
2810                         dit.pop_back();
2811                         dit.push_back(CursorSlice(head));
2812                         head.setMacrocontextPositionRecursive(dit);
2813                         bool hassep = false;
2814                         InsetTableCell tail = splitCell(head, column_info[c].decimal_point, hassep);
2815                         if (hassep) {
2816                                 tail.setBuffer(head.buffer());
2817                                 dit.pop_back();
2818                                 dit.push_back(CursorSlice(tail));
2819                                 tail.setMacrocontextPositionRecursive(dit);
2820                         }
2821                         if (bidi_rtl) {
2822                                 if (hassep) {
2823                                         tail.latex(os, newrp);
2824                                         os << '&';
2825                                 }
2826                                 head.latex(os, newrp);
2827                         } else {
2828                                 head.latex(os, newrp);
2829                                 if (hassep) {
2830                                         os << '&';
2831                                         tail.latex(os, newrp);
2832                                 }
2833                         }
2834                 } else if (ltCaption(row)) {
2835                         // Inside longtable caption rows, we must only output the caption inset
2836                         // with its content and omit anything outside of that (see #10791)
2837                         InsetIterator it = inset_iterator_begin(*const_cast<InsetTableCell *>(inset));
2838                         InsetIterator i_end = inset_iterator_end(*const_cast<InsetTableCell *>(inset));
2839                         for (; it != i_end; ++it) {
2840                                 if (it->lyxCode() != CAPTION_CODE)
2841                                         continue;
2842                                 it->latex(os, runparams);
2843                                 break;
2844                         }
2845                 } else if (!isPartOfMultiRow(row, c)) {
2846                         if (!runparams.nice)
2847                                 os.texrow().start(par.id(), 0);
2848                         inset->latex(os, newrp);
2849                 }
2850
2851                 runparams.encoding = newrp.encoding;
2852                 if (rtl)
2853                         os << '}';
2854
2855                 TeXCellPostamble(os, cell, ismulticol, ismultirow);
2856                 if (cell != lastcell) { // not last cell in row
2857                         if (runparams.nice)
2858                                 os << " & ";
2859                         else
2860                                 os << " &\n";
2861                 }
2862         }
2863         os << "\\tabularnewline";
2864         if (row_info[row].bottom_space_default) {
2865                 if (use_booktabs)
2866                         os << "\\addlinespace";
2867                 else
2868                         os << "[\\doublerulesep]";
2869         } else if (!row_info[row].bottom_space.zero()) {
2870                 if (use_booktabs)
2871                         os << "\\addlinespace";
2872                 os << '['
2873                    << from_ascii(row_info[row].bottom_space.asLatexString())
2874                    << ']';
2875         }
2876         os << '\n';
2877
2878         //output the bottom line
2879         TeXBottomHLine(os, row, clang, columns);
2880
2881         if (row_info[row].interline_space_default) {
2882                 if (use_booktabs)
2883                         os << "\\addlinespace\n";
2884                 else
2885                         os << "\\noalign{\\vskip\\doublerulesep}\n";
2886         } else if (!row_info[row].interline_space.zero()) {
2887                 if (use_booktabs)
2888                         os << "\\addlinespace["
2889                            << from_ascii(row_info[row].interline_space.asLatexString())
2890                            << "]\n";
2891                 else
2892                         os << "\\noalign{\\vskip"
2893                            << from_ascii(row_info[row].interline_space.asLatexString())
2894                            << "}\n";
2895         }
2896 }
2897
2898
2899 void Tabular::latex(otexstream & os, OutputParams const & runparams) const
2900 {
2901         bool const is_tabular_star = !is_long_tabular && !tabular_width.zero()
2902                 && !hasVarwidthColumn();
2903         bool const is_xltabular = is_long_tabular
2904                 && (hasVarwidthColumn() || !tabular_width.zero());
2905         TexRow::RowEntry pos = TexRow::textEntry(runparams.lastid, runparams.lastpos);
2906
2907         //+---------------------------------------------------------------------
2908         //+                      first the opening preamble                    +
2909         //+---------------------------------------------------------------------
2910
2911         os << safebreakln;
2912         if (!TexRow::isNone(pos))
2913                 os.texrow().start(pos);
2914
2915         if (rotate != 0) {
2916                 if (is_long_tabular)
2917                         os << "\\begin{landscape}\n";
2918                 else
2919                         os << "\\begin{turn}{" << convert<string>(rotate) << "}\n";
2920         }
2921
2922         if (is_long_tabular) {
2923                 if (is_xltabular)
2924                         os << "\\begin{xltabular}";
2925                 else
2926                         os << "\\begin{longtable}";
2927                 switch (longtabular_alignment) {
2928                 case LYX_LONGTABULAR_ALIGN_LEFT:
2929                         os << "[l]";
2930                         break;
2931                 case LYX_LONGTABULAR_ALIGN_CENTER:
2932                         os << "[c]";
2933                         break;
2934                 case LYX_LONGTABULAR_ALIGN_RIGHT:
2935                         os << "[r]";
2936                         break;
2937                 }
2938                 if (is_xltabular) {
2939                         if (tabular_width.zero())
2940                                 os << "{" << from_ascii("\\columnwidth") << "}";
2941                         else
2942                                 os << "{" << from_ascii(tabular_width.asLatexString()) << "}";
2943                 }
2944         } else {
2945                 if (is_tabular_star)
2946                         os << "\\begin{tabular*}{" << from_ascii(tabular_width.asLatexString()) << "}";
2947                 else if (hasVarwidthColumn()) {
2948                         os << "\\begin{tabularx}{";
2949                         if (tabular_width.zero())
2950                                 os << from_ascii("\\columnwidth") << "}";
2951                         else
2952                                 os << from_ascii(tabular_width.asLatexString()) << "}";
2953                 } else
2954                         os << "\\begin{tabular}";
2955                 switch (tabular_valignment) {
2956                 case LYX_VALIGN_TOP:
2957                         os << "[t]";
2958                         break;
2959                 case LYX_VALIGN_MIDDLE:
2960                         break;
2961                 case LYX_VALIGN_BOTTOM:
2962                         os << "[b]";
2963                         break;
2964                 }
2965         }
2966
2967         os << "{";
2968
2969         if (is_tabular_star)
2970                 os << "@{\\extracolsep{\\fill}}";
2971
2972         // The bidi package (loaded by polyglossia) swaps the column
2973         // order for RTL (#9686). Thus we use this list.
2974         bool const bidi_rtl =
2975                 runparams.local_font->isRightToLeft()
2976                 && runparams.use_polyglossia;
2977         list<col_type> columns;
2978         for (col_type cl = 0; cl < ncols(); ++cl) {
2979                 if (bidi_rtl)
2980                         columns.push_front(cl);
2981                 else
2982                         columns.push_back(cl);
2983         }
2984
2985         for (auto const & c : columns) {
2986                 if ((bidi_rtl && columnRightLine(c)) || (!bidi_rtl && columnLeftLine(c)))
2987                         os << '|';
2988                 if (!column_info[c].align_special.empty()) {
2989                         os << column_info[c].align_special;
2990                 } else {
2991                         if (!column_info[c].p_width.zero()) {
2992                                 bool decimal = false;
2993                                 switch (column_info[c].alignment) {
2994                                 case LYX_ALIGN_LEFT:
2995                                         os << ">{\\raggedright}";
2996                                         break;
2997                                 case LYX_ALIGN_RIGHT:
2998                                         os << ">{\\raggedleft}";
2999                                         break;
3000                                 case LYX_ALIGN_CENTER:
3001                                         os << ">{\\centering}";
3002                                         break;
3003                                 case LYX_ALIGN_NONE:
3004                                 case LYX_ALIGN_BLOCK:
3005                                 case LYX_ALIGN_LAYOUT:
3006                                 case LYX_ALIGN_SPECIAL:
3007                                         break;
3008                                 case LYX_ALIGN_DECIMAL: {
3009                                         if (bidi_rtl)
3010                                                 os << ">{\\raggedright}";
3011                                         else
3012                                                 os << ">{\\raggedleft}";
3013                                         decimal = true;
3014                                         break;
3015                                 }
3016                                 }
3017
3018                                 char valign = 'p';
3019                                 switch (column_info[c].valignment) {
3020                                 case LYX_VALIGN_TOP:
3021                                         // this is the default
3022                                         break;
3023                                 case LYX_VALIGN_MIDDLE:
3024                                         valign = 'm';
3025                                         break;
3026                                 case LYX_VALIGN_BOTTOM:
3027                                         valign = 'b';
3028                                         break;
3029                                 }
3030                                 os << valign;
3031
3032                                 // Fixed-width cells with alignment at decimal separator
3033                                 // are output as two cells of half the width with the decimal
3034                                 // separator as column sep. This effectively puts the content
3035                                 // centered, which differs from the normal decimal sep alignment
3036                                 // and is not ideal, but we cannot do better ATM (see #9568).
3037                                 // FIXME: Implement proper decimal sep alignment, e.g. via siunitx.
3038                                 if (decimal) {
3039                                         docstring const halffixedwith =
3040                                                 from_ascii(Length(column_info[c].p_width.value() / 2,
3041                                                                   column_info[c].p_width.unit()).asLatexString());
3042                                         os << '{'
3043                                            << halffixedwith
3044                                            << '}'
3045                                            << "@{\\extracolsep{0pt}" << column_info[c].decimal_point << "}"
3046                                            << valign
3047                                            << '{'
3048                                            << halffixedwith
3049                                            << '}';
3050                                 } else
3051                                         os << '{'
3052                                            << from_ascii(column_info[c].p_width.asLatexString())
3053                                            << '}';
3054                         } else if (column_info[c].varwidth) {
3055                                 switch (column_info[c].alignment) {
3056                                 case LYX_ALIGN_LEFT:
3057                                         os << ">{\\raggedright\\arraybackslash}";
3058                                         break;
3059                                 case LYX_ALIGN_RIGHT:
3060                                         os << ">{\\raggedleft\\arraybackslash}";
3061                                         break;
3062                                 case LYX_ALIGN_CENTER:
3063                                         os << ">{\\centering\\arraybackslash}";
3064                                         break;
3065                                 case LYX_ALIGN_NONE:
3066                                 case LYX_ALIGN_BLOCK:
3067                                 case LYX_ALIGN_LAYOUT:
3068                                 case LYX_ALIGN_SPECIAL:
3069                                 case LYX_ALIGN_DECIMAL:
3070                                         break;
3071                                 }
3072                                 os << 'X';
3073                         } else if (isVTypeColumn(c)) {
3074                                 switch (column_info[c].alignment) {
3075                                 case LYX_ALIGN_LEFT:
3076                                         os << ">{\\raggedright}";
3077                                         break;
3078                                 case LYX_ALIGN_RIGHT:
3079                                         os << ">{\\raggedleft}";
3080                                         break;
3081                                 case LYX_ALIGN_CENTER:
3082                                         os << ">{\\centering}";
3083                                         break;
3084                                 case LYX_ALIGN_NONE:
3085                                 case LYX_ALIGN_BLOCK:
3086                                 case LYX_ALIGN_LAYOUT:
3087                                 case LYX_ALIGN_SPECIAL:
3088                                 case LYX_ALIGN_DECIMAL:
3089                                         break;
3090                                 }
3091                                 os << "V{\\linewidth}";
3092                         } else {
3093                                 switch (column_info[c].alignment) {
3094                                 case LYX_ALIGN_LEFT:
3095                                         os << 'l';
3096                                         break;
3097                                 case LYX_ALIGN_RIGHT:
3098                                         os << 'r';
3099                                         break;
3100                                 case LYX_ALIGN_DECIMAL:
3101                                         os << "r@{\\extracolsep{0pt}" << column_info[c].decimal_point << "}l";
3102                                         break;
3103                                 default:
3104                                         os << 'c';
3105                                         break;
3106                                 }
3107                         } // end if else !column_info[i].p_width
3108                 } // end if else !column_info[i].align_special
3109                 if ((bidi_rtl && columnLeftLine(c)) || (!bidi_rtl && columnRightLine(c)))
3110                         os << '|';
3111         }
3112         os << "}\n";
3113
3114         TeXLongtableHeaderFooter(os, runparams, columns);
3115
3116         //+---------------------------------------------------------------------
3117         //+                      the single row and columns (cells)            +
3118         //+---------------------------------------------------------------------
3119
3120         for (row_type r = 0; r < nrows(); ++r) {
3121                 if (isValidRow(r)) {
3122                         TeXRow(os, r, runparams, columns);
3123                         if (is_long_tabular && row_info[r].newpage)
3124                                 os << "\\newpage\n";
3125                 }
3126         }
3127
3128         //+---------------------------------------------------------------------
3129         //+                      the closing of the tabular                    +
3130         //+---------------------------------------------------------------------
3131
3132         if (is_long_tabular) {
3133                 if (is_xltabular)
3134                         os << "\\end{xltabular}";
3135                 else
3136                         os << "\\end{longtable}";
3137         } else {
3138                 if (is_tabular_star)
3139                         os << "\\end{tabular*}";
3140                 else if (hasVarwidthColumn())
3141                         os << "\\end{tabularx}";
3142                 else
3143                         os << "\\end{tabular}";
3144         }
3145
3146         if (rotate != 0) {
3147                 if (is_long_tabular)
3148                         os << breakln << "\\end{landscape}";
3149                 else
3150                         os << breakln << "\\end{turn}";
3151         }
3152
3153         if (!TexRow::isNone(pos))
3154                 os.texrow().start(pos);
3155 }
3156
3157
3158 int Tabular::docbookRow(odocstream & os, row_type row,
3159                            OutputParams const & runparams) const
3160 {
3161         int ret = 0;
3162         idx_type cell = getFirstCellInRow(row);
3163
3164         os << "<row>\n";
3165         for (col_type c = 0; c < ncols(); ++c) {
3166                 if (isPartOfMultiColumn(row, c))
3167                         continue;
3168
3169                 os << "<entry align=\"";
3170                 switch (getAlignment(cell)) {
3171                 case LYX_ALIGN_LEFT:
3172                         os << "left";
3173                         break;
3174                 case LYX_ALIGN_RIGHT:
3175                         os << "right";
3176                         break;
3177                 default:
3178                         os << "center";
3179                         break;
3180                 }
3181
3182                 os << "\" valign=\"";
3183                 switch (getVAlignment(cell)) {
3184                 case LYX_VALIGN_TOP:
3185                         os << "top";
3186                         break;
3187                 case LYX_VALIGN_BOTTOM:
3188                         os << "bottom";
3189                         break;
3190                 case LYX_VALIGN_MIDDLE:
3191                         os << "middle";
3192                 }
3193                 os << '"';
3194
3195                 if (isMultiColumn(cell)) {
3196                         os << " namest=\"col" << c << "\" ";
3197                         os << "nameend=\"col" << c + columnSpan(cell) - 1 << '"';
3198                 }
3199
3200                 os << '>';
3201                 ret += cellInset(cell)->docbook(os, runparams);
3202                 os << "</entry>\n";
3203                 ++cell;
3204         }
3205         os << "</row>\n";
3206         return ret;
3207 }
3208
3209
3210 int Tabular::docbook(odocstream & os, OutputParams const & runparams) const
3211 {
3212         int ret = 0;
3213
3214         //+---------------------------------------------------------------------
3215         //+                      first the opening preamble                    +
3216         //+---------------------------------------------------------------------
3217
3218         os << "<tgroup cols=\"" << ncols()
3219            << "\" colsep=\"1\" rowsep=\"1\">\n";
3220
3221         for (col_type c = 0; c < ncols(); ++c) {
3222                 os << "<colspec colname=\"col" << c << "\" align=\"";
3223                 switch (column_info[c].alignment) {
3224                 case LYX_ALIGN_LEFT:
3225                         os << "left";
3226                         break;
3227                 case LYX_ALIGN_RIGHT:
3228                         os << "right";
3229                         break;
3230                 default:
3231                         os << "center";
3232                         break;
3233                 }
3234                 os << '"';
3235                 if (runparams.flavor == OutputParams::XML)
3236                         os << '/';
3237                 os << ">\n";
3238                 ++ret;
3239         }
3240
3241         //+---------------------------------------------------------------------
3242         //+                      Long Tabular case                             +
3243         //+---------------------------------------------------------------------
3244
3245         // output caption info
3246         // The caption flag wins over head/foot
3247         if (haveLTCaption()) {
3248                 os << "<caption>\n";
3249                 ++ret;
3250                 for (row_type r = 0; r < nrows(); ++r) {
3251                         if (row_info[r].caption) {
3252                                 ret += docbookRow(os, r, runparams);
3253                         }
3254                 }
3255                 os << "</caption>\n";
3256                 ++ret;
3257         }
3258         // output header info
3259         if (haveLTHead(false) || haveLTFirstHead(false)) {
3260                 os << "<thead>\n";
3261                 ++ret;
3262                 for (row_type r = 0; r < nrows(); ++r) {
3263                         if ((row_info[r].endhead || row_info[r].endfirsthead) &&
3264                             !row_info[r].caption) {
3265                                 ret += docbookRow(os, r, runparams);
3266                         }
3267                 }
3268                 os << "</thead>\n";
3269                 ++ret;
3270         }
3271         // output footer info
3272         if (haveLTFoot(false) || haveLTLastFoot(false)) {
3273                 os << "<tfoot>\n";
3274                 ++ret;
3275                 for (row_type r = 0; r < nrows(); ++r) {
3276                         if ((row_info[r].endfoot || row_info[r].endlastfoot) &&
3277                             !row_info[r].caption) {
3278                                 ret += docbookRow(os, r, runparams);
3279                         }
3280                 }
3281                 os << "</tfoot>\n";
3282                 ++ret;
3283         }
3284
3285         //+---------------------------------------------------------------------
3286         //+                      the single row and columns (cells)            +
3287         //+---------------------------------------------------------------------
3288
3289         os << "<tbody>\n";
3290         ++ret;
3291         for (row_type r = 0; r < nrows(); ++r) {
3292                 if (isValidRow(r)) {
3293                         ret += docbookRow(os, r, runparams);
3294                 }
3295         }
3296         os << "</tbody>\n";
3297         ++ret;
3298         //+---------------------------------------------------------------------
3299         //+                      the closing of the tabular                    +
3300         //+---------------------------------------------------------------------
3301
3302         os << "</tgroup>";
3303         ++ret;
3304
3305         return ret;
3306 }
3307
3308
3309 docstring Tabular::xhtmlRow(XHTMLStream & xs, row_type row,
3310                            OutputParams const & runparams, bool header) const
3311 {
3312         docstring ret;
3313         string const celltag = header ? "th" : "td";
3314         idx_type cell = getFirstCellInRow(row);
3315
3316         xs << html::StartTag("tr");
3317         for (col_type c = 0; c < ncols(); ++c) {
3318                 if (isPartOfMultiColumn(row, c) || isPartOfMultiRow(row, c))
3319                         continue;
3320
3321                 stringstream attr;
3322
3323                 Length const cwidth = column_info[c].p_width;
3324                 if (!cwidth.zero()) {
3325                         string const hwidth = cwidth.asHTMLString();
3326                         attr << "style =\"width: " << hwidth << ";\" ";
3327                 }
3328
3329                 attr << "align='";
3330                 switch (getAlignment(cell)) {
3331                 case LYX_ALIGN_LEFT:
3332                         attr << "left";
3333                         break;
3334                 case LYX_ALIGN_RIGHT:
3335                         attr << "right";
3336                         break;
3337                 default:
3338                         attr << "center";
3339                         break;
3340                 }
3341                 attr << "'";
3342                 attr << " valign='";
3343                 switch (getVAlignment(cell)) {
3344                 case LYX_VALIGN_TOP:
3345                         attr << "top";
3346                         break;
3347                 case LYX_VALIGN_BOTTOM:
3348                         attr << "bottom";
3349                         break;
3350                 case LYX_VALIGN_MIDDLE:
3351                         attr << "middle";
3352                 }
3353                 attr << "'";
3354
3355                 if (isMultiColumn(cell))
3356                         attr << " colspan='" << columnSpan(cell) << "'";
3357                 else if (isMultiRow(cell))
3358                         attr << " rowspan='" << rowSpan(cell) << "'";
3359
3360                 xs << html::StartTag(celltag, attr.str(), true) << html::CR();
3361                 ret += cellInset(cell)->xhtml(xs, runparams);
3362                 xs << html::EndTag(celltag) << html::CR();
3363                 ++cell;
3364         }
3365         xs << html::EndTag("tr");
3366         return ret;
3367 }
3368
3369
3370 docstring Tabular::xhtml(XHTMLStream & xs, OutputParams const & runparams) const
3371 {
3372         docstring ret;
3373
3374         if (is_long_tabular) {
3375                 // we'll wrap it in a div, so as to deal with alignment
3376                 string align;
3377                 switch (longtabular_alignment) {
3378                 case LYX_LONGTABULAR_ALIGN_LEFT:
3379                         align = "left";
3380                         break;
3381                 case LYX_LONGTABULAR_ALIGN_CENTER:
3382                         align = "center";
3383                         break;
3384                 case LYX_LONGTABULAR_ALIGN_RIGHT:
3385                         align = "right";
3386                         break;
3387                 }
3388                 xs << html::StartTag("div", "class='longtable' style='text-align: " + align + ";'")
3389                    << html::CR();
3390                 // The caption flag wins over head/foot
3391                 if (haveLTCaption()) {
3392                         xs << html::StartTag("div", "class='longtable-caption' style='text-align: " + align + ";'")
3393                            << html::CR();
3394                         for (row_type r = 0; r < nrows(); ++r)
3395                                 if (row_info[r].caption)
3396                                         ret += xhtmlRow(xs, r, runparams);
3397                         xs << html::EndTag("div") << html::CR();
3398                 }
3399         }
3400
3401         xs << html::StartTag("table") << html::CR();
3402
3403         // output header info
3404         bool const havefirsthead = haveLTFirstHead(false);
3405         // if we have a first head, then we are going to ignore the
3406         // headers for the additional pages, since there aren't any
3407         // in XHTML. this test accomplishes that.
3408         bool const havehead = !havefirsthead && haveLTHead(false);
3409         if (havehead || havefirsthead) {
3410                 xs << html::StartTag("thead") << html::CR();
3411                 for (row_type r = 0; r < nrows(); ++r) {
3412                         if (((havefirsthead && row_info[r].endfirsthead) ||
3413                              (havehead && row_info[r].endhead)) &&
3414                             !row_info[r].caption) {
3415                                 ret += xhtmlRow(xs, r, runparams, true);
3416                         }
3417                 }
3418                 xs << html::EndTag("thead") << html::CR();
3419         }
3420         // output footer info
3421         bool const havelastfoot = haveLTLastFoot(false);
3422         // as before.
3423         bool const havefoot = !havelastfoot && haveLTFoot(false);
3424         if (havefoot || havelastfoot) {
3425                 xs << html::StartTag("tfoot") << html::CR();
3426                 for (row_type r = 0; r < nrows(); ++r) {
3427                         if (((havelastfoot && row_info[r].endlastfoot) ||
3428                              (havefoot && row_info[r].endfoot)) &&
3429                             !row_info[r].caption) {
3430                                 ret += xhtmlRow(xs, r, runparams);
3431                         }
3432                 }
3433                 xs << html::EndTag("tfoot") << html::CR();
3434         }
3435
3436         xs << html::StartTag("tbody") << html::CR();
3437         for (row_type r = 0; r < nrows(); ++r) {
3438                 if (isValidRow(r)) {
3439                         ret += xhtmlRow(xs, r, runparams);
3440                 }
3441         }
3442         xs << html::EndTag("tbody")
3443            << html::CR()
3444            << html::EndTag("table")
3445            << html::CR();
3446         if (is_long_tabular)
3447                 xs << html::EndTag("div") << html::CR();
3448         return ret;
3449 }
3450
3451
3452 bool Tabular::plaintextTopHLine(odocstringstream & os, row_type row,
3453                                    vector<unsigned int> const & clen) const
3454 {
3455         idx_type const fcell = getFirstCellInRow(row);
3456         idx_type const n = numberOfCellsInRow(row) + fcell;
3457         idx_type tmp = 0;
3458
3459         for (idx_type i = fcell; i < n; ++i) {
3460                 if (topLine(i)) {
3461                         ++tmp;
3462                         break;
3463                 }
3464         }
3465         if (!tmp)
3466                 return false;
3467
3468         char_type ch;
3469         for (idx_type i = fcell; i < n; ++i) {
3470                 if (topLine(i)) {
3471                         if (leftLine(i))
3472                                 os << "+-";
3473                         else
3474                                 os << "--";
3475                         ch = '-';
3476                 } else {
3477                         os << "  ";
3478                         ch = ' ';
3479                 }
3480                 col_type column = cellColumn(i);
3481                 int len = clen[column];
3482                 while (column < ncols() - 1
3483                        && isPartOfMultiColumn(row, ++column))
3484                         len += clen[column] + 4;
3485                 os << docstring(len, ch);
3486                 if (topLine(i)) {
3487                         if (rightLine(i))
3488                                 os << "-+";
3489                         else
3490                                 os << "--";
3491                 } else {
3492                         os << "  ";
3493                 }
3494         }
3495         os << endl;
3496         return true;
3497 }
3498
3499
3500 bool Tabular::plaintextBottomHLine(odocstringstream & os, row_type row,
3501                                       vector<unsigned int> const & clen) const
3502 {
3503         idx_type const fcell = getFirstCellInRow(row);
3504         idx_type const n = numberOfCellsInRow(row) + fcell;
3505         idx_type tmp = 0;
3506
3507         for (idx_type i = fcell; i < n; ++i) {
3508                 if (bottomLine(i)) {
3509                         ++tmp;
3510                         break;
3511                 }
3512         }
3513         if (!tmp)
3514                 return false;
3515
3516         char_type ch;
3517         for (idx_type i = fcell; i < n; ++i) {
3518                 if (bottomLine(i)) {
3519                         if (leftLine(i))
3520                                 os << "+-";
3521                         else
3522                                 os << "--";
3523                         ch = '-';
3524                 } else {
3525                         os << "  ";
3526                         ch = ' ';
3527                 }
3528                 col_type column = cellColumn(i);
3529                 int len = clen[column];
3530                 while (column < ncols() - 1 && isPartOfMultiColumn(row, ++column))
3531                         len += clen[column] + 4;
3532                 os << docstring(len, ch);
3533                 if (bottomLine(i)) {
3534                         if (rightLine(i))
3535                                 os << "-+";
3536                         else
3537                                 os << "--";
3538                 } else {
3539                         os << "  ";
3540                 }
3541         }
3542         os << endl;
3543         return true;
3544 }
3545
3546
3547 void Tabular::plaintextPrintCell(odocstringstream & os,
3548                                OutputParams const & runparams,
3549                                idx_type cell, row_type row, col_type column,
3550                                vector<unsigned int> const & clen,
3551                                bool onlydata, size_t max_length) const
3552 {
3553         odocstringstream sstr;
3554         cellInset(cell)->plaintext(sstr, runparams, max_length);
3555
3556         if (onlydata) {
3557                 os << sstr.str();
3558                 return;
3559         }
3560
3561         if (leftLine(cell))
3562                 os << "| ";
3563         else
3564                 os << "  ";
3565
3566         unsigned int len1 = sstr.str().length();
3567         unsigned int len2 = clen[column];
3568         while (column < ncols() - 1 && isPartOfMultiColumn(row, ++column))
3569                 len2 += clen[column] + 4;
3570         len2 -= len1;
3571
3572         switch (getAlignment(cell)) {
3573         default:
3574         case LYX_ALIGN_LEFT:
3575                 len1 = 0;
3576                 break;
3577         case LYX_ALIGN_RIGHT:
3578                 len1 = len2;
3579                 len2 = 0;
3580                 break;
3581         case LYX_ALIGN_CENTER:
3582                 len1 = len2 / 2;
3583                 len2 -= len1;
3584                 break;
3585         }
3586
3587         os << docstring(len1, ' ') << sstr.str()
3588            << docstring(len2, ' ');
3589
3590         if (rightLine(cell))
3591                 os << " |";
3592         else
3593                 os << "  ";
3594 }
3595
3596
3597 void Tabular::plaintext(odocstringstream & os,
3598                            OutputParams const & runparams, int const depth,
3599                            bool onlydata, char_type delim, size_t max_length) const
3600 {
3601         // first calculate the width of the single columns
3602         vector<unsigned int> clen(ncols());
3603
3604         if (!onlydata) {
3605                 // first all non multicolumn cells!
3606                 for (col_type c = 0; c < ncols(); ++c) {
3607                         clen[c] = 0;
3608                         for (row_type r = 0; r < nrows(); ++r) {
3609                                 idx_type cell = cellIndex(r, c);
3610                                 if (isMultiColumn(cell))
3611                                         continue;
3612                                 odocstringstream sstr;
3613                                 cellInset(cell)->plaintext(sstr, runparams, max_length);
3614                                 if (clen[c] < sstr.str().length())
3615                                         clen[c] = sstr.str().length();
3616                         }
3617                 }
3618                 // then all multicolumn cells!
3619                 for (col_type c = 0; c < ncols(); ++c) {
3620                         for (row_type r = 0; r < nrows(); ++r) {
3621                                 idx_type cell = cellIndex(r, c);
3622                                 if (cell_info[r][c].multicolumn != CELL_BEGIN_OF_MULTICOLUMN)
3623                                         continue;
3624                                 odocstringstream sstr;
3625                                 cellInset(cell)->plaintext(sstr, runparams, max_length);
3626                                 int len = int(sstr.str().length());
3627                                 idx_type const n = columnSpan(cell);
3628                                 for (col_type k = c; len > 0 && k < c + n - 1; ++k)
3629                                         len -= clen[k];
3630                                 if (len > int(clen[c + n - 1]))
3631                                         clen[c + n - 1] = len;
3632                         }
3633                 }
3634         }
3635         idx_type cell = 0;
3636         for (row_type r = 0; r < nrows(); ++r) {
3637                 if (!onlydata && plaintextTopHLine(os, r, clen))
3638                         os << docstring(depth * 2, ' ');
3639                 for (col_type c = 0; c < ncols(); ++c) {
3640                         if (isPartOfMultiColumn(r, c) || isPartOfMultiRow(r,c))
3641                                 continue;
3642                         if (onlydata && c > 0)
3643                                 // we don't use operator<< for single UCS4 character.
3644                                 // see explanation in docstream.h
3645                                 os.put(delim);
3646                         plaintextPrintCell(os, runparams, cell, r, c, clen, onlydata, max_length);
3647                         ++cell;
3648                         if (os.str().size() > max_length)
3649                                 break;
3650                 }
3651                 os << endl;
3652                 if (!onlydata) {
3653                         os << docstring(depth * 2, ' ');
3654                         if (plaintextBottomHLine(os, r, clen))
3655                                 os << docstring(depth * 2, ' ');
3656                 }
3657                 if (os.str().size() > max_length)
3658                         break;
3659         }
3660 }
3661
3662
3663 shared_ptr<InsetTableCell> Tabular::cellInset(idx_type cell)
3664 {
3665         return cell_info[cellRow(cell)][cellColumn(cell)].inset;
3666 }
3667
3668
3669 shared_ptr<InsetTableCell> Tabular::cellInset(row_type row, col_type column)
3670 {
3671         return cell_info[row][column].inset;
3672 }
3673
3674
3675 InsetTableCell const * Tabular::cellInset(idx_type cell) const
3676 {
3677         return cell_info[cellRow(cell)][cellColumn(cell)].inset.get();
3678 }
3679
3680
3681 void Tabular::setCellInset(row_type row, col_type column,
3682                            shared_ptr<InsetTableCell> ins)
3683 {
3684         CellData & cd = cell_info[row][column];
3685         cd.inset = ins;
3686 }
3687
3688
3689 void Tabular::validate(LaTeXFeatures & features) const
3690 {
3691         features.require("NeedTabularnewline");
3692         if (use_booktabs)
3693                 features.require("booktabs");
3694         if (is_long_tabular && !hasVarwidthColumn()) {
3695                 if (tabular_width.zero())
3696                         features.require("longtable");
3697                 else
3698                         features.require("xltabular");
3699         }
3700         if (rotate && is_long_tabular)
3701                 features.require("lscape");
3702         if (needRotating())
3703                 features.require("rotating");
3704         if (hasVarwidthColumn()) {
3705                 if (is_long_tabular)
3706                         features.require("xltabular");
3707                 else
3708                         features.require("tabularx");
3709         }
3710         for (idx_type cell = 0; cell < numberofcells; ++cell) {
3711                 if (isMultiRow(cell))
3712                         features.require("multirow");
3713                 if (getUsebox(cell) == BOX_VARWIDTH)
3714                         features.require("varwidth");
3715                 if (getVAlignment(cell) != LYX_VALIGN_TOP
3716                     || !getPWidth(cell).zero()
3717                     || isVTypeColumn(cellColumn(cell)))
3718                         features.require("array");
3719                 // Tell footnote that we need a savenote
3720                 // environment in non-long tables or
3721                 // longtable headers/footers
3722                 else if (!is_long_tabular && !features.inFloat())
3723                         features.saveNoteEnv("tabular");
3724                 else if (!isValidRow(cellRow(cell)))
3725                         features.saveNoteEnv("longtable");
3726
3727                 cellInset(cell)->validate(features);
3728                 features.saveNoteEnv(string());
3729         }
3730 }
3731
3732
3733 Tabular::BoxType Tabular::useBox(idx_type cell) const
3734 {
3735         ParagraphList const & parlist = cellInset(cell)->paragraphs();
3736         if (parlist.size() > 1)
3737                 return BOX_VARWIDTH;
3738
3739         ParagraphList::const_iterator cit = parlist.begin();
3740         ParagraphList::const_iterator end = parlist.end();
3741
3742         for (; cit != end; ++cit)
3743                 for (int i = 0; i < cit->size(); ++i)
3744                         if (cit->isNewline(i) || cit->layout().isEnvironment())
3745                                 return BOX_VARWIDTH;
3746
3747         return BOX_NONE;
3748 }
3749
3750
3751 /////////////////////////////////////////////////////////////////////
3752 //
3753 // InsetTableCell
3754 //
3755 /////////////////////////////////////////////////////////////////////
3756
3757 InsetTableCell::InsetTableCell(Buffer * buf)
3758         : InsetText(buf, InsetText::PlainLayout), isFixedWidth(false),
3759           isMultiColumn(false), isMultiRow(false), contentAlign(LYX_ALIGN_CENTER)
3760 {}
3761
3762
3763 bool InsetTableCell::forcePlainLayout(idx_type) const
3764 {
3765         return isMultiRow || (isMultiColumn && !isFixedWidth);
3766 }
3767
3768
3769 bool InsetTableCell::allowParagraphCustomization(idx_type) const
3770 {
3771         return isFixedWidth;
3772 }
3773
3774
3775 bool InsetTableCell::forceLocalFontSwitch() const
3776 {
3777         return isFixedWidth;
3778 }
3779
3780
3781 bool InsetTableCell::getStatus(Cursor & cur, FuncRequest const & cmd,
3782         FuncStatus & status) const
3783 {
3784         bool enabled = true;
3785         switch (cmd.action()) {
3786         case LFUN_MATH_DISPLAY:
3787                 if (!hasFixedWidth()) {
3788                         enabled = false;
3789                         break;
3790                 } //fall-through
3791         default:
3792                 return InsetText::getStatus(cur, cmd, status);
3793         }
3794         status.setEnabled(enabled);
3795         return true;
3796 }
3797
3798 docstring InsetTableCell::asString(bool intoInsets)
3799 {
3800         docstring retval;
3801         if (paragraphs().empty())
3802                 return retval;
3803         ParagraphList::const_iterator it = paragraphs().begin();
3804         ParagraphList::const_iterator en = paragraphs().end();
3805         bool first = true;
3806         for (; it != en; ++it) {
3807                 if (!first)
3808                         retval += "\n";
3809                 else
3810                         first = false;
3811                 retval += it->asString(intoInsets ? AS_STR_INSETS : AS_STR_NONE);
3812         }
3813         return retval;
3814 }
3815
3816
3817 void InsetTableCell::addToToc(DocIterator const & di, bool output_active,
3818                                                           UpdateType utype, TocBackend & backend) const
3819 {
3820         InsetText::iterateForToc(di, output_active, utype, backend);
3821 }
3822
3823
3824 docstring InsetTableCell::xhtml(XHTMLStream & xs, OutputParams const & rp) const
3825 {
3826         if (!isFixedWidth)
3827                 return InsetText::insetAsXHTML(xs, rp, InsetText::JustText);
3828         return InsetText::xhtml(xs, rp);
3829 }
3830
3831
3832 void InsetTableCell::metrics(MetricsInfo & mi, Dimension & dim) const
3833 {
3834         TextMetrics & tm = mi.base.bv->textMetrics(&text());
3835
3836         // Hand font through to contained lyxtext:
3837         tm.font_.fontInfo() = mi.base.font;
3838         mi.base.textwidth -= 2 * TEXT_TO_INSET_OFFSET;
3839
3840         // This can happen when a layout has a left and right margin,
3841         // and the view is made very narrow. We can't do better than
3842         // to draw it partly out of view (bug 5890).
3843         if (mi.base.textwidth < 1)
3844                 mi.base.textwidth = 1;
3845
3846         // We tell metrics here not to expand on multiple pars
3847         // This is the difference to InsetText::Metrics
3848         // FIXME: pars with newlines are still too wide!
3849         if (hasFixedWidth())
3850                 tm.metrics(mi, dim, mi.base.textwidth, false);
3851         else
3852                 tm.metrics(mi, dim, 0, false);
3853         mi.base.textwidth += 2 * TEXT_TO_INSET_OFFSET;
3854         dim.asc += TEXT_TO_INSET_OFFSET;
3855         dim.des += TEXT_TO_INSET_OFFSET;
3856         dim.wid += 2 * TEXT_TO_INSET_OFFSET;
3857 }
3858
3859
3860
3861 /////////////////////////////////////////////////////////////////////
3862 //
3863 // InsetTabular
3864 //
3865 /////////////////////////////////////////////////////////////////////
3866
3867 InsetTabular::InsetTabular(Buffer * buf, row_type rows,
3868                            col_type columns)
3869         : Inset(buf), tabular(buf, max(rows, row_type(1)), max(columns, col_type(1))),
3870           rowselect_(false), colselect_(false)
3871 {
3872 }
3873
3874
3875 InsetTabular::InsetTabular(InsetTabular const & tab)
3876         : Inset(tab), tabular(tab.tabular),
3877           rowselect_(false), colselect_(false)
3878 {
3879 }
3880
3881
3882 InsetTabular::~InsetTabular()
3883 {
3884         hideDialogs("tabular", this);
3885 }
3886
3887
3888 void InsetTabular::setBuffer(Buffer & buf)
3889 {
3890         tabular.setBuffer(buf);
3891         Inset::setBuffer(buf);
3892 }
3893
3894
3895 bool InsetTabular::insetAllowed(InsetCode code) const
3896 {
3897         switch (code) {
3898         case FLOAT_CODE:
3899         case MARGIN_CODE:
3900         case MATHMACRO_CODE:
3901         case WRAP_CODE:
3902                 return false;
3903
3904         case CAPTION_CODE:
3905                 return tabular.is_long_tabular;
3906
3907         default:
3908                 return true;
3909         }
3910 }
3911
3912
3913 bool InsetTabular::allowsCaptionVariation(std::string const & newtype) const
3914 {
3915         return tabular.is_long_tabular &&
3916                 (newtype == "Standard" || newtype == "Unnumbered");
3917 }
3918
3919
3920 void InsetTabular::write(ostream & os) const
3921 {
3922         os << "Tabular" << endl;
3923         tabular.write(os);
3924 }
3925
3926
3927 string InsetTabular::contextMenu(BufferView const &, int, int) const
3928 {
3929         // FIXME: depending on the selection state,
3930         // we could offer a different menu.
3931         return cell(0)->contextMenuName() + ";" + contextMenuName();
3932 }
3933
3934
3935 string InsetTabular::contextMenuName() const
3936 {
3937         return "context-tabular";
3938 }
3939
3940
3941 void InsetTabular::read(Lexer & lex)
3942 {
3943         //bool const old_format = (lex.getString() == "\\LyXTable");
3944
3945         tabular.read(lex);
3946
3947         //if (old_format)
3948         //      return;
3949
3950         lex.next();
3951         string token = lex.getString();
3952         while (lex && token != "\\end_inset") {
3953                 lex.next();
3954                 token = lex.getString();
3955         }
3956         if (!lex)
3957                 lex.printError("Missing \\end_inset at this point. ");
3958 }
3959
3960
3961 int InsetTabular::rowFromY(Cursor & cur, int y) const
3962 {
3963         // top y coordinate of tabular
3964         int h = yo(cur.bv()) - tabular.rowAscent(0) + tabular.offsetVAlignment();
3965         row_type r = 0;
3966         for (; r < tabular.nrows() && y > h; ++r)
3967                 h += tabular.rowAscent(r) + tabular.rowDescent(r)
3968                         + tabular.interRowSpace(r);
3969
3970         return r - 1;
3971 }
3972
3973
3974 int InsetTabular::columnFromX(Cursor & cur, int x) const
3975 {
3976         // left x coordinate of tabular
3977         int w = xo(cur.bv()) + ADD_TO_TABULAR_WIDTH;
3978         col_type c = 0;
3979         for (; c < tabular.ncols() && x > w; ++c)
3980                 w += tabular.cellWidth(c);
3981         return c - 1;
3982 }
3983
3984
3985 void InsetTabular::metrics(MetricsInfo & mi, Dimension & dim) const
3986 {
3987         //lyxerr << "InsetTabular::metrics: " << mi.base.bv << " width: " <<
3988         //      mi.base.textwidth << "\n";
3989         LBUFERR(mi.base.bv);
3990
3991         for (row_type r = 0; r < tabular.nrows(); ++r) {
3992                 int maxasc = 0;
3993                 int maxdes = 0;
3994                 for (col_type c = 0; c < tabular.ncols(); ++c) {
3995                         if (tabular.isPartOfMultiColumn(r, c)
3996                                 || tabular.isPartOfMultiRow(r, c))
3997                                 // multicolumn or multirow cell, but not first one
3998                                 continue;
3999                         idx_type const cell = tabular.cellIndex(r, c);
4000                         Dimension dim0;
4001                         MetricsInfo m = mi;
4002                         Length const p_width = tabular.getPWidth(cell);
4003                         if (!p_width.zero())
4004                                 m.base.textwidth = mi.base.inPixels(p_width);
4005                         tabular.cellInset(cell)->metrics(m, dim0);
4006                         if (!p_width.zero())
4007                                 dim0.wid = m.base.textwidth;
4008                         tabular.cellInfo(cell).width = dim0.wid + 2 * WIDTH_OF_LINE
4009                                 + tabular.interColumnSpace(cell);
4010
4011                         // FIXME(?): do we need a second metrics call?
4012                         TextMetrics const & tm =
4013                                 mi.base.bv->textMetrics(tabular.cellInset(cell)->getText(0));
4014
4015                         // determine horizontal offset because of decimal align (if necessary)
4016                         int decimal_width = 0;
4017                         if (tabular.getAlignment(cell) == LYX_ALIGN_DECIMAL) {
4018                                 InsetTableCell tail = InsetTableCell(*tabular.cellInset(cell));
4019                                 tail.setBuffer(tabular.buffer());
4020                                 // we need to set macrocontext position everywhere
4021                                 // otherwise we crash with nested insets (e.g. footnotes)
4022                                 // after decimal point
4023                                 DocIterator dit = tabular.cellInset(cell)->getText(0)->macrocontextPosition();
4024                                 dit.pop_back();
4025                                 dit.push_back(CursorSlice(tail));
4026                                 tail.setMacrocontextPositionRecursive(dit);
4027
4028                                 // remove text leading decimal point
4029                                 docstring const align_d = tabular.column_info[c].decimal_point;
4030                                 dit = separatorPos(&tail, align_d);
4031
4032                                 pit_type const psize = tail.paragraphs().front().size();
4033                                 if (dit) {
4034                                         tail.paragraphs().front().eraseChars(0,
4035                                                 dit.pos() < psize ? dit.pos() + 1 : psize, false);
4036                                         Dimension dim1;
4037                                         tail.metrics(m, dim1);
4038                                         decimal_width = dim1.width();
4039                                 }
4040                         }
4041                         tabular.cell_info[r][c].decimal_hoffset = tm.width() - decimal_width;
4042                         tabular.cell_info[r][c].decimal_width = decimal_width;
4043
4044                         // with LYX_VALIGN_BOTTOM the descent is relative to the last par
4045                         // = descent of text in last par + TEXT_TO_INSET_OFFSET:
4046                         int const lastpardes = tm.last().second->descent()
4047                                 + TEXT_TO_INSET_OFFSET;
4048                         int offset = 0;
4049                         switch (tabular.getVAlignment(cell)) {
4050                                 case Tabular::LYX_VALIGN_TOP:
4051                                         break;
4052                                 case Tabular::LYX_VALIGN_MIDDLE:
4053                                         offset = -(dim0.des - lastpardes)/2;
4054                                         break;
4055                                 case Tabular::LYX_VALIGN_BOTTOM:
4056                                         offset = -(dim0.des - lastpardes);
4057                                         break;
4058                         }
4059                         tabular.cell_info[r][c].voffset = offset;
4060                         maxasc = max(maxasc, dim0.asc - offset);
4061                         maxdes = max(maxdes, dim0.des + offset);
4062                 }
4063                 int const top_space = tabular.row_info[r].top_space_default ?
4064                     default_line_space :
4065                     mi.base.inPixels(tabular.row_info[r].top_space);
4066                 tabular.setRowAscent(r, maxasc + ADD_TO_HEIGHT + top_space);
4067                 int const bottom_space = tabular.row_info[r].bottom_space_default ?
4068                     default_line_space :
4069                     mi.base.inPixels(tabular.row_info[r].bottom_space);
4070                 tabular.setRowDescent(r, maxdes + ADD_TO_HEIGHT + bottom_space);
4071         }
4072
4073         tabular.updateColumnWidths(mi);
4074         dim.asc = tabular.rowAscent(0) - tabular.offsetVAlignment();
4075         dim.des = tabular.height() - dim.asc;
4076         dim.wid = tabular.width() + 2 * ADD_TO_TABULAR_WIDTH;
4077 }
4078
4079
4080 bool InsetTabular::isCellSelected(Cursor & cur, row_type row, col_type col) const
4081 {
4082         if (&cur.inset() == this && cur.selection()) {
4083                 if (cur.selIsMultiCell()) {
4084                         row_type rs, re;
4085                         col_type cs, ce;
4086                         getSelection(cur, rs, re, cs, ce);
4087
4088                         idx_type const cell = tabular.cellIndex(row, col);
4089                         col_type const cspan = tabular.columnSpan(cell);
4090                         row_type const rspan = tabular.rowSpan(cell);
4091                         if (col + cspan - 1 >= cs && col <= ce
4092                                 && row + rspan - 1 >= rs && row <= re)
4093                                 return true;
4094                 } else
4095                         if (col == tabular.cellColumn(cur.idx())
4096                                 && row == tabular.cellRow(cur.idx())) {
4097                         CursorSlice const & beg = cur.selBegin();
4098                         CursorSlice const & end = cur.selEnd();
4099
4100                         if ((end.lastpos() > 0 || end.lastpit() > 0)
4101                                   && end.pos() == end.lastpos() && beg.pos() == 0
4102                                   && end.pit() == end.lastpit() && beg.pit() == 0)
4103                                 return true;
4104                 }
4105         }
4106         return false;
4107 }
4108
4109
4110 void InsetTabular::draw(PainterInfo & pi, int x, int y) const
4111 {
4112         x += ADD_TO_TABULAR_WIDTH;
4113
4114         BufferView * bv = pi.base.bv;
4115         Cursor & cur = pi.base.bv->cursor();
4116
4117         // FIXME: As the full background is painted in drawBackground(),
4118         // we have no choice but to do a full repaint for the Text cells.
4119         pi.full_repaint = true;
4120
4121         bool const original_selection_state = pi.selected;
4122
4123         idx_type idx = 0;
4124
4125         int yy = y + tabular.offsetVAlignment();
4126         for (row_type r = 0; r < tabular.nrows(); ++r) {
4127                 int nx = x;
4128                 for (col_type c = 0; c < tabular.ncols(); ++c) {
4129                         if (tabular.isPartOfMultiColumn(r, c))
4130                                 continue;
4131
4132                         idx = tabular.cellIndex(r, c);
4133
4134                         if (tabular.isPartOfMultiRow(r, c)) {
4135                                 nx += tabular.cellWidth(idx);
4136                                 continue;
4137                         }
4138
4139                         pi.selected |= isCellSelected(cur, r, c);
4140                         int const cx = nx + tabular.textHOffset(idx);
4141                         int const cy = yy + tabular.textVOffset(idx);
4142                         // Cache the Inset position.
4143                         bv->coordCache().insets().add(cell(idx).get(), cx, cy);
4144                         cell(idx)->draw(pi, cx, cy);
4145                         drawCellLines(pi, nx, yy, r, idx);
4146                         nx += tabular.cellWidth(idx);
4147                         pi.selected = original_selection_state;
4148                 }
4149
4150                 if (r + 1 < tabular.nrows())
4151                         yy += tabular.rowDescent(r) + tabular.rowAscent(r + 1)
4152                                 + tabular.interRowSpace(r + 1);
4153         }
4154 }
4155
4156
4157 void InsetTabular::drawBackground(PainterInfo & pi, int x, int y) const
4158 {
4159         x += ADD_TO_TABULAR_WIDTH;
4160         y += tabular.offsetVAlignment() - tabular.rowAscent(0);
4161         pi.pain.fillRectangle(x, y, tabular.width(), tabular.height(),
4162                 pi.backgroundColor(this));
4163 }
4164
4165
4166 void InsetTabular::drawSelection(PainterInfo & pi, int x, int y) const
4167 {
4168         Cursor & cur = pi.base.bv->cursor();
4169
4170         x += ADD_TO_TABULAR_WIDTH;
4171
4172         if (!cur.selection())
4173                 return;
4174         if (&cur.inset() != this)
4175                 return;
4176
4177         //resetPos(cur);
4178
4179         bool const full_cell_selected = isCellSelected(cur,
4180                 tabular.cellRow(cur.idx()), tabular.cellColumn(cur.idx()));
4181
4182         if (cur.selIsMultiCell() || full_cell_selected) {
4183                 for (row_type r = 0; r < tabular.nrows(); ++r) {
4184                         int xx = x;
4185                         for (col_type c = 0; c < tabular.ncols(); ++c) {
4186                                 if (tabular.isPartOfMultiColumn(r, c))
4187                                         continue;
4188
4189                                 idx_type const cell = tabular.cellIndex(r, c);
4190
4191                                 if (tabular.isPartOfMultiRow(r, c)) {
4192                                         xx += tabular.cellWidth(cell);
4193                                         continue;
4194                                 }
4195                                 int const w = tabular.cellWidth(cell);
4196                                 int const h = tabular.cellHeight(cell);
4197                                 int const yy = y - tabular.rowAscent(r) + tabular.offsetVAlignment();
4198                                 if (isCellSelected(cur, r, c))
4199                                         pi.pain.fillRectangle(xx, yy, w, h, Color_selection);
4200                                 xx += w;
4201                         }
4202                         if (r + 1 < tabular.nrows())
4203                                 y += tabular.rowDescent(r) + tabular.rowAscent(r + 1)
4204                                      + tabular.interRowSpace(r + 1);
4205                 }
4206
4207         }
4208         // FIXME: This code has no effect because InsetTableCell does not handle
4209         // drawSelection other than the trivial implementation in Inset.
4210         //else {
4211         //      x += cellXPos(cur.idx());
4212         //      x += tabular.textHOffset(cur.idx());
4213         //      cell(cur.idx())->drawSelection(pi, x, 0 /* ignored */);
4214         //}
4215 }
4216
4217
4218 void InsetTabular::drawCellLines(PainterInfo & pi, int x, int y,
4219                                  row_type row, idx_type cell) const
4220 {
4221         y -= tabular.rowAscent(row);
4222         int const w = tabular.cellWidth(cell);
4223         int const h = tabular.cellHeight(cell);
4224         Color const linecolor = pi.textColor(Color_tabularline);
4225         Color const gridcolor = pi.textColor(Color_tabularonoffline);
4226
4227         // Top
4228         bool drawline = tabular.topLine(cell)
4229                 || (row > 0 && tabular.bottomLine(tabular.cellAbove(cell)));
4230         pi.pain.line(x, y, x + w, y,
4231                 drawline ? linecolor : gridcolor,
4232                 drawline ? Painter::line_solid : Painter::line_onoffdash);
4233
4234         // Bottom
4235         drawline = tabular.bottomLine(cell);
4236         pi.pain.line(x, y + h, x + w, y + h,
4237                 drawline ? linecolor : gridcolor,
4238                 drawline ? Painter::line_solid : Painter::line_onoffdash);
4239
4240         // Left
4241         col_type const col = tabular.cellColumn(cell);
4242         drawline = tabular.leftLine(cell)
4243                 || (col > 0 && tabular.rightLine(tabular.cellIndex(row, col - 1)));
4244         pi.pain.line(x, y, x, y + h,
4245                 drawline ? linecolor : gridcolor,
4246                 drawline ? Painter::line_solid : Painter::line_onoffdash);
4247
4248         // Right
4249         x -= tabular.interColumnSpace(cell);
4250         col_type next_cell_col = col + 1;
4251         while (next_cell_col < tabular.ncols()
4252                 && tabular.isMultiColumn(tabular.cellIndex(row, next_cell_col)))
4253                 next_cell_col++;
4254         drawline = tabular.rightLine(cell)
4255                    || (next_cell_col < tabular.ncols()
4256                        && tabular.leftLine(tabular.cellIndex(row, next_cell_col)));
4257         pi.pain.line(x + w, y, x + w, y + h,
4258                 drawline ? linecolor : gridcolor,
4259                 drawline ? Painter::line_solid : Painter::line_onoffdash);
4260 }
4261
4262
4263 void InsetTabular::edit(Cursor & cur, bool front, EntryDirection)
4264 {
4265         //lyxerr << "InsetTabular::edit: " << this << endl;
4266         cur.finishUndo();
4267         cur.push(*this);
4268         if (front) {
4269                 if (isRightToLeft(cur))
4270                         cur.idx() = tabular.getLastCellInRow(0);
4271                 else
4272                         cur.idx() = 0;
4273                 cur.pit() = 0;
4274                 cur.pos() = 0;
4275         } else {
4276                 if (isRightToLeft(cur))
4277                         cur.idx() = tabular.getFirstCellInRow(tabular.nrows() - 1);
4278                 else
4279                         cur.idx() = tabular.numberofcells - 1;
4280                 cur.pit() = 0;
4281                 cur.pos() = cur.lastpos(); // FIXME crude guess
4282         }
4283         cur.setCurrentFont();
4284         // FIXME: this accesses the position cache before it is initialized
4285         //cur.bv().fitCursor();
4286 }
4287
4288
4289 void InsetTabular::updateBuffer(ParIterator const & it, UpdateType utype)
4290 {
4291         // In a longtable, tell captions what the current float is
4292         Counters & cnts = buffer().masterBuffer()->params().documentClass().counters();
4293         string const saveflt = cnts.current_float();
4294         if (tabular.is_long_tabular) {
4295                 cnts.current_float("table");
4296                 // in longtables, we only step the counter once
4297                 cnts.step(from_ascii("table"), utype);
4298                 cnts.isLongtable(true);
4299         }
4300
4301         ParIterator it2 = it;
4302         it2.forwardPos();
4303         size_t const end = it2.nargs();
4304         for ( ; it2.idx() < end; it2.top().forwardIdx())
4305                 buffer().updateBuffer(it2, utype);
4306
4307         //reset afterwards
4308         if (tabular.is_long_tabular) {
4309                 cnts.current_float(saveflt);
4310                 cnts.isLongtable(false);
4311         }
4312 }
4313
4314
4315 void InsetTabular::addToToc(DocIterator const & cpit, bool output_active,
4316                                                         UpdateType utype, TocBackend & backend) const
4317 {
4318         DocIterator dit = cpit;
4319         dit.forwardPos();
4320         size_t const end = dit.nargs();
4321         for ( ; dit.idx() < end; dit.top().forwardIdx())
4322                 cell(dit.idx())->addToToc(dit, output_active, utype, backend);
4323 }
4324
4325
4326 bool InsetTabular::hitSelectRow(BufferView const & bv, int x) const
4327 {
4328         int const x0 = xo(bv) + ADD_TO_TABULAR_WIDTH;
4329         return x < x0 || x > x0 + tabular.width();
4330 }
4331
4332
4333 bool InsetTabular::hitSelectColumn(BufferView const & bv, int y) const
4334 {
4335         int const y0 = yo(bv) - tabular.rowAscent(0) + tabular.offsetVAlignment();
4336         // FIXME: using ADD_TO_TABULAR_WIDTH is not really correct since
4337         // there is no margin added vertically to tabular insets.
4338         // However, it works for now.
4339         return y < y0 + ADD_TO_TABULAR_WIDTH || y > y0 + tabular.height() - ADD_TO_TABULAR_WIDTH;
4340 }
4341
4342
4343 bool InsetTabular::clickable(BufferView const & bv, int x, int y) const
4344 {
4345         return hitSelectRow(bv, x) || hitSelectColumn(bv, y);
4346 }
4347
4348
4349 void InsetTabular::doDispatch(Cursor & cur, FuncRequest & cmd)
4350 {
4351         LYXERR(Debug::DEBUG, "# InsetTabular::doDispatch: cmd: " << cmd
4352                              << "\n  cur:" << cur);
4353         CursorSlice sl = cur.top();
4354         Cursor & bvcur = cur.bv().cursor();
4355
4356         FuncCode const act = cmd.action();
4357
4358         switch (act) {
4359
4360         case LFUN_MOUSE_PRESS: {
4361                 //lyxerr << "# InsetTabular::MousePress\n" << cur.bv().cursor() << endl;
4362                 // select row
4363                 if (hitSelectRow(cur.bv(), cmd.x())) {
4364                         row_type r = rowFromY(cur, cmd.y());
4365                         cur.idx() = tabular.getFirstCellInRow(r);
4366                         cur.pit() = 0;
4367                         cur.pos() = 0;
4368                         cur.resetAnchor();
4369                         cur.idx() = tabular.getLastCellInRow(r);
4370                         cur.pit() = cur.lastpit();
4371                         cur.pos() = cur.lastpos();
4372                         cur.selection(true);
4373                         bvcur = cur;
4374                         rowselect_ = true;
4375                         break;
4376                 }
4377                 // select column
4378                 if (hitSelectColumn(cur.bv(), cmd.y())) {
4379                         col_type c = columnFromX(cur, cmd.x());
4380                         cur.idx() = tabular.cellIndex(0, c);
4381                         cur.pit() = 0;
4382                         cur.pos() = 0;
4383                         cur.resetAnchor();
4384                         cur.idx() = tabular.cellIndex(tabular.nrows() - 1, c);
4385                         cur.pit() = cur.lastpit();
4386                         cur.pos() = cur.lastpos();
4387                         cur.selection(true);
4388                         bvcur = cur;
4389                         colselect_ = true;
4390                         break;
4391                 }
4392                 // do not reset cursor/selection if we have selected
4393                 // some cells (bug 2715).
4394                 if (cmd.button() == mouse_button::button3
4395                     && &bvcur.selBegin().inset() == this
4396                     && bvcur.selIsMultiCell())
4397                         ;
4398                 else
4399                         // Let InsetTableCell do it
4400                         cell(cur.idx())->dispatch(cur, cmd);
4401                 break;
4402         }
4403         case LFUN_MOUSE_MOTION:
4404                 //lyxerr << "# InsetTabular::MouseMotion\n" << bvcur << endl;
4405                 if (cmd.button() == mouse_button::button1) {
4406                         // only accept motions to places not deeper nested than the real anchor
4407                         if (!bvcur.realAnchor().hasPart(cur)) {
4408                                 cur.undispatched();
4409                                 break;
4410                         }
4411                         // select (additional) row
4412                         if (rowselect_) {
4413                                 row_type r = rowFromY(cur, cmd.y());
4414                                 cur.idx() = tabular.getLastCellInRow(r);
4415                                 // we need to reset the cursor's pit and pos now, as the old ones
4416                                 // may no longer be valid.
4417                                 cur.pit() = 0;
4418                                 cur.pos() = 0;
4419                                 bvcur.setCursor(cur);
4420                                 bvcur.selection(true);
4421                                 break;
4422                         }
4423                         // select (additional) column
4424                         if (colselect_) {
4425                                 col_type c = columnFromX(cur, cmd.x());
4426                                 cur.idx() = tabular.cellIndex(tabular.nrows() - 1, c);
4427                                 // we need to reset the cursor's pit and pos now, as the old ones
4428                                 // may no longer be valid.
4429                                 cur.pit() = 0;
4430                                 cur.pos() = 0;
4431                                 bvcur.setCursor(cur);
4432                                 bvcur.selection(true);
4433                                 break;
4434                         }
4435                         // only update if selection changes
4436                         if (bvcur.idx() == cur.idx() &&
4437                                 !(bvcur.realAnchor().idx() == cur.idx() && bvcur.pos() != cur.pos()))
4438                                 cur.noScreenUpdate();
4439                         setCursorFromCoordinates(cur, cmd.x(), cmd.y());
4440                         bvcur.setCursor(cur);
4441                         bvcur.selection(true);
4442                         // if this is a multicell selection, we just set the cursor to
4443                         // the beginning of the cell's text.
4444                         if (bvcur.selIsMultiCell()) {
4445                                 bvcur.pit() = bvcur.lastpit();
4446                                 bvcur.pos() = bvcur.lastpos();
4447                         }
4448                 }
4449                 break;
4450
4451         case LFUN_MOUSE_RELEASE:
4452                 rowselect_ = false;
4453                 colselect_ = false;
4454                 break;
4455
4456         case LFUN_CELL_BACKWARD:
4457                 movePrevCell(cur);
4458                 cur.selection(false);
4459                 break;
4460
4461         case LFUN_CELL_FORWARD:
4462                 moveNextCell(cur);
4463                 cur.selection(false);
4464                 break;
4465
4466         case LFUN_CHAR_FORWARD_SELECT:
4467         case LFUN_CHAR_FORWARD:
4468         case LFUN_CHAR_BACKWARD_SELECT:
4469         case LFUN_CHAR_BACKWARD:
4470         case LFUN_CHAR_RIGHT_SELECT:
4471         case LFUN_CHAR_RIGHT:
4472         case LFUN_CHAR_LEFT_SELECT:
4473         case LFUN_CHAR_LEFT:
4474         case LFUN_WORD_FORWARD:
4475         case LFUN_WORD_FORWARD_SELECT:
4476         case LFUN_WORD_BACKWARD:
4477         case LFUN_WORD_BACKWARD_SELECT:
4478         case LFUN_WORD_RIGHT:
4479         case LFUN_WORD_RIGHT_SELECT:
4480         case LFUN_WORD_LEFT:
4481         case LFUN_WORD_LEFT_SELECT: {
4482                 // determine whether we move to next or previous cell, where to enter
4483                 // the new cell from, and which command to "finish" (i.e., exit the
4484                 // inset) with:
4485                 bool next_cell;
4486                 EntryDirection entry_from = ENTRY_DIRECTION_IGNORE;
4487                 FuncCode finish_lfun;
4488
4489                 if (act == LFUN_CHAR_FORWARD
4490                                 || act == LFUN_CHAR_FORWARD_SELECT
4491                                 || act == LFUN_WORD_FORWARD
4492                                 || act == LFUN_WORD_FORWARD_SELECT) {
4493                         next_cell = true;
4494                         finish_lfun = LFUN_FINISHED_FORWARD;
4495                 }
4496                 else if (act == LFUN_CHAR_BACKWARD
4497                                 || act == LFUN_CHAR_BACKWARD_SELECT
4498                                 || act == LFUN_WORD_BACKWARD
4499                                 || act == LFUN_WORD_BACKWARD_SELECT) {
4500                         next_cell = false;
4501                         finish_lfun = LFUN_FINISHED_BACKWARD;
4502                 }
4503                 // LEFT or RIGHT commands --- the interpretation will depend on the
4504                 // table's direction.
4505                 else {
4506                         bool const right = act == LFUN_CHAR_RIGHT
4507                                 || act == LFUN_CHAR_RIGHT_SELECT
4508                                 || act == LFUN_WORD_RIGHT
4509                                 || act == LFUN_WORD_RIGHT_SELECT;
4510                         next_cell = isRightToLeft(cur) != right;
4511
4512                         if (lyxrc.visual_cursor)
4513                                 entry_from = right ? ENTRY_DIRECTION_LEFT:ENTRY_DIRECTION_RIGHT;
4514
4515                         finish_lfun = right ? LFUN_FINISHED_RIGHT : LFUN_FINISHED_LEFT;
4516                 }
4517
4518                 bool const select =     act == LFUN_CHAR_FORWARD_SELECT
4519                     || act == LFUN_CHAR_BACKWARD_SELECT
4520                     || act == LFUN_CHAR_RIGHT_SELECT
4521                     || act == LFUN_CHAR_LEFT_SELECT
4522                         || act == LFUN_WORD_FORWARD_SELECT
4523                         || act == LFUN_WORD_RIGHT_SELECT
4524                         || act == LFUN_WORD_BACKWARD_SELECT
4525                         || act == LFUN_WORD_LEFT_SELECT;
4526
4527                 // If we have a multicell selection or we're
4528                 // not doing some LFUN_*_SELECT thing anyway...
4529                 if (!cur.selIsMultiCell() || !select) {
4530                         col_type const c = tabular.cellColumn(cur.idx());
4531                         row_type const r = tabular.cellRow(cur.idx());
4532                         // Are we trying to select the whole cell and is the whole cell
4533                         // not yet selected?
4534                         bool const select_whole = select && !isCellSelected(cur, r, c) &&
4535                                 ((next_cell && cur.pit() == cur.lastpit()
4536                                 && cur.pos() == cur.lastpos())
4537                                 || (!next_cell && cur.pit() == 0 && cur.pos() == 0));
4538
4539                         bool const empty_cell = cur.lastpos() == 0 && cur.lastpit() == 0;
4540
4541                         // ...try to dispatch to the cell's inset.
4542                         cell(cur.idx())->dispatch(cur, cmd);
4543
4544                         // When we already have a selection we want to select the whole cell
4545                         // before going to the next cell.
4546                         if (select_whole && !empty_cell){
4547                                 getText(cur.idx())->selectAll(cur);
4548                                 cur.dispatched();
4549                                 cur.screenUpdateFlags(Update::Force | Update::FitCursor);
4550                                 break;
4551                         }
4552
4553                         // FIXME: When we support the selection of an empty cell, remove
4554                         // the !empty_cell from this condition. For now we jump to the next
4555                         // cell if the current cell is empty.
4556                         if (cur.result().dispatched() && !empty_cell)
4557                                 break;
4558                 }
4559
4560                 // move to next/prev cell, as appropriate
4561                 // note that we will always do this if we're selecting and we have
4562                 // a multicell selection
4563                 LYXERR(Debug::RTL, "entering " << (next_cell ? "next" : "previous")
4564                         << " cell from: " << int(entry_from));
4565                 if (next_cell)
4566                         moveNextCell(cur, entry_from);
4567                 else
4568                         movePrevCell(cur, entry_from);
4569                 // if we're exiting the table, call the appropriate FINISHED lfun
4570                 if (sl == cur.top()) {
4571                         cmd = FuncRequest(finish_lfun);
4572                         cur.undispatched();
4573                 } else
4574                         cur.dispatched();
4575
4576                 cur.screenUpdateFlags(Update::Force | Update::FitCursor);
4577                 break;
4578
4579         }
4580
4581         case LFUN_DOWN_SELECT:
4582         case LFUN_DOWN:
4583                 if (!(cur.selection() && cur.selIsMultiCell()))
4584                         cell(cur.idx())->dispatch(cur, cmd);
4585
4586                 cur.dispatched(); // override the cell's decision
4587                 if (sl == cur.top()) {
4588                         // if our Text didn't do anything to the cursor
4589                         // then we try to put the cursor into the cell below
4590                         // setting also the right targetX.
4591                         cur.selHandle(act == LFUN_DOWN_SELECT);
4592                         if (tabular.cellRow(cur.idx()) != tabular.nrows() - 1) {
4593                                 int const xtarget = cur.targetX();
4594                                 // WARNING: Once cur.idx() has been reset, the cursor is in
4595                                 // an inconsistent state until pos() has been set. Be careful
4596                                 // what you do with it!
4597                                 cur.idx() = tabular.cellBelow(cur.idx());
4598                                 cur.pit() = 0;
4599                                 TextMetrics const & tm =
4600                                         cur.bv().textMetrics(cell(cur.idx())->getText(0));
4601                                 cur.pos() = tm.x2pos(cur.pit(), 0, xtarget);
4602                                 cur.setCurrentFont();
4603                         }
4604                 }
4605                 if (sl == cur.top()) {
4606                         // we trick it to go to forward after leaving the
4607                         // tabular.
4608                         cmd = FuncRequest(LFUN_FINISHED_FORWARD);
4609                         cur.undispatched();
4610                 }
4611                 if (cur.selIsMultiCell()) {
4612                         cur.pit() = cur.lastpit();
4613                         cur.pos() = cur.lastpos();
4614                         cur.setCurrentFont();
4615                         cur.screenUpdateFlags(Update::Force | Update::FitCursor);
4616                         return;
4617                 }
4618                 cur.screenUpdateFlags(Update::Force | Update::FitCursor);
4619                 break;
4620
4621         case LFUN_UP_SELECT:
4622         case LFUN_UP:
4623                 if (!(cur.selection() && cur.selIsMultiCell()))
4624                         cell(cur.idx())->dispatch(cur, cmd);
4625                 cur.dispatched(); // override the cell's decision
4626                 if (sl == cur.top()) {
4627                         // if our Text didn't do anything to the cursor
4628                         // then we try to put the cursor into the cell above
4629                         // setting also the right targetX.
4630                         cur.selHandle(act == LFUN_UP_SELECT);
4631                         if (tabular.cellRow(cur.idx()) != 0) {
4632                                 int const xtarget = cur.targetX();
4633                                 // WARNING: Once cur.idx() has been reset, the cursor is in
4634                                 // an inconsistent state until pos() has been set. Be careful
4635                                 // what you do with it!
4636                                 cur.idx() = tabular.cellAbove(cur.idx());
4637                                 cur.pit() = cur.lastpit();
4638                                 Text const * text = cell(cur.idx())->getText(0);
4639                                 TextMetrics const & tm = cur.bv().textMetrics(text);
4640                                 ParagraphMetrics const & pm =
4641                                         tm.parMetrics(cur.lastpit());
4642                                 cur.pos() = tm.x2pos(cur.pit(), pm.rows().size()-1, xtarget);
4643                                 cur.setCurrentFont();
4644                         }
4645                 }
4646                 if (sl == cur.top()) {
4647                         cmd = FuncRequest(LFUN_UP);
4648                         cur.undispatched();
4649                 }
4650                 if (cur.selIsMultiCell()) {
4651                         cur.pit() = 0;
4652                         cur.pos() = cur.lastpos();
4653                         cur.setCurrentFont();
4654                         cur.screenUpdateFlags(Update::Force | Update::FitCursor);
4655                         return;
4656                 }
4657                 cur.screenUpdateFlags(Update::Force | Update::FitCursor);
4658                 break;
4659
4660         case LFUN_LAYOUT_TABULAR:
4661                 cur.bv().showDialog("tabular");
4662                 break;
4663
4664         case LFUN_INSET_MODIFY:
4665                 // we come from the dialog
4666                 if (cmd.getArg(0) == "tabular")
4667                         tabularFeatures(cur, cmd.getLongArg(1));
4668                 else
4669                         cur.undispatched();
4670                 break;
4671
4672         case LFUN_TABULAR_FEATURE:
4673                 tabularFeatures(cur, to_utf8(cmd.argument()));
4674                 break;
4675
4676         // insert file functions
4677         case LFUN_FILE_INSERT_PLAINTEXT_PARA:
4678         case LFUN_FILE_INSERT_PLAINTEXT:
4679                 // FIXME UNICODE
4680                 if (FileName::isAbsolute(to_utf8(cmd.argument()))) {
4681                         docstring const tmpstr = cur.bv().contentsOfPlaintextFile(
4682                                 FileName(to_utf8(cmd.argument())));
4683                         if (tmpstr.empty())
4684                                 break;
4685                         cur.recordUndoInset();
4686                         if (insertPlaintextString(cur.bv(), tmpstr, false)) {
4687                                 // content has been replaced,
4688                                 // so cursor might be invalid
4689                                 cur.pos() = cur.lastpos();
4690                                 cur.pit() = cur.lastpit();
4691                                 bvcur.setCursor(cur);
4692                         } else
4693                                 cur.undispatched();
4694                 }
4695                 break;
4696
4697         case LFUN_CUT:
4698                 if (cur.selIsMultiCell()) {
4699                         if (copySelection(cur)) {
4700                                 cur.recordUndoInset();
4701                                 cutSelection(cur);
4702                         }
4703                 } else
4704                         cell(cur.idx())->dispatch(cur, cmd);
4705                 break;
4706
4707         case LFUN_SELF_INSERT:
4708                 if (cur.selIsMultiCell()) {
4709                         cur.recordUndoInset();
4710                         cutSelection(cur);
4711                         BufferView * bv = &cur.bv();
4712                         docstring::const_iterator cit = cmd.argument().begin();
4713                         docstring::const_iterator const end = cmd.argument().end();
4714                         for (; cit != end; ++cit)
4715                                 bv->translateAndInsert(*cit, getText(cur.idx()), cur);
4716
4717                         cur.resetAnchor();
4718                         bv->bookmarkEditPosition();
4719                 } else
4720                         cell(cur.idx())->dispatch(cur, cmd);
4721                 break;
4722
4723         case LFUN_CHAR_DELETE_BACKWARD:
4724         case LFUN_CHAR_DELETE_FORWARD:
4725                 if (cur.selIsMultiCell()) {
4726                         cur.recordUndoInset();
4727                         cutSelection(cur);
4728                 } else
4729                         cell(cur.idx())->dispatch(cur, cmd);
4730                 break;
4731
4732         case LFUN_COPY:
4733                 if (!cur.selection())
4734                         break;
4735                 if (cur.selIsMultiCell()) {
4736                         cur.finishUndo();
4737                         copySelection(cur);
4738                 } else
4739                         cell(cur.idx())->dispatch(cur, cmd);
4740                 break;
4741
4742         case LFUN_CLIPBOARD_PASTE:
4743         case LFUN_PRIMARY_SELECTION_PASTE: {
4744                 docstring const clip = (act == LFUN_CLIPBOARD_PASTE) ?
4745                         theClipboard().getAsText(Clipboard::PlainTextType) :
4746                         theSelection().get();
4747                 if (clip.empty())
4748                         break;
4749                 // pass to InsertPlaintextString, but
4750                 // only if we have multi-cell content
4751                 if (clip.find_first_of(from_ascii("\t\n")) != docstring::npos) {
4752                         cur.recordUndoInset();
4753                         if (insertPlaintextString(cur.bv(), clip, false)) {
4754                                 // content has been replaced,
4755                                 // so cursor might be invalid
4756                                 cur.pos() = cur.lastpos();
4757                                 cur.pit() = cur.lastpit();
4758                                 bvcur.setCursor(cur);
4759                                 break;
4760                         }
4761                 }
4762                 // Let the cell handle normal text
4763                 cell(cur.idx())->dispatch(cur, cmd);
4764                 break;
4765         }
4766
4767         case LFUN_PASTE:
4768                 if (!tabularStackDirty()) {
4769                         if (!cur.selIsMultiCell())
4770                                 cell(cur.idx())->dispatch(cur, cmd);
4771                         break;
4772                 }
4773                 if (theClipboard().isInternal()) {
4774                         cur.recordUndoInset();
4775                         pasteClipboard(cur);
4776                 }
4777                 break;
4778
4779         case LFUN_FONT_EMPH:
4780         case LFUN_FONT_BOLD:
4781         case LFUN_FONT_BOLDSYMBOL:
4782         case LFUN_FONT_ROMAN:
4783         case LFUN_FONT_NOUN:
4784         case LFUN_FONT_ITAL:
4785         case LFUN_FONT_FRAK:
4786         case LFUN_FONT_TYPEWRITER:
4787         case LFUN_FONT_SANS:
4788         case LFUN_TEXTSTYLE_APPLY:
4789         case LFUN_TEXTSTYLE_UPDATE:
4790         case LFUN_FONT_SIZE:
4791         case LFUN_FONT_UNDERLINE:
4792         case LFUN_FONT_STRIKEOUT:
4793         case LFUN_FONT_CROSSOUT:
4794         case LFUN_FONT_UNDERUNDERLINE:
4795         case LFUN_FONT_UNDERWAVE:
4796         case LFUN_LANGUAGE:
4797         case LFUN_PARAGRAPH_PARAMS_APPLY:
4798         case LFUN_PARAGRAPH_PARAMS:
4799         case LFUN_WORD_CAPITALIZE:
4800         case LFUN_WORD_UPCASE:
4801         case LFUN_WORD_LOWCASE:
4802         case LFUN_CHARS_TRANSPOSE:
4803                 if (cur.selIsMultiCell()) {
4804                         row_type rs, re;
4805                         col_type cs, ce;
4806                         getSelection(cur, rs, re, cs, ce);
4807                         Cursor tmpcur = cur;
4808                         for (row_type r = rs; r <= re; ++r) {
4809                                 for (col_type c = cs; c <= ce; ++c) {
4810                                         // cursor follows cell:
4811                                         tmpcur.idx() = tabular.cellIndex(r, c);
4812                                         // select this cell only:
4813                                         tmpcur.pit() = 0;
4814                                         tmpcur.pos() = 0;
4815                                         tmpcur.resetAnchor();
4816                                         tmpcur.pit() = tmpcur.lastpit();
4817                                         tmpcur.pos() = tmpcur.top().lastpos();
4818                                         tmpcur.setCursor(tmpcur);
4819                                         tmpcur.setSelection();
4820                                         cell(tmpcur.idx())->dispatch(tmpcur, cmd);
4821                                 }
4822                         }
4823                         break;
4824                 } else {
4825                         cell(cur.idx())->dispatch(cur, cmd);
4826                         break;
4827                 }
4828
4829         case LFUN_INSET_SETTINGS:
4830                 // relay this lfun to Inset, not to the cell.
4831                 Inset::doDispatch(cur, cmd);
4832                 break;
4833
4834         default:
4835                 // we try to handle this event in the insets dispatch function.
4836                 cell(cur.idx())->dispatch(cur, cmd);
4837                 break;
4838         }
4839 }
4840
4841
4842 bool InsetTabular::getFeatureStatus(Cursor & cur, string const & s,
4843                       string const & argument, FuncStatus & status) const
4844 {
4845
4846                 int action = Tabular::LAST_ACTION;
4847                 int i = 0;
4848                 for (; tabularFeature[i].action != Tabular::LAST_ACTION; ++i) {
4849                         if (tabularFeature[i].feature == s) {
4850                                 action = tabularFeature[i].action;
4851                                 break;
4852                         }
4853                 }
4854                 if (action == Tabular::LAST_ACTION) {
4855                         status.clear();
4856                         status.setUnknown(true);
4857                         return true;
4858                 }
4859
4860                 row_type sel_row_start = 0;
4861                 row_type sel_row_end = 0;
4862                 col_type sel_col_start = 0;
4863                 col_type sel_col_end = 0;
4864                 Tabular::ltType dummyltt;
4865                 bool flag = true;
4866
4867                 getSelection(cur, sel_row_start, sel_row_end, sel_col_start, sel_col_end);
4868
4869                 switch (action) {
4870                 case Tabular::SET_PWIDTH:
4871                 case Tabular::SET_MPWIDTH:
4872                 case Tabular::TOGGLE_VARWIDTH_COLUMN:
4873                 case Tabular::SET_SPECIAL_COLUMN:
4874                 case Tabular::SET_SPECIAL_MULTICOLUMN:
4875                 case Tabular::APPEND_ROW:
4876                 case Tabular::APPEND_COLUMN:
4877                 case Tabular::DELETE_ROW:
4878                 case Tabular::DELETE_COLUMN:
4879                 case Tabular::COPY_ROW:
4880                 case Tabular::COPY_COLUMN:
4881                 case Tabular::SET_TOP_SPACE:
4882                 case Tabular::SET_BOTTOM_SPACE:
4883                 case Tabular::SET_INTERLINE_SPACE:
4884                         status.clear();
4885                         return true;
4886
4887                 case Tabular::SET_TABULAR_WIDTH:
4888                         status.setEnabled(!tabular.rotate
4889                                 && tabular.tabular_valignment == Tabular::LYX_VALIGN_MIDDLE);
4890                         break;
4891
4892                 case Tabular::MOVE_COLUMN_RIGHT:
4893                 case Tabular::MOVE_COLUMN_LEFT:
4894                 case Tabular::MOVE_ROW_DOWN:
4895                 case Tabular::MOVE_ROW_UP: {
4896                         if (cur.selection()) {
4897                                 status.message(_("Selections not supported."));
4898                                 status.setEnabled(false);
4899                                 break;
4900                         }
4901
4902                         if ((action == Tabular::MOVE_COLUMN_RIGHT &&
4903                                 tabular.ncols() == tabular.cellColumn(cur.idx()) + 1) ||
4904                             (action == Tabular::MOVE_COLUMN_LEFT &&
4905                                 tabular.cellColumn(cur.idx()) == 0) ||
4906                             (action == Tabular::MOVE_ROW_DOWN &&
4907                                 tabular.nrows() == tabular.cellRow(cur.idx()) + 1) ||
4908                             (action == Tabular::MOVE_ROW_UP &&
4909                                 tabular.cellRow(cur.idx()) == 0)) {
4910                                         status.setEnabled(false);
4911                                         break;
4912                         }
4913
4914                         if (action == Tabular::MOVE_COLUMN_RIGHT ||
4915                             action == Tabular::MOVE_COLUMN_LEFT) {
4916                                 if (tabular.hasMultiColumn(tabular.cellColumn(cur.idx())) ||
4917                                     tabular.hasMultiColumn(tabular.cellColumn(cur.idx()) +
4918                                         (action == Tabular::MOVE_COLUMN_RIGHT ? 1 : -1))) {
4919                                         status.message(_("Multi-column in current or"
4920                                                          " destination column."));
4921                                         status.setEnabled(false);
4922                                         break;
4923                                 }
4924                         }
4925
4926                         if (action == Tabular::MOVE_ROW_DOWN ||
4927                             action == Tabular::MOVE_ROW_UP) {
4928                                 if (tabular.hasMultiRow(tabular.cellRow(cur.idx())) ||
4929                                     tabular.hasMultiRow(tabular.cellRow(cur.idx()) +
4930                                         (action == Tabular::MOVE_ROW_DOWN ? 1 : -1))) {
4931                                         status.message(_("Multi-row in current or"
4932                                                          " destination row."));
4933                                         status.setEnabled(false);
4934                                         break;
4935                                 }
4936                         }
4937
4938                         status.setEnabled(true);
4939                         break;
4940                 }
4941
4942                 case Tabular::SET_DECIMAL_POINT:
4943                         status.setEnabled(
4944                                 tabular.getAlignment(cur.idx()) == LYX_ALIGN_DECIMAL);
4945                         break;
4946
4947                 case Tabular::SET_MULTICOLUMN:
4948                 case Tabular::UNSET_MULTICOLUMN:
4949                 case Tabular::MULTICOLUMN:
4950                         // If a row is set as longtable caption, it must not be allowed
4951                         // to unset that this row is a multicolumn.
4952                         status.setEnabled(sel_row_start == sel_row_end
4953                                 && !tabular.ltCaption(tabular.cellRow(cur.idx())));
4954                         status.setOnOff(tabular.isMultiColumn(cur.idx()));
4955                         break;
4956
4957                 case Tabular::SET_MULTIROW:
4958                 case Tabular::UNSET_MULTIROW:
4959                 case Tabular::MULTIROW:
4960                         // If a row is set as longtable caption, it must not be allowed
4961                         // to unset that this row is a multirow.
4962                         status.setEnabled(sel_col_start == sel_col_end
4963                                 && !tabular.ltCaption(tabular.cellRow(cur.idx())));
4964                         status.setOnOff(tabular.isMultiRow(cur.idx()));
4965                         break;
4966
4967                 case Tabular::SET_ALL_LINES:
4968                 case Tabular::UNSET_ALL_LINES:
4969                 case Tabular::SET_BORDER_LINES:
4970                         status.setEnabled(!tabular.ltCaption(tabular.cellRow(cur.idx())));
4971                         break;
4972
4973                 case Tabular::SET_LINE_TOP:
4974                 case Tabular::SET_LINE_BOTTOM:
4975                         status.setEnabled(!tabular.ltCaption(tabular.cellRow(cur.idx())));
4976                         break;
4977
4978                 case Tabular::SET_LINE_LEFT:
4979                 case Tabular::SET_LINE_RIGHT:
4980                         status.setEnabled(!tabular.use_booktabs
4981                                           && !tabular.ltCaption(tabular.cellRow(cur.idx())));
4982                         break;
4983
4984                 case Tabular::TOGGLE_LINE_TOP:
4985                         status.setEnabled(!tabular.ltCaption(tabular.cellRow(cur.idx())));
4986                         status.setOnOff(tabular.topLine(cur.idx()));
4987                         break;
4988
4989                 case Tabular::TOGGLE_LINE_BOTTOM:
4990                         status.setEnabled(!tabular.ltCaption(tabular.cellRow(cur.idx())));
4991                         status.setOnOff(tabular.bottomLine(cur.idx()));
4992                         break;
4993
4994                 case Tabular::TOGGLE_LINE_LEFT:
4995                         status.setEnabled(!tabular.use_booktabs
4996                                           && !tabular.ltCaption(tabular.cellRow(cur.idx())));
4997                         status.setOnOff(tabular.leftLine(cur.idx()));
4998                         break;
4999
5000                 case Tabular::TOGGLE_LINE_RIGHT:
5001                         status.setEnabled(!tabular.use_booktabs
5002                                           && !tabular.ltCaption(tabular.cellRow(cur.idx())));
5003                         status.setOnOff(tabular.rightLine(cur.idx()));
5004                         break;
5005
5006                 // multirow cells only inherit the alignment of the column if the column has
5007                 // no width, otherwise they are left-aligned
5008                 // therefore allow always left but right and center only if there is no width
5009                 case Tabular::M_ALIGN_LEFT:
5010                         flag = false;
5011                         // fall through
5012                 case Tabular::ALIGN_LEFT:
5013                         status.setOnOff(tabular.getAlignment(cur.idx(), flag) == LYX_ALIGN_LEFT);
5014                         break;
5015
5016                 case Tabular::M_ALIGN_RIGHT:
5017                         flag = false;
5018                         // fall through
5019                 case Tabular::ALIGN_RIGHT:
5020                         status.setEnabled(!(tabular.isMultiRow(cur.idx())
5021                                 && !tabular.getPWidth(cur.idx()).zero()));
5022                         status.setOnOff(tabular.getAlignment(cur.idx(), flag) == LYX_ALIGN_RIGHT);
5023                         break;
5024
5025                 case Tabular::M_ALIGN_CENTER:
5026                         flag = false;
5027                         // fall through
5028                 case Tabular::ALIGN_CENTER:
5029                         status.setEnabled(!(tabular.isMultiRow(cur.idx())
5030                                 && !tabular.getPWidth(cur.idx()).zero()));
5031                         status.setOnOff(tabular.getAlignment(cur.idx(), flag) == LYX_ALIGN_CENTER);
5032                         break;
5033
5034                 case Tabular::ALIGN_BLOCK:
5035                         status.setEnabled(!tabular.getPWidth(cur.idx()).zero()
5036                                 && !tabular.isMultiRow(cur.idx()));
5037                         status.setOnOff(tabular.getAlignment(cur.idx(), flag) == LYX_ALIGN_BLOCK);
5038                         break;
5039
5040                 case Tabular::ALIGN_DECIMAL:
5041                         status.setEnabled(!tabular.isMultiRow(cur.idx())
5042                                 && !tabular.isMultiColumn(cur.idx()));
5043                         status.setOnOff(tabular.getAlignment(cur.idx(), true) == LYX_ALIGN_DECIMAL);
5044                         break;
5045
5046                 case Tabular::M_VALIGN_TOP:
5047                         flag = false;
5048                         // fall through
5049                 case Tabular::VALIGN_TOP:
5050                         status.setEnabled(!tabular.getPWidth(cur.idx()).zero()
5051                                 && !tabular.isMultiRow(cur.idx()));
5052                         status.setOnOff(
5053                                 tabular.getVAlignment(cur.idx(), flag) == Tabular::LYX_VALIGN_TOP);
5054                         break;
5055
5056                 case Tabular::M_VALIGN_BOTTOM:
5057                         flag = false;
5058                         // fall through
5059                 case Tabular::VALIGN_BOTTOM:
5060                         status.setEnabled(!tabular.getPWidth(cur.idx()).zero()
5061                                 && !tabular.isMultiRow(cur.idx()));
5062                         status.setOnOff(
5063                                 tabular.getVAlignment(cur.idx(), flag) == Tabular::LYX_VALIGN_BOTTOM);
5064                         break;
5065
5066                 case Tabular::M_VALIGN_MIDDLE:
5067                         flag = false;
5068                         // fall through
5069                 case Tabular::VALIGN_MIDDLE:
5070                         status.setEnabled(!tabular.getPWidth(cur.idx()).zero()
5071                                 && !tabular.isMultiRow(cur.idx()));
5072                         status.setOnOff(
5073                                 tabular.getVAlignment(cur.idx(), flag) == Tabular::LYX_VALIGN_MIDDLE);
5074                         break;
5075
5076                 case Tabular::SET_LONGTABULAR:
5077                 case Tabular::TOGGLE_LONGTABULAR:
5078                         // setting as longtable is not allowed when table is inside a float
5079                         if (cur.innerInsetOfType(FLOAT_CODE) != 0
5080                                 || cur.innerInsetOfType(WRAP_CODE) != 0)
5081                                 status.setEnabled(false);
5082                         else
5083                                 status.setEnabled(true);
5084                         status.setOnOff(tabular.is_long_tabular);
5085                         break;
5086
5087                 case Tabular::UNSET_LONGTABULAR:
5088                         status.setOnOff(!tabular.is_long_tabular);
5089                         break;
5090
5091                 case Tabular::TOGGLE_ROTATE_TABULAR:
5092                 case Tabular::SET_ROTATE_TABULAR:
5093                         status.setOnOff(tabular.rotate != 0);
5094                         break;
5095
5096                 case Tabular::TABULAR_VALIGN_TOP:
5097                         status.setEnabled(tabular.tabular_width.zero());
5098                         status.setOnOff(tabular.tabular_valignment
5099                                 == Tabular::LYX_VALIGN_TOP);
5100                         break;
5101                 case Tabular::TABULAR_VALIGN_MIDDLE:
5102                         status.setEnabled(tabular.tabular_width.zero());
5103                         status.setOnOff(tabular.tabular_valignment
5104                                 == Tabular::LYX_VALIGN_MIDDLE);
5105                         break;
5106                 case Tabular::TABULAR_VALIGN_BOTTOM:
5107                         status.setEnabled(tabular.tabular_width.zero());
5108                         status.setOnOff(tabular.tabular_valignment
5109                                 == Tabular::LYX_VALIGN_BOTTOM);
5110                         break;
5111
5112                 case Tabular::LONGTABULAR_ALIGN_LEFT:
5113                         status.setOnOff(tabular.longtabular_alignment
5114                                 == Tabular::LYX_LONGTABULAR_ALIGN_LEFT);
5115                         break;
5116                 case Tabular::LONGTABULAR_ALIGN_CENTER:
5117                         status.setOnOff(tabular.longtabular_alignment
5118                                 == Tabular::LYX_LONGTABULAR_ALIGN_CENTER);
5119                         break;
5120                 case Tabular::LONGTABULAR_ALIGN_RIGHT:
5121                         status.setOnOff(tabular.longtabular_alignment
5122                                 == Tabular::LYX_LONGTABULAR_ALIGN_RIGHT);
5123                         break;
5124
5125                 case Tabular::UNSET_ROTATE_TABULAR:
5126                         status.setOnOff(tabular.rotate == 0);
5127                         break;
5128
5129                 case Tabular::TOGGLE_ROTATE_CELL:
5130                 case Tabular::SET_ROTATE_CELL:
5131                         status.setOnOff(!oneCellHasRotationState(false,
5132                                 sel_row_start, sel_row_end, sel_col_start, sel_col_end));
5133                         break;
5134
5135                 case Tabular::UNSET_ROTATE_CELL:
5136                         status.setOnOff(!oneCellHasRotationState(true,
5137                                 sel_row_start, sel_row_end, sel_col_start, sel_col_end));
5138                         break;
5139
5140                 case Tabular::SET_USEBOX:
5141                         status.setOnOff(convert<int>(argument) == tabular.getUsebox(cur.idx()));
5142                         break;
5143
5144                 // every row can only be one thing:
5145                 // either a footer or header
5146                 case Tabular::SET_LTFIRSTHEAD:
5147                         status.setEnabled(sel_row_start == sel_row_end);
5148                         status.setOnOff(tabular.getRowOfLTFirstHead(sel_row_start, dummyltt));
5149                         break;
5150
5151                 case Tabular::UNSET_LTFIRSTHEAD:
5152                         status.setEnabled(sel_row_start == sel_row_end);
5153                         status.setOnOff(!tabular.getRowOfLTFirstHead(sel_row_start, dummyltt));
5154                         break;
5155
5156                 case Tabular::SET_LTHEAD:
5157                         status.setEnabled(sel_row_start == sel_row_end);
5158                         status.setOnOff(tabular.getRowOfLTHead(sel_row_start, dummyltt));
5159                         break;
5160
5161                 case Tabular::UNSET_LTHEAD:
5162                         status.setEnabled(sel_row_start == sel_row_end);
5163                         status.setOnOff(!tabular.getRowOfLTHead(sel_row_start, dummyltt));
5164                         break;
5165
5166                 case Tabular::SET_LTFOOT:
5167                         status.setEnabled(sel_row_start == sel_row_end);
5168                         status.setOnOff(tabular.getRowOfLTFoot(sel_row_start, dummyltt));
5169                         break;
5170
5171                 case Tabular::UNSET_LTFOOT:
5172                         status.setEnabled(sel_row_start == sel_row_end);
5173                         status.setOnOff(!tabular.getRowOfLTFoot(sel_row_start, dummyltt));
5174                         break;
5175
5176                 case Tabular::SET_LTLASTFOOT:
5177                         status.setEnabled(sel_row_start == sel_row_end);
5178                         status.setOnOff(tabular.getRowOfLTLastFoot(sel_row_start, dummyltt));
5179                         break;
5180
5181                 case Tabular::UNSET_LTLASTFOOT:
5182                         status.setEnabled(sel_row_start == sel_row_end);
5183                         status.setOnOff(!tabular.getRowOfLTLastFoot(sel_row_start, dummyltt));
5184                         break;
5185
5186                 case Tabular::SET_LTNEWPAGE:
5187                         status.setOnOff(tabular.getLTNewPage(sel_row_start));
5188                         break;
5189                 case Tabular::UNSET_LTNEWPAGE:
5190                         status.setOnOff(!tabular.getLTNewPage(sel_row_start));
5191                         break;
5192
5193                 // only one row in head/firsthead/foot/lasthead can be the caption
5194                 // and a multirow cannot be set as caption
5195                 case Tabular::SET_LTCAPTION:
5196                         status.setEnabled(sel_row_start == sel_row_end
5197                                 && (!tabular.getRowOfLTFirstHead(sel_row_start, dummyltt)
5198                                  || !tabular.haveLTCaption(Tabular::CAPTION_FIRSTHEAD))
5199                                 && (!tabular.getRowOfLTHead(sel_row_start, dummyltt)
5200                                  || !tabular.haveLTCaption(Tabular::CAPTION_HEAD))
5201                                 && (!tabular.getRowOfLTFoot(sel_row_start, dummyltt)
5202                                  || !tabular.haveLTCaption(Tabular::CAPTION_FOOT))
5203                                 && (!tabular.getRowOfLTLastFoot(sel_row_start, dummyltt)
5204                                  || !tabular.haveLTCaption(Tabular::CAPTION_LASTFOOT))
5205                                 && !tabular.isMultiRow(sel_row_start));
5206                         status.setOnOff(tabular.ltCaption(sel_row_start));
5207                         break;
5208
5209                 case Tabular::UNSET_LTCAPTION:
5210                         status.setEnabled(sel_row_start == sel_row_end && tabular.ltCaption(sel_row_start));
5211                         break;
5212
5213                 case Tabular::TOGGLE_LTCAPTION:
5214                         status.setEnabled(sel_row_start == sel_row_end && (tabular.ltCaption(sel_row_start)
5215                                 || ((!tabular.getRowOfLTFirstHead(sel_row_start, dummyltt)
5216                                   || !tabular.haveLTCaption(Tabular::CAPTION_FIRSTHEAD))
5217                                  && (!tabular.getRowOfLTHead(sel_row_start, dummyltt)
5218                                   || !tabular.haveLTCaption(Tabular::CAPTION_HEAD))
5219                                  && (!tabular.getRowOfLTFoot(sel_row_start, dummyltt)
5220                                   || !tabular.haveLTCaption(Tabular::CAPTION_FOOT))
5221                                  && (!tabular.getRowOfLTLastFoot(sel_row_start, dummyltt)
5222                                   || !tabular.haveLTCaption(Tabular::CAPTION_LASTFOOT)))));
5223                         status.setOnOff(tabular.ltCaption(sel_row_start));
5224                         break;
5225
5226                 case Tabular::TOGGLE_BOOKTABS:
5227                 case Tabular::SET_BOOKTABS:
5228                         status.setOnOff(tabular.use_booktabs);
5229                         break;
5230
5231                 case Tabular::UNSET_BOOKTABS:
5232                         status.setOnOff(!tabular.use_booktabs);
5233                         break;
5234
5235                 default:
5236                         status.clear();
5237                         status.setEnabled(false);
5238                         break;
5239                 }
5240                 return true;
5241 }
5242
5243
5244 // function sets an object as defined in FuncStatus.h:
5245 // states OK, Unknown, Disabled, On, Off.
5246 bool InsetTabular::getStatus(Cursor & cur, FuncRequest const & cmd,
5247                              FuncStatus & status) const
5248 {
5249         switch (cmd.action()) {
5250         case LFUN_INSET_MODIFY:
5251                 if (cmd.getArg(0) != "tabular")
5252                         break;
5253                 if (cmd.getArg(1) == "for-dialog") {
5254                         // The dialog is asking the status of a command
5255                         if (&cur.inset() != this)
5256                                 break;
5257                         string action = cmd.getArg(2);
5258                         string arg = cmd.getLongArg(3);
5259                         return getFeatureStatus(cur, action, arg, status);
5260                 } else {
5261                         // We always enable the lfun if it is coming from the dialog
5262                         // because the dialog makes sure all the settings are valid,
5263                         // even though the first argument might not be valid now.
5264                         status.setEnabled(true);
5265                         return true;
5266                 }
5267
5268         case LFUN_TABULAR_FEATURE: {
5269                 if (&cur.inset() != this)
5270                         break;
5271                 string action = cmd.getArg(0);
5272                 string arg = cmd.getLongArg(1);
5273                 return getFeatureStatus(cur, action, arg, status);
5274         }
5275
5276         case LFUN_CAPTION_INSERT: {
5277                 // caption is only allowed in caption cell of longtable
5278                 if (!tabular.ltCaption(tabular.cellRow(cur.idx()))) {
5279                         status.setEnabled(false);
5280                         return true;
5281                 }
5282                 // only standard caption is allowed
5283                 string arg = cmd.getArg(0);
5284                 if (!arg.empty() && arg != "Standard") {
5285                         status.setEnabled(false);
5286                         return true;
5287                 }
5288                 // check if there is already a caption
5289                 bool have_caption = false;
5290                 InsetTableCell itc = InsetTableCell(*tabular.cellInset(cur.idx()));
5291                 ParagraphList::const_iterator pit = itc.paragraphs().begin();
5292                 ParagraphList::const_iterator pend = itc.paragraphs().end();
5293                 for (; pit != pend; ++pit) {
5294                         InsetList::const_iterator it  = pit->insetList().begin();
5295                         InsetList::const_iterator end = pit->insetList().end();
5296                         for (; it != end; ++it) {
5297                                 if (it->inset->lyxCode() == CAPTION_CODE) {
5298                                         have_caption = true;
5299                                         break;
5300                                 }
5301                         }
5302                 }
5303                 status.setEnabled(!have_caption);
5304                 return true;
5305         }
5306
5307         // These are only enabled inside tabular
5308         case LFUN_CELL_BACKWARD:
5309         case LFUN_CELL_FORWARD:
5310                 status.setEnabled(true);
5311                 return true;
5312
5313         // disable these with multiple cells selected
5314         case LFUN_INSET_INSERT:
5315         case LFUN_TABULAR_INSERT:
5316         case LFUN_FLEX_INSERT:
5317         case LFUN_FLOAT_INSERT:
5318         case LFUN_FLOAT_WIDE_INSERT:
5319         case LFUN_FOOTNOTE_INSERT:
5320         case LFUN_MARGINALNOTE_INSERT:
5321         case LFUN_MATH_INSERT:
5322         case LFUN_MATH_MODE:
5323         case LFUN_MATH_MUTATE:
5324         case LFUN_MATH_DISPLAY:
5325         case LFUN_NOTE_INSERT:
5326         case LFUN_ARGUMENT_INSERT:
5327         case LFUN_BOX_INSERT:
5328         case LFUN_BRANCH_INSERT:
5329         case LFUN_PHANTOM_INSERT:
5330         case LFUN_WRAP_INSERT:
5331         case LFUN_PREVIEW_INSERT:
5332         case LFUN_ERT_INSERT: {
5333                 if (cur.selIsMultiCell()) {
5334                         status.setEnabled(false);
5335                         return true;
5336                 } else
5337                         return cell(cur.idx())->getStatus(cur, cmd, status);
5338         }
5339
5340         // disable in non-fixed-width cells
5341         case LFUN_PARAGRAPH_BREAK:
5342                 // multirow does not allow paragraph breaks
5343                 if (tabular.isMultiRow(cur.idx())) {
5344                         status.setEnabled(false);
5345                         return true;
5346                 }
5347                 // fall through
5348         case LFUN_NEWLINE_INSERT:
5349                 if ((tabular.isMultiColumn(cur.idx()) || tabular.isMultiRow(cur.idx()))
5350                     && tabular.getPWidth(cur.idx()).zero()) {
5351                         status.setEnabled(false);
5352                         return true;
5353                 }
5354                 return cell(cur.idx())->getStatus(cur, cmd, status);
5355
5356         case LFUN_NEWPAGE_INSERT:
5357                 status.setEnabled(false);
5358                 return true;
5359
5360         case LFUN_PASTE:
5361                 if (tabularStackDirty() && theClipboard().isInternal()) {
5362                         if (cur.selIsMultiCell()) {
5363                                 row_type rs, re;
5364                                 col_type cs, ce;
5365                                 getSelection(cur, rs, re, cs, ce);
5366                                 if (paste_tabular && paste_tabular->ncols() == ce - cs + 1
5367                                           && paste_tabular->nrows() == re - rs + 1)
5368                                         status.setEnabled(true);
5369                                 else {
5370                                         status.setEnabled(false);
5371                                         status.message(_("Selection size should match clipboard content."));
5372                                 }
5373                         } else
5374                                 status.setEnabled(true);
5375                         return true;
5376                 }
5377                 return cell(cur.idx())->getStatus(cur, cmd, status);
5378
5379         case LFUN_INSET_SETTINGS:
5380                 // relay this lfun to Inset, not to the cell.
5381                 return Inset::getStatus(cur, cmd, status);
5382
5383         default:
5384                 // we try to handle this event in the insets dispatch function.
5385                 return cell(cur.idx())->getStatus(cur, cmd, status);
5386         }
5387         return false;
5388 }
5389
5390
5391 Inset::DisplayType InsetTabular::display() const
5392 {
5393                 if (tabular.is_long_tabular) {
5394                         switch (tabular.longtabular_alignment) {
5395                         case Tabular::LYX_LONGTABULAR_ALIGN_LEFT:
5396                                 return AlignLeft;
5397                         case Tabular::LYX_LONGTABULAR_ALIGN_CENTER:
5398                                 return AlignCenter;
5399                         case Tabular::LYX_LONGTABULAR_ALIGN_RIGHT:
5400                                 return AlignRight;
5401                         default:
5402                                 return AlignCenter;
5403                         }
5404                 } else
5405                         return Inline;
5406 }
5407
5408
5409 void InsetTabular::latex(otexstream & os, OutputParams const & runparams) const
5410 {
5411         tabular.latex(os, runparams);
5412 }
5413
5414
5415 int InsetTabular::plaintext(odocstringstream & os,
5416         OutputParams const & runparams, size_t max_length) const
5417 {
5418         os << '\n'; // output table on a new line
5419         int const dp = runparams.linelen > 0 ? runparams.depth : 0;
5420         tabular.plaintext(os, runparams, dp, false, 0, max_length);
5421         return PLAINTEXT_NEWLINE;
5422 }
5423
5424
5425 int InsetTabular::docbook(odocstream & os, OutputParams const & runparams) const
5426 {
5427         int ret = 0;
5428         Inset * master = 0;
5429
5430         // FIXME: Why not pass a proper DocIterator here?
5431 #if 0
5432         // if the table is inside a float it doesn't need the informaltable
5433         // wrapper. Search for it.
5434         for (master = owner(); master; master = master->owner())
5435                 if (master->lyxCode() == FLOAT_CODE)
5436                         break;
5437 #endif
5438
5439         if (!master) {
5440                 os << "<informaltable>";
5441                 ++ret;
5442         }
5443         ret += tabular.docbook(os, runparams);
5444         if (!master) {
5445                 os << "</informaltable>";
5446                 ++ret;
5447         }
5448         return ret;
5449 }
5450
5451
5452 docstring InsetTabular::xhtml(XHTMLStream & xs, OutputParams const & rp) const
5453 {
5454         return tabular.xhtml(xs, rp);
5455 }
5456
5457
5458 void InsetTabular::validate(LaTeXFeatures & features) const
5459 {
5460         tabular.validate(features);
5461         features.useInsetLayout(getLayout());
5462 }
5463
5464
5465 shared_ptr<InsetTableCell const> InsetTabular::cell(idx_type idx) const
5466 {
5467         return tabular.cellInset(idx);
5468 }
5469
5470
5471 shared_ptr<InsetTableCell> InsetTabular::cell(idx_type idx)
5472 {
5473         return tabular.cellInset(idx);
5474 }
5475
5476
5477 void InsetTabular::cursorPos(BufferView const & bv,
5478                 CursorSlice const & sl, bool boundary, int & x, int & y) const
5479 {
5480         cell(sl.idx())->cursorPos(bv, sl, boundary, x, y);
5481
5482         // y offset     correction
5483         y += cellYPos(sl.idx());
5484         y += tabular.textVOffset(sl.idx());
5485         y += tabular.offsetVAlignment();
5486
5487         // x offset correction
5488         x += cellXPos(sl.idx());
5489         x += tabular.textHOffset(sl.idx());
5490         x += ADD_TO_TABULAR_WIDTH;
5491 }
5492
5493
5494 int InsetTabular::dist(BufferView & bv, idx_type const cell, int x, int y) const
5495 {
5496         int xx = 0;
5497         int yy = 0;
5498         Inset const & inset = *tabular.cellInset(cell);
5499         Point o = bv.coordCache().getInsets().xy(&inset);
5500         int const xbeg = o.x_ - tabular.textHOffset(cell);
5501         int const xend = xbeg + tabular.cellWidth(cell);
5502         row_type const row = tabular.cellRow(cell);
5503         int const ybeg = o.y_ - tabular.rowAscent(row)
5504                 - tabular.interRowSpace(row) - tabular.textVOffset(cell);
5505         int const yend = ybeg + tabular.cellHeight(cell);
5506
5507         if (x < xbeg)
5508                 xx = xbeg - x;
5509         else if (x > xend)
5510                 xx = x - xend;
5511
5512         if (y < ybeg)
5513                 yy = ybeg - y;
5514         else if (y > yend)
5515                 yy = y - yend;
5516
5517         //lyxerr << " xbeg=" << xbeg << "  xend=" << xend
5518         //       << " ybeg=" << ybeg << " yend=" << yend
5519         //       << " xx=" << xx << " yy=" << yy
5520         //       << " dist=" << xx + yy << endl;
5521         return xx + yy;
5522 }
5523
5524
5525 Inset * InsetTabular::editXY(Cursor & cur, int x, int y)
5526 {
5527         //lyxerr << "InsetTabular::editXY: " << this << endl;
5528         cur.push(*this);
5529         cur.idx() = getNearestCell(cur.bv(), x, y);
5530         return cur.bv().textMetrics(&cell(cur.idx())->text()).editXY(cur, x, y);
5531 }
5532
5533
5534 void InsetTabular::setCursorFromCoordinates(Cursor & cur, int x, int y) const
5535 {
5536         cur.idx() = getNearestCell(cur.bv(), x, y);
5537         cur.bv().textMetrics(&cell(cur.idx())->text()).setCursorFromCoordinates(cur, x, y);
5538 }
5539
5540
5541 InsetTabular::idx_type InsetTabular::getNearestCell(BufferView & bv, int x, int y) const
5542 {
5543         idx_type idx_min = 0;
5544         int dist_min = numeric_limits<int>::max();
5545         for (idx_type i = 0, n = nargs(); i != n; ++i) {
5546                 if (bv.coordCache().getInsets().has(tabular.cellInset(i).get())) {
5547                         int const d = dist(bv, i, x, y);
5548                         if (d < dist_min) {
5549                                 dist_min = d;
5550                                 idx_min = i;
5551                         }
5552                 }
5553         }
5554         return idx_min;
5555 }
5556
5557
5558 int InsetTabular::cellYPos(idx_type const cell) const
5559 {
5560         row_type row = tabular.cellRow(cell);
5561         int ly = 0;
5562         for (row_type r = 0; r < row; ++r)
5563                 ly += tabular.rowDescent(r) + tabular.rowAscent(r + 1)
5564                         + tabular.interRowSpace(r + 1);
5565         return ly;
5566 }
5567
5568
5569 int InsetTabular::cellXPos(idx_type const cell) const
5570 {
5571         col_type col = tabular.cellColumn(cell);
5572         int lx = 0;
5573         for (col_type c = 0; c < col; ++c)
5574                 lx += tabular.column_info[c].width;
5575         return lx;
5576 }
5577
5578
5579 void InsetTabular::moveNextCell(Cursor & cur, EntryDirection entry_from)
5580 {
5581         row_type const row = tabular.cellRow(cur.idx());
5582         col_type const col = tabular.cellColumn(cur.idx());
5583
5584         if (isRightToLeft(cur)) {
5585                 if (tabular.cellColumn(cur.idx()) == 0) {
5586                         if (row == tabular.nrows() - 1)
5587                                 return;
5588                         cur.idx() = tabular.cellBelow(tabular.getLastCellInRow(row));
5589                 } else {
5590                         if (cur.idx() == 0)
5591                                 return;
5592                         if (col == 0)
5593                                 cur.idx() = tabular.getLastCellInRow(row - 1);
5594                         else
5595                                 cur.idx() = tabular.cellIndex(row, col - 1);
5596                 }
5597         } else {
5598                 if (tabular.isLastCell(cur.idx()))
5599                         return;
5600                 if (cur.idx() == tabular.getLastCellInRow(row))
5601                         cur.idx() = tabular.cellIndex(row + 1, 0);
5602                 else {
5603                         col_type const colnextcell = col + tabular.columnSpan(cur.idx());
5604                         cur.idx() = tabular.cellIndex(row, colnextcell);
5605                 }
5606         }
5607
5608         cur.boundary(false);
5609
5610         if (cur.selIsMultiCell()) {
5611                 cur.pit() = cur.lastpit();
5612                 cur.pos() = cur.lastpos();
5613                 return;
5614         }
5615
5616         cur.pit() = 0;
5617         cur.pos() = 0;
5618
5619         // in visual mode, place cursor at extreme left or right
5620
5621         switch(entry_from) {
5622
5623         case ENTRY_DIRECTION_RIGHT:
5624                 cur.posVisToRowExtremity(false /* !left */);
5625                 break;
5626         case ENTRY_DIRECTION_LEFT:
5627                 cur.posVisToRowExtremity(true /* left */);
5628                 break;
5629         case ENTRY_DIRECTION_IGNORE:
5630                 // nothing to do in this case
5631                 break;
5632
5633         }
5634         cur.setCurrentFont();
5635 }
5636
5637
5638 void InsetTabular::movePrevCell(Cursor & cur, EntryDirection entry_from)
5639 {
5640         row_type const row = tabular.cellRow(cur.idx());
5641         col_type const col = tabular.cellColumn(cur.idx());
5642
5643         if (isRightToLeft(cur)) {
5644                 if (cur.idx() == tabular.getLastCellInRow(row)) {
5645                         if (row == 0)
5646                                 return;
5647                         cur.idx() = tabular.getFirstCellInRow(row);
5648                         cur.idx() = tabular.cellAbove(cur.idx());
5649                 } else {
5650                         if (tabular.isLastCell(cur.idx()))
5651                                 return;
5652                         if (cur.idx() == tabular.getLastCellInRow(row))
5653                                 cur.idx() = tabular.cellIndex(row + 1, 0);
5654                         else
5655                                 cur.idx() = tabular.cellIndex(row, col + 1);
5656                 }
5657         } else {
5658                 if (cur.idx() == 0) // first cell
5659                         return;
5660                 if (col == 0)
5661                         cur.idx() = tabular.getLastCellInRow(row - 1);
5662                 else
5663                         cur.idx() = tabular.cellIndex(row, col - 1);
5664         }
5665
5666         if (cur.selIsMultiCell()) {
5667                 cur.pit() = cur.lastpit();
5668                 cur.pos() = cur.lastpos();
5669                 return;
5670         }
5671
5672         cur.pit() = cur.lastpit();
5673         cur.pos() = cur.lastpos();
5674
5675         // in visual mode, place cursor at extreme left or right
5676
5677         switch(entry_from) {
5678
5679         case ENTRY_DIRECTION_RIGHT:
5680                 cur.posVisToRowExtremity(false /* !left */);
5681                 break;
5682         case ENTRY_DIRECTION_LEFT:
5683                 cur.posVisToRowExtremity(true /* left */);
5684                 break;
5685         case ENTRY_DIRECTION_IGNORE:
5686                 // nothing to do in this case
5687                 break;
5688
5689         }
5690         cur.setCurrentFont();
5691 }
5692
5693
5694 void InsetTabular::tabularFeatures(Cursor & cur, string const & argument)
5695 {
5696         cur.recordUndoInset(this);
5697
5698         istringstream is(argument);
5699         // limit the size of strings we read to avoid memory problems
5700         is >> setw(65636);
5701         string s;
5702         // Safe guard.
5703         size_t safe_guard = 0;
5704         for (;;) {
5705                 if (is.eof())
5706                         return;
5707                 safe_guard++;
5708                 if (safe_guard > 1000) {
5709                         LYXERR0("parameter max count reached!");
5710                         return;
5711                 }
5712                 is >> s;
5713                 Tabular::Feature action = Tabular::LAST_ACTION;
5714
5715                 size_t i = 0;
5716                 for (; tabularFeature[i].action != Tabular::LAST_ACTION; ++i) {
5717                         if (s != tabularFeature[i].feature)
5718                                 continue;
5719                         action = tabularFeature[i].action;
5720                         break;
5721                 }
5722                 if (action == Tabular::LAST_ACTION) {
5723                         LYXERR0("Feature not found " << s);
5724                         continue;
5725                 }
5726                 string val;
5727                 if (tabularFeature[i].need_value)
5728                         is >> val;
5729                 LYXERR(Debug::DEBUG, "Feature: " << s << "\t\tvalue: " << val);
5730                 tabularFeatures(cur, action, val);
5731         }
5732 }
5733
5734
5735 static void checkLongtableSpecial(Tabular::ltType & ltt,
5736                           string const & special, bool & flag)
5737 {
5738         if (special == "dl_above") {
5739                 ltt.topDL = flag;
5740                 ltt.set = false;
5741         } else if (special == "dl_below") {
5742                 ltt.bottomDL = flag;
5743                 ltt.set = false;
5744         } else if (special == "empty") {
5745                 ltt.empty = flag;
5746                 ltt.set = false;
5747         } else if (flag) {
5748                 ltt.empty = false;
5749                 ltt.set = true;
5750         }
5751 }
5752
5753
5754 bool InsetTabular::oneCellHasRotationState(bool rotated,
5755                 row_type row_start, row_type row_end,
5756                 col_type col_start, col_type col_end) const
5757 {
5758         for (row_type r = row_start; r <= row_end; ++r)
5759                 for (col_type c = col_start; c <= col_end; ++c)
5760                         if (rotated) {
5761                                 if (tabular.getRotateCell(tabular.cellIndex(r, c)) != 0)
5762                                         return true;
5763                         } else {
5764                                 if (tabular.getRotateCell(tabular.cellIndex(r, c)) == 0)
5765                                         return true;
5766                         }
5767         return false;
5768 }
5769
5770
5771 void InsetTabular::tabularFeatures(Cursor & cur,
5772         Tabular::Feature feature, string const & value)
5773 {
5774         col_type sel_col_start;
5775         col_type sel_col_end;
5776         row_type sel_row_start;
5777         row_type sel_row_end;
5778         bool setLines = false;
5779         LyXAlignment setAlign = LYX_ALIGN_LEFT;
5780         Tabular::VAlignment setVAlign = Tabular::LYX_VALIGN_TOP;
5781
5782         switch (feature) {
5783
5784         case Tabular::M_ALIGN_LEFT:
5785         case Tabular::ALIGN_LEFT:
5786                 setAlign = LYX_ALIGN_LEFT;
5787                 break;
5788
5789         case Tabular::M_ALIGN_RIGHT:
5790         case Tabular::ALIGN_RIGHT:
5791                 setAlign = LYX_ALIGN_RIGHT;
5792                 break;
5793
5794         case Tabular::M_ALIGN_CENTER:
5795         case Tabular::ALIGN_CENTER:
5796                 setAlign = LYX_ALIGN_CENTER;
5797                 break;
5798
5799         case Tabular::ALIGN_BLOCK:
5800                 setAlign = LYX_ALIGN_BLOCK;
5801                 break;
5802
5803         case Tabular::ALIGN_DECIMAL:
5804                 setAlign = LYX_ALIGN_DECIMAL;
5805                 break;
5806
5807         case Tabular::M_VALIGN_TOP:
5808         case Tabular::VALIGN_TOP:
5809                 setVAlign = Tabular::LYX_VALIGN_TOP;
5810                 break;
5811
5812         case Tabular::M_VALIGN_BOTTOM:
5813         case Tabular::VALIGN_BOTTOM:
5814                 setVAlign = Tabular::LYX_VALIGN_BOTTOM;
5815                 break;
5816
5817         case Tabular::M_VALIGN_MIDDLE:
5818         case Tabular::VALIGN_MIDDLE:
5819                 setVAlign = Tabular::LYX_VALIGN_MIDDLE;
5820                 break;
5821
5822         default:
5823                 break;
5824         }
5825
5826         getSelection(cur, sel_row_start, sel_row_end, sel_col_start, sel_col_end);
5827         row_type const row = tabular.cellRow(cur.idx());
5828         col_type const column = tabular.cellColumn(cur.idx());
5829         bool flag = true;
5830         Tabular::ltType ltt;
5831
5832         switch (feature) {
5833
5834         case Tabular::SET_TABULAR_WIDTH:
5835                 tabular.setTabularWidth(Length(value));
5836                 break;
5837
5838         case Tabular::SET_PWIDTH: {
5839                 Length const len(value);
5840                 tabular.setColumnPWidth(cur, cur.idx(), len);
5841                 if (len.zero()
5842                     && tabular.getAlignment(cur.idx(), true) == LYX_ALIGN_BLOCK)
5843                         tabularFeatures(cur, Tabular::ALIGN_CENTER, string());
5844                 break;
5845         }
5846
5847         case Tabular::SET_MPWIDTH:
5848                 tabular.setMColumnPWidth(cur, cur.idx(), Length(value));
5849                 break;
5850
5851         case Tabular::TOGGLE_VARWIDTH_COLUMN: {
5852                 bool const varwidth = value == "on";
5853                 tabular.toggleVarwidth(cur.idx(), varwidth);
5854                 break;
5855         }
5856
5857         case Tabular::SET_MROFFSET:
5858                 tabular.setMROffset(cur, cur.idx(), Length(value));
5859                 break;
5860
5861         case Tabular::SET_SPECIAL_COLUMN:
5862         case Tabular::SET_SPECIAL_MULTICOLUMN:
5863                 if (value == "none")
5864                         tabular.setAlignSpecial(cur.idx(), docstring(), feature);
5865                 else
5866                         tabular.setAlignSpecial(cur.idx(), from_utf8(value), feature);
5867                 break;
5868
5869         case Tabular::APPEND_ROW:
5870                 // append the row into the tabular
5871                 tabular.appendRow(row);
5872                 break;
5873
5874         case Tabular::APPEND_COLUMN:
5875                 // append the column into the tabular
5876                 tabular.appendColumn(column);
5877                 cur.idx() = tabular.cellIndex(row, column);
5878                 break;
5879
5880         case Tabular::DELETE_ROW:
5881                 if (sel_row_end == tabular.nrows() - 1 && sel_row_start != 0) {
5882                         for (col_type c = 0; c < tabular.ncols(); c++)
5883                                 tabular.setBottomLine(tabular.cellIndex(sel_row_start - 1, c),
5884                                         tabular.bottomLine(tabular.cellIndex(sel_row_end, c)));
5885                 }
5886
5887                 for (row_type r = sel_row_start; r <= sel_row_end; ++r)
5888                         tabular.deleteRow(sel_row_start);
5889                 if (sel_row_start >= tabular.nrows())
5890                         --sel_row_start;
5891                 cur.idx() = tabular.cellIndex(sel_row_start, column);
5892                 cur.pit() = 0;
5893                 cur.pos() = 0;
5894                 cur.selection(false);
5895                 break;
5896
5897         case Tabular::DELETE_COLUMN:
5898                 if (sel_col_end == tabular.ncols() - 1 && sel_col_start != 0) {
5899                         for (row_type r = 0; r < tabular.nrows(); r++)
5900                                 tabular.setRightLine(tabular.cellIndex(r, sel_col_start - 1),
5901                                         tabular.rightLine(tabular.cellIndex(r, sel_col_end)));
5902                 }
5903
5904                 if (sel_col_start == 0 && sel_col_end != tabular.ncols() - 1) {
5905                         for (row_type r = 0; r < tabular.nrows(); r++)
5906                                 tabular.setLeftLine(tabular.cellIndex(r, sel_col_end + 1),
5907                                         tabular.leftLine(tabular.cellIndex(r, 0)));
5908                 }
5909
5910                 for (col_type c = sel_col_start; c <= sel_col_end; ++c)
5911                         tabular.deleteColumn(sel_col_start);
5912                 if (sel_col_start >= tabular.ncols())
5913                         --sel_col_start;
5914                 cur.idx() = tabular.cellIndex(row, sel_col_start);
5915                 cur.pit() = 0;
5916                 cur.pos() = 0;
5917                 cur.selection(false);
5918                 break;
5919
5920         case Tabular::COPY_ROW:
5921                 tabular.copyRow(row);
5922                 break;
5923
5924         case Tabular::COPY_COLUMN:
5925                 tabular.copyColumn(column);
5926                 cur.idx() = tabular.cellIndex(row, column);
5927                 break;
5928
5929         case Tabular::MOVE_COLUMN_RIGHT:
5930                 tabular.moveColumn(column, Tabular::RIGHT);
5931                 cur.idx() = tabular.cellIndex(row, column + 1);
5932                 break;
5933
5934         case Tabular::MOVE_COLUMN_LEFT:
5935                 tabular.moveColumn(column, Tabular::LEFT);
5936                 cur.idx() = tabular.cellIndex(row, column - 1);
5937                 break;
5938
5939         case Tabular::MOVE_ROW_DOWN:
5940                 tabular.moveRow(row, Tabular::DOWN);
5941                 cur.idx() = tabular.cellIndex(row + 1, column);
5942                 break;
5943
5944         case Tabular::MOVE_ROW_UP:
5945                 tabular.moveRow(row, Tabular::UP);
5946                 cur.idx() = tabular.cellIndex(row - 1, column);
5947                 break;
5948
5949         case Tabular::SET_LINE_TOP:
5950         case Tabular::TOGGLE_LINE_TOP: {
5951                 bool lineSet = (feature == Tabular::SET_LINE_TOP)
5952                                ? (value == "true") : !tabular.topLine(cur.idx());
5953                 for (row_type r = sel_row_start; r <= sel_row_end; ++r)
5954                         for (col_type c = sel_col_start; c <= sel_col_end; ++c)
5955                                 tabular.setTopLine(tabular.cellIndex(r, c), lineSet);
5956                 break;
5957         }
5958
5959         case Tabular::SET_LINE_BOTTOM:
5960         case Tabular::TOGGLE_LINE_BOTTOM: {
5961                 bool lineSet = (feature == Tabular::SET_LINE_BOTTOM)
5962                                ? (value == "true") : !tabular.bottomLine(cur.idx());
5963                 for (row_type r = sel_row_start; r <= sel_row_end; ++r)
5964                         for (col_type c = sel_col_start; c <= sel_col_end; ++c)
5965                                 tabular.setBottomLine(tabular.cellIndex(r, c), lineSet);
5966                 break;
5967         }
5968
5969         case Tabular::SET_LINE_LEFT:
5970         case Tabular::TOGGLE_LINE_LEFT: {
5971                 bool lineSet = (feature == Tabular::SET_LINE_LEFT)
5972                                ? (value == "true") : !tabular.leftLine(cur.idx());
5973                 for (row_type r = sel_row_start; r <= sel_row_end; ++r)
5974                         for (col_type c = sel_col_start; c <= sel_col_end; ++c)
5975                                 tabular.setLeftLine(tabular.cellIndex(r, c), lineSet);
5976                 break;
5977         }
5978
5979         case Tabular::SET_LINE_RIGHT:
5980         case Tabular::TOGGLE_LINE_RIGHT: {
5981                 bool lineSet = (feature == Tabular::SET_LINE_RIGHT)
5982                                ? (value == "true") : !tabular.rightLine(cur.idx());
5983                 for (row_type r = sel_row_start; r <= sel_row_end; ++r)
5984                         for (col_type c = sel_col_start; c <= sel_col_end; ++c)
5985                                 tabular.setRightLine(tabular.cellIndex(r, c), lineSet);
5986                 break;
5987         }
5988
5989         case Tabular::M_ALIGN_LEFT:
5990         case Tabular::M_ALIGN_RIGHT:
5991         case Tabular::M_ALIGN_CENTER:
5992         case Tabular::ALIGN_LEFT:
5993         case Tabular::ALIGN_RIGHT:
5994         case Tabular::ALIGN_CENTER:
5995         case Tabular::ALIGN_BLOCK:
5996         case Tabular::ALIGN_DECIMAL:
5997                 for (row_type r = sel_row_start; r <= sel_row_end; ++r)
5998                         for (col_type c = sel_col_start; c <= sel_col_end; ++c)
5999                                 tabular.setAlignment(tabular.cellIndex(r, c), setAlign,
6000                                 !tabular.getPWidth(c).zero());
6001                 break;
6002
6003         case Tabular::M_VALIGN_TOP:
6004         case Tabular::M_VALIGN_BOTTOM:
6005         case Tabular::M_VALIGN_MIDDLE:
6006                 flag = false;
6007                 // fall through
6008         case Tabular::VALIGN_TOP:
6009         case Tabular::VALIGN_BOTTOM:
6010         case Tabular::VALIGN_MIDDLE:
6011                 for (row_type r = sel_row_start; r <= sel_row_end; ++r)
6012                         for (col_type c = sel_col_start; c <= sel_col_end; ++c)
6013                                 tabular.setVAlignment(tabular.cellIndex(r, c), setVAlign, flag);
6014                 break;
6015
6016         case Tabular::SET_MULTICOLUMN: {
6017                 if (!cur.selection()) {
6018                         // just multicol for one single cell
6019                         // check whether we are completely in a multicol
6020                         if (!tabular.isMultiColumn(cur.idx()))
6021                                 tabular.setMultiColumn(cur, cur.idx(), 1,
6022                                         tabular.rightLine(cur.idx()));
6023                         break;
6024                 }
6025                 // we have a selection so this means we just add all these
6026                 // cells to form a multicolumn cell
6027                 idx_type const s_start = cur.selBegin().idx();
6028                 row_type const col_start = tabular.cellColumn(s_start);
6029                 row_type const col_end = tabular.cellColumn(cur.selEnd().idx());
6030                 cur.idx() = tabular.setMultiColumn(cur, s_start, col_end - col_start + 1,
6031                                                    tabular.rightLine(cur.selEnd().idx()));
6032                 cur.pit() = 0;
6033                 cur.pos() = 0;
6034                 cur.selection(false);
6035                 break;
6036         }
6037
6038         case Tabular::UNSET_MULTICOLUMN: {
6039                 if (!cur.selection()) {
6040                         if (tabular.isMultiColumn(cur.idx()))
6041                                 tabular.unsetMultiColumn(cur.idx());
6042                 }
6043                 break;
6044         }
6045
6046         case Tabular::MULTICOLUMN: {
6047                 if (!cur.selection()) {
6048                         if (tabular.isMultiColumn(cur.idx()))
6049                                 tabularFeatures(cur, Tabular::UNSET_MULTICOLUMN);
6050                         else
6051                                 tabularFeatures(cur, Tabular::SET_MULTICOLUMN);
6052                         break;
6053                 }
6054                 bool merge = false;
6055                 for (col_type c = sel_col_start; c <= sel_col_end; ++c) {
6056                         row_type const r = sel_row_start;
6057                         if (!tabular.isMultiColumn(tabular.cellIndex(r, c))
6058                             || (r > sel_row_start && !tabular.isPartOfMultiColumn(r, c)))
6059                                 merge = true;
6060                 }
6061                 // If the selection contains at least one singlecol cell
6062                 // or multiple multicol cells,
6063                 // we assume the user will merge is to a single multicol
6064                 if (merge)
6065                         tabularFeatures(cur, Tabular::SET_MULTICOLUMN);
6066                 else
6067                         tabularFeatures(cur, Tabular::UNSET_MULTICOLUMN);
6068                 break;
6069         }
6070
6071         case Tabular::SET_MULTIROW: {
6072                 if (!cur.selection()) {
6073                         // just multirow for one single cell
6074                         // check whether we are completely in a multirow
6075                         if (!tabular.isMultiRow(cur.idx()))
6076                                 tabular.setMultiRow(cur, cur.idx(), 1,
6077                                                     tabular.bottomLine(cur.idx()),
6078                                                     tabular.getAlignment(cur.idx()));
6079                         break;
6080                 }
6081                 // we have a selection so this means we just add all this
6082                 // cells to form a multirow cell
6083                 idx_type const s_start = cur.selBegin().idx();
6084                 row_type const row_start = tabular.cellRow(s_start);
6085                 row_type const row_end = tabular.cellRow(cur.selEnd().idx());
6086                 cur.idx() = tabular.setMultiRow(cur, s_start, row_end - row_start + 1,
6087                                                 tabular.bottomLine(cur.selEnd().idx()),
6088                                                 tabular.getAlignment(cur.selEnd().idx()));
6089                 cur.pit() = 0;
6090                 cur.pos() = 0;
6091                 cur.selection(false);
6092                 break;
6093         }
6094
6095         case Tabular::UNSET_MULTIROW: {
6096                 if (!cur.selection()) {
6097                         if (tabular.isMultiRow(cur.idx()))
6098                                 tabular.unsetMultiRow(cur.idx());
6099                 }
6100                 break;
6101         }
6102
6103         case Tabular::MULTIROW: {
6104                 if (!cur.selection()) {
6105                         if (tabular.isMultiRow(cur.idx()))
6106                                 tabularFeatures(cur, Tabular::UNSET_MULTIROW);
6107                         else
6108                                 tabularFeatures(cur, Tabular::SET_MULTIROW);
6109                         break;
6110                 }
6111                 bool merge = false;
6112                 for (row_type r = sel_row_start; r <= sel_row_end; ++r) {
6113                         col_type const c = sel_col_start;
6114                         if (!tabular.isMultiRow(tabular.cellIndex(r, c))
6115                             || (r > sel_row_start && !tabular.isPartOfMultiRow(r, c)))
6116                                 merge = true;
6117                 }
6118                 // If the selection contains at least one singlerow cell
6119                 // or multiple multirow cells,
6120                 // we assume the user will merge is to a single multirow
6121                 if (merge)
6122                         tabularFeatures(cur, Tabular::SET_MULTIROW);
6123                 else
6124                         tabularFeatures(cur, Tabular::UNSET_MULTIROW);
6125                 break;
6126         }
6127
6128         case Tabular::SET_ALL_LINES:
6129                 setLines = true;
6130                 // fall through
6131         case Tabular::UNSET_ALL_LINES:
6132                 for (row_type r = sel_row_start; r <= sel_row_end; ++r)
6133                         for (col_type c = sel_col_start; c <= sel_col_end; ++c) {
6134                                 idx_type const cell = tabular.cellIndex(r, c);
6135                                 tabular.setTopLine(cell, setLines);
6136                                 tabular.setBottomLine(cell, setLines);
6137                                 tabular.setRightLine(cell, setLines);
6138                                 tabular.setLeftLine(cell, setLines);
6139                         }
6140                 break;
6141
6142         case Tabular::SET_BORDER_LINES:
6143                 for (row_type r = sel_row_start; r <= sel_row_end; ++r) {
6144                         tabular.setLeftLine(tabular.cellIndex(r, sel_col_start), true);
6145                         tabular.setRightLine(tabular.cellIndex(r, sel_col_end), true);
6146                 }
6147                 for (col_type c = sel_col_start; c <= sel_col_end; ++c) {
6148                         tabular.setTopLine(tabular.cellIndex(sel_row_start, c), true);
6149                         tabular.setBottomLine(tabular.cellIndex(sel_row_end, c), true);
6150                 }
6151                 break;
6152
6153         case Tabular::TOGGLE_LONGTABULAR:
6154                 if (tabular.is_long_tabular)
6155                         tabularFeatures(cur, Tabular::UNSET_LONGTABULAR);
6156                 else
6157                         tabular.is_long_tabular = true;
6158                 break;
6159
6160         case Tabular::SET_LONGTABULAR:
6161                 tabular.is_long_tabular = true;
6162                 break;
6163
6164         case Tabular::UNSET_LONGTABULAR:
6165                 for (row_type r = 0; r < tabular.nrows(); ++r) {
6166                         if (tabular.ltCaption(r)) {
6167                                 cur.idx() = tabular.cellIndex(r, 0);
6168                                 cur.pit() = 0;
6169                                 cur.pos() = 0;
6170                                 tabularFeatures(cur, Tabular::TOGGLE_LTCAPTION);
6171                         }
6172                 }
6173                 tabular.is_long_tabular = false;
6174                 break;
6175
6176         case Tabular::SET_ROTATE_TABULAR:
6177                 tabular.rotate = convert<int>(value);
6178                 break;
6179
6180         case Tabular::UNSET_ROTATE_TABULAR:
6181                 tabular.rotate = 0;
6182                 break;
6183
6184         case Tabular::TOGGLE_ROTATE_TABULAR:
6185                 // when pressing the rotate button we default to 90° rotation
6186                 tabular.rotate != 0 ? tabular.rotate = 0 : tabular.rotate = 90;
6187                 break;
6188
6189         case Tabular::TABULAR_VALIGN_TOP:
6190                 tabular.tabular_valignment = Tabular::LYX_VALIGN_TOP;
6191                 break;
6192
6193         case Tabular::TABULAR_VALIGN_MIDDLE:
6194                 tabular.tabular_valignment = Tabular::LYX_VALIGN_MIDDLE;
6195                 break;
6196
6197         case Tabular::TABULAR_VALIGN_BOTTOM:
6198                 tabular.tabular_valignment = Tabular::LYX_VALIGN_BOTTOM;
6199                 break;
6200
6201         case Tabular::LONGTABULAR_ALIGN_LEFT:
6202                 tabular.longtabular_alignment = Tabular::LYX_LONGTABULAR_ALIGN_LEFT;
6203                 break;
6204
6205         case Tabular::LONGTABULAR_ALIGN_CENTER:
6206                 tabular.longtabular_alignment = Tabular::LYX_LONGTABULAR_ALIGN_CENTER;
6207                 break;
6208
6209         case Tabular::LONGTABULAR_ALIGN_RIGHT:
6210                 tabular.longtabular_alignment = Tabular::LYX_LONGTABULAR_ALIGN_RIGHT;
6211                 break;
6212
6213
6214
6215         case Tabular::SET_ROTATE_CELL:
6216                 for (row_type r = sel_row_start; r <= sel_row_end; ++r)
6217                         for (col_type c = sel_col_start; c <= sel_col_end; ++c)
6218                                 tabular.setRotateCell(tabular.cellIndex(r, c), convert<int>(value));
6219                 break;
6220
6221         case Tabular::UNSET_ROTATE_CELL:
6222                 for (row_type r = sel_row_start; r <= sel_row_end; ++r)
6223                         for (col_type c = sel_col_start; c <= sel_col_end; ++c)
6224                                 tabular.setRotateCell(tabular.cellIndex(r, c), 0);
6225                 break;
6226
6227         case Tabular::TOGGLE_ROTATE_CELL:
6228                 {
6229                 bool oneNotRotated = oneCellHasRotationState(false,
6230                         sel_row_start, sel_row_end, sel_col_start, sel_col_end);
6231
6232                 for (row_type r = sel_row_start; r <= sel_row_end; ++r)
6233                         for (col_type c = sel_col_start; c <= sel_col_end; ++c) {
6234                                 // when pressing the rotate cell button we default to 90° rotation
6235                                 if (oneNotRotated)
6236                                         tabular.setRotateCell(tabular.cellIndex(r, c), 90);
6237                                 else
6238                                         tabular.setRotateCell(tabular.cellIndex(r, c), 0);
6239                         }
6240                 }
6241                 break;
6242
6243         case Tabular::SET_USEBOX: {
6244                 Tabular::BoxType val = Tabular::BoxType(convert<int>(value));
6245                 if (val == tabular.getUsebox(cur.idx()))
6246                         val = Tabular::BOX_NONE;
6247                 for (row_type r = sel_row_start; r <= sel_row_end; ++r)
6248                         for (col_type c = sel_col_start; c <= sel_col_end; ++c)
6249                                 tabular.setUsebox(tabular.cellIndex(r, c), val);
6250                 break;
6251         }
6252
6253         case Tabular::UNSET_LTFIRSTHEAD:
6254                 flag = false;
6255                 // fall through
6256         case Tabular::SET_LTFIRSTHEAD:
6257                 tabular.getRowOfLTFirstHead(row, ltt);
6258                 checkLongtableSpecial(ltt, value, flag);
6259                 tabular.setLTHead(row, flag, ltt, true);
6260                 break;
6261
6262         case Tabular::UNSET_LTHEAD:
6263                 flag = false;
6264                 // fall through
6265         case Tabular::SET_LTHEAD:
6266                 tabular.getRowOfLTHead(row, ltt);
6267                 checkLongtableSpecial(ltt, value, flag);
6268                 tabular.setLTHead(row, flag, ltt, false);
6269                 break;
6270
6271         case Tabular::UNSET_LTFOOT:
6272                 flag = false;
6273                 // fall through
6274         case Tabular::SET_LTFOOT:
6275                 tabular.getRowOfLTFoot(row, ltt);
6276                 checkLongtableSpecial(ltt, value, flag);
6277                 tabular.setLTFoot(row, flag, ltt, false);
6278                 break;
6279
6280         case Tabular::UNSET_LTLASTFOOT:
6281                 flag = false;
6282                 // fall through
6283         case Tabular::SET_LTLASTFOOT:
6284                 tabular.getRowOfLTLastFoot(row, ltt);
6285                 checkLongtableSpecial(ltt, value, flag);
6286                 tabular.setLTFoot(row, flag, ltt, true);
6287                 break;
6288
6289         case Tabular::UNSET_LTNEWPAGE:
6290                 flag = false;
6291                 // fall through
6292         case Tabular::SET_LTNEWPAGE:
6293                 tabular.setLTNewPage(row, flag);
6294                 break;
6295
6296         case Tabular::SET_LTCAPTION: {
6297                 if (tabular.ltCaption(row))
6298                         break;
6299                 cur.idx() = tabular.setLTCaption(cur, row, true);
6300                 cur.pit() = 0;
6301                 cur.pos() = 0;
6302                 cur.selection(false);
6303                 // If a row is set as caption, then also insert
6304                 // a caption. Otherwise the LaTeX output is broken.
6305                 // Select cell if it is non-empty
6306                 if (cur.lastpos() > 0 || cur.lastpit() > 0)
6307                         lyx::dispatch(FuncRequest(LFUN_INSET_SELECT_ALL));
6308                 lyx::dispatch(FuncRequest(LFUN_CAPTION_INSERT));
6309                 break;
6310         }
6311
6312         case Tabular::UNSET_LTCAPTION: {
6313                 if (!tabular.ltCaption(row))
6314                         break;
6315                 cur.idx() = tabular.setLTCaption(cur, row, false);
6316                 cur.pit() = 0;
6317                 cur.pos() = 0;
6318                 cur.selection(false);
6319                 FuncRequest fr(LFUN_INSET_DISSOLVE, "caption");
6320                 if (lyx::getStatus(fr).enabled())
6321                         lyx::dispatch(fr);
6322                 break;
6323         }
6324
6325         case Tabular::TOGGLE_LTCAPTION: {
6326                 if (tabular.ltCaption(row))
6327                         tabularFeatures(cur, Tabular::UNSET_LTCAPTION);
6328                 else
6329                         tabularFeatures(cur, Tabular::SET_LTCAPTION);
6330                 break;
6331         }
6332
6333         case Tabular::TOGGLE_BOOKTABS:
6334                 tabular.use_booktabs = !tabular.use_booktabs;
6335                 break;
6336
6337         case Tabular::SET_BOOKTABS:
6338                 tabular.use_booktabs = true;
6339                 break;
6340
6341         case Tabular::UNSET_BOOKTABS:
6342                 tabular.use_booktabs = false;
6343                 break;
6344
6345         case Tabular::SET_TOP_SPACE: {
6346                 Length len;
6347                 if (value == "default")
6348                         for (row_type r = sel_row_start; r <= sel_row_end; ++r)
6349                                 tabular.row_info[r].top_space_default = true;
6350                 else if (value == "none")
6351                         for (row_type r = sel_row_start; r <= sel_row_end; ++r) {
6352                                 tabular.row_info[r].top_space_default = false;
6353                                 tabular.row_info[r].top_space = len;
6354                         }
6355                 else if (isValidLength(value, &len))
6356                         for (row_type r = sel_row_start; r <= sel_row_end; ++r) {
6357                                 tabular.row_info[r].top_space_default = false;
6358                                 tabular.row_info[r].top_space = len;
6359                         }
6360                 break;
6361         }
6362
6363         case Tabular::SET_BOTTOM_SPACE: {
6364                 Length len;
6365                 if (value == "default")
6366                         for (row_type r = sel_row_start; r <= sel_row_end; ++r)
6367                                 tabular.row_info[r].bottom_space_default = true;
6368                 else if (value == "none")
6369                         for (row_type r = sel_row_start; r <= sel_row_end; ++r) {
6370                                 tabular.row_info[r].bottom_space_default = false;
6371                                 tabular.row_info[r].bottom_space = len;
6372                         }
6373                 else if (isValidLength(value, &len))
6374                         for (row_type r = sel_row_start; r <= sel_row_end; ++r) {
6375                                 tabular.row_info[r].bottom_space_default = false;
6376                                 tabular.row_info[r].bottom_space = len;
6377                         }
6378                 break;
6379         }
6380
6381         case Tabular::SET_INTERLINE_SPACE: {
6382                 Length len;
6383                 if (value == "default")
6384                         for (row_type r = sel_row_start; r <= sel_row_end; ++r)
6385                                 tabular.row_info[r].interline_space_default = true;
6386                 else if (value == "none")
6387                         for (row_type r = sel_row_start; r <= sel_row_end; ++r) {
6388                                 tabular.row_info[r].interline_space_default = false;
6389                                 tabular.row_info[r].interline_space = len;
6390                         }
6391                 else if (isValidLength(value, &len))
6392                         for (row_type r = sel_row_start; r <= sel_row_end; ++r) {
6393                                 tabular.row_info[r].interline_space_default = false;
6394                                 tabular.row_info[r].interline_space = len;
6395                         }
6396                 break;
6397         }
6398
6399         case Tabular::SET_DECIMAL_POINT:
6400                 for (col_type c = sel_col_start; c <= sel_col_end; ++c)
6401                         tabular.column_info[c].decimal_point = from_utf8(value);
6402                 break;
6403
6404         // dummy stuff just to avoid warnings
6405         case Tabular::LAST_ACTION:
6406                 break;
6407         }
6408 }
6409
6410
6411 bool InsetTabular::copySelection(Cursor & cur)
6412 {
6413         if (!cur.selection())
6414                 return false;
6415
6416         row_type rs, re;
6417         col_type cs, ce;
6418         getSelection(cur, rs, re, cs, ce);
6419
6420         paste_tabular.reset(new Tabular(tabular));
6421
6422         for (row_type r = 0; r < rs; ++r)
6423                 paste_tabular->deleteRow(0);
6424
6425         row_type const rows = re - rs + 1;
6426         while (paste_tabular->nrows() > rows)
6427                 paste_tabular->deleteRow(rows);
6428
6429         for (col_type c = 0; c < cs; ++c)
6430                 paste_tabular->deleteColumn(0);
6431
6432         col_type const columns = ce - cs + 1;
6433         while (paste_tabular->ncols() > columns)
6434                 paste_tabular->deleteColumn(columns);
6435
6436         paste_tabular->setBuffer(tabular.buffer());
6437
6438         odocstringstream os;
6439         OutputParams const runparams(0);
6440         paste_tabular->plaintext(os, runparams, 0, true, '\t', INT_MAX);
6441         // Needed for the "Edit->Paste recent" menu and the system clipboard.
6442         cap::copySelection(cur, os.str());
6443
6444         // mark tabular stack dirty
6445         // FIXME: this is a workaround for bug 1919. Should be removed for 1.5,
6446         // when we (hopefully) have a one-for-all paste mechanism.
6447         // This must be called after cap::copySelection.
6448         dirtyTabularStack(true);
6449
6450         return true;
6451 }
6452
6453
6454 bool InsetTabular::pasteClipboard(Cursor & cur)
6455 {
6456         if (!paste_tabular)
6457                 return false;
6458         col_type actcol = tabular.cellColumn(cur.idx());
6459         row_type actrow = tabular.cellRow(cur.idx());
6460
6461         if (cur.selIsMultiCell()) {
6462                 row_type re;
6463                 col_type ce;
6464                 getSelection(cur, actrow, re, actcol, ce);
6465         }
6466
6467         for (row_type r1 = 0, r2 = actrow;
6468              r1 < paste_tabular->nrows() && r2 < tabular.nrows();
6469              ++r1, ++r2) {
6470                 for (col_type c1 = 0, c2 = actcol;
6471                     c1 < paste_tabular->ncols() && c2 < tabular.ncols();
6472                     ++c1, ++c2) {
6473                         if (paste_tabular->isPartOfMultiColumn(r1, c1) &&
6474                               tabular.isPartOfMultiColumn(r2, c2))
6475                                 continue;
6476                         if (paste_tabular->isPartOfMultiColumn(r1, c1)) {
6477                                 --c2;
6478                                 continue;
6479                         }
6480                         if (tabular.isPartOfMultiColumn(r2, c2)) {
6481                                 --c1;
6482                                 continue;
6483                         }
6484                         shared_ptr<InsetTableCell> inset(
6485                                 new InsetTableCell(*paste_tabular->cellInset(r1, c1)));
6486                         tabular.setCellInset(r2, c2, inset);
6487                         // FIXME?: why do we need to do this explicitly? (EL)
6488                         tabular.cellInset(r2, c2)->setBuffer(tabular.buffer());
6489
6490                         // FIXME: change tracking (MG)
6491                         inset->setChange(Change(buffer().params().track_changes ?
6492                                                 Change::INSERTED : Change::UNCHANGED));
6493                         cur.pos() = 0;
6494                         cur.pit() = 0;
6495                 }
6496         }
6497         return true;
6498 }
6499
6500
6501 void InsetTabular::cutSelection(Cursor & cur)
6502 {
6503         if (!cur.selection())
6504                 return;
6505
6506         row_type rs, re;
6507         col_type cs, ce;
6508         getSelection(cur, rs, re, cs, ce);
6509         for (row_type r = rs; r <= re; ++r) {
6510                 for (col_type c = cs; c <= ce; ++c) {
6511                         shared_ptr<InsetTableCell> t
6512                                 = cell(tabular.cellIndex(r, c));
6513                         if (buffer().params().track_changes)
6514                                 // FIXME: Change tracking (MG)
6515                                 t->setChange(Change(Change::DELETED));
6516                         else
6517                                 t->clear();
6518                 }
6519         }
6520
6521         // cursor position might be invalid now
6522         if (cur.pit() > cur.lastpit())
6523                 cur.pit() = cur.lastpit();
6524         if (cur.pos() > cur.lastpos())
6525                 cur.pos() = cur.lastpos();
6526         cur.clearSelection();
6527 }
6528
6529
6530 bool InsetTabular::isRightToLeft(Cursor & cur) const
6531 {
6532         // LASSERT: It might be better to abandon this Buffer.
6533         LASSERT(cur.depth() > 1, return false);
6534         Paragraph const & parentpar = cur[cur.depth() - 2].paragraph();
6535         pos_type const parentpos = cur[cur.depth() - 2].pos();
6536         return parentpar.getFontSettings(buffer().params(),
6537                                          parentpos).language()->rightToLeft();
6538 }
6539
6540
6541 docstring InsetTabular::asString(idx_type stidx, idx_type enidx,
6542                                  bool intoInsets)
6543 {
6544         LASSERT(stidx <= enidx, return docstring());
6545         docstring retval;
6546         col_type const col1 = tabular.cellColumn(stidx);
6547         col_type const col2 = tabular.cellColumn(enidx);
6548         row_type const row1 = tabular.cellRow(stidx);
6549         row_type const row2 = tabular.cellRow(enidx);
6550         bool first = true;
6551         for (col_type col = col1; col <= col2; col++)
6552                 for (row_type row = row1; row <= row2; row++) {
6553                         if (!first)
6554                                 retval += "\n";
6555                         else
6556                                 first = false;
6557                         retval += tabular.cellInset(row, col)->asString(intoInsets);
6558                 }
6559         return retval;
6560 }
6561
6562
6563 void InsetTabular::getSelection(Cursor & cur,
6564         row_type & rs, row_type & re, col_type & cs, col_type & ce) const
6565 {
6566         CursorSlice const & beg = cur.selBegin();
6567         CursorSlice const & end = cur.selEnd();
6568         cs = tabular.cellColumn(beg.idx());
6569         ce = tabular.cellColumn(end.idx());
6570         if (cs > ce)
6571                 swap(cs, ce);
6572
6573         rs = tabular.cellRow(beg.idx());
6574         re = tabular.cellRow(end.idx());
6575         if (rs > re)
6576                 swap(rs, re);
6577 }
6578
6579
6580 Text * InsetTabular::getText(int idx) const
6581 {
6582         return size_t(idx) < nargs() ? cell(idx)->getText(0) : 0;
6583 }
6584
6585
6586 void InsetTabular::setChange(Change const & change)
6587 {
6588         for (idx_type idx = 0; idx < nargs(); ++idx)
6589                 cell(idx)->setChange(change);
6590 }
6591
6592
6593 void InsetTabular::acceptChanges()
6594 {
6595         for (idx_type idx = 0; idx < nargs(); ++idx)
6596                 cell(idx)->acceptChanges();
6597 }
6598
6599
6600 void InsetTabular::rejectChanges()
6601 {
6602         for (idx_type idx = 0; idx < nargs(); ++idx)
6603                 cell(idx)->rejectChanges();
6604 }
6605
6606
6607 bool InsetTabular::allowParagraphCustomization(idx_type cell) const
6608 {
6609         return tabular.getPWidth(cell).zero();
6610 }
6611
6612
6613 bool InsetTabular::forcePlainLayout(idx_type cell) const
6614 {
6615         return tabular.isMultiColumn(cell) && !tabular.getPWidth(cell).zero();
6616 }
6617
6618
6619 bool InsetTabular::insertPlaintextString(BufferView & bv, docstring const & buf,
6620                                      bool usePaste)
6621 {
6622         if (buf.length() <= 0)
6623                 return true;
6624
6625         col_type cols = 1;
6626         row_type rows = 1;
6627         col_type maxCols = 1;
6628         size_t const len = buf.length();
6629         size_t p = 0;
6630
6631         while (p < len &&
6632                (p = buf.find_first_of(from_ascii("\t\n"), p)) != docstring::npos) {
6633                 switch (buf[p]) {
6634                 case '\t':
6635                         ++cols;
6636                         break;
6637                 case '\n':
6638                         if (p + 1 < len)
6639                                 ++rows;
6640                         maxCols = max(cols, maxCols);
6641                         cols = 1;
6642                         break;
6643                 }
6644                 ++p;
6645         }
6646         maxCols = max(cols, maxCols);
6647         Tabular * loctab;
6648         idx_type cell = 0;
6649         col_type ocol = 0;
6650         row_type row = 0;
6651         if (usePaste) {
6652                 paste_tabular.reset(new Tabular(buffer_, rows, maxCols));
6653                 loctab = paste_tabular.get();
6654                 dirtyTabularStack(true);
6655         } else {
6656                 loctab = &tabular;
6657                 cell = bv.cursor().idx();
6658                 ocol = tabular.cellColumn(cell);
6659                 row = tabular.cellRow(cell);
6660         }
6661
6662         size_t op = 0;
6663         idx_type const cells = loctab->numberofcells;
6664         p = 0;
6665         cols = ocol;
6666         rows = loctab->nrows();
6667         col_type const columns = loctab->ncols();
6668
6669         while (cell < cells && p < len && row < rows &&
6670                (p = buf.find_first_of(from_ascii("\t\n"), p)) != docstring::npos)
6671         {
6672                 if (p >= len)
6673                         break;
6674                 switch (buf[p]) {
6675                 case '\t':
6676                         // we can only set this if we are not too far right
6677                         if (cols < columns) {
6678                                 shared_ptr<InsetTableCell> inset = loctab->cellInset(cell);
6679                                 Font const font = bv.textMetrics(&inset->text()).
6680                                         displayFont(0, 0);
6681                                 inset->setText(buf.substr(op, p - op), font,
6682                                                buffer().params().track_changes);
6683                                 ++cols;
6684                                 ++cell;
6685                         }
6686                         break;
6687                 case '\n':
6688                         // we can only set this if we are not too far right
6689                         if (cols < columns) {
6690                                 shared_ptr<InsetTableCell> inset = tabular.cellInset(cell);
6691                                 Font const font = bv.textMetrics(&inset->text()).
6692                                         displayFont(0, 0);
6693                                 inset->setText(buf.substr(op, p - op), font,
6694                                                buffer().params().track_changes);
6695                         }
6696                         cols = ocol;
6697                         ++row;
6698                         if (row < rows)
6699                                 cell = loctab->cellIndex(row, cols);
6700                         break;
6701                 }
6702                 ++p;
6703                 op = p;
6704         }
6705         // check for the last cell if there is no trailing '\n'
6706         if (cell < cells && op < len) {
6707                 shared_ptr<InsetTableCell> inset = loctab->cellInset(cell);
6708                 Font const font = bv.textMetrics(&inset->text()).displayFont(0, 0);
6709                 inset->setText(buf.substr(op, len - op), font,
6710                         buffer().params().track_changes);
6711         }
6712         return true;
6713 }
6714
6715
6716 void InsetTabular::addPreview(DocIterator const & inset_pos,
6717         PreviewLoader & loader) const
6718 {
6719         DocIterator cell_pos = inset_pos;
6720
6721         cell_pos.push_back(CursorSlice(*const_cast<InsetTabular *>(this)));
6722         for (row_type r = 0; r < tabular.nrows(); ++r) {
6723                 for (col_type c = 0; c < tabular.ncols(); ++c) {
6724                         cell_pos.top().idx() = tabular.cellIndex(r, c);
6725                         tabular.cellInset(r, c)->addPreview(cell_pos, loader);
6726                 }
6727         }
6728 }
6729
6730
6731 bool InsetTabular::completionSupported(Cursor const & cur) const
6732 {
6733         Cursor const & bvCur = cur.bv().cursor();
6734         if (&bvCur.inset() != this)
6735                 return false;
6736         return cur.text()->completionSupported(cur);
6737 }
6738
6739
6740 bool InsetTabular::inlineCompletionSupported(Cursor const & cur) const
6741 {
6742         return completionSupported(cur);
6743 }
6744
6745
6746 bool InsetTabular::automaticInlineCompletion() const
6747 {
6748         return lyxrc.completion_inline_text;
6749 }
6750
6751
6752 bool InsetTabular::automaticPopupCompletion() const
6753 {
6754         return lyxrc.completion_popup_text;
6755 }
6756
6757
6758 bool InsetTabular::showCompletionCursor() const
6759 {
6760         return lyxrc.completion_cursor_text;
6761 }
6762
6763
6764 CompletionList const * InsetTabular::createCompletionList(Cursor const & cur) const
6765 {
6766         return completionSupported(cur) ? cur.text()->createCompletionList(cur) : 0;
6767 }
6768
6769
6770 docstring InsetTabular::completionPrefix(Cursor const & cur) const
6771 {
6772         if (!completionSupported(cur))
6773                 return docstring();
6774         return cur.text()->completionPrefix(cur);
6775 }
6776
6777
6778 bool InsetTabular::insertCompletion(Cursor & cur, docstring const & s, bool finished)
6779 {
6780         if (!completionSupported(cur))
6781                 return false;
6782
6783         return cur.text()->insertCompletion(cur, s, finished);
6784 }
6785
6786
6787 void InsetTabular::completionPosAndDim(Cursor const & cur, int & x, int & y,
6788                                     Dimension & dim) const
6789 {
6790         TextMetrics const & tm = cur.bv().textMetrics(cur.text());
6791         tm.completionPosAndDim(cur, x, y, dim);
6792 }
6793
6794
6795 void InsetTabular::string2params(string const & in, InsetTabular & inset)
6796 {
6797         istringstream data(in);
6798         Lexer lex;
6799         lex.setStream(data);
6800
6801         if (in.empty())
6802                 return;
6803
6804         string token;
6805         lex >> token;
6806         if (!lex || token != "tabular") {
6807                 LYXERR0("Expected arg 1 to be \"tabular\" in " << in);
6808                 return;
6809         }
6810
6811         // This is part of the inset proper that is usually swallowed
6812         // by Buffer::readInset
6813         lex >> token;
6814         if (!lex || token != "Tabular") {
6815                 LYXERR0("Expected arg 2 to be \"Tabular\" in " << in);
6816                 return;
6817         }
6818
6819         inset.read(lex);
6820 }
6821
6822
6823 string InsetTabular::params2string(InsetTabular const & inset)
6824 {
6825         ostringstream data;
6826         data << "tabular" << ' ';
6827         inset.write(data);
6828         data << "\\end_inset\n";
6829         return data.str();
6830 }
6831
6832
6833 void InsetTabular::setLayoutForHiddenCells(DocumentClass const & dc)
6834 {
6835         for (Tabular::col_type c = 0; c < tabular.ncols(); ++c) {
6836                 for (Tabular::row_type r = 0; r < tabular.nrows(); ++r) {
6837                         if (!tabular.isPartOfMultiColumn(r,c) &&
6838                             !tabular.isPartOfMultiRow(r,c))
6839                                 continue;
6840
6841                         ParagraphList & parlist = tabular.cellInset(r,c)->paragraphs();
6842                         ParagraphList::iterator it = parlist.begin();
6843                         ParagraphList::iterator const en = parlist.end();
6844                         for (; it != en; ++it)
6845                                         it->setLayout(dc.plainLayout());
6846                 }
6847         }
6848 }
6849
6850
6851 } // namespace lyx