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