X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2Finsets%2FInsetTabular.cpp;h=69f9e73e59f0f0f11e125f2c80e4ba5449048455;hb=8124e6c02ea1fd6779bb6c47ffe2bca2c8bd2d97;hp=ba9462c38a8163fe6c54f00cfd68d88a12298d16;hpb=93794d6fea9845a0b6ce7227cf966906a8238215;p=lyx.git diff --git a/src/insets/InsetTabular.cpp b/src/insets/InsetTabular.cpp index ba9462c38a..69f9e73e59 100644 --- a/src/insets/InsetTabular.cpp +++ b/src/insets/InsetTabular.cpp @@ -22,6 +22,7 @@ #include "InsetTabular.h" +#include "Author.h" #include "buffer_funcs.h" #include "Buffer.h" #include "BufferParams.h" @@ -41,10 +42,9 @@ #include "LyX.h" #include "LyXRC.h" #include "MetricsInfo.h" -#include "OutputParams.h" +#include "xml.h" #include "output_xhtml.h" #include "Paragraph.h" -#include "ParagraphParameters.h" #include "ParIterator.h" #include "TexRow.h" #include "texstream.h" @@ -57,6 +57,7 @@ #include "frontends/Painter.h" #include "frontends/Selection.h" +#include "support/Changer.h" #include "support/convert.h" #include "support/debug.h" #include "support/docstream.h" @@ -163,6 +164,7 @@ TabularFeature tabularFeature[] = { Tabular::UNSET_MULTIROW, "unset-multirow", false }, { Tabular::SET_MROFFSET, "set-mroffset", true }, { Tabular::SET_ALL_LINES, "set-all-lines", false }, + { Tabular::TOGGLE_ALL_LINES, "toggle-all-lines", false }, { Tabular::RESET_FORMAL_DEFAULT, "reset-formal-default", false }, { Tabular::UNSET_ALL_LINES, "unset-all-lines", false }, { Tabular::TOGGLE_LONGTABULAR, "toggle-longtabular", false }, @@ -200,6 +202,7 @@ TabularFeature tabularFeature[] = { Tabular::SET_BOTTOM_SPACE, "set-bottom-space", true }, { Tabular::SET_INTERLINE_SPACE, "set-interline-space", true }, { Tabular::SET_BORDER_LINES, "set-border-lines", false }, + { Tabular::TOGGLE_BORDER_LINES, "toggle-border-lines", false }, { Tabular::TABULAR_VALIGN_TOP, "tabular-valign-top", false}, { Tabular::TABULAR_VALIGN_MIDDLE, "tabular-valign-middle", false}, { Tabular::TABULAR_VALIGN_BOTTOM, "tabular-valign-bottom", false}, @@ -209,6 +212,7 @@ TabularFeature tabularFeature[] = { Tabular::SET_DECIMAL_POINT, "set-decimal-point", true }, { Tabular::SET_TABULAR_WIDTH, "set-tabular-width", true }, { Tabular::SET_INNER_LINES, "set-inner-lines", false }, + { Tabular::TOGGLE_INNER_LINES, "toggle-inner-lines", false }, { Tabular::LAST_ACTION, "", false } }; @@ -451,18 +455,42 @@ bool getTokenValue(string const & str, char const * token, Length & len) } -bool getTokenValue(string const & str, char const * token, Change::Type & change) +bool getTokenValue(string const & str, char const * token, Change & change, BufferParams & bp) { - // set the length to be zero() as default as this it should be if not + // set the change to be Change() as default as this it should be if not // in the file format. - change = Change::UNCHANGED; + change = Change(); string tmp; if (getTokenValue(str, token, tmp)) { - if (tmp == "inserted") { - change = Change::INSERTED; + vector const changedata = getVectorFromString(tmp, " "); + if (changedata.size() != 3) { + Alert::warning(_("Change tracking data incomplete"), + _("Change tracking information for tabular row/column " + "is incomplete. I will ignore this.")); + return false; + } + BufferParams::AuthorMap const & am = bp.author_map_; + int aid = convert(changedata[1]); + if (am.find(aid) == am.end()) { + // FIXME Use ErrorList + Alert::warning(_("Change tracking author index missing"), + bformat(_("A change tracking author information for index " + "%1$d is missing. This can happen after a wrong " + "merge by a version control system. In this case, " + "either fix the merge, or have this information " + "missing until the corresponding tracked changes " + "are merged or this user edits the file again.\n"), + aid)); + bp.addAuthor(Author(aid)); + } + istringstream is(changedata[2]); + time_t ct; + is >> ct; + if (changedata[0] == "inserted") { + change = Change(Change::INSERTED, am.find(aid)->second, ct); return true; - } else if (tmp == "deleted") { - change = Change::DELETED; + } else if (changedata[0] == "deleted") { + change = Change(Change::DELETED, am.find(aid)->second, ct); return true; } } @@ -535,7 +563,7 @@ string const write_attribute(string const & name, int const & i) template <> -string const write_attribute(string const & name, Tabular::idx_type const & i) +string const write_attribute(string const & name, idx_type const & i) { // we write only true attribute values so we remove a bit of the // file format bloat for tabulars. @@ -546,17 +574,22 @@ string const write_attribute(string const & name, Tabular::idx_type const & i) template <> string const write_attribute(string const & name, Length const & value) { - // we write only the value if we really have one same reson as above. + // we write only the value if we really have one same reason as above. return value.zero() ? string() : write_attribute(name, value.asString()); } -template <> -string const write_attribute(string const & name, Change::Type const & type) +string const write_attribute(string const & name, Change const & change, BufferParams const & bp) { - if (type == Change::INSERTED) - return write_attribute(name, from_ascii("inserted")); - else if (type == Change::DELETED) - return write_attribute(name, from_ascii("deleted")); + odocstringstream ods; + if (change.inserted()) + ods << from_ascii("inserted"); + else if (change.deleted()) + ods << from_ascii("deleted"); + if (change.changed()) { + ods << " " << bp.authors().get(change.author).bufferId() + << " " << change.changetime; + return write_attribute(name, ods.str()); + } return string(); } @@ -587,11 +620,11 @@ DocIterator separatorPos(InsetTableCell const * cell, docstring const & align_d) InsetTableCell splitCell(InsetTableCell & head, docstring const & align_d, bool & hassep) { - InsetTableCell tail = InsetTableCell(head); + InsetTableCell tail = head; DocIterator const dit = separatorPos(&head, align_d); - hassep = (bool)dit; + hassep = static_cast(dit); if (hassep) { - pit_type const psize = head.paragraphs().front().size(); + pos_type const psize = head.paragraphs().front().size(); head.paragraphs().front().eraseChars(dit.pos(), psize, false); tail.paragraphs().front().eraseChars(0, dit.pos() < psize ? dit.pos() + 1 : psize, false); @@ -703,7 +736,7 @@ Tabular::RowData::RowData() endlastfoot(false), newpage(false), caption(false), - change(Change::UNCHANGED) + change(Change()) {} @@ -712,7 +745,7 @@ Tabular::ColumnData::ColumnData() valignment(LYX_VALIGN_TOP), width(0), varwidth(false), - change(Change::UNCHANGED) + change(Change()) { } @@ -778,18 +811,15 @@ void Tabular::deleteRow(row_type const row, bool const force) bool const ct = force ? false : buffer().params().track_changes; for (col_type c = 0; c < ncols(); ++c) { - // mark track changes - if (ct) - cell_info[row][c].inset->setChange(Change(Change::DELETED)); // Care about multirow cells if (row + 1 < nrows() && cell_info[row][c].multirow == CELL_BEGIN_OF_MULTIROW && cell_info[row + 1][c].multirow == CELL_PART_OF_MULTIROW) { - cell_info[row + 1][c].multirow = CELL_BEGIN_OF_MULTIROW; + cell_info[row + 1][c] = cell_info[row][c]; } } if (ct) - row_info[row].change = Change::DELETED; + row_info[row].change.setDeleted(); else { row_info.erase(row_info.begin() + row); cell_info.erase(cell_info.begin() + row); @@ -812,15 +842,13 @@ void Tabular::appendRow(row_type row) void Tabular::insertRow(row_type const row, bool copy) { - row_info.insert(row_info.begin() + row + 1, RowData(row_info[row])); + row_info.insert(row_info.begin() + row + 1, row_info[row]); cell_info.insert(cell_info.begin() + row + 1, cell_vector(0, CellData(buffer_))); for (col_type c = 0; c < ncols(); ++c) { cell_info[row + 1].insert(cell_info[row + 1].begin() + c, - copy ? CellData(cell_info[row][c]) : CellData(buffer_)); - if (buffer().params().track_changes) - cell_info[row + 1][c].inset->setChange(Change(Change::INSERTED)); + copy ? cell_info[row][c] : CellData(buffer_)); if (cell_info[row][c].multirow == CELL_BEGIN_OF_MULTIROW) cell_info[row + 1][c].multirow = CELL_PART_OF_MULTIROW; } @@ -839,58 +867,100 @@ void Tabular::insertRow(row_type const row, bool copy) setBottomLine(i, true); setBottomLine(j, false); } - // mark track changes - if (buffer().params().track_changes) - cellInfo(i).inset->setChange(Change(Change::INSERTED)); } - if (buffer().params().track_changes) - row_info[row + 1].change = Change::INSERTED; + if (buffer().params().track_changes) { + row_info[row + 1].change.setInserted(); + updateIndexes(); + } } -void Tabular::moveColumn(col_type col, ColDirection direction) +void Tabular::moveColumn(col_type col_start, col_type col_end, + ColDirection direction) { - if (direction == Tabular::LEFT) - col = col - 1; - - std::swap(column_info[col], column_info[col + 1]); - - for (row_type r = 0; r < nrows(); ++r) { - std::swap(cell_info[r][col], cell_info[r][col + 1]); - std::swap(cell_info[r][col].left_line, cell_info[r][col + 1].left_line); - std::swap(cell_info[r][col].right_line, cell_info[r][col + 1].right_line); - - idx_type const i = cellIndex(r, col); - idx_type const j = cellIndex(r, col + 1); - if (buffer().params().track_changes) { - cellInfo(i).inset->setChange(Change(Change::INSERTED)); - cellInfo(j).inset->setChange(Change(Change::INSERTED)); + if (direction == Tabular::LEFT) { + for (col_type col = col_start; col <= col_end; ++col) { + std::swap(column_info[col - 1], column_info[col]); + for (row_type r = 0; r < nrows(); ++r) { + std::swap(cell_info[r][col - 1], cell_info[r][col]); + std::swap(cell_info[r][col - 1].left_line, cell_info[r][col].left_line); + std::swap(cell_info[r][col - 1].right_line, cell_info[r][col].right_line); + + if (buffer().params().track_changes) { + idx_type const i = cellIndex(r, col - 1); + idx_type const j = cellIndex(r, col); + cellInfo(i).inset->setChange(Change(Change::INSERTED)); + cellInfo(j).inset->setChange(Change(Change::INSERTED)); + } + } + updateIndexes(); + if (col == ncols()) + break; + } + } else { + for (col_type col = col_end; col >= col_start; --col) { + std::swap(column_info[col], column_info[col + 1]); + for (row_type r = 0; r < nrows(); ++r) { + std::swap(cell_info[r][col], cell_info[r][col + 1]); + std::swap(cell_info[r][col].left_line, cell_info[r][col + 1].left_line); + std::swap(cell_info[r][col].right_line, cell_info[r][col + 1].right_line); + + if (buffer().params().track_changes) { + idx_type const i = cellIndex(r, col); + idx_type const j = cellIndex(r, col + 1); + cellInfo(i).inset->setChange(Change(Change::INSERTED)); + cellInfo(j).inset->setChange(Change(Change::INSERTED)); + } + } + updateIndexes(); + if (col == 0) + break; } } - updateIndexes(); } -void Tabular::moveRow(row_type row, RowDirection direction) +void Tabular::moveRow(row_type row_start, row_type row_end, RowDirection direction) { - if (direction == Tabular::UP) - row = row - 1; - - std::swap(row_info[row], row_info[row + 1]); - - for (col_type c = 0; c < ncols(); ++c) { - std::swap(cell_info[row][c], cell_info[row + 1][c]); - std::swap(cell_info[row][c].top_line, cell_info[row + 1][c].top_line); - std::swap(cell_info[row][c].bottom_line, cell_info[row + 1][c].bottom_line); - - idx_type const i = cellIndex(row, c); - idx_type const j = cellIndex(row + 1, c); - if (buffer().params().track_changes) { - cellInfo(i).inset->setChange(Change(Change::INSERTED)); - cellInfo(j).inset->setChange(Change(Change::INSERTED)); + if (direction == Tabular::UP) { + for (row_type row = row_start; row <= row_end; ++row) { + std::swap(row_info[row - 1], row_info[row]); + for (col_type c = 0; c < ncols(); ++c) { + std::swap(cell_info[row - 1][c], cell_info[row][c]); + std::swap(cell_info[row - 1][c].top_line, cell_info[row][c].top_line); + std::swap(cell_info[row - 1][c].bottom_line, cell_info[row][c].bottom_line); + + idx_type const i = cellIndex(row - 1, c); + idx_type const j = cellIndex(row, c); + if (buffer().params().track_changes) { + cellInfo(i).inset->setChange(Change(Change::INSERTED)); + cellInfo(j).inset->setChange(Change(Change::INSERTED)); + } + } + updateIndexes(); + if (row == nrows()) + break; + } + } else { + for (row_type row = row_end; row >= row_start; --row) { + std::swap(row_info[row], row_info[row + 1]); + for (col_type c = 0; c < ncols(); ++c) { + std::swap(cell_info[row][c], cell_info[row + 1][c]); + std::swap(cell_info[row][c].top_line, cell_info[row + 1][c].top_line); + std::swap(cell_info[row][c].bottom_line, cell_info[row + 1][c].bottom_line); + + idx_type const i = cellIndex(row, c); + idx_type const j = cellIndex(row + 1, c); + if (buffer().params().track_changes) { + cellInfo(i).inset->setChange(Change(Change::INSERTED)); + cellInfo(j).inset->setChange(Change(Change::INSERTED)); + } + } + updateIndexes(); + if (row == 0) + break; } } - updateIndexes(); } @@ -903,20 +973,17 @@ void Tabular::deleteColumn(col_type const col, bool const force) bool const ct = force ? false : buffer().params().track_changes; for (row_type r = 0; r < nrows(); ++r) { - // mark track changes - if (ct) - cell_info[r][col].inset->setChange(Change(Change::DELETED)); // Care about multicolumn cells if (col + 1 < ncols() && cell_info[r][col].multicolumn == CELL_BEGIN_OF_MULTICOLUMN && cell_info[r][col + 1].multicolumn == CELL_PART_OF_MULTICOLUMN) { - cell_info[r][col + 1].multicolumn = CELL_BEGIN_OF_MULTICOLUMN; + cell_info[r][col + 1] = cell_info[r][col]; } if (!ct) cell_info[r].erase(cell_info[r].begin() + col); } if (ct) - column_info[col].change = Change::DELETED; + column_info[col].change.setDeleted(); else column_info.erase(column_info.begin() + col); updateIndexes(); @@ -937,14 +1004,12 @@ void Tabular::appendColumn(col_type col) void Tabular::insertColumn(col_type const col, bool copy) { - BufferParams const & bp = buffer().params(); - column_info.insert(column_info.begin() + col + 1, ColumnData(column_info[col])); + bool const ct = buffer().params().track_changes; + column_info.insert(column_info.begin() + col + 1, column_info[col]); for (row_type r = 0; r < nrows(); ++r) { cell_info[r].insert(cell_info[r].begin() + col + 1, - copy ? CellData(cell_info[r][col]) : CellData(buffer_)); - if (bp.track_changes) - cell_info[r][col + 1].inset->setChange(Change(Change::INSERTED)); + copy ? cell_info[r][col] : CellData(buffer_)); if (cell_info[r][col].multicolumn == CELL_BEGIN_OF_MULTICOLUMN) cell_info[r][col + 1].multicolumn = CELL_PART_OF_MULTICOLUMN; } @@ -960,11 +1025,11 @@ void Tabular::insertColumn(col_type const col, bool copy) if (rightLine(i) && rightLine(j)) { setRightLine(j, false); } - if (buffer().params().track_changes) - cellInfo(i).inset->setChange(Change(Change::INSERTED)); } - if (buffer().params().track_changes) - column_info[col + 1].change = Change::INSERTED; + if (ct) { + column_info[col + 1].change.setInserted(); + updateIndexes(); + } } @@ -987,15 +1052,15 @@ void Tabular::updateIndexes() rowofcell.resize(numberofcells); columnofcell.resize(numberofcells); idx_type i = 0; - // reset column and row of cells and update their width and alignment - for (row_type row = 0; row < nrows(); ++row) + // reset column and row of cells and update their width, alignment and ct status + for (row_type row = 0; row < nrows(); ++row) { for (col_type column = 0; column < ncols(); ++column) { if (isPartOfMultiColumn(row, column)) { cell_info[row][column].inset->toggleMultiCol(true); continue; } cell_info[row][column].inset->toggleMultiCol(false); - // columnofcell needs to be called before setting width and aligment + // columnofcell needs to be called before setting width and alignment // multirow cells inherit the width from the column width if (!isPartOfMultiRow(row, column)) { columnofcell[i] = column; @@ -1009,12 +1074,19 @@ void Tabular::updateIndexes() cell_info[row][column].inset->toggleMultiRow(false); cell_info[row][column].inset->setContentAlignment( getAlignment(cellIndex(row, column))); + if (buffer().params().track_changes) { + if (row_info[row].change.changed()) + cell_info[row][column].inset->setChange(row_info[row].change); + if (column_info[column].change.changed()) + cell_info[row][column].inset->setChange(column_info[column].change); + } ++i; } + } } -Tabular::idx_type Tabular::numberOfCellsInRow(row_type const row) const +idx_type Tabular::numberOfCellsInRow(row_type const row) const { idx_type result = 0; for (col_type c = 0; c < ncols(); ++c) @@ -1052,6 +1124,76 @@ bool Tabular::rightLine(idx_type cell, bool const ignore_bt) const } +bool Tabular::outsideBorders( + row_type const sel_row_start, row_type const sel_row_end, + col_type const sel_col_start, col_type const sel_col_end) const +{ + if (!use_booktabs) + for (row_type r = sel_row_start; r <= sel_row_end; ++r) { + if (!leftLine(cellIndex(r, sel_col_start)) + || !rightLine(cellIndex(r, sel_col_end))) + return false; + } + for (col_type c = sel_col_start; c <= sel_col_end; ++c) { + if (!topLine(cellIndex(sel_row_start, c)) + || !bottomLine(cellIndex(sel_row_end, c))) + return false; + } + return true; +} + + +bool Tabular::innerBorders( + row_type const sel_row_start, row_type const sel_row_end, + col_type const sel_col_start, col_type const sel_col_end) const +{ + // Single cell has no inner borders + if (sel_row_start == sel_row_end && sel_col_start == sel_col_end) + return false; + for (row_type r = sel_row_start; r <= sel_row_end; ++r) + for (col_type c = sel_col_start; c <= sel_col_end; ++c) { + idx_type const cell = cellIndex(r, c); + if ((r != sel_row_start && !topLine(cell) + && cell_info[r][c].multirow != CELL_PART_OF_MULTIROW) + || (!use_booktabs + && c != sel_col_start && !leftLine(cell) + && cell_info[r][c].multicolumn != CELL_PART_OF_MULTICOLUMN)) + return false; + } + return true; +} + + +void Tabular::setLines( + row_type const sel_row_start, row_type const sel_row_end, + col_type const sel_col_start, col_type const sel_col_end, + bool setLinesInnerOnly, bool setLines) +{ + for (row_type r = sel_row_start; r <= sel_row_end; ++r) + for (col_type c = sel_col_start; c <= sel_col_end; ++c) { + idx_type const cell = cellIndex(r, c); + if (!(setLinesInnerOnly && r == sel_row_start) + // for multirows, cell is taken care of at beginning + && cell_info[r][c].multirow != CELL_PART_OF_MULTIROW) + setTopLine(cell, setLines); + if (!(setLinesInnerOnly && r == sel_row_end) + && (r == sel_row_end || (!setLines + // for multirows, cell is taken care of at the last part + && cell_info[r + 1][c].multirow != CELL_PART_OF_MULTIROW))) + setBottomLine(cell, setLines); + if (!(setLinesInnerOnly && c == sel_col_start) + // for multicolumns, cell is taken care of at beginning + && cell_info[r][c].multicolumn != CELL_PART_OF_MULTICOLUMN) + setLeftLine(cell, setLines); + if (!(setLinesInnerOnly && c == sel_col_end) + && (c == sel_col_end || (!setLines + // for multicolumns, cell is taken care of at the last part + && cell_info[r][c + 1].multicolumn != CELL_PART_OF_MULTICOLUMN))) + setRightLine(cell, setLines); + } +} + + pair Tabular::topLineTrim(idx_type const cell) const { if (!use_booktabs) @@ -1149,8 +1291,8 @@ bool Tabular::updateColumnWidths(MetricsInfo & mi) int restwidth = -1; if (!tab_width.zero()) { restwidth = mi.base.inPixels(tab_width); - // Substract the fixed widths from the table width - for (auto const w : max_pwidth) + // Subtract the fixed widths from the table width + for (auto const & w : max_pwidth) restwidth -= w.second; } @@ -1164,7 +1306,7 @@ bool Tabular::updateColumnWidths(MetricsInfo & mi) // Now consider that some variable width columns exceed the vcolwidth if (vcolwidth > 0) { bool changed = false; - for (auto const w : max_width) { + for (auto const & w : max_width) { if (tabularx || w.second > vcolwidth) { --restcols; restwidth -= w.second; @@ -1285,36 +1427,18 @@ void Tabular::setVAlignment(idx_type cell, VAlignment align, namespace { /** - * Allow line and paragraph breaks for fixed width multicol/multirow cells + * Allow line and paragraph breaks for fixed width multirow cells * or disallow them, merge cell paragraphs and reset layout to standard * for variable width multicol cells. */ void toggleFixedWidth(Cursor & cur, InsetTableCell * inset, - bool const fixedWidth, bool const multicol, - bool const multirow) + bool const fixedWidth, bool const multirow) { inset->toggleFixedWidth(fixedWidth); - if (!multirow && (fixedWidth || !multicol)) + if (!multirow) return; - // merge all paragraphs to one BufferParams const & bp = cur.bv().buffer().params(); - while (inset->paragraphs().size() > 1) - mergeParagraph(bp, inset->paragraphs(), 0); - - // This is relevant for multirows - if (fixedWidth) - return; - - // remove newlines - ParagraphList::iterator pit = inset->paragraphs().begin(); - for (; pit != inset->paragraphs().end(); ++pit) { - for (pos_type j = 0; j != pit->size(); ++j) { - if (pit->isNewline(j)) - pit->eraseChar(j, bp.track_changes); - } - } - // reset layout cur.push(*inset); // undo information has already been recorded @@ -1341,10 +1465,10 @@ void Tabular::setColumnPWidth(Cursor & cur, idx_type cell, idx_type const cidx = cellIndex(r, c); // because of multicolumns toggleFixedWidth(cur, cellInset(cidx).get(), - !getPWidth(cidx).zero(), isMultiColumn(cidx), - isMultiRow(cidx)); + !getPWidth(cidx).zero(), isMultiRow(cidx)); if (isMultiRow(cidx)) - setAlignment(cidx, LYX_ALIGN_LEFT, false); + setAlignment(cidx, column_info[c].alignment, + !column_info[c].p_width.zero()); } // cur can become invalid after paragraphs were merged cur.fixIfBroken(); @@ -1369,7 +1493,7 @@ bool Tabular::setMColumnPWidth(Cursor & cur, idx_type cell, cellInfo(cell).p_width = width; toggleFixedWidth(cur, cellInset(cell).get(), !width.zero(), - isMultiColumn(cell), isMultiRow(cell)); + isMultiRow(cell)); // cur can become invalid after paragraphs were merged cur.fixIfBroken(); return true; @@ -1379,6 +1503,7 @@ bool Tabular::setMColumnPWidth(Cursor & cur, idx_type cell, bool Tabular::toggleVarwidth(idx_type cell, bool const varwidth) { column_info[cellColumn(cell)].varwidth = varwidth; + cellInset(cell).get()->toggleVarWidth(varwidth); return true; } @@ -1622,7 +1747,7 @@ int Tabular::textVOffset(idx_type cell) const } -Tabular::idx_type Tabular::getFirstCellInRow(row_type row) const +idx_type Tabular::getFirstCellInRow(row_type row, bool const ct) const { col_type c = 0; idx_type const numcells = numberOfCellsInRow(row); @@ -1631,27 +1756,53 @@ Tabular::idx_type Tabular::getFirstCellInRow(row_type row) const // is really invalid, i.e., it is NOT the first cell in the row. but // i do not know what to do here. (rgh) while (c < numcells - 1 - && cell_info[row][c].multirow == CELL_PART_OF_MULTIROW) + && (cell_info[row][c].multirow == CELL_PART_OF_MULTIROW + || (ct && column_info[c].change.deleted()))) ++c; return cell_info[row][c].cellno; } -Tabular::idx_type Tabular::getLastCellInRow(row_type row) const +idx_type Tabular::getLastCellInRow(row_type row, bool const ct) const { col_type c = ncols() - 1; // of course we check against 0 so we don't crash. but we have the same // problem as in the previous routine: if all the cells are part of a // multirow or part of a multi column, then our return value is invalid. while (c > 0 - && (cell_info[row][c].multirow == CELL_PART_OF_MULTIROW - || cell_info[row][c].multicolumn == CELL_PART_OF_MULTICOLUMN)) + && ((cell_info[row][c].multirow == CELL_PART_OF_MULTIROW + || cell_info[row][c].multicolumn == CELL_PART_OF_MULTICOLUMN) + || (ct && column_info[c].change.deleted()))) --c; return cell_info[row][c].cellno; } -Tabular::row_type Tabular::cellRow(idx_type cell) const +row_type Tabular::getFirstRow(bool const ct) const +{ + row_type r = 0; + if (!ct) + return r; + // exclude deleted rows if ct == true + while (r < nrows() && row_info[r].change.deleted()) + ++r; + return r; +} + + +row_type Tabular::getLastRow(bool const ct) const +{ + row_type r = nrows() - 1; + if (!ct) + return r; + // exclude deleted rows if ct == true + while (r > 0 && row_info[r].change.deleted()) + --r; + return r; +} + + +row_type Tabular::cellRow(idx_type cell) const { if (cell >= numberofcells) return nrows() - 1; @@ -1661,7 +1812,7 @@ Tabular::row_type Tabular::cellRow(idx_type cell) const } -Tabular::col_type Tabular::cellColumn(idx_type cell) const +col_type Tabular::cellColumn(idx_type cell) const { if (cell >= numberofcells) return ncols() - 1; @@ -1706,7 +1857,7 @@ void Tabular::write(ostream & os) const << write_attribute("alignment", column_info[c].alignment); if (column_info[c].alignment == LYX_ALIGN_DECIMAL) os << write_attribute("decimal_point", column_info[c].decimal_point); - os << write_attribute("change", column_info[c].change) + os << write_attribute("change", column_info[c].change, buffer().params()) << write_attribute("valignment", column_info[c].valignment) << write_attribute("width", column_info[c].p_width.asString()) << write_attribute("varwidth", column_info[c].varwidth) @@ -1728,7 +1879,7 @@ void Tabular::write(ostream & os) const os << write_attribute("interlinespace", def); else os << write_attribute("interlinespace", row_info[r].interline_space); - os << write_attribute("change", row_info[r].change) + os << write_attribute("change", row_info[r].change, buffer().params()) << write_attribute("endhead", row_info[r].endhead) << write_attribute("endfirsthead", row_info[r].endfirsthead) << write_attribute("endfoot", row_info[r].endfoot) @@ -1828,7 +1979,7 @@ void Tabular::read(Lexer & lex) getTokenValue(line, "width", column_info[c].p_width); getTokenValue(line, "special", column_info[c].align_special); getTokenValue(line, "varwidth", column_info[c].varwidth); - getTokenValue(line, "change", column_info[c].change); + getTokenValue(line, "change", column_info[c].change, buffer().params()); } for (row_type i = 0; i < nrows(); ++i) { @@ -1850,7 +2001,7 @@ void Tabular::read(Lexer & lex) getTokenValue(line, "endlastfoot", row_info[i].endlastfoot); getTokenValue(line, "newpage", row_info[i].newpage); getTokenValue(line, "caption", row_info[i].caption); - getTokenValue(line, "change", row_info[i].change); + getTokenValue(line, "change", row_info[i].change, buffer().params()); for (col_type j = 0; j < ncols(); ++j) { l_getline(is, line); if (!prefixIs(line, "= numberofcells; } -Tabular::idx_type Tabular::cellAbove(idx_type cell) const +idx_type Tabular::cellAbove(idx_type cell) const { if (cellRow(cell) == 0) return cell; @@ -2157,7 +2307,7 @@ Tabular::idx_type Tabular::cellAbove(idx_type cell) const } -Tabular::idx_type Tabular::cellBelow(idx_type cell) const +idx_type Tabular::cellBelow(idx_type cell) const { row_type const nextrow = cellRow(cell) + rowSpan(cell); if (nextrow < nrows()) @@ -2166,7 +2316,7 @@ Tabular::idx_type Tabular::cellBelow(idx_type cell) const } -Tabular::idx_type Tabular::cellIndex(row_type row, col_type column) const +idx_type Tabular::cellIndex(row_type row, col_type column) const { LASSERT(column != npos && column < ncols(), column = 0); LASSERT(row != npos && row < nrows(), row = 0); @@ -2317,7 +2467,7 @@ bool Tabular::haveLTLastFoot(bool withcaptions) const } -Tabular::idx_type Tabular::setLTCaption(Cursor & cur, row_type row, bool what) +idx_type Tabular::setLTCaption(Cursor & cur, row_type row, bool what) { idx_type i = getFirstCellInRow(row); if (what) { @@ -2437,7 +2587,8 @@ bool Tabular::isPartOfMultiRow(row_type row, col_type column) const } -void Tabular::TeXTopHLine(otexstream & os, row_type row, list columns) const +void Tabular::TeXTopHLine(otexstream & os, row_type row, list columns, + list logical_columns) const { // we only output complete row lines and the 1st row here, the rest // is done in Tabular::TeXBottomHLine(...) @@ -2472,15 +2623,16 @@ void Tabular::TeXTopHLine(otexstream & os, row_type row, list columns) } // do nothing if empty first row, or incomplete row line after - if ((row == 0 && nset == 0) || (row > 0 && nset != ncols())) + row_type first = getFirstRow(!buffer().params().output_changes); + if ((row == first && nset == 0) || (row > first && nset != columns.size())) return; // Is this the actual first row (excluding longtable caption row)? - bool const realfirstrow = (row == 0 - || (is_long_tabular && row == 1 && ltCaption(0))); + bool const realfirstrow = (row == first + || (is_long_tabular && row == first + 1 && ltCaption(first))); // only output complete row lines and the 1st row's clines - if (nset == ncols() && !have_trims) { + if (nset == columns.size() && !have_trims) { if (use_booktabs) { os << (realfirstrow ? "\\toprule " : "\\midrule "); } else { @@ -2489,7 +2641,12 @@ void Tabular::TeXTopHLine(otexstream & os, row_type row, list columns) } else if (realfirstrow || have_trims) { string const cline = use_booktabs ? "\\cmidrule" : "\\cline"; col_type c = 0; - for (auto & cl : columns) { + std::list::const_iterator it1 = logical_columns.begin(); + std::list::const_iterator it2 = columns.begin(); + // We need to iterate over the logical columns here, but take care for + // bidi swapping + for (; it1 != logical_columns.end() && it2 != columns.end(); ++it1, ++it2) { + col_type cl = *it1; if (cl < c) continue; c = cl; @@ -2498,7 +2655,8 @@ void Tabular::TeXTopHLine(otexstream & os, row_type row, list columns) for (col_type j = 0 ; j < c; ++j) if (column_info[j].alignment == LYX_ALIGN_DECIMAL) ++offset; - string const firstcol = convert(c + 1 + offset); + // If the two iterators differ, we are in bidi with swapped columns + col_type firstcol = (*it1 == *it2) ? c + 1 + offset : columns.size() - c + offset; while (isPartOfMultiColumn(row, c)) ++c; string trim; @@ -2520,10 +2678,10 @@ void Tabular::TeXTopHLine(otexstream & os, row_type row, list columns) break; } - for (col_type j = cstart ; j < c ; ++j) + for (col_type j = cstart ; j <= c ; ++j) if (column_info[j].alignment == LYX_ALIGN_DECIMAL) ++offset; - col_type const lastcol = c + 1 + offset; + col_type lastcol = (*it1 == *it2) ? c + 1 + offset : columns.size() - c + offset; if (toprtrims.find(c) != toprtrims.end() && toprtrims.find(c)->second) trim += "r"; @@ -2531,8 +2689,12 @@ void Tabular::TeXTopHLine(otexstream & os, row_type row, list columns) os << cline; if (!trim.empty()) os << "(" << trim << ")"; - os << "{" << firstcol << '-' << lastcol << "}"; - if (c == ncols() - 1) + if (firstcol > lastcol) + // This can happen with bidi (swapped columns) + os << "{" << lastcol << '-' << firstcol << "}"; + else + os << "{" << firstcol << '-' << lastcol << "}"; + if (c == columns.size() - 1) break; ++c; } @@ -2542,13 +2704,14 @@ void Tabular::TeXTopHLine(otexstream & os, row_type row, list columns) } -void Tabular::TeXBottomHLine(otexstream & os, row_type row, list columns) const +void Tabular::TeXBottomHLine(otexstream & os, row_type row, list columns, + list logical_columns) const { // we output bottomlines of row r and the toplines of row r+1 // if the latter do not span the whole tabular // get the bottomlines of row r, and toplines in next row - bool lastrow = row == nrows() - 1; + bool lastrow = row == getLastRow(!buffer().params().output_changes); map bottomline, topline, topltrims, toprtrims, bottomltrims, bottomrtrims; bool nextrowset = true; for (auto const & c : columns) { @@ -2591,7 +2754,7 @@ void Tabular::TeXBottomHLine(otexstream & os, row_type row, list colum bottomline[c] = bottomline.find(c)->second || topline.find(c)->second; bottomltrims[c] = (bottomltrims.find(c) != bottomltrims.end() && bottomltrims.find(c)->second) || (topltrims.find(c) != topltrims.end() && topltrims.find(c)->second); - bottomrtrims[c] =(bottomrtrims.find(c) != bottomrtrims.end() && bottomrtrims.find(c)->second) + bottomrtrims[c] = (bottomrtrims.find(c) != bottomrtrims.end() && bottomrtrims.find(c)->second) || (toprtrims.find(c) != toprtrims.end() && toprtrims.find(c)->second); if (bottomline.find(c)->second) ++nset; @@ -2601,10 +2764,10 @@ void Tabular::TeXBottomHLine(otexstream & os, row_type row, list colum } // do nothing if empty, OR incomplete row line with a topline in next row - if (nset == 0 || (nextrowset && nset != ncols())) + if (nset == 0 || (nextrowset && nset != columns.size())) return; - if (nset == ncols() && !have_trims) { + if (nset == columns.size() && !have_trims) { if (use_booktabs) os << (lastrow ? "\\bottomrule" : "\\midrule"); else @@ -2612,7 +2775,12 @@ void Tabular::TeXBottomHLine(otexstream & os, row_type row, list colum } else { string const cline = use_booktabs ? "\\cmidrule" : "\\cline"; col_type c = 0; - for (auto & cl : columns) { + std::list::const_iterator it1 = logical_columns.begin(); + std::list::const_iterator it2 = columns.begin(); + // We need to iterate over the logical columns here, but take care for + // bidi swapping + for (; it1 != logical_columns.end() && it2 != columns.end(); ++it1, ++it2) { + col_type cl = *it1; if (cl < c) continue; c = cl; @@ -2621,7 +2789,8 @@ void Tabular::TeXBottomHLine(otexstream & os, row_type row, list colum for (col_type j = 0 ; j < c; ++j) if (column_info[j].alignment == LYX_ALIGN_DECIMAL) ++offset; - string const firstcol = convert(c + 1 + offset); + // If the two iterators differ, we are in bidi with swapped columns + col_type firstcol = (*it1 == *it2) ? c + 1 + offset : columns.size() - c + offset; while (isPartOfMultiColumn(row, c)) ++c; string trim; @@ -2645,10 +2814,10 @@ void Tabular::TeXBottomHLine(otexstream & os, row_type row, list colum break; } - for (col_type j = cstart ; j < c ; ++j) + for (col_type j = cstart ; j <= c ; ++j) if (column_info[j].alignment == LYX_ALIGN_DECIMAL) ++offset; - col_type const lastcol = c + 1 + offset; + col_type lastcol = (*it1 == *it2) ? c + 1 + offset : columns.size() - c + offset; if (bottomrtrims.find(c) != bottomrtrims.end() && bottomrtrims.find(c)->second) trim += "r"; @@ -2656,8 +2825,12 @@ void Tabular::TeXBottomHLine(otexstream & os, row_type row, list colum os << cline; if (!trim.empty()) os << "(" << trim << ")"; - os << "{" << firstcol << '-' << lastcol << "}"; - if (c == ncols() - 1) + if (firstcol > lastcol) + // This can happen with bidi (swapped columns) + os << "{" << lastcol << '-' << firstcol << "}"; + else + os << "{" << firstcol << '-' << lastcol << "}"; + if (c == columns.size() - 1) break; ++c; } @@ -2753,16 +2926,21 @@ void Tabular::TeXCellPreamble(otexstream & os, idx_type cell, << from_ascii(getPWidth(cell).asLatexString()) << '}'; } else { - switch (align) { - case LYX_ALIGN_LEFT: - os << 'l'; - break; - case LYX_ALIGN_RIGHT: - os << 'r'; - break; - default: - os << 'c'; - break; + if ((getRotateCell(cell) == 0 && useBox(cell) == BOX_VARWIDTH + && align == LYX_ALIGN_LEFT)) + os << "V{\\linewidth}"; + else { + switch (align) { + case LYX_ALIGN_LEFT: + os << 'l'; + break; + case LYX_ALIGN_RIGHT: + os << 'r'; + break; + default: + os << 'c'; + break; + } } } // end if else !getPWidth } // end if else !cellinfo_of_cell @@ -2780,9 +2958,14 @@ void Tabular::TeXCellPreamble(otexstream & os, idx_type cell, os << "\\multirow{" << rowSpan(cell) << "}{"; if (!getPWidth(cell).zero()) os << from_ascii(getPWidth(cell).asLatexString()); - else - // we need to set a default value - os << "*"; + else { + if (column_info[c].varwidth) + // this inherits varwidth size + os << "="; + else + // we need to set a default value + os << "*"; + } os << "}"; if (!getMROffset(cell).zero()) os << "[" << from_ascii(getMROffset(cell).asLatexString()) << "]"; @@ -2822,8 +3005,10 @@ void Tabular::TeXCellPreamble(otexstream & os, idx_type cell, } os << "]{" << from_ascii(getPWidth(cell).asLatexString()) << "}\n"; - } else if (getRotateCell(cell) != 0 && getUsebox(cell) == BOX_VARWIDTH) { - os << "\\begin{varwidth}["; + } else if (getUsebox(cell) == BOX_VARWIDTH + && (getRotateCell(cell) != 0 || align != LYX_ALIGN_LEFT + || valign != LYX_VALIGN_TOP || hasNewlines(cell))) { + os << "\\begin{cellvarwidth}["; switch (valign) { case LYX_VALIGN_TOP: os << 't'; @@ -2834,9 +3019,26 @@ void Tabular::TeXCellPreamble(otexstream & os, idx_type cell, case LYX_VALIGN_BOTTOM: os << 'b'; break; + } + os << "]\n"; + switch (align) { + case LYX_ALIGN_RIGHT: + os << "\\raggedleft\n"; + break; + case LYX_ALIGN_CENTER: + os << "\\centering\n"; + break; + case LYX_ALIGN_LEFT: + //os << "\\narrowragged\n"; + break; + case LYX_ALIGN_BLOCK: + case LYX_ALIGN_DECIMAL: + case LYX_ALIGN_NONE: + case LYX_ALIGN_LAYOUT: + case LYX_ALIGN_SPECIAL: + break; + } } - os << "]{\\linewidth}\n"; -} } @@ -2852,8 +3054,10 @@ void Tabular::TeXCellPostamble(otexstream & os, idx_type cell, os << '}'; else if (getUsebox(cell) == BOX_MINIPAGE) os << breakln << "\\end{minipage}"; - else if (getRotateCell(cell) != 0 && getUsebox(cell) == BOX_VARWIDTH) - os << breakln << "\\end{varwidth}"; + else if (getUsebox(cell) == BOX_VARWIDTH + && (getRotateCell(cell) != 0 || getAlignment(cell) != LYX_ALIGN_LEFT + || getVAlignment(cell) != LYX_VALIGN_TOP || hasNewlines(cell))) + os << breakln << "\\end{cellvarwidth}"; if (getRotateCell(cell) != 0) os << breakln << "\\end{turn}"; if (ismultirow) @@ -2865,7 +3069,8 @@ void Tabular::TeXCellPostamble(otexstream & os, idx_type cell, void Tabular::TeXLongtableHeaderFooter(otexstream & os, OutputParams const & runparams, - list columns) const + list columns, + list logical_columns) const { if (!is_long_tabular) return; @@ -2877,7 +3082,7 @@ void Tabular::TeXLongtableHeaderFooter(otexstream & os, if (row_info[r].caption && !row_info[r].endfirsthead && !row_info[r].endhead && !row_info[r].endfoot && !row_info[r].endlastfoot) - TeXRow(os, r, runparams, columns); + TeXRow(os, r, runparams, columns, logical_columns); } } // output first header info @@ -2886,7 +3091,7 @@ void Tabular::TeXLongtableHeaderFooter(otexstream & os, os << "\\hline\n"; for (row_type r = 0; r < nrows(); ++r) { if (row_info[r].endfirsthead) - TeXRow(os, r, runparams, columns); + TeXRow(os, r, runparams, columns, logical_columns); } if (endfirsthead.bottomDL) os << "\\hline\n"; @@ -2900,7 +3105,7 @@ void Tabular::TeXLongtableHeaderFooter(otexstream & os, os << "\\hline\n"; for (row_type r = 0; r < nrows(); ++r) { if (row_info[r].endhead) - TeXRow(os, r, runparams, columns); + TeXRow(os, r, runparams, columns, logical_columns); } if (endhead.bottomDL) os << "\\hline\n"; @@ -2912,7 +3117,7 @@ void Tabular::TeXLongtableHeaderFooter(otexstream & os, os << "\\hline\n"; for (row_type r = 0; r < nrows(); ++r) { if (row_info[r].endfoot) - TeXRow(os, r, runparams, columns); + TeXRow(os, r, runparams, columns, logical_columns); } if (endfoot.bottomDL) os << "\\hline\n"; @@ -2926,7 +3131,7 @@ void Tabular::TeXLongtableHeaderFooter(otexstream & os, os << "\\hline\n"; for (row_type r = 0; r < nrows(); ++r) { if (row_info[r].endlastfoot) - TeXRow(os, r, runparams, columns); + TeXRow(os, r, runparams, columns, logical_columns); } if (endlastfoot.bottomDL) os << "\\hline\n"; @@ -2947,12 +3152,10 @@ bool Tabular::isValidRow(row_type row) const void Tabular::TeXRow(otexstream & os, row_type row, OutputParams const & runparams, - list columns) const + list columns, list logical_columns) const { - idx_type cell = cellIndex(row, 0); - //output the top line - TeXTopHLine(os, row, columns); + TeXTopHLine(os, row, columns, logical_columns); if (row_info[row].top_space_default) { if (use_booktabs) @@ -2978,14 +3181,15 @@ void Tabular::TeXRow(otexstream & os, row_type row, bool const bidi_rtl = runparams.local_font->isRightToLeft() && runparams.useBidiPackage(); + bool const ct = !buffer().params().output_changes; idx_type lastcell = - bidi_rtl ? getFirstCellInRow(row) : getLastCellInRow(row); + bidi_rtl ? getFirstCellInRow(row, ct) : getLastCellInRow(row, ct); for (auto const & c : columns) { if (isPartOfMultiColumn(row, c)) continue; - cell = cellIndex(row, c); + idx_type cell = cellIndex(row, c); if (isPartOfMultiRow(row, c) && column_info[c].alignment != LYX_ALIGN_DECIMAL) { @@ -3004,7 +3208,7 @@ void Tabular::TeXRow(otexstream & os, row_type row, bool rtl = par.isRTL(buffer().params()) && !par.empty() && getPWidth(cell).zero() - && !runparams.use_polyglossia; + && !runparams.isFullUnicode(); if (rtl) { string const lang = @@ -3058,17 +3262,18 @@ void Tabular::TeXRow(otexstream & os, row_type row, } else if (ltCaption(row)) { // Inside longtable caption rows, we must only output the caption inset // with its content and omit anything outside of that (see #10791) - InsetIterator it = inset_iterator_begin(*const_cast(inset)); - InsetIterator i_end = inset_iterator_end(*const_cast(inset)); - for (; it != i_end; ++it) { - if (it->lyxCode() != CAPTION_CODE) + InsetTableCell & tc_inset = *const_cast(inset); + for (Inset const & it : tc_inset) { + if (it.lyxCode() != CAPTION_CODE) continue; - it->latex(os, runparams); + it.latex(os, runparams); break; } } else if (!isPartOfMultiRow(row, c)) { if (!runparams.nice) os.texrow().start(par.id(), 0); + if (isMultiRow(cell) && !LaTeXFeatures::isAvailableAtLeastFrom("multirow", 2021, 1, 29)) + newrp.isNonLong = true; inset->latex(os, newrp); } @@ -3100,7 +3305,7 @@ void Tabular::TeXRow(otexstream & os, row_type row, os << '\n'; //output the bottom line - TeXBottomHLine(os, row, columns); + TeXBottomHLine(os, row, columns, logical_columns); if (row_info[row].interline_space_default) { if (use_booktabs) @@ -3149,11 +3354,17 @@ void Tabular::latex(otexstream & os, OutputParams const & runparams) const runparams.local_font->isRightToLeft() && runparams.useBidiPackage(); list columns; + list logical_columns; for (col_type cl = 0; cl < ncols(); ++cl) { + if (!buffer().params().output_changes && column_info[cl].change.deleted()) + continue; if (bidi_rtl) columns.push_front(cl); else columns.push_back(cl); + // for some calculations, we need the logical (non-swapped) + // columns also in bidi. + logical_columns.push_back(cl); } // If we use \cline or \cmidrule, we need to locally de-activate @@ -3327,26 +3538,9 @@ void Tabular::latex(otexstream & os, OutputParams const & runparams) const break; } os << 'X'; - } else if (isVTypeColumn(c)) { - switch (column_info[c].alignment) { - case LYX_ALIGN_LEFT: - os << ">{\\raggedright}"; - break; - case LYX_ALIGN_RIGHT: - os << ">{\\raggedleft}"; - break; - case LYX_ALIGN_CENTER: - os << ">{\\centering}"; - break; - case LYX_ALIGN_NONE: - case LYX_ALIGN_BLOCK: - case LYX_ALIGN_LAYOUT: - case LYX_ALIGN_SPECIAL: - case LYX_ALIGN_DECIMAL: - break; - } + } else if (isVTypeColumn(c)) os << "V{\\linewidth}"; - } else { + else { switch (column_info[c].alignment) { case LYX_ALIGN_LEFT: os << 'l'; @@ -3368,15 +3562,17 @@ void Tabular::latex(otexstream & os, OutputParams const & runparams) const } os << "}\n"; - TeXLongtableHeaderFooter(os, runparams, columns); + TeXLongtableHeaderFooter(os, runparams, columns, logical_columns); //+--------------------------------------------------------------------- //+ the single row and columns (cells) + //+--------------------------------------------------------------------- for (row_type r = 0; r < nrows(); ++r) { + if (!buffer().params().output_changes && row_info[r].change.deleted()) + continue; if (isValidRow(r)) { - TeXRow(os, r, runparams, columns); + TeXRow(os, r, runparams, columns, logical_columns); if (is_long_tabular && row_info[r].newpage) os << "\\newpage\n"; } @@ -3416,224 +3612,370 @@ void Tabular::latex(otexstream & os, OutputParams const & runparams) const } -int Tabular::docbookRow(odocstream & os, row_type row, - OutputParams const & runparams) const +std::string Tabular::getVAlignAsXmlAttribute(idx_type cell) const { - int ret = 0; - idx_type cell = getFirstCellInRow(row); + switch (getVAlignment(cell)) { + case LYX_VALIGN_TOP: + return "valign='top'"; + case LYX_VALIGN_BOTTOM: + return "valign='bottom'"; + case LYX_VALIGN_MIDDLE: + return "valign='middle'"; + default: + // This case only silences a compiler warning, as all the cases are covered above. + return ""; + } +} - os << "\n"; - for (col_type c = 0; c < ncols(); ++c) { - if (isPartOfMultiColumn(row, c)) - continue; - os << "decimalSeparator()) + "\""; + } + default: + return "text-align: center"; + } +} + + +Tabular::XmlRowWiseBorders Tabular::computeXmlBorders(row_type row) const +{ + Tabular::XmlRowWiseBorders borders; + + // Determine whether borders are required. + for (col_type c = 0; c < ncols(); ++c) { + if (row < nrows() - 1) { + if (!bottomLine(cellIndex(row, c)) + || !topLine(cellIndex(row + 1, c))) { + borders.completeBorder = false; + } + if (!bottomLine(cellIndex(row, c)) + && !topLine(cellIndex(row + 1, c))) { + borders.completeBorderBelow = false; + } + } else if (row == nrows() - 1 && !bottomLine(cellIndex(row, c))) { + borders.completeBorderBelow = false; } - os << '"'; - if (isMultiColumn(cell)) { - os << " namest=\"col" << c << "\" "; - os << "nameend=\"col" << c + columnSpan(cell) - 1 << '"'; + if ((row > 0 && !bottomLine(cellIndex(row - 1, c)) && !topLine(cellIndex(row, c))) || + (row == 0 && !topLine(cellIndex(row, c)))) { + borders.completeBorderAbove = false; } + } - os << '>'; - ret += cellInset(cell)->docbook(os, runparams); - os << "\n"; - ++cell; + // Size of booktabs borders. + if (use_booktabs) { + if (borders.completeBorderAbove) + borders.borderTopWidth = row == 0 ? 2 : 1.5; + if (borders.completeBorderBelow) { + borders.borderBottomWidth = row == nrows() - 1 ? 2 : 1.5; + borders.borderBottomWidthComplete = 3 * borders.borderBottomWidth; + } } - os << "\n"; - return ret; + + return borders; } -int Tabular::docbook(odocstream & os, OutputParams const & runparams) const +std::vector Tabular::computeCssStylePerCell(row_type row, col_type col, idx_type cell) const { - int ret = 0; + std::vector styles; - //+--------------------------------------------------------------------- - //+ first the opening preamble + - //+--------------------------------------------------------------------- + // Fixed width. + Length const col_width = column_info[col].p_width; + if (!col_width.zero()) + styles.emplace_back("width: " + col_width.asHTMLString()); - os << "\n"; + // Borders and booktabs. + const Tabular::XmlRowWiseBorders borders = computeXmlBorders(row); - for (col_type c = 0; c < ncols(); ++c) { - os << "\n"; - ++ret; + if (bottomLine(cell)) { + if (row < nrows() - 1 && borders.completeBorder) + styles.emplace_back("border-bottom: " + to_string(borders.borderBottomWidthComplete) + "px double"); + else + styles.emplace_back("border-bottom: " + to_string(borders.borderBottomWidth) + "px solid"); + } + if (rightLine(cell)) { + if (col < ncols() - 1 && leftLine(cell + 1)) + styles.emplace_back("border-right: 3px double"); + else + styles.emplace_back("border-right: 1px solid"); } + if (leftLine(cell)) + styles.emplace_back("border-left: 1px solid"); + if (topLine(cell)) + styles.emplace_back("border-top: " + to_string(borders.borderTopWidth) + "px solid"); - //+--------------------------------------------------------------------- - //+ Long Tabular case + - //+--------------------------------------------------------------------- + return styles; +} - // output caption info - // The caption flag wins over head/foot - if (haveLTCaption()) { - os << "\n"; - ++ret; - for (row_type r = 0; r < nrows(); ++r) { - if (row_info[r].caption) { - ret += docbookRow(os, r, runparams); - } + +docstring Tabular::xmlRow(XMLStream & xs, const row_type row, OutputParams const & runparams, + const bool header, const XmlOutputFormat output_format, BufferParams::TableOutput docbook_table_output) const +{ + docstring ret; + const bool is_xhtml_table = output_format == XmlOutputFormat::XHTML || + docbook_table_output == BufferParams::TableOutput::HTMLTable; + const bool is_cals_table = output_format == XmlOutputFormat::DOCBOOK && + docbook_table_output == BufferParams::TableOutput::CALSTable; + + std::string const row_tag = is_xhtml_table ? "tr" : "row"; + std::string const cell_tag = is_xhtml_table ? (header ? "th" : "td") : "entry"; + Tabular::XmlRowWiseBorders const borders = computeXmlBorders(row); + idx_type cell = getFirstCellInRow(row); + + std::string row_attr; + 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? + // CALS only: all cases where there should be a line *below* this row. + if (is_cals_table && (row_info[row].bottom_space_default || bottomLine(cell))) { + if (borders.completeBorderBelow) { + row_attr = "rowsep='1'"; + cals_row_has_rowsep = true; } - os << "\n"; - ++ret; } - // output header info - if (haveLTHead(false) || haveLTFirstHead(false)) { - os << "\n"; - ++ret; - for (row_type r = 0; r < nrows(); ++r) { - if ((row_info[r].endhead || row_info[r].endfirsthead) && - !row_info[r].caption) { - ret += docbookRow(os, r, runparams); + + xs << xml::StartTag(row_tag, row_attr); + xs << xml::CR(); + for (col_type c = 0; c < ncols(); ++c, ++cell) { + if (isPartOfMultiColumn(row, c) || isPartOfMultiRow(row, c)) + continue; + + stringstream attr; // Tag-level attributes, both for HTML and CALS. + stringstream style; // Tag-level CSS, only for HTML tables output in HTML. + + if (is_cals_table) { + if (!cals_row_has_rowsep && bottomLine(cell)) + attr << "rowsep='1' "; + } + + if (output_format == XmlOutputFormat::XHTML) { + // In HTML5, prefer to use CSS instead of attributes for alignment + // (align and valign). + style << getHAlignAsCssAttribute(cell) << "; " + << getVAlignAsCssAttribute(cell); + } else { + // In DocBook, both for HTML and CALS tables, stick to attributes. + attr << getHAlignAsXmlAttribute(cell) << " " + << getVAlignAsXmlAttribute(cell); + } + + if (is_xhtml_table) { + if (isMultiColumn(cell)) + attr << " colspan='" << columnSpan(cell) << "'"; + else if (isMultiRow(cell)) + attr << " rowspan='" << rowSpan(cell) << "'"; + } else if (is_cals_table) { + if (isMultiColumn(cell)) + attr << " namest='c" << c << " nameend='c" << (c + columnSpan(cell)) << "'"; + else if (isMultiRow(cell)) + attr << " morerows='" << rowSpan(cell) << "'"; + else + attr << " colname='c" << (c + 1) << "'"; // CALS column numbering starts at 1. + } + + // Final step: prepend the CSS style. + std::string attr_str = attr.str(); + if (is_xhtml_table) { + const std::vector styles = computeCssStylePerCell(row, c, cell); + + std::string attr_str_prefix = "style='" + style.str(); + if (!styles.empty()) + attr_str_prefix += "; "; + for (auto it = styles.begin(); it != styles.end(); ++it) { + attr_str_prefix += *it; + if (it != styles.end() - 1) + attr_str_prefix += "; "; } + attr_str_prefix += "' "; + + attr_str.insert(0, attr_str_prefix); } - os << "\n"; - ++ret; + + // Render the cell as either XHTML or DocBook. + xs << xml::StartTag(cell_tag, attr_str, true); + if (output_format == XmlOutputFormat::XHTML) { + ret += cellInset(cell)->xhtml(xs, runparams); + } else if (output_format == XmlOutputFormat::DOCBOOK) { + // DocBook: no return value for this function. + OutputParams rp = runparams; + rp.docbook_in_par = false; + rp.docbook_force_pars = true; + cellInset(cell)->docbook(xs, rp); + } + xs << xml::EndTag(cell_tag); + xs << xml::CR(); } - // output footer info - if (haveLTFoot(false) || haveLTLastFoot(false)) { - os << "\n"; - ++ret; + xs << xml::EndTag(row_tag); + xs << xml::CR(); + + return ret; +} + + +void Tabular::xmlHeader(XMLStream & xs, OutputParams const & runparams, const XmlOutputFormat output_format) const +{ + // Output the header of the table. For both HTML and CALS, this is surrounded by a thead. + bool const have_first_head = haveLTFirstHead(false); + // if we have a first head, then we are going to ignore the + // headers for the additional pages, since there aren't any + // in HTML or DocBook. + bool const have_head = !have_first_head && haveLTHead(false); + + if (have_head || have_first_head) { + xs << xml::StartTag("thead") << xml::CR(); for (row_type r = 0; r < nrows(); ++r) { - if ((row_info[r].endfoot || row_info[r].endlastfoot) && + if (((have_first_head && row_info[r].endfirsthead) || + (have_head && row_info[r].endhead)) && !row_info[r].caption) { - ret += docbookRow(os, r, runparams); + xmlRow(xs, r, runparams, true, output_format, buffer().params().docbook_table_output); } } - os << "\n"; - ++ret; + xs << xml::EndTag("thead"); + xs << xml::CR(); } +} - //+--------------------------------------------------------------------- - //+ the single row and columns (cells) + - //+--------------------------------------------------------------------- - os << "\n"; - ++ret; - for (row_type r = 0; r < nrows(); ++r) { - if (isValidRow(r)) { - ret += docbookRow(os, r, runparams); +void Tabular::xmlFooter(XMLStream & xs, OutputParams const & runparams, const XmlOutputFormat output_format) const +{ + // Output the footer of the table. For both HTML and CALS, this is surrounded by a tfoot and output just after + // the header (and before the body). + bool const have_last_foot = haveLTLastFoot(false); + bool const have_foot = !have_last_foot && haveLTFoot(false); + + if (have_foot || have_last_foot) { + xs << xml::StartTag("tfoot") << xml::CR(); + for (row_type r = 0; r < nrows(); ++r) { + if (((have_last_foot && row_info[r].endlastfoot) || + (have_foot && row_info[r].endfoot)) && + !row_info[r].caption) { + xmlRow(xs, r, runparams, false, output_format, buffer().params().docbook_table_output); + } } + xs << xml::EndTag("tfoot"); + xs << xml::CR(); } - os << "\n"; - ++ret; - //+--------------------------------------------------------------------- - //+ the closing of the tabular + - //+--------------------------------------------------------------------- +} - os << ""; - ++ret; - return ret; +void Tabular::xmlBody(XMLStream & xs, OutputParams const & runparams, const XmlOutputFormat output_format) const +{ + // Output the main part of the table. The tbody container is mandatory for CALS, but optional for HTML (only if + // there is no header and no footer). It never hurts to have it, though. + xs << xml::StartTag("tbody"); + xs << xml::CR(); + for (row_type r = 0; r < nrows(); ++r) + if (isValidRow(r)) + xmlRow(xs, r, runparams, false, output_format, buffer().params().docbook_table_output); + xs << xml::EndTag("tbody"); + xs << xml::CR(); } -docstring Tabular::xhtmlRow(XHTMLStream & xs, row_type row, - OutputParams const & runparams, bool header) const +void Tabular::docbook(XMLStream & xs, OutputParams const & runparams) const { - docstring ret; - string const celltag = header ? "th" : "td"; - idx_type cell = getFirstCellInRow(row); + // Some tables are inline. Likely limitation: cannot output a table within a table; is that really a limitation? + if (!runparams.docbook_in_table) { // Check on the *outer* set of parameters, so that the table can be closed + // properly at the end of this function. + xs << xml::StartTag("informaltable"); + xs << xml::CR(); + } - xs << html::StartTag("tr"); - for (col_type c = 0; c < ncols(); ++c) { - if (isPartOfMultiColumn(row, c) || isPartOfMultiRow(row, c)) - continue; + // "Formal" tables have a title and use the tag ; the distinction with is done outside. + // HTML has the caption first with titles forbidden, and CALS has a title first. + if (haveLTCaption()) { + std::string caption_tag = ((buffer().params().docbook_table_output) == BufferParams::HTMLTable) ? "caption" : "title"; - stringstream attr; + xs << xml::StartTag(caption_tag); + for (row_type r = 0; r < nrows(); ++r) + if (row_info[r].caption) + xmlRow(xs, r, runparams, false, XmlOutputFormat::DOCBOOK, buffer().params().docbook_table_output); + xs << xml::EndTag(caption_tag); + xs << xml::CR(); + } - Length const cwidth = column_info[c].p_width; - if (!cwidth.zero()) { - string const hwidth = cwidth.asHTMLString(); - attr << "style =\"width: " << hwidth << ";\" "; - } + // CALS header: describe all columns in this table. For names, take 'c' then the ID of the column. + // Start at one, as is customary with CALS! + if (buffer().params().docbook_table_output == BufferParams::CALSTable) { + for (col_type c = 0; c < ncols(); ++c) { + std::stringstream attr; + attr << "colnum='" << (c + 1) << "' "; + attr << "colname='c" << (c + 1) << "' "; + Length const cwidth = column_info[c].p_width; + if (!cwidth.zero()) + attr << "colwidth='" << cwidth.asHTMLString() << "' "; + attr << "rowheader='norowheader'"; // Last attribute, hence no space at the end. - attr << "align='"; - switch (getAlignment(cell)) { - case LYX_ALIGN_LEFT: - attr << "left"; - break; - case LYX_ALIGN_RIGHT: - attr << "right"; - break; - default: - attr << "center"; - break; - } - attr << "'"; - attr << " valign='"; - switch (getVAlignment(cell)) { - case LYX_VALIGN_TOP: - attr << "top"; - break; - case LYX_VALIGN_BOTTOM: - attr << "bottom"; - break; - case LYX_VALIGN_MIDDLE: - attr << "middle"; + xs << xml::CompTag("colspec", attr.str()); + xs << xml::CR(); } - attr << "'"; + } - if (isMultiColumn(cell)) - attr << " colspan='" << columnSpan(cell) << "'"; - else if (isMultiRow(cell)) - attr << " rowspan='" << rowSpan(cell) << "'"; + xmlHeader(xs, runparams, XmlOutputFormat::DOCBOOK); + xmlFooter(xs, runparams, XmlOutputFormat::DOCBOOK); + xmlBody(xs, runparams, XmlOutputFormat::DOCBOOK); - xs << html::StartTag(celltag, attr.str(), true) << html::CR(); - ret += cellInset(cell)->xhtml(xs, runparams); - xs << html::EndTag(celltag) << html::CR(); - ++cell; + // If this method started the table tag, also make it close it. + if (!runparams.docbook_in_table) { + xs << xml::EndTag("informaltable"); + xs << xml::CR(); } - xs << html::EndTag("tr"); - return ret; } -docstring Tabular::xhtml(XHTMLStream & xs, OutputParams const & runparams) const +docstring Tabular::xhtml(XMLStream & xs, OutputParams const & runparams) const { docstring ret; if (is_long_tabular) { - // we'll wrap it in a div, so as to deal with alignment + // We'll wrap it in a div to deal with alignment. string align; switch (longtabular_alignment) { case LYX_LONGTABULAR_ALIGN_LEFT: @@ -3646,66 +3988,34 @@ docstring Tabular::xhtml(XHTMLStream & xs, OutputParams const & runparams) const align = "right"; break; } - xs << html::StartTag("div", "class='longtable' style='text-align: " + align + ";'") - << html::CR(); - // The caption flag wins over head/foot + xs << xml::StartTag("div", "class='longtable' style='text-align: " + align + ";'"); + xs << xml::CR(); + + // The caption flag is output before header/footer. if (haveLTCaption()) { - xs << html::StartTag("div", "class='longtable-caption' style='text-align: " + align + ";'") - << html::CR(); + xs << xml::StartTag("div", "class='longtable-caption' style='text-align: " + align + ";'"); + xs << xml::CR(); for (row_type r = 0; r < nrows(); ++r) if (row_info[r].caption) - ret += xhtmlRow(xs, r, runparams); - xs << html::EndTag("div") << html::CR(); + ret += xmlRow(xs, r, runparams, false, XmlOutputFormat::XHTML); + xs << xml::EndTag("div"); + xs << xml::CR(); } } - xs << html::StartTag("table") << html::CR(); + xs << xml::StartTag("table"); + xs << xml::CR(); - // output header info - bool const havefirsthead = haveLTFirstHead(false); - // if we have a first head, then we are going to ignore the - // headers for the additional pages, since there aren't any - // in XHTML. this test accomplishes that. - bool const havehead = !havefirsthead && haveLTHead(false); - if (havehead || havefirsthead) { - xs << html::StartTag("thead") << html::CR(); - for (row_type r = 0; r < nrows(); ++r) { - if (((havefirsthead && row_info[r].endfirsthead) || - (havehead && row_info[r].endhead)) && - !row_info[r].caption) { - ret += xhtmlRow(xs, r, runparams, true); - } - } - xs << html::EndTag("thead") << html::CR(); - } - // output footer info - bool const havelastfoot = haveLTLastFoot(false); - // as before. - bool const havefoot = !havelastfoot && haveLTFoot(false); - if (havefoot || havelastfoot) { - xs << html::StartTag("tfoot") << html::CR(); - for (row_type r = 0; r < nrows(); ++r) { - if (((havelastfoot && row_info[r].endlastfoot) || - (havefoot && row_info[r].endfoot)) && - !row_info[r].caption) { - ret += xhtmlRow(xs, r, runparams); - } - } - xs << html::EndTag("tfoot") << html::CR(); - } + xmlHeader(xs, runparams, XmlOutputFormat::XHTML); + xmlFooter(xs, runparams, XmlOutputFormat::XHTML); + xmlBody(xs, runparams, XmlOutputFormat::XHTML); - xs << html::StartTag("tbody") << html::CR(); - for (row_type r = 0; r < nrows(); ++r) { - if (isValidRow(r)) { - ret += xhtmlRow(xs, r, runparams); - } + xs << xml::EndTag("table"); + xs << xml::CR(); + if (is_long_tabular) { + xs << xml::EndTag("div"); + xs << xml::CR(); } - xs << html::EndTag("tbody") - << html::CR() - << html::EndTag("table") - << html::CR(); - if (is_long_tabular) - xs << html::EndTag("div") << html::CR(); return ret; } @@ -3971,8 +4281,10 @@ void Tabular::validate(LaTeXFeatures & features) const for (idx_type cell = 0; cell < numberofcells; ++cell) { if (isMultiRow(cell)) features.require("multirow"); - if (getUsebox(cell) == BOX_VARWIDTH) + if (getUsebox(cell) == BOX_VARWIDTH) { features.require("varwidth"); + features.require("cellvarwidth"); + } if (getVAlignment(cell) != LYX_VALIGN_TOP || !getPWidth(cell).zero() || isVTypeColumn(cellColumn(cell))) @@ -4009,6 +4321,21 @@ Tabular::BoxType Tabular::useBox(idx_type cell) const } +bool Tabular::hasNewlines(idx_type cell) const +{ + ParagraphList const & parlist = cellInset(cell)->paragraphs(); + ParagraphList::const_iterator cit = parlist.begin(); + ParagraphList::const_iterator end = parlist.end(); + + for (; cit != end; ++cit) + for (int i = 0; i < cit->size(); ++i) + if (cit->isNewline(i)) + return true; + + return false; +} + + ///////////////////////////////////////////////////////////////////// // // InsetTableCell @@ -4016,17 +4343,10 @@ Tabular::BoxType Tabular::useBox(idx_type cell) const ///////////////////////////////////////////////////////////////////// InsetTableCell::InsetTableCell(Buffer * buf) - : InsetText(buf, InsetText::PlainLayout), isFixedWidth(false), + : InsetText(buf, InsetText::PlainLayout), isFixedWidth(false), isVarwidth(false), isMultiColumn(false), isMultiRow(false), contentAlign(LYX_ALIGN_CENTER) {} - -bool InsetTableCell::forcePlainLayout(idx_type) const -{ - return isMultiRow || (isMultiColumn && !isFixedWidth); -} - - bool InsetTableCell::allowParagraphCustomization(idx_type) const { return isFixedWidth; @@ -4044,6 +4364,7 @@ bool InsetTableCell::getStatus(Cursor & cur, FuncRequest const & cmd, { bool enabled = true; switch (cmd.action()) { + case LFUN_INSET_SPLIT: case LFUN_INSET_DISSOLVE: enabled = false; break; @@ -4085,7 +4406,7 @@ void InsetTableCell::addToToc(DocIterator const & di, bool output_active, } -docstring InsetTableCell::xhtml(XHTMLStream & xs, OutputParams const & rp) const +docstring InsetTableCell::xhtml(XMLStream & xs, OutputParams const & rp) const { if (!isFixedWidth) return InsetText::insetAsXHTML(xs, rp, InsetText::JustText); @@ -4093,13 +4414,20 @@ docstring InsetTableCell::xhtml(XHTMLStream & xs, OutputParams const & rp) const } +void InsetTableCell::docbook(XMLStream & xs, OutputParams const & runparams) const +{ + InsetText::docbook(xs, runparams); +} + + void InsetTableCell::metrics(MetricsInfo & mi, Dimension & dim) const { TextMetrics & tm = mi.base.bv->textMetrics(&text()); + int const horiz_offset = leftOffset(mi.base.bv) + rightOffset(mi.base.bv); // Hand font through to contained lyxtext: tm.font_.fontInfo() = mi.base.font; - mi.base.textwidth -= 2 * TEXT_TO_INSET_OFFSET; + mi.base.textwidth -= horiz_offset; // This can happen when a layout has a left and right margin, // and the view is made very narrow. We can't do better than @@ -4109,18 +4437,18 @@ void InsetTableCell::metrics(MetricsInfo & mi, Dimension & dim) const // We tell metrics here not to expand on multiple pars // This is the difference to InsetText::Metrics + Changer changetight = changeVar(mi.tight_insets, true); if (hasFixedWidth()) - tm.metrics(mi, dim, mi.base.textwidth, false); + tm.metrics(mi, dim, mi.base.textwidth); else - tm.metrics(mi, dim, 0, false); - mi.base.textwidth += 2 * TEXT_TO_INSET_OFFSET; - dim.asc += TEXT_TO_INSET_OFFSET; - dim.des += TEXT_TO_INSET_OFFSET; - dim.wid += 2 * TEXT_TO_INSET_OFFSET; + tm.metrics(mi, dim, 0); + mi.base.textwidth += horiz_offset; + dim.asc += topOffset(mi.base.bv); + dim.des += bottomOffset(mi.base.bv); + dim.wid += horiz_offset; } - ///////////////////////////////////////////////////////////////////// // // InsetTabular @@ -4173,6 +4501,18 @@ bool InsetTabular::insetAllowed(InsetCode code) const } +bool InsetTabular::allowMultiPar() const +{ + for (col_type c = 0; c < tabular.ncols(); ++c) { + for (row_type r = 0; r < tabular.nrows(); ++r) { + if (tabular.cellInset(r,c)->allowMultiPar()) + return true; + } + } + return false; +} + + bool InsetTabular::allowsCaptionVariation(std::string const & newtype) const { return tabular.is_long_tabular && @@ -4280,7 +4620,7 @@ void InsetTabular::metrics(MetricsInfo & mi, Dimension & dim) const // determine horizontal offset because of decimal align (if necessary) int decimal_width = 0; if (tabular.getAlignment(cell) == LYX_ALIGN_DECIMAL) { - InsetTableCell tail = InsetTableCell(*tabular.cellInset(cell)); + InsetTableCell tail = *tabular.cellInset(cell); tail.setBuffer(tabular.buffer()); // we need to set macrocontext position everywhere // otherwise we crash with nested insets (e.g. footnotes) @@ -4294,7 +4634,7 @@ void InsetTabular::metrics(MetricsInfo & mi, Dimension & dim) const docstring const align_d = tabular.column_info[c].decimal_point; dit = separatorPos(&tail, align_d); - pit_type const psize = tail.paragraphs().front().size(); + pos_type const psize = tail.paragraphs().front().size(); if (dit) { tail.paragraphs().front().eraseChars(0, dit.pos() < psize ? dit.pos() + 1 : psize, false); @@ -4306,10 +4646,11 @@ void InsetTabular::metrics(MetricsInfo & mi, Dimension & dim) const tabular.cell_info[r][c].decimal_hoffset = tm.width() - decimal_width; tabular.cell_info[r][c].decimal_width = decimal_width; - // with LYX_VALIGN_BOTTOM the descent is relative to the last par - // = descent of text in last par + TEXT_TO_INSET_OFFSET: - int const lastpardes = tm.last().second->descent() - + TEXT_TO_INSET_OFFSET; + // with LYX_VALIGN_BOTTOM the descent is relative to the last + // row of the last par (note that the par might have multile rows!) + // = descent of text in last row + bottomOffset: + int const lastpardes = tm.last().second->rows().back().descent() + + bottomOffset(mi.base.bv); int offset = 0; switch (tabular.getVAlignment(cell)) { case Tabular::LYX_VALIGN_TOP: @@ -4389,6 +4730,9 @@ void InsetTabular::draw(PainterInfo & pi, int x, int y) const bool const original_selection_state = pi.selected; idx_type idx = 0; + + // Save tabular change status + Change tab_change = pi.change; int yy = y + tabular.offsetVAlignment(); for (row_type r = 0; r < tabular.nrows(); ++r) { @@ -4405,6 +4749,15 @@ void InsetTabular::draw(PainterInfo & pi, int x, int y) const } pi.selected |= isCellSelected(cur, r, c); + + // Mark deleted rows/columns + if (tabular.column_info[c].change.changed()) + pi.change = tabular.column_info[c].change; + else if (tabular.row_info[r].change.changed()) + pi.change = tabular.row_info[r].change; + else + pi.change = tab_change; + int const cx = nx + tabular.textHOffset(idx); int const cy = yy + tabular.textVOffset(idx); // Cache the Inset position. @@ -4486,9 +4839,9 @@ void InsetTabular::drawSelection(PainterInfo & pi, int x, int y) const namespace { void tabline(PainterInfo const & pi, int x1, int y1, int x2, int y2, int lt, int rt, - bool drawline, bool heavy = false) + Color const incol, bool drawline, bool heavy = false) { - ColorCode const col = drawline ? Color_tabularline : Color_tabularonoffline; + Color const col = drawline ? incol : Color_tabularonoffline; if (drawline && lt > 0) pi.pain.line(x1, y1, x1 + lt, y2, pi.textColor(Color_tabularonoffline), Painter::line_onoffdash, @@ -4516,6 +4869,12 @@ void InsetTabular::drawCellLines(PainterInfo & pi, int x, int y, col_type const col = tabular.cellColumn(cell); + // Colour the frame if rows/columns are added or deleted + Color colour = Color_tabularline; + if (tabular.column_info[col].change.changed() + || tabular.row_info[row].change.changed()) + colour = tabular.cellInset(cell)->paragraphs().front().lookupChange(0).color(); + // Top bool drawline = tabular.topLine(cell) || (row > 0 && tabular.bottomLine(tabular.cellAbove(cell))); @@ -4528,7 +4887,7 @@ void InsetTabular::drawCellLines(PainterInfo & pi, int x, int y, if (tabular.topLineTrim(cell).second || (row > 0 && tabular.bottomLineTrim(tabular.cellIndex(row - 1, col)).second)) rt = 10; - tabline(pi, x, y, x + w, y, lt, rt, drawline, heavy); + tabline(pi, x, y, x + w, y, lt, rt, colour, drawline, heavy); // Bottom lt = rt = 0; @@ -4546,12 +4905,12 @@ void InsetTabular::drawCellLines(PainterInfo & pi, int x, int y, lt = 10; if (tabular.bottomLineTrim(cell).second) rt = 10; - tabline(pi, x, y + h, x + w, y + h, lt, rt, drawline, heavy); + tabline(pi, x, y + h, x + w, y + h, lt, rt, colour, drawline, heavy); // Left drawline = tabular.leftLine(cell) || (col > 0 && tabular.rightLine(tabular.cellIndex(row, col - 1))); - tabline(pi, x, y, x, y + h, 0, 0, drawline); + tabline(pi, x, y, x, y + h, 0, 0, colour, drawline); // Right x -= tabular.interColumnSpace(cell); @@ -4562,7 +4921,7 @@ void InsetTabular::drawCellLines(PainterInfo & pi, int x, int y, drawline = tabular.rightLine(cell) || (next_cell_col < tabular.ncols() && tabular.leftLine(tabular.cellIndex(row, next_cell_col))); - tabline(pi, x + w, y, x + w, y + h, 0, 0, drawline); + tabline(pi, x + w, y, x + w, y + h, 0, 0, colour, drawline); } @@ -4592,7 +4951,7 @@ void InsetTabular::edit(Cursor & cur, bool front, EntryDirection) } -void InsetTabular::updateBuffer(ParIterator const & it, UpdateType utype) +void InsetTabular::updateBuffer(ParIterator const & it, UpdateType utype, bool const /*deleted*/) { // In a longtable, tell captions what the current float is Counters & cnts = buffer().masterBuffer()->params().documentClass().counters(); @@ -4750,7 +5109,9 @@ void InsetTabular::doDispatch(Cursor & cur, FuncRequest & cmd) if (bvcur.selIsMultiCell()) { bvcur.pit() = bvcur.lastpit(); bvcur.pos() = bvcur.lastpos(); - } + } else + // Let InsetTableCell do it + cell(cur.idx())->dispatch(cur, cmd); } break; @@ -5000,6 +5361,13 @@ void InsetTabular::doDispatch(Cursor & cur, FuncRequest & cmd) } break; + case LFUN_COPY: + if (cur.selIsMultiCell()) + copySelection(cur); + else + cell(cur.idx())->dispatch(cur, cmd); + break; + case LFUN_CUT: if (cur.selIsMultiCell()) { if (copySelection(cur)) { @@ -5035,16 +5403,6 @@ void InsetTabular::doDispatch(Cursor & cur, FuncRequest & cmd) cell(cur.idx())->dispatch(cur, cmd); break; - case LFUN_COPY: - if (!cur.selection()) - break; - if (cur.selIsMultiCell()) { - cur.finishUndo(); - copySelection(cur); - } else - cell(cur.idx())->dispatch(cur, cmd); - break; - case LFUN_CLIPBOARD_PASTE: case LFUN_PRIMARY_SELECTION_PASTE: { docstring const clip = (act == LFUN_CLIPBOARD_PASTE) ? @@ -5059,8 +5417,7 @@ void InsetTabular::doDispatch(Cursor & cur, FuncRequest & cmd) if (insertPlaintextString(cur.bv(), clip, false)) { // content has been replaced, // so cursor might be invalid - cur.pos() = cur.lastpos(); - cur.pit() = cur.lastpit(); + cur.fixIfBroken(); bvcur.setCursor(cur); break; } @@ -5084,6 +5441,17 @@ void InsetTabular::doDispatch(Cursor & cur, FuncRequest & cmd) break; } } + else if (!theClipboard().isInternal() + && theClipboard().hasTextContents(Clipboard::LyXTextType)) { + // This might be tabular data from another LyX instance. Check! + docstring const clip = + theClipboard().getAsText(Clipboard::PlainTextType); + if (clip.find_first_of(from_ascii("\t\n")) != docstring::npos) { + FuncRequest ncmd = FuncRequest(LFUN_CLIPBOARD_PASTE, cmd.argument()); + doDispatch(cur, ncmd); + break; + } + } if (!cur.selIsMultiCell()) cell(cur.idx())->dispatch(cur, cmd); break; @@ -5096,6 +5464,7 @@ void InsetTabular::doDispatch(Cursor & cur, FuncRequest & cmd) case LFUN_CHANGE_ACCEPT: case LFUN_CHANGE_REJECT: + case LFUN_FONT_DEFAULT: case LFUN_FONT_EMPH: case LFUN_FONT_BOLD: case LFUN_FONT_BOLDSYMBOL: @@ -5130,17 +5499,17 @@ void InsetTabular::doDispatch(Cursor & cur, FuncRequest & cmd) if (ct && cs == 0 && ce == tabular.ncols() - 1) { // whole row selected if (act == LFUN_CHANGE_ACCEPT) { - if (tabular.row_info[r].change == Change::INSERTED) - tabular.row_info[r].change = Change::UNCHANGED; - else if (tabular.row_info[r].change == Change::DELETED) { + if (tabular.row_info[r].change.inserted()) + tabular.row_info[r].change.setUnchanged(); + else if (tabular.row_info[r].change.deleted()) { tabular.deleteRow(r, true); --re; continue; } } else { - if (tabular.row_info[r].change == Change::DELETED) - tabular.row_info[r].change = Change::UNCHANGED; - else if (tabular.row_info[r].change == Change::INSERTED) { + if (tabular.row_info[r].change.deleted()) + tabular.row_info[r].change.setUnchanged(); + else if (tabular.row_info[r].change.inserted()) { tabular.deleteRow(r, true); --re; continue; @@ -5151,17 +5520,17 @@ void InsetTabular::doDispatch(Cursor & cur, FuncRequest & cmd) if (ct && rs == 0 && re == tabular.nrows() - 1) { // whole col selected if (act == LFUN_CHANGE_ACCEPT) { - if (tabular.column_info[c].change == Change::INSERTED) - tabular.column_info[c].change = Change::UNCHANGED; - else if (tabular.column_info[c].change == Change::DELETED) { + if (tabular.column_info[c].change.inserted()) + tabular.column_info[c].change.setUnchanged(); + else if (tabular.column_info[c].change.deleted()) { tabular.deleteColumn(c, true); --ce; continue; } } else { - if (tabular.column_info[c].change == Change::DELETED) - tabular.column_info[c].change = Change::UNCHANGED; - else if (tabular.column_info[c].change == Change::INSERTED) { + if (tabular.column_info[c].change.deleted()) + tabular.column_info[c].change.setUnchanged(); + else if (tabular.column_info[c].change.inserted()) { tabular.deleteColumn(c, true); --ce; continue; @@ -5182,8 +5551,12 @@ void InsetTabular::doDispatch(Cursor & cur, FuncRequest & cmd) } } if (ct) { + tabular.updateIndexes(); // cursor might be invalid cur.fixIfBroken(); + // change bar might need to be redrawn + cur.screenUpdateFlags(Update::Force); + cur.forceBufferUpdate(); } break; } else { @@ -5196,7 +5569,7 @@ void InsetTabular::doDispatch(Cursor & cur, FuncRequest & cmd) case LFUN_CHANGE_PREVIOUS: { // BufferView::dispatch has already moved the cursor, we just // need to select here if we have a changed row or column - if (tabular.row_info[tabular.cellRow(cur.idx())].change != Change::UNCHANGED) { + if (tabular.row_info[tabular.cellRow(cur.idx())].change.changed()) { // select row cur.idx() = tabular.getFirstCellInRow(tabular.cellRow(cur.idx())); cur.pit() = 0; @@ -5209,7 +5582,7 @@ void InsetTabular::doDispatch(Cursor & cur, FuncRequest & cmd) bvcur = cur; rowselect_ = true; } - else if (tabular.column_info[tabular.cellColumn(cur.idx())].change != Change::UNCHANGED) { + else if (tabular.column_info[tabular.cellColumn(cur.idx())].change.changed()) { // select column cur.idx() = tabular.cellIndex(0, tabular.cellColumn(cur.idx())); cur.pit() = 0; @@ -5273,8 +5646,6 @@ bool InsetTabular::getFeatureStatus(Cursor & cur, string const & s, case Tabular::SET_SPECIAL_MULTICOLUMN: case Tabular::APPEND_ROW: case Tabular::APPEND_COLUMN: - case Tabular::DELETE_ROW: - case Tabular::DELETE_COLUMN: case Tabular::COPY_ROW: case Tabular::COPY_COLUMN: case Tabular::SET_TOP_SPACE: @@ -5283,6 +5654,13 @@ bool InsetTabular::getFeatureStatus(Cursor & cur, string const & s, status.clear(); return true; + case Tabular::DELETE_ROW: + status.setEnabled(tabular.nrows() > 1); + break; + case Tabular::DELETE_COLUMN: + status.setEnabled(tabular.ncols() > 1); + break; + case Tabular::SET_TABULAR_WIDTH: status.setEnabled(!tabular.rotate && tabular.tabular_valignment == Tabular::LYX_VALIGN_MIDDLE); @@ -5292,43 +5670,57 @@ bool InsetTabular::getFeatureStatus(Cursor & cur, string const & s, case Tabular::MOVE_COLUMN_LEFT: case Tabular::MOVE_ROW_DOWN: case Tabular::MOVE_ROW_UP: { - if (cur.selection()) { - status.message(_("Selections not supported.")); - status.setEnabled(false); - break; + row_type rs, re; + col_type cs, ce; + if (cur.selIsMultiCell()) + getSelection(cur, rs, re, cs, ce); + else { + rs = tabular.cellRow(cur.idx()); + re = rs; + cs = tabular.cellColumn(cur.idx()); + ce = cs; } - - if ((action == Tabular::MOVE_COLUMN_RIGHT && - tabular.ncols() == tabular.cellColumn(cur.idx()) + 1) || - (action == Tabular::MOVE_COLUMN_LEFT && - tabular.cellColumn(cur.idx()) == 0) || - (action == Tabular::MOVE_ROW_DOWN && - tabular.nrows() == tabular.cellRow(cur.idx()) + 1) || - (action == Tabular::MOVE_ROW_UP && - tabular.cellRow(cur.idx()) == 0)) { + if ((action == Tabular::MOVE_COLUMN_RIGHT + && tabular.ncols() == ce + 1) + || (action == Tabular::MOVE_COLUMN_LEFT && cs == 0) + || (action == Tabular::MOVE_ROW_DOWN + && tabular.nrows() == re + 1) + || (action == Tabular::MOVE_ROW_UP && rs == 0)) { status.setEnabled(false); break; } - if (action == Tabular::MOVE_COLUMN_RIGHT || - action == Tabular::MOVE_COLUMN_LEFT) { - if (tabular.hasMultiColumn(tabular.cellColumn(cur.idx())) || - tabular.hasMultiColumn(tabular.cellColumn(cur.idx()) + - (action == Tabular::MOVE_COLUMN_RIGHT ? 1 : -1))) { - status.message(_("Multi-column in current or" - " destination column.")); + if (action == Tabular::MOVE_COLUMN_RIGHT + || action == Tabular::MOVE_COLUMN_LEFT) { + bool has_multicol = (action == Tabular::MOVE_COLUMN_RIGHT) + ? tabular.hasMultiColumn(ce + 1) + : tabular.hasMultiColumn(cs - 1); + for (col_type c = cs; c <= ce; ++c) { + if (tabular.hasMultiColumn(c)) { + has_multicol = true; + break; + } + } + if (has_multicol) { + status.message(_("Column movement not supported with multi-columns.")); status.setEnabled(false); break; } } - if (action == Tabular::MOVE_ROW_DOWN || - action == Tabular::MOVE_ROW_UP) { - if (tabular.hasMultiRow(tabular.cellRow(cur.idx())) || - tabular.hasMultiRow(tabular.cellRow(cur.idx()) + - (action == Tabular::MOVE_ROW_DOWN ? 1 : -1))) { - status.message(_("Multi-row in current or" - " destination row.")); + if (action == Tabular::MOVE_ROW_DOWN + || action == Tabular::MOVE_ROW_UP) { + bool has_multirow = (action == Tabular::MOVE_ROW_DOWN) + ? tabular.hasMultiRow(re + 1) + : tabular.hasMultiRow(rs - 1); + for (row_type r = rs; r <= re; ++r) { + if (tabular.hasMultiRow(r)) { + has_multirow = true; + break; + } + } + if (has_multirow) { + status.message(_("Row movement not supported with multi-rows.")); status.setEnabled(false); break; } @@ -5363,9 +5755,28 @@ bool InsetTabular::getFeatureStatus(Cursor & cur, string const & s, status.setOnOff(tabular.isMultiRow(cur.idx())); break; + case Tabular::TOGGLE_INNER_LINES: + status.setOnOff(tabular.innerBorders(sel_row_start, sel_row_end, + sel_col_start, sel_col_end)); + status.setEnabled(!tabular.ltCaption(tabular.cellRow(cur.idx()))); + break; + case Tabular::TOGGLE_ALL_LINES: + status.setOnOff(tabular.innerBorders(sel_row_start, sel_row_end, + sel_col_start, sel_col_end) + && tabular.outsideBorders(sel_row_start, sel_row_end, + sel_col_start, sel_col_end)); + status.setEnabled(!tabular.ltCaption(tabular.cellRow(cur.idx()))); + break; case Tabular::SET_ALL_LINES: case Tabular::UNSET_ALL_LINES: case Tabular::SET_INNER_LINES: + status.setEnabled(!tabular.ltCaption(tabular.cellRow(cur.idx()))); + break; + + case Tabular::TOGGLE_BORDER_LINES: + status.setOnOff(tabular.outsideBorders(sel_row_start, sel_row_end, + sel_col_start, sel_col_end)); + // fall through case Tabular::SET_BORDER_LINES: status.setEnabled(!tabular.ltCaption(tabular.cellRow(cur.idx()))); break; @@ -5489,8 +5900,8 @@ bool InsetTabular::getFeatureStatus(Cursor & cur, string const & s, flag = false; // fall through case Tabular::VALIGN_TOP: - status.setEnabled(!tabular.getPWidth(cur.idx()).zero() - && !tabular.isMultiRow(cur.idx())); + status.setEnabled((!tabular.getPWidth(cur.idx()).zero() + || tabular.getUsebox(cur.idx()) == Tabular::BOX_VARWIDTH)); status.setOnOff( tabular.getVAlignment(cur.idx(), flag) == Tabular::LYX_VALIGN_TOP); break; @@ -5499,8 +5910,8 @@ bool InsetTabular::getFeatureStatus(Cursor & cur, string const & s, flag = false; // fall through case Tabular::VALIGN_BOTTOM: - status.setEnabled(!tabular.getPWidth(cur.idx()).zero() - && !tabular.isMultiRow(cur.idx())); + status.setEnabled((!tabular.getPWidth(cur.idx()).zero() + || tabular.getUsebox(cur.idx()) == Tabular::BOX_VARWIDTH)); status.setOnOff( tabular.getVAlignment(cur.idx(), flag) == Tabular::LYX_VALIGN_BOTTOM); break; @@ -5509,8 +5920,8 @@ bool InsetTabular::getFeatureStatus(Cursor & cur, string const & s, flag = false; // fall through case Tabular::VALIGN_MIDDLE: - status.setEnabled(!tabular.getPWidth(cur.idx()).zero() - && !tabular.isMultiRow(cur.idx())); + status.setEnabled((!tabular.getPWidth(cur.idx()).zero() + || tabular.getUsebox(cur.idx()) == Tabular::BOX_VARWIDTH)); status.setOnOff( tabular.getVAlignment(cur.idx(), flag) == Tabular::LYX_VALIGN_MIDDLE); break; @@ -5518,8 +5929,8 @@ bool InsetTabular::getFeatureStatus(Cursor & cur, string const & s, case Tabular::SET_LONGTABULAR: case Tabular::TOGGLE_LONGTABULAR: // setting as longtable is not allowed when table is inside a float - if (cur.innerInsetOfType(FLOAT_CODE) != 0 - || cur.innerInsetOfType(WRAP_CODE) != 0) + if (cur.innerInsetOfType(FLOAT_CODE) != nullptr + || cur.innerInsetOfType(WRAP_CODE) != nullptr) status.setEnabled(false); else status.setEnabled(true); @@ -5729,7 +6140,7 @@ bool InsetTabular::getStatus(Cursor & cur, FuncRequest const & cmd, } // check if there is already a caption bool have_caption = false; - InsetTableCell itc = InsetTableCell(*tabular.cellInset(cur.idx())); + InsetTableCell itc = *tabular.cellInset(cur.idx()); ParagraphList::const_iterator pit = itc.paragraphs().begin(); ParagraphList::const_iterator pend = itc.paragraphs().end(); for (; pit != pend; ++pit) { @@ -5787,23 +6198,23 @@ bool InsetTabular::getStatus(Cursor & cur, FuncRequest const & cmd, col_type cs, ce; getSelection(cur, rs, re, cs, ce); for (row_type r = rs; r <= re; ++r) { - if (tabular.row_info[r].change != Change::UNCHANGED) { + if (tabular.row_info[r].change.changed()) { status.setEnabled(true); return true; } for (col_type c = cs; c <= ce; ++c) { - if (tabular.column_info[c].change != Change::UNCHANGED) { + if (tabular.column_info[c].change.changed()) { status.setEnabled(true); return true; } } } } else { - if (tabular.row_info[tabular.cellRow(cur.idx())].change != Change::UNCHANGED) { + if (tabular.row_info[tabular.cellRow(cur.idx())].change.changed()) { status.setEnabled(true); return true; } - else if (tabular.column_info[tabular.cellColumn(cur.idx())].change != Change::UNCHANGED) { + else if (tabular.column_info[tabular.cellColumn(cur.idx())].change.changed()) { status.setEnabled(true); return true; } @@ -5811,20 +6222,8 @@ bool InsetTabular::getStatus(Cursor & cur, FuncRequest const & cmd, return cell(cur.idx())->getStatus(cur, cmd, status); } - // disable in non-fixed-width cells case LFUN_PARAGRAPH_BREAK: - // multirow does not allow paragraph breaks - if (tabular.isMultiRow(cur.idx())) { - status.setEnabled(false); - return true; - } - // fall through case LFUN_NEWLINE_INSERT: - if ((tabular.isMultiColumn(cur.idx()) || tabular.isMultiRow(cur.idx())) - && tabular.getPWidth(cur.idx()).zero()) { - status.setEnabled(false); - return true; - } return cell(cur.idx())->getStatus(cur, cmd, status); case LFUN_NEWPAGE_INSERT: @@ -5862,21 +6261,21 @@ bool InsetTabular::getStatus(Cursor & cur, FuncRequest const & cmd, } -Inset::DisplayType InsetTabular::display() const +int InsetTabular::rowFlags() const { - if (tabular.is_long_tabular) { - switch (tabular.longtabular_alignment) { - case Tabular::LYX_LONGTABULAR_ALIGN_LEFT: - return AlignLeft; - case Tabular::LYX_LONGTABULAR_ALIGN_CENTER: - return AlignCenter; - case Tabular::LYX_LONGTABULAR_ALIGN_RIGHT: - return AlignRight; - default: - return AlignCenter; - } - } else - return Inline; + if (tabular.is_long_tabular) { + switch (tabular.longtabular_alignment) { + case Tabular::LYX_LONGTABULAR_ALIGN_LEFT: + return Display | AlignLeft; + case Tabular::LYX_LONGTABULAR_ALIGN_CENTER: + return Display; + case Tabular::LYX_LONGTABULAR_ALIGN_RIGHT: + return Display | AlignRight; + default: + return Display; + } + } else + return Inline; } @@ -5896,34 +6295,13 @@ int InsetTabular::plaintext(odocstringstream & os, } -int InsetTabular::docbook(odocstream & os, OutputParams const & runparams) const +void InsetTabular::docbook(XMLStream & xs, OutputParams const & runparams) const { - int ret = 0; - Inset * master = 0; - - // FIXME: Why not pass a proper DocIterator here? -#if 0 - // if the table is inside a float it doesn't need the informaltable - // wrapper. Search for it. - for (master = owner(); master; master = master->owner()) - if (master->lyxCode() == FLOAT_CODE) - break; -#endif - - if (!master) { - os << ""; - ++ret; - } - ret += tabular.docbook(os, runparams); - if (!master) { - os << ""; - ++ret; - } - return ret; + tabular.docbook(xs, runparams); } -docstring InsetTabular::xhtml(XHTMLStream & xs, OutputParams const & rp) const +docstring InsetTabular::xhtml(XMLStream & xs, OutputParams const & rp) const { return tabular.xhtml(xs, rp); } @@ -6012,7 +6390,7 @@ void InsetTabular::setCursorFromCoordinates(Cursor & cur, int x, int y) const } -InsetTabular::idx_type InsetTabular::getNearestCell(BufferView & bv, int x, int y) const +idx_type InsetTabular::getNearestCell(BufferView & bv, int x, int y) const { idx_type idx_min = 0; int dist_min = numeric_limits::max(); @@ -6250,7 +6628,7 @@ void InsetTabular::tabularFeatures(Cursor & cur, row_type sel_row_start; row_type sel_row_end; bool setLines = false; - bool setLinesInnerOnly = false; + bool toggle = false; LyXAlignment setAlign = LYX_ALIGN_LEFT; Tabular::VAlignment setVAlign = Tabular::LYX_VALIGN_TOP; @@ -6408,23 +6786,51 @@ void InsetTabular::tabularFeatures(Cursor & cur, break; case Tabular::MOVE_COLUMN_RIGHT: - tabular.moveColumn(column, Tabular::RIGHT); - cur.idx() = tabular.cellIndex(row, column + 1); + tabular.moveColumn(sel_col_start, sel_col_end, Tabular::RIGHT); + if (cur.selection()) { + cur.selection(false); + cur.idx() = tabular.cellIndex(sel_row_start, sel_col_start + 1); + cur.resetAnchor(); + cur.idx() = tabular.cellIndex(sel_row_end, sel_col_end + 1); + cur.setSelection(); + } else + cur.idx() = tabular.cellIndex(row, column + 1); break; case Tabular::MOVE_COLUMN_LEFT: - tabular.moveColumn(column, Tabular::LEFT); - cur.idx() = tabular.cellIndex(row, column - 1); + tabular.moveColumn(sel_col_start, sel_col_end, Tabular::LEFT); + if (cur.selection()) { + cur.selection(false); + cur.idx() = tabular.cellIndex(sel_row_start, sel_col_start - 1); + cur.resetAnchor(); + cur.idx() = tabular.cellIndex(sel_row_end, sel_col_end - 1); + cur.setSelection(); + } else + cur.idx() = tabular.cellIndex(row, column - 1); break; case Tabular::MOVE_ROW_DOWN: - tabular.moveRow(row, Tabular::DOWN); - cur.idx() = tabular.cellIndex(row + 1, column); + tabular.moveRow(sel_row_start, sel_row_end, Tabular::DOWN); + if (cur.selection()) { + cur.selection(false); + cur.idx() = tabular.cellIndex(sel_row_start + 1, sel_col_start); + cur.resetAnchor(); + cur.idx() = tabular.cellIndex(sel_row_end + 1, sel_col_end); + cur.setSelection(); + } else + cur.idx() = tabular.cellIndex(row + 1, column); break; case Tabular::MOVE_ROW_UP: - tabular.moveRow(row, Tabular::UP); - cur.idx() = tabular.cellIndex(row - 1, column); + tabular.moveRow(sel_row_start, sel_row_end, Tabular::UP); + if (cur.selection()) { + cur.selection(false); + cur.idx() = tabular.cellIndex(sel_row_start - 1, sel_col_start); + cur.resetAnchor(); + cur.idx() = tabular.cellIndex(sel_row_end - 1, sel_col_end); + cur.setSelection(); + } else + cur.idx() = tabular.cellIndex(row - 1, column); break; case Tabular::SET_LINE_TOP: @@ -6646,27 +7052,36 @@ void InsetTabular::tabularFeatures(Cursor & cur, break; } + case Tabular::TOGGLE_INNER_LINES: + toggle = true; + // fall through case Tabular::SET_INNER_LINES: - setLinesInnerOnly = true; + if (toggle) + setLines = !tabular.innerBorders(sel_row_start, sel_row_end, + sel_col_start, sel_col_end); + else + setLines = true; + tabular.setLines(sel_row_start, sel_row_end, + sel_col_start, sel_col_end, + true, setLines); + break; + + case Tabular::TOGGLE_ALL_LINES: + toggle = true; // fall through case Tabular::SET_ALL_LINES: - setLines = true; + if (toggle) + setLines = !tabular.innerBorders(sel_row_start, sel_row_end, + sel_col_start, sel_col_end) + || !tabular.outsideBorders(sel_row_start, sel_row_end, + sel_col_start, sel_col_end); + else + setLines = true; // fall through case Tabular::UNSET_ALL_LINES: - for (row_type r = sel_row_start; r <= sel_row_end; ++r) - for (col_type c = sel_col_start; c <= sel_col_end; ++c) { - idx_type const cell = tabular.cellIndex(r, c); - if (!setLinesInnerOnly || r != sel_row_start) - tabular.setTopLine(cell, setLines); - if ((!setLinesInnerOnly || r != sel_row_end) - && (!setLines || r == sel_row_end)) - tabular.setBottomLine(cell, setLines); - if ((!setLinesInnerOnly || c != sel_col_end) - && (!setLines || c == sel_col_end)) - tabular.setRightLine(cell, setLines); - if ((!setLinesInnerOnly || c != sel_col_start)) - tabular.setLeftLine(cell, setLines); - } + tabular.setLines(sel_row_start, sel_row_end, + sel_col_start, sel_col_end, + false, setLines); break; case Tabular::RESET_FORMAL_DEFAULT: @@ -6680,16 +7095,24 @@ void InsetTabular::tabularFeatures(Cursor & cur, } break; - case Tabular::SET_BORDER_LINES: + case Tabular::TOGGLE_BORDER_LINES: + toggle = true; + // fall through + case Tabular::SET_BORDER_LINES: { + bool const border = toggle && + tabular.outsideBorders(sel_row_start, sel_row_end, + sel_col_start, sel_col_end) + ? false : true; for (row_type r = sel_row_start; r <= sel_row_end; ++r) { - tabular.setLeftLine(tabular.cellIndex(r, sel_col_start), true); - tabular.setRightLine(tabular.cellIndex(r, sel_col_end), true); + tabular.setLeftLine(tabular.cellIndex(r, sel_col_start), border); + tabular.setRightLine(tabular.cellIndex(r, sel_col_end), border); } for (col_type c = sel_col_start; c <= sel_col_end; ++c) { - tabular.setTopLine(tabular.cellIndex(sel_row_start, c), true); - tabular.setBottomLine(tabular.cellIndex(sel_row_end, c), true); + tabular.setTopLine(tabular.cellIndex(sel_row_start, c), border); + tabular.setBottomLine(tabular.cellIndex(sel_row_end, c), border); } break; + } case Tabular::TOGGLE_LONGTABULAR: if (tabular.is_long_tabular) @@ -6959,23 +7382,23 @@ bool InsetTabular::copySelection(Cursor & cur) paste_tabular.reset(new Tabular(tabular)); for (row_type r = 0; r < rs; ++r) - paste_tabular->deleteRow(0); + paste_tabular->deleteRow(0, true); row_type const rows = re - rs + 1; while (paste_tabular->nrows() > rows) - paste_tabular->deleteRow(rows); + paste_tabular->deleteRow(rows, true); for (col_type c = 0; c < cs; ++c) - paste_tabular->deleteColumn(0); + paste_tabular->deleteColumn(0, true); col_type const columns = ce - cs + 1; while (paste_tabular->ncols() > columns) - paste_tabular->deleteColumn(columns); + paste_tabular->deleteColumn(columns, true); paste_tabular->setBuffer(tabular.buffer()); odocstringstream os; - OutputParams const runparams(0); + OutputParams const runparams(nullptr); paste_tabular->plaintext(os, runparams, 0, true, '\t', INT_MAX); // Needed for the "Edit->Paste recent" menu and the system clipboard. cap::copySelection(cur, os.str()); @@ -7003,12 +7426,15 @@ bool InsetTabular::pasteClipboard(Cursor & cur) getSelection(cur, actrow, re, actcol, ce); } - for (row_type r1 = 0, r2 = actrow; - r1 < paste_tabular->nrows() && r2 < tabular.nrows(); - ++r1, ++r2) { - for (col_type c1 = 0, c2 = actcol; - c1 < paste_tabular->ncols() && c2 < tabular.ncols(); - ++c1, ++c2) { + col_type const oldncols = tabular.ncols(); + for (row_type r1 = 0, r2 = actrow; r1 < paste_tabular->nrows(); ++r1, ++r2) { + // Append rows if needed + if (r2 == tabular.nrows()) + tabular.insertRow(r2 - 1, false); + for (col_type c1 = 0, c2 = actcol; c1 < paste_tabular->ncols(); ++c1, ++c2) { + // Append columns if needed + if (c2 == tabular.ncols()) + tabular.insertColumn(c2 - 1, false); if (paste_tabular->isPartOfMultiColumn(r1, c1) && tabular.isPartOfMultiColumn(r2, c2)) continue; @@ -7026,13 +7452,21 @@ bool InsetTabular::pasteClipboard(Cursor & cur) // FIXME?: why do we need to do this explicitly? (EL) tabular.cellInset(r2, c2)->setBuffer(tabular.buffer()); - // FIXME: change tracking (MG) - inset->setChange(Change(buffer().params().track_changes ? - Change::INSERTED : Change::UNCHANGED)); + if (lyxrc.ct_markup_copied) { + // Only change to inserted if ct is active, + // otherwise leave markup as is + if (buffer().params().track_changes) + inset->setChange(Change(Change::INSERTED)); + } else + // Resolve all markup to inserted or unchanged + inset->setChange(Change(buffer().params().track_changes ? + Change::INSERTED : Change::UNCHANGED)); cur.pos() = 0; cur.pit() = 0; } } + // amend cursor position if cols have been appended + cur.idx() += actrow * (tabular.ncols() - oldncols); return true; } @@ -7082,10 +7516,15 @@ docstring InsetTabular::asString(idx_type stidx, idx_type enidx, { LASSERT(stidx <= enidx, return docstring()); docstring retval; - col_type const col1 = tabular.cellColumn(stidx); - col_type const col2 = tabular.cellColumn(enidx); - row_type const row1 = tabular.cellRow(stidx); - row_type const row2 = tabular.cellRow(enidx); + col_type col1 = tabular.cellColumn(stidx); + col_type col2 = tabular.cellColumn(enidx); + row_type row1 = tabular.cellRow(stidx); + row_type row2 = tabular.cellRow(enidx); + // stidx might be in a later column or row than enidx + if (col1 > col2) + swap(col1, col2); + if (row1 > row2) + swap(row1, row2); bool first = true; for (col_type col = col1; col <= col2; col++) for (row_type row = row1; row <= row2; row++) { @@ -7099,6 +7538,27 @@ docstring InsetTabular::asString(idx_type stidx, idx_type enidx, } +ParagraphList InsetTabular::asParList(idx_type stidx, idx_type enidx) +{ + LASSERT(stidx <= enidx, return ParagraphList()); + ParagraphList retval; + col_type col1 = tabular.cellColumn(stidx); + col_type col2 = tabular.cellColumn(enidx); + row_type row1 = tabular.cellRow(stidx); + row_type row2 = tabular.cellRow(enidx); + // stidx might be in a later column or row than enidx + if (col1 > col2) + swap(col1, col2); + if (row1 > row2) + swap(row1, row2); + for (col_type col = col1; col <= col2; col++) + for (row_type row = row1; row <= row2; row++) + for (auto const & par : tabular.cellInset(row, col)->paragraphs()) + retval.push_back(par); + return retval; +} + + void InsetTabular::getSelection(Cursor & cur, row_type & rs, row_type & re, col_type & cs, col_type & ce) const { @@ -7118,16 +7578,20 @@ void InsetTabular::getSelection(Cursor & cur, Text * InsetTabular::getText(int idx) const { - return size_t(idx) < nargs() ? cell(idx)->getText(0) : 0; + return size_t(idx) < nargs() ? cell(idx)->getText(0) : nullptr; } bool InsetTabular::isChanged() const { - for (idx_type idx = 0; idx < nargs(); ++idx) + for (idx_type idx = 0; idx < nargs(); ++idx) { if (cell(idx)->isChanged()) return true; - // FIXME: shall we look at row/columns changed status? + if (tabular.row_info[tabular.cellRow(idx)].change.changed()) + return true; + if (tabular.column_info[tabular.cellColumn(idx)].change.changed()) + return true; + } return false; } @@ -7144,17 +7608,18 @@ void InsetTabular::acceptChanges() for (idx_type idx = 0; idx < nargs(); ++idx) cell(idx)->acceptChanges(); for (row_type row = 0; row < tabular.nrows(); ++row) { - if (tabular.row_info[row].change == Change::INSERTED) - tabular.row_info[row].change = Change::UNCHANGED; - else if (tabular.row_info[row].change == Change::DELETED) + if (tabular.row_info[row].change.inserted()) + tabular.row_info[row].change.setUnchanged(); + else if (tabular.row_info[row].change.deleted()) tabular.deleteRow(row, true); } for (col_type col = 0; col < tabular.ncols(); ++col) { - if (tabular.column_info[col].change == Change::INSERTED) - tabular.column_info[col].change = Change::UNCHANGED; - else if (tabular.column_info[col].change == Change::DELETED) + if (tabular.column_info[col].change.inserted()) + tabular.column_info[col].change.setUnchanged(); + else if (tabular.column_info[col].change.deleted()) tabular.deleteColumn(col, true); } + tabular.updateIndexes(); } @@ -7163,17 +7628,18 @@ void InsetTabular::rejectChanges() for (idx_type idx = 0; idx < nargs(); ++idx) cell(idx)->rejectChanges(); for (row_type row = 0; row < tabular.nrows(); ++row) { - if (tabular.row_info[row].change == Change::DELETED) - tabular.row_info[row].change = Change::UNCHANGED; - else if (tabular.row_info[row].change == Change::INSERTED) + if (tabular.row_info[row].change.deleted()) + tabular.row_info[row].change.setUnchanged(); + else if (tabular.row_info[row].change.inserted()) tabular.deleteRow(row, true); } for (col_type col = 0; col < tabular.ncols(); ++col) { - if (tabular.column_info[col].change == Change::DELETED) - tabular.column_info[col].change = Change::UNCHANGED; - else if (tabular.column_info[col].change == Change::INSERTED) + if (tabular.column_info[col].change.deleted()) + tabular.column_info[col].change.setUnchanged(); + else if (tabular.column_info[col].change.inserted()) tabular.deleteColumn(col, true); } + tabular.updateIndexes(); } @@ -7183,12 +7649,6 @@ bool InsetTabular::allowParagraphCustomization(idx_type cell) const } -bool InsetTabular::forcePlainLayout(idx_type cell) const -{ - return tabular.isMultiColumn(cell) && !tabular.getPWidth(cell).zero(); -} - - bool InsetTabular::insertPlaintextString(BufferView & bv, docstring const & buf, bool usePaste) { @@ -7342,13 +7802,14 @@ bool InsetTabular::automaticPopupCompletion() const bool InsetTabular::showCompletionCursor() const { - return lyxrc.completion_cursor_text; + return lyxrc.completion_cursor_text && + (lyxrc.completion_inline_text || lyxrc.completion_popup_text); } CompletionList const * InsetTabular::createCompletionList(Cursor const & cur) const { - return completionSupported(cur) ? cur.text()->createCompletionList(cur) : 0; + return completionSupported(cur) ? cur.text()->createCompletionList(cur) : nullptr; } @@ -7360,12 +7821,12 @@ docstring InsetTabular::completionPrefix(Cursor const & cur) const } -bool InsetTabular::insertCompletion(Cursor & cur, docstring const & s, bool finished) +bool InsetTabular::insertCompletion(Cursor & cur, docstring const & s, bool /*finished*/) { if (!completionSupported(cur)) return false; - return cur.text()->insertCompletion(cur, s, finished); + return cur.text()->insertCompletion(cur, s); } @@ -7417,8 +7878,8 @@ string InsetTabular::params2string(InsetTabular const & inset) void InsetTabular::setLayoutForHiddenCells(DocumentClass const & dc) { - for (Tabular::col_type c = 0; c < tabular.ncols(); ++c) { - for (Tabular::row_type r = 0; r < tabular.nrows(); ++r) { + for (col_type c = 0; c < tabular.ncols(); ++c) { + for (row_type r = 0; r < tabular.nrows(); ++r) { if (!tabular.isPartOfMultiColumn(r,c) && !tabular.isPartOfMultiRow(r,c)) continue;