X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2Finsets%2FInsetTabular.cpp;h=68fff9b51f816e16746a13ee3571a60674876434;hb=110f8f67ac1afe9892dad5566d1c697044427cf0;hp=1fc492eea03262b9fa70e70ae8bff9dcbf2205cb;hpb=4ea0fd6af41beffc3eea1efcf8a0427a9d122b9c;p=lyx.git diff --git a/src/insets/InsetTabular.cpp b/src/insets/InsetTabular.cpp index 1fc492eea0..68fff9b51f 100644 --- a/src/insets/InsetTabular.cpp +++ b/src/insets/InsetTabular.cpp @@ -33,6 +33,7 @@ #include "DispatchResult.h" #include "FuncRequest.h" #include "FuncStatus.h" +#include "InsetIterator.h" #include "InsetList.h" #include "Language.h" #include "LaTeXFeatures.h" @@ -126,12 +127,20 @@ TabularFeature tabularFeature[] = { Tabular::MOVE_ROW_UP, "move-row-up", false }, { Tabular::SET_LINE_TOP, "set-line-top", true }, { Tabular::SET_LINE_BOTTOM, "set-line-bottom", true }, + { Tabular::SET_LTRIM_TOP, "set-ltrim-top", true }, + { Tabular::SET_LTRIM_BOTTOM, "set-ltrim-bottom", true }, + { Tabular::SET_RTRIM_TOP, "set-rtrim-top", true }, + { Tabular::SET_RTRIM_BOTTOM, "set-rtrim-bottom", true }, { Tabular::SET_LINE_LEFT, "set-line-left", true }, { Tabular::SET_LINE_RIGHT, "set-line-right", true }, { Tabular::TOGGLE_LINE_TOP, "toggle-line-top", false }, { Tabular::TOGGLE_LINE_BOTTOM, "toggle-line-bottom", false }, { Tabular::TOGGLE_LINE_LEFT, "toggle-line-left", false }, { Tabular::TOGGLE_LINE_RIGHT, "toggle-line-right", false }, + { Tabular::TOGGLE_LTRIM_TOP, "toggle-ltrim-top", false }, + { Tabular::TOGGLE_LTRIM_BOTTOM, "toggle-ltrim-bottom", false }, + { Tabular::TOGGLE_RTRIM_TOP, "toggle-rtrim-top", false }, + { Tabular::TOGGLE_RTRIM_BOTTOM, "toggle-rtrim-bottom", false }, { Tabular::ALIGN_LEFT, "align-left", false }, { Tabular::ALIGN_RIGHT, "align-right", false }, { Tabular::ALIGN_CENTER, "align-center", false }, @@ -154,12 +163,14 @@ TabularFeature tabularFeature[] = { Tabular::UNSET_MULTIROW, "unset-multirow", false }, { Tabular::SET_MROFFSET, "set-mroffset", true }, { Tabular::SET_ALL_LINES, "set-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 }, { Tabular::SET_LONGTABULAR, "set-longtabular", false }, { Tabular::UNSET_LONGTABULAR, "unset-longtabular", false }, { Tabular::SET_PWIDTH, "set-pwidth", true }, { Tabular::SET_MPWIDTH, "set-mpwidth", true }, + { Tabular::TOGGLE_VARWIDTH_COLUMN, "toggle-varwidth-column", true }, { Tabular::SET_ROTATE_TABULAR, "set-rotate-tabular", true }, { Tabular::UNSET_ROTATE_TABULAR, "unset-rotate-tabular", true }, { Tabular::TOGGLE_ROTATE_TABULAR, "toggle-rotate-tabular", true }, @@ -197,6 +208,7 @@ TabularFeature tabularFeature[] = { Tabular::LONGTABULAR_ALIGN_RIGHT, "longtabular-align-right", false }, { 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::LAST_ACTION, "", false } }; @@ -262,6 +274,8 @@ string const tostr(Tabular::BoxType const & num) return "parbox"; case Tabular::BOX_MINIPAGE: return "minipage"; + case Tabular::BOX_VARWIDTH: + return "varwidth"; } return string(); } @@ -324,6 +338,8 @@ bool string2type(string const & str, Tabular::BoxType & num) num = Tabular::BOX_PARBOX; else if (str == "minipage") num = Tabular::BOX_MINIPAGE; + else if (str == "varwidth") + num = Tabular::BOX_VARWIDTH; else return false; return true; @@ -435,6 +451,49 @@ bool getTokenValue(string const & str, char const * token, Length & len) } +bool getTokenValue(string const & str, char const * token, Change & change, BufferParams & bp) +{ + // set the change to be Change() as default as this it should be if not + // in the file format. + change = Change(); + string tmp; + if (getTokenValue(str, token, tmp)) { + 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 (changedata[0] == "deleted") { + change = Change(Change::DELETED, am.find(aid)->second, ct); + return true; + } + } + return false; +} + + bool getTokenValue(string const & str, char const * token, Length & len, bool & flag) { len = Length(); @@ -511,10 +570,25 @@ 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()); } +string const write_attribute(string const & name, Change const & change, BufferParams const & bp) +{ + 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(); +} + } // namespace @@ -546,7 +620,7 @@ InsetTableCell splitCell(InsetTableCell & head, docstring const & align_d, bool DocIterator const dit = separatorPos(&head, align_d); hassep = (bool)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); @@ -577,6 +651,10 @@ Tabular::CellData::CellData(Buffer * buf) bottom_line(false), left_line(false), right_line(false), + top_line_rtrimmed(false), + top_line_ltrimmed(false), + bottom_line_rtrimmed(false), + bottom_line_ltrimmed(false), usebox(BOX_NONE), rotate(0), inset(new InsetTableCell(buf)) @@ -600,6 +678,10 @@ Tabular::CellData::CellData(CellData const & cs) bottom_line(cs.bottom_line), left_line(cs.left_line), right_line(cs.right_line), + top_line_rtrimmed(cs.top_line_rtrimmed), + top_line_ltrimmed(cs.top_line_ltrimmed), + bottom_line_rtrimmed(cs.bottom_line_rtrimmed), + bottom_line_ltrimmed(cs.bottom_line_ltrimmed), usebox(cs.usebox), rotate(cs.rotate), align_special(cs.align_special), @@ -626,6 +708,10 @@ Tabular::CellData & Tabular::CellData::operator=(CellData const & cs) bottom_line = cs.bottom_line; left_line = cs.left_line; right_line = cs.right_line; + top_line_rtrimmed = cs.top_line_rtrimmed; + top_line_ltrimmed = cs.top_line_ltrimmed; + bottom_line_rtrimmed = cs.bottom_line_rtrimmed; + bottom_line_ltrimmed = cs.bottom_line_ltrimmed; usebox = cs.usebox; rotate = cs.rotate; align_special = cs.align_special; @@ -645,14 +731,17 @@ Tabular::RowData::RowData() endfoot(false), endlastfoot(false), newpage(false), - caption(false) + caption(false), + change(Change()) {} Tabular::ColumnData::ColumnData() : alignment(LYX_ALIGN_CENTER), valignment(LYX_VALIGN_TOP), - width(0) + width(0), + varwidth(false), + change(Change()) { } @@ -709,12 +798,14 @@ void Tabular::init(Buffer * buf, row_type rows_arg, } -void Tabular::deleteRow(row_type const row) +void Tabular::deleteRow(row_type const row, bool const force) { // Not allowed to delete last row if (nrows() == 1) return; + bool const ct = force ? false : buffer().params().track_changes; + for (col_type c = 0; c < ncols(); ++c) { // Care about multirow cells if (row + 1 < nrows() && @@ -723,8 +814,12 @@ void Tabular::deleteRow(row_type const row) cell_info[row + 1][c].multirow = CELL_BEGIN_OF_MULTIROW; } } - row_info.erase(row_info.begin() + row); - cell_info.erase(cell_info.begin() + row); + if (ct) + row_info[row].change.setDeleted(); + else { + row_info.erase(row_info.begin() + row); + cell_info.erase(cell_info.begin() + row); + } updateIndexes(); } @@ -750,8 +845,6 @@ void Tabular::insertRow(row_type const row, bool copy) 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)); if (cell_info[row][c].multirow == CELL_BEGIN_OF_MULTIROW) cell_info[row + 1][c].multirow = CELL_PART_OF_MULTIROW; } @@ -770,9 +863,10 @@ 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.setInserted(); + updateIndexes(); } } @@ -789,7 +883,6 @@ void Tabular::moveColumn(col_type col, ColDirection direction) 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); - // FIXME track changes is broken for tabular features (#8469) idx_type const i = cellIndex(r, col); idx_type const j = cellIndex(r, col + 1); if (buffer().params().track_changes) { @@ -813,7 +906,6 @@ void Tabular::moveRow(row_type row, RowDirection direction) 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); - // FIXME track changes is broken for tabular features (#8469) idx_type const i = cellIndex(row, c); idx_type const j = cellIndex(row + 1, c); if (buffer().params().track_changes) { @@ -825,12 +917,14 @@ void Tabular::moveRow(row_type row, RowDirection direction) } -void Tabular::deleteColumn(col_type const col) +void Tabular::deleteColumn(col_type const col, bool const force) { // Not allowed to delete last column if (ncols() == 1) return; + bool const ct = force ? false : buffer().params().track_changes; + for (row_type r = 0; r < nrows(); ++r) { // Care about multicolumn cells if (col + 1 < ncols() && @@ -838,9 +932,13 @@ void Tabular::deleteColumn(col_type const col) cell_info[r][col + 1].multicolumn == CELL_PART_OF_MULTICOLUMN) { cell_info[r][col + 1].multicolumn = CELL_BEGIN_OF_MULTICOLUMN; } - cell_info[r].erase(cell_info[r].begin() + col); + if (!ct) + cell_info[r].erase(cell_info[r].begin() + col); } - column_info.erase(column_info.begin() + col); + if (ct) + column_info[col].change.setDeleted(); + else + column_info.erase(column_info.begin() + col); updateIndexes(); } @@ -859,14 +957,12 @@ void Tabular::appendColumn(col_type col) void Tabular::insertColumn(col_type const col, bool copy) { - BufferParams const & bp = buffer().params(); + bool const ct = buffer().params().track_changes; column_info.insert(column_info.begin() + col + 1, ColumnData(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)); if (cell_info[r][col].multicolumn == CELL_BEGIN_OF_MULTICOLUMN) cell_info[r][col + 1].multicolumn = CELL_PART_OF_MULTICOLUMN; } @@ -878,12 +974,14 @@ void Tabular::insertColumn(col_type const col, bool copy) setBottomLine(i, bottomLine(j)); setTopLine(i, topLine(j)); setLeftLine(i, leftLine(j)); + setRightLine(i, rightLine(j)); if (rightLine(i) && rightLine(j)) { - setRightLine(i, true); setRightLine(j, false); } - if (buffer().params().track_changes) - cellInfo(i).inset->setChange(Change(Change::INSERTED)); + } + if (ct) { + column_info[col + 1].change.setInserted(); + updateIndexes(); } } @@ -907,24 +1005,37 @@ 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)) + if (isPartOfMultiColumn(row, column)) { + cell_info[row][column].inset->toggleMultiCol(true); continue; - // columnofcell needs to be called before setting width and aligment + } + cell_info[row][column].inset->toggleMultiCol(false); + // 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; rowofcell[i] = row; } setFixedWidth(row, column); - if (isPartOfMultiRow(row, column)) + if (isPartOfMultiRow(row, column)) { + cell_info[row][column].inset->toggleMultiRow(true); continue; + } + 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; } + } } @@ -950,22 +1061,40 @@ bool Tabular::bottomLine(idx_type const cell) const } -bool Tabular::leftLine(idx_type cell) const +bool Tabular::leftLine(idx_type cell, bool const ignore_bt) const { - if (use_booktabs) + if (use_booktabs && !ignore_bt) return false; return cellInfo(cell).left_line; } -bool Tabular::rightLine(idx_type cell) const +bool Tabular::rightLine(idx_type cell, bool const ignore_bt) const { - if (use_booktabs) + if (use_booktabs && !ignore_bt) return false; return cellInfo(cell).right_line; } +pair Tabular::topLineTrim(idx_type const cell) const +{ + if (!use_booktabs) + return make_pair(false, false); + return make_pair(cellInfo(cell).top_line_ltrimmed, + cellInfo(cell).top_line_rtrimmed); +} + + +pair Tabular::bottomLineTrim(idx_type const cell) const +{ + if (!use_booktabs) + return make_pair(false, false); + return make_pair(cellInfo(cell).bottom_line_ltrimmed, + cellInfo(cell).bottom_line_rtrimmed); +} + + int Tabular::interRowSpace(row_type row) const { if (!row || row >= nrows()) @@ -1016,15 +1145,60 @@ int Tabular::cellHeight(idx_type cell) const } -bool Tabular::updateColumnWidths() +bool Tabular::updateColumnWidths(MetricsInfo & mi) { vector max_dwidth(ncols(), 0); + // collect max. fixed width of column + map max_pwidth; + // collect max. variable width of column + map max_width; + for(col_type c = 0; c < ncols(); ++c) for(row_type r = 0; r < nrows(); ++r) { idx_type const i = cellIndex(r, c); if (getAlignment(i) == LYX_ALIGN_DECIMAL) max_dwidth[c] = max(max_dwidth[c], cell_info[r][c].decimal_width); + if (!getPWidth(i).zero()) + max_pwidth[c] = max(max_pwidth[c], cell_info[r][c].width); + else if (!column_info[c].varwidth) + max_width[c] = max(max_width[c], cell_info[r][c].width); + } + + // If we have a fixed tabular width, we take this into account + Length tab_width = tabular_width; + bool const tabularx = hasVarwidthColumn(); + if (tabularx && tab_width.zero()) + // If no tabular width is specified with X columns, + // we use 100% colwidth + tab_width = Length(100, Length::PCW); + int restwidth = -1; + if (!tab_width.zero()) { + restwidth = mi.base.inPixels(tab_width); + // Subtract the fixed widths from the table width + for (auto const w : max_pwidth) + restwidth -= w.second; + } + + // If we have a fixed width, distribute the available table width + // (minus the fixed widths) to the variable-width columns + int vcolwidth = -1; + int restcols = ncols() - max_pwidth.size(); + if ((restwidth > 0) && (restcols != 0)) + vcolwidth = restwidth / restcols; + + // Now consider that some variable width columns exceed the vcolwidth + if (vcolwidth > 0) { + bool changed = false; + for (auto const w : max_width) { + if (tabularx || w.second > vcolwidth) { + --restcols; + restwidth -= w.second; + changed = true; + } } + if (changed && restwidth > 0) + vcolwidth = restwidth / restcols; + } bool update = false; // for each col get max of single col cells @@ -1037,14 +1211,23 @@ bool Tabular::updateColumnWidths() && cell_info[r][c].decimal_width != 0) new_width = max(new_width, cellInfo(i).width + max_dwidth[c] - cellInfo(i).decimal_width); - else + else if (getPWidth(i).zero() && vcolwidth > 0) { + if (tabularx && !column_info[c].varwidth) + new_width = max(new_width, cellInfo(i).width); + else if (tabularx) + new_width = vcolwidth; + else + new_width = max(vcolwidth, max(new_width, cellInfo(i).width)); + } else new_width = max(new_width, cellInfo(i).width); } } if (column_info[c].width != new_width) { column_info[c].width = new_width; - update = true; + // Do not trigger update when no space is left for variable + // columns, as this will loop + update = tab_width.zero() || restwidth > 0; } } // update col widths to fit merged cells @@ -1061,7 +1244,9 @@ bool Tabular::updateColumnWidths() if (cellInfo(i).width > old_width) { column_info[c + span - 1].width += cellInfo(i).width - old_width; - update = true; + // Do not trigger update when no space is left for variable + // columns, as this will loop + update = tab_width.zero() || restwidth > 0; } } @@ -1101,8 +1286,10 @@ void Tabular::setAlignment(idx_type cell, LyXAlignment align, } column_info[col].alignment = align; docstring & dpoint = column_info[col].decimal_point; - if (align == LYX_ALIGN_DECIMAL && dpoint.empty()) - dpoint = from_utf8(lyxrc.default_decimal_point); + if (align == LYX_ALIGN_DECIMAL && dpoint.empty()) { + Language const * tlang = buffer().paragraphs().front().getParLanguage(buffer().params()); + dpoint = tlang->decimalSeparator(); + } } else { cellInfo(cell).alignment = align; cellInset(cell)->setContentAlignment(align); @@ -1123,14 +1310,16 @@ void Tabular::setVAlignment(idx_type cell, VAlignment align, namespace { /** - * Allow line and paragraph breaks for fixed width cells or disallow them, - * merge cell paragraphs and reset layout to standard for variable width - * cells. + * Allow line and paragraph breaks for fixed width multicol/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 fixedWidth) +void toggleFixedWidth(Cursor & cur, InsetTableCell * inset, + bool const fixedWidth, bool const multicol, + bool const multirow) { inset->toggleFixedWidth(fixedWidth); - if (fixedWidth) + if (!multirow && (fixedWidth || !multicol)) return; // merge all paragraphs to one @@ -1138,6 +1327,19 @@ void toggleFixedWidth(Cursor & cur, InsetTableCell * inset, bool fixedWidth) 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 @@ -1146,7 +1348,7 @@ void toggleFixedWidth(Cursor & cur, InsetTableCell * inset, bool fixedWidth) cur.pop(); } -} +} // namespace void Tabular::setColumnPWidth(Cursor & cur, idx_type cell, @@ -1161,19 +1363,16 @@ void Tabular::setColumnPWidth(Cursor & cur, idx_type cell, if (column_info[c].p_width.zero()) column_info[c].valignment = LYX_VALIGN_TOP; for (row_type r = 0; r < nrows(); ++r) { - idx_type const cell = cellIndex(r, c); + idx_type const cidx = cellIndex(r, c); // because of multicolumns - toggleFixedWidth(cur, cellInset(cell).get(), - !getPWidth(cell).zero()); - if (isMultiRow(cell)) - setAlignment(cell, LYX_ALIGN_LEFT, false); + toggleFixedWidth(cur, cellInset(cidx).get(), + !getPWidth(cidx).zero(), isMultiColumn(cidx), + isMultiRow(cidx)); + if (isMultiRow(cidx)) + setAlignment(cidx, LYX_ALIGN_LEFT, false); } - // cur paragraph can become invalid after paragraphs were merged - if (cur.pit() > cur.lastpit()) - cur.pit() = cur.lastpit(); - // cur position can become invalid after newlines were removed - if (cur.pos() > cur.lastpos()) - cur.pos() = cur.lastpos(); + // cur can become invalid after paragraphs were merged + cur.fixIfBroken(); } @@ -1194,13 +1393,17 @@ bool Tabular::setMColumnPWidth(Cursor & cur, idx_type cell, return false; cellInfo(cell).p_width = width; - toggleFixedWidth(cur, cellInset(cell).get(), !width.zero()); - // cur paragraph can become invalid after paragraphs were merged - if (cur.pit() > cur.lastpit()) - cur.pit() = cur.lastpit(); - // cur position can become invalid after newlines were removed - if (cur.pos() > cur.lastpos()) - cur.pos() = cur.lastpos(); + toggleFixedWidth(cur, cellInset(cell).get(), !width.zero(), + isMultiColumn(cell), isMultiRow(cell)); + // cur can become invalid after paragraphs were merged + cur.fixIfBroken(); + return true; +} + + +bool Tabular::toggleVarwidth(idx_type cell, bool const varwidth) +{ + column_info[cellColumn(cell)].varwidth = varwidth; return true; } @@ -1234,6 +1437,42 @@ void Tabular::setBottomLine(idx_type i, bool line) } +void Tabular::setTopLineLTrim(idx_type i, bool val) +{ + cellInfo(i).top_line_ltrimmed = val; +} + + +void Tabular::setTopLineRTrim(idx_type i, bool val) +{ + cellInfo(i).top_line_rtrimmed = val; +} + + +void Tabular::setBottomLineLTrim(idx_type i, bool val) +{ + cellInfo(i).bottom_line_ltrimmed = val; +} + + +void Tabular::setBottomLineRTrim(idx_type i, bool val) +{ + cellInfo(i).bottom_line_rtrimmed = val; +} + + +void Tabular::setTopLineTrim(idx_type i, pair trim) +{ + setTopLineLTrim(i, trim.first); + setTopLineRTrim(i, trim.second); +} + +void Tabular::setBottomLineTrim(idx_type i, pair trim) +{ + setBottomLineLTrim(i, trim.first); + setBottomLineRTrim(i, trim.second); +} + void Tabular::setLeftLine(idx_type cell, bool line) { cellInfo(cell).left_line = line; @@ -1408,7 +1647,7 @@ int Tabular::textVOffset(idx_type cell) const } -Tabular::idx_type Tabular::getFirstCellInRow(row_type row) const +Tabular::idx_type Tabular::getFirstCellInRow(row_type row, bool const ct) const { col_type c = 0; idx_type const numcells = numberOfCellsInRow(row); @@ -1417,26 +1656,52 @@ 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 +Tabular::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::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; +} + + +Tabular::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; +} + + Tabular::row_type Tabular::cellRow(idx_type cell) const { if (cell >= numberofcells) @@ -1481,10 +1746,9 @@ void Tabular::write(ostream & os) const << write_attribute("lastFootBottomDL", endlastfoot.bottomDL) << write_attribute("lastFootEmpty", endlastfoot.empty); // longtables cannot be aligned vertically - if (!is_long_tabular) { + if (!is_long_tabular) os << write_attribute("tabularvalignment", tabular_valignment); - os << write_attribute("tabularwidth", tabular_width); - } + os << write_attribute("tabularwidth", tabular_width); if (is_long_tabular) os << write_attribute("longtabularalignment", longtabular_alignment); os << ">\n"; @@ -1492,9 +1756,11 @@ void Tabular::write(ostream & os) const os << "\n"; } @@ -1513,7 +1779,8 @@ void Tabular::write(ostream & os) const os << write_attribute("interlinespace", def); else os << write_attribute("interlinespace", row_info[r].interline_space); - os << write_attribute("endhead", row_info[r].endhead) + 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) << write_attribute("endlastfoot", row_info[r].endlastfoot) @@ -1528,7 +1795,11 @@ void Tabular::write(ostream & os) const << write_attribute("alignment", cell_info[r][c].alignment) << write_attribute("valignment", cell_info[r][c].valignment) << write_attribute("topline", cell_info[r][c].top_line) + << write_attribute("toplineltrim", cell_info[r][c].top_line_ltrimmed) + << write_attribute("toplinertrim", cell_info[r][c].top_line_rtrimmed) << write_attribute("bottomline", cell_info[r][c].bottom_line) + << write_attribute("bottomlineltrim", cell_info[r][c].bottom_line_ltrimmed) + << write_attribute("bottomlinertrim", cell_info[r][c].bottom_line_rtrimmed) << write_attribute("leftline", cell_info[r][c].left_line) << write_attribute("rightline", cell_info[r][c].right_line) << write_attribute("rotate", cell_info[r][c].rotate) @@ -1607,6 +1878,8 @@ void Tabular::read(Lexer & lex) getTokenValue(line, "valignment", column_info[c].valignment); 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, buffer().params()); } for (row_type i = 0; i < nrows(); ++i) { @@ -1628,6 +1901,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, buffer().params()); for (col_type j = 0; j < ncols(); ++j) { l_getline(is, line); if (!prefixIs(line, " 1) return cellInfo(cell).usebox; - return useParbox(cell); + return useBox(cell); } @@ -2053,11 +2368,11 @@ bool Tabular::haveLTLastFoot(bool withcaptions) const } -Tabular::idx_type Tabular::setLTCaption(row_type row, bool what) +Tabular::idx_type Tabular::setLTCaption(Cursor & cur, row_type row, bool what) { idx_type i = getFirstCellInRow(row); if (what) { - setMultiColumn(i, numberOfCellsInRow(row), false); + setMultiColumn(cur, i, numberOfCellsInRow(row), false); setTopLine(i, false); setBottomLine(i, false); setLeftLine(i, false); @@ -2173,61 +2488,116 @@ bool Tabular::isPartOfMultiRow(row_type row, col_type column) const } -void Tabular::TeXTopHLine(otexstream & os, row_type row, string const & lang) 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(...) // get for each column the topline (if any) - vector topline; + map topline, topltrims, toprtrims; col_type nset = 0; - for (col_type c = 0; c < ncols(); ++c) { - topline.push_back(topLine(cellIndex(row, c))); + bool have_trims = false; + for (auto const & c : columns) { + topline[c] = topLine(cellIndex(row, c)); + topltrims[c] = topLineTrim(cellIndex(row, c)).first; + toprtrims[c] = topLineTrim(cellIndex(row, c)).second; // If cell is part of a multirow and not the first cell of the // multirow, no line must be drawn. if (row != 0) if (isMultiRow(cellIndex(row, c)) - && cell_info[row][c].multirow != CELL_BEGIN_OF_MULTIROW) + && cell_info[row][c].multirow != CELL_BEGIN_OF_MULTIROW) { topline[c] = false; - if (topline[c]) + topltrims[c] = false; + toprtrims[c] = false; + } + // copy trimming to multicolumn parts + if (isPartOfMultiColumn(row, c)) { + topltrims[c] = topltrims[c-1]; + toprtrims[c] = toprtrims[c-1]; + } + if (topline.find(c) != topline.end() && topline.find(c)->second) ++nset; + if ((topltrims.find(c) != topltrims.end() && topltrims.find(c)->second) + || (toprtrims.find(c) != toprtrims.end() && toprtrims.find(c)->second)) + have_trims = true; } // 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 == first + || (is_long_tabular && row == first + 1 && ltCaption(first))); + // only output complete row lines and the 1st row's clines - if (nset == ncols()) { + if (nset == columns.size() && !have_trims) { if (use_booktabs) { - os << (row == 0 ? "\\toprule " : "\\midrule "); + os << (realfirstrow ? "\\toprule " : "\\midrule "); } else { os << "\\hline "; } - } else if (row == 0) { - for (col_type c = 0; c < ncols(); ++c) { - if (topline[c]) { + } else if (realfirstrow || have_trims) { + string const cline = use_booktabs ? "\\cmidrule" : "\\cline"; + col_type c = 0; + 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; + if (topline.find(c)->second) { col_type offset = 0; for (col_type j = 0 ; j < c; ++j) if (column_info[j].alignment == LYX_ALIGN_DECIMAL) ++offset; - - //babel makes the "-" character an active one, so we have to suppress this here - //see http://groups.google.com/group/comp.text.tex/browse_thread/thread/af769424a4a0f289# - if (lang == "slovak" || lang == "czech") - os << "\\expandafter" << (use_booktabs ? "\\cmidrule" : "\\cline") - << "\\expandafter{\\expandafter" << c + 1 + offset << "\\string-"; - else - os << (use_booktabs ? "\\cmidrule{" : "\\cline{") << 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; + if (topltrims.find(c) != topltrims.end() + && topltrims.find(c)->second) + trim = "l"; col_type cstart = c; - for ( ; c < ncols() && topline[c]; ++c) {} + for ( ; c < ncols() - 1 && topline.find(c + 1)->second ; ++c) { + if (isMultiColumn(cellIndex(row, c)) + && c < ncols() - 1 && isPartOfMultiColumn(row, c + 1)) + continue; + if (c > cstart && topltrims.find(c) != topltrims.end() + && topltrims.find(c)->second) { + if (!isPartOfMultiColumn(row, c)) + --c; + break; + } else if (toprtrims.find(c) != toprtrims.end() + && toprtrims.find(c)->second) + break; + } for (col_type j = cstart ; j < c ; ++j) if (column_info[j].alignment == LYX_ALIGN_DECIMAL) ++offset; - - os << c + 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"; + + os << cline; + if (!trim.empty()) + os << "(" << trim << ")"; + if (firstcol > lastcol) + // This can happen with bidi (swapped columns) + os << "{" << lastcol << '-' << firstcol << "}"; + else + os << "{" << firstcol << '-' << lastcol << "}"; + if (c == columns.size() - 1) + break; + ++c; } } } @@ -2235,18 +2605,24 @@ void Tabular::TeXTopHLine(otexstream & os, row_type row, string const & lang) co } -void Tabular::TeXBottomHLine(otexstream & os, row_type row, string const & lang) 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; - vector bottomline, topline; + bool lastrow = row == getLastRow(!buffer().params().output_changes); + map bottomline, topline, topltrims, toprtrims, bottomltrims, bottomrtrims; bool nextrowset = true; - for (col_type c = 0; c < ncols(); ++c) { - bottomline.push_back(bottomLine(cellIndex(row, c))); - topline.push_back(!lastrow && topLine(cellIndex(row + 1, c))); + for (auto const & c : columns) { + idx_type const idx = cellIndex(row, c); + bottomline[c] = bottomLine(cellIndex(row, c)); + bottomltrims[c] = bottomLineTrim(idx).first; + bottomrtrims[c] = bottomLineTrim(idx).second; + topline[c] = !lastrow && topLine(cellIndex(row + 1, c)); + topltrims[c] = !lastrow && topLineTrim(cellIndex(row + 1, c)).first; + toprtrims[c] = !lastrow && topLineTrim(cellIndex(row + 1, c)).second; // If cell is part of a multirow and not the last cell of the // multirow, no line must be drawn. if (!lastrow) @@ -2255,52 +2631,109 @@ void Tabular::TeXBottomHLine(otexstream & os, row_type row, string const & lang) && cell_info[row + 1][c].multirow != CELL_BEGIN_OF_MULTIROW) { bottomline[c] = false; topline[c] = false; - } - nextrowset &= topline[c]; + bottomltrims[c] = false; + bottomrtrims[c] = false; + topltrims[c] = false; + toprtrims[c] = false; + } + // copy trimming in multicolumn parts + if (isPartOfMultiColumn(row, c)) { + topltrims[c] = topltrims[c-1]; + toprtrims[c] = toprtrims[c-1]; + bottomltrims[c] = bottomltrims[c-1]; + bottomrtrims[c] = bottomrtrims[c-1]; + } + + nextrowset &= topline.find(c) != topline.end() && topline.find(c)->second; } // combine this row's bottom lines and next row's toplines if necessary col_type nset = 0; - for (col_type c = 0; c < ncols(); ++c) { + bool have_trims = false; + for (auto const & c : columns) { if (!nextrowset) - bottomline[c] = bottomline[c] || topline[c]; - if (bottomline[c]) + 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) + || (toprtrims.find(c) != toprtrims.end() && toprtrims.find(c)->second); + if (bottomline.find(c)->second) ++nset; + if ((bottomltrims.find(c) != bottomltrims.end() && bottomltrims.find(c)->second) + || (bottomrtrims.find(c) != bottomrtrims.end() && bottomrtrims.find(c)->second)) + have_trims = true; } // 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()) { + if (nset == columns.size() && !have_trims) { if (use_booktabs) os << (lastrow ? "\\bottomrule" : "\\midrule"); else os << "\\hline "; } else { - for (col_type c = 0; c < ncols(); ++c) { - if (bottomline[c]) { + string const cline = use_booktabs ? "\\cmidrule" : "\\cline"; + col_type c = 0; + 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; + if (bottomline.find(c)->second) { col_type offset = 0; for (col_type j = 0 ; j < c; ++j) if (column_info[j].alignment == LYX_ALIGN_DECIMAL) ++offset; - - //babel makes the "-" character an active one, so we have to suppress this here - //see http://groups.google.com/group/comp.text.tex/browse_thread/thread/af769424a4a0f289# - if (lang == "slovak" || lang == "czech") - os << "\\expandafter" << (use_booktabs ? "\\cmidrule" : "\\cline") - << "\\expandafter{\\expandafter" << c + 1 + offset << "\\string-"; - else - os << (use_booktabs ? "\\cmidrule{" : "\\cline{") << 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; + if (bottomltrims.find(c) != bottomltrims.end() + && bottomltrims.find(c)->second) + trim = "l"; col_type cstart = c; - for ( ; c < ncols() && bottomline[c]; ++c) {} + for ( ; c < ncols() - 1 && bottomline.find(c + 1)->second ; ++c) { + if (isMultiColumn(cellIndex(row, c)) + && c < ncols() - 1 + && isPartOfMultiColumn(row, c + 1)) + continue; + if (c > cstart + && bottomltrims.find(c) != bottomltrims.end() + && bottomltrims.find(c)->second) { + if (!isPartOfMultiColumn(row, c)) + --c; + break; + } else if (bottomrtrims.find(c) != bottomrtrims.end() + && bottomrtrims.find(c)->second) + break; + } for (col_type j = cstart ; j < c ; ++j) if (column_info[j].alignment == LYX_ALIGN_DECIMAL) ++offset; - - os << c + 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"; + + os << cline; + if (!trim.empty()) + os << "(" << trim << ")"; + if (firstcol > lastcol) + // This can happen with bidi (swapped columns) + os << "{" << lastcol << '-' << firstcol << "}"; + else + os << "{" << firstcol << '-' << lastcol << "}"; + if (c == columns.size() - 1) + break; + ++c; } } } @@ -2309,7 +2742,8 @@ void Tabular::TeXBottomHLine(otexstream & os, row_type row, string const & lang) void Tabular::TeXCellPreamble(otexstream & os, idx_type cell, - bool & ismulticol, bool & ismultirow) const + bool & ismulticol, bool & ismultirow, + bool const bidi) const { row_type const r = cellRow(cell); if (is_long_tabular && row_info[r].caption) @@ -2319,8 +2753,10 @@ void Tabular::TeXCellPreamble(otexstream & os, idx_type cell, LyXAlignment align = getAlignment(cell, !isMultiColumn(cell)); // figure out how to set the lines // we always set double lines to the right of the cell + // or left in bidi RTL, respectively. col_type const c = cellColumn(cell); col_type const nextcol = c + columnSpan(cell); + bool const decimal = column_info[c].alignment == LYX_ALIGN_DECIMAL; bool colright = columnRightLine(c); bool colleft = columnLeftLine(c); bool nextcolleft = nextcol < ncols() && columnLeftLine(nextcol); @@ -2329,28 +2765,35 @@ void Tabular::TeXCellPreamble(otexstream & os, idx_type cell, bool coldouble = colright && nextcolleft; bool celldouble = rightLine(cell) && nextcellleft; - ismulticol = isMultiColumn(cell) - || (c == 0 && colleft != leftLine(cell)) - || ((colright || nextcolleft) && !rightLine(cell) && !nextcellleft) - || (!colright && !nextcolleft && (rightLine(cell) || nextcellleft)) - || (coldouble != celldouble); + ismulticol = (isMultiColumn(cell) + || (c == 0 && colleft != leftLine(cell)) + || ((colright || nextcolleft) && !rightLine(cell) && !nextcellleft) + || (!colright && !nextcolleft && (rightLine(cell) || nextcellleft)) + || (coldouble != celldouble)) + && !decimal; // we center in multicol when no decimal point - if (column_info[c].alignment == LYX_ALIGN_DECIMAL) { + if (decimal) { docstring const align_d = column_info[c].decimal_point; DocIterator const dit = separatorPos(cellInset(cell), align_d); - ismulticol |= !dit; + bool const nosep = !dit; + ismulticol |= nosep; + celldouble &= nosep; } // up counter by 1 for each decimally aligned col since they use 2 latex cols int latexcolspan = columnSpan(cell); - for(col_type col = c; col < c + columnSpan(cell); ++col) + for (col_type col = c; col < c + columnSpan(cell); ++col) if (column_info[col].alignment == LYX_ALIGN_DECIMAL) ++latexcolspan; if (ismulticol) { os << "\\multicolumn{" << latexcolspan << "}{"; - if (c ==0 && leftLine(cell)) + if (((bidi && c == getLastCellInRow(cellRow(0)) && rightLine(cell)) + || (!bidi && c == 0 && leftLine(cell)))) + os << '|'; + if (bidi && celldouble) + // add extra vertical line if we want a double one os << '|'; if (!cellInfo(cell).align_special.empty()) { os << cellInfo(cell).align_special; @@ -2397,9 +2840,9 @@ void Tabular::TeXCellPreamble(otexstream & os, idx_type cell, } } // end if else !getPWidth } // end if else !cellinfo_of_cell - if (rightLine(cell) || nextcellleft) + if ((bidi && leftLine(cell)) || (!bidi && rightLine(cell)) || nextcellleft) os << '|'; - if (celldouble) + if (!bidi && celldouble) // add extra vertical line if we want a double one os << '|'; os << "}{"; @@ -2453,7 +2896,21 @@ 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}["; + switch (valign) { + case LYX_VALIGN_TOP: + os << 't'; + break; + case LYX_VALIGN_MIDDLE: + os << 'm'; + break; + case LYX_VALIGN_BOTTOM: + os << 'b'; + break; } + os << "]{\\linewidth}\n"; +} } @@ -2469,6 +2926,8 @@ 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}"; if (getRotateCell(cell) != 0) os << breakln << "\\end{turn}"; if (ismultirow) @@ -2479,7 +2938,9 @@ void Tabular::TeXCellPostamble(otexstream & os, idx_type cell, void Tabular::TeXLongtableHeaderFooter(otexstream & os, - OutputParams const & runparams) const + OutputParams const & runparams, + list columns, + list logical_columns) const { if (!is_long_tabular) return; @@ -2491,7 +2952,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); + TeXRow(os, r, runparams, columns, logical_columns); } } // output first header info @@ -2500,7 +2961,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); + TeXRow(os, r, runparams, columns, logical_columns); } if (endfirsthead.bottomDL) os << "\\hline\n"; @@ -2514,7 +2975,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); + TeXRow(os, r, runparams, columns, logical_columns); } if (endhead.bottomDL) os << "\\hline\n"; @@ -2526,7 +2987,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); + TeXRow(os, r, runparams, columns, logical_columns); } if (endfoot.bottomDL) os << "\\hline\n"; @@ -2540,7 +3001,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); + TeXRow(os, r, runparams, columns, logical_columns); } if (endlastfoot.bottomDL) os << "\\hline\n"; @@ -2560,15 +3021,11 @@ bool Tabular::isValidRow(row_type row) const void Tabular::TeXRow(otexstream & os, row_type row, - OutputParams const & runparams) const + OutputParams const & runparams, + list columns, list logical_columns) const { - idx_type cell = cellIndex(row, 0); - InsetTableCell const * inset = cellInset(cell); - Paragraph const & par = inset->paragraphs().front(); - string const lang = par.getParLanguage(buffer().params())->lang(); - //output the top line - TeXTopHLine(os, row, lang); + TeXTopHLine(os, row, columns, logical_columns); if (row_info[row].top_space_default) { if (use_booktabs) @@ -2588,20 +3045,30 @@ void Tabular::TeXRow(otexstream & os, row_type row, } bool ismulticol = false; bool ismultirow = false; - for (col_type c = 0; c < ncols(); ++c) { + + // The bidi package (loaded by polyglossia with XeTeX) reverses RTL table columns + // Luabibdi (used by LuaTeX) behaves like classic + bool const bidi_rtl = + runparams.local_font->isRightToLeft() + && runparams.useBidiPackage(); + bool const ct = !buffer().params().output_changes; + idx_type lastcell = + 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) { - if (cell != getLastCellInRow(row)) + if (cell != lastcell) os << " & "; continue; } - TeXCellPreamble(os, cell, ismulticol, ismultirow); + TeXCellPreamble(os, cell, ismulticol, ismultirow, bidi_rtl); InsetTableCell const * inset = cellInset(cell); Paragraph const & par = inset->paragraphs().front(); @@ -2643,14 +3110,35 @@ void Tabular::TeXRow(otexstream & os, row_type row, head.setMacrocontextPositionRecursive(dit); bool hassep = false; InsetTableCell tail = splitCell(head, column_info[c].decimal_point, hassep); - head.latex(os, newrp); if (hassep) { - os << '&'; tail.setBuffer(head.buffer()); dit.pop_back(); dit.push_back(CursorSlice(tail)); tail.setMacrocontextPositionRecursive(dit); - tail.latex(os, newrp); + } + if (bidi_rtl) { + if (hassep) { + tail.latex(os, newrp); + os << '&'; + } + head.latex(os, newrp); + } else { + head.latex(os, newrp); + if (hassep) { + os << '&'; + tail.latex(os, newrp); + } + } + } 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) + continue; + it->latex(os, runparams); + break; } } else if (!isPartOfMultiRow(row, c)) { if (!runparams.nice) @@ -2663,7 +3151,7 @@ void Tabular::TeXRow(otexstream & os, row_type row, os << '}'; TeXCellPostamble(os, cell, ismulticol, ismultirow); - if (cell != getLastCellInRow(row)) { // not last cell in row + if (cell != lastcell) { // not last cell in row if (runparams.nice) os << " & "; else @@ -2686,7 +3174,7 @@ void Tabular::TeXRow(otexstream & os, row_type row, os << '\n'; //output the bottom line - TeXBottomHLine(os, row, lang); + TeXBottomHLine(os, row, columns, logical_columns); if (row_info[row].interline_space_default) { if (use_booktabs) @@ -2708,7 +3196,10 @@ void Tabular::TeXRow(otexstream & os, row_type row, void Tabular::latex(otexstream & os, OutputParams const & runparams) const { - bool const is_tabular_star = !tabular_width.zero(); + bool const is_tabular_star = !is_long_tabular && !tabular_width.zero() + && !hasVarwidthColumn(); + bool const is_xltabular = is_long_tabular + && (hasVarwidthColumn() || !tabular_width.zero()); TexRow::RowEntry pos = TexRow::textEntry(runparams.lastid, runparams.lastpos); //+--------------------------------------------------------------------- @@ -2719,25 +3210,97 @@ void Tabular::latex(otexstream & os, OutputParams const & runparams) const if (!TexRow::isNone(pos)) os.texrow().start(pos); - if (rotate != 0) - os << "\\begin{turn}{" << convert(rotate) << "}\n"; + if (rotate != 0) { + if (is_long_tabular) + os << "\\begin{landscape}\n"; + else + os << "\\begin{turn}{" << convert(rotate) << "}\n"; + } + + // The bidi package (loaded by polyglossia with XeTeX) swaps the column + // order for RTL (#9686). Thus we use this list. + bool const bidi_rtl = + 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 + // the - character when using languages that activate it (e.g., Czech, Slovak). + bool deactivate_chars = false; + if ((runparams.use_babel || runparams.use_polyglossia) + && contains(runparams.active_chars, '-')) { + bool have_clines = false; + // Check if we use \cline or \cmidrule + for (row_type row = 0; row < nrows(); ++row) { + col_type bset = 0, tset = 0; + for (auto const & c : columns) { + idx_type const idx = cellIndex(row, c); + if (bottomLineTrim(idx).first || bottomLineTrim(idx).second + || topLineTrim(idx).first || topLineTrim(idx).second) { + have_clines = true; + break; + } + if (bottomLine(cellIndex(row, c))) + ++bset; + if (topLine(cellIndex(row, c))) + ++tset; + } + if ((bset > 0 && bset < ncols()) || (tset > 0 && tset < ncols())) { + have_clines = true; + break; + } + } + if (have_clines) { + deactivate_chars = true; + os << "\\begingroup\n" + << "\\catcode`\\-=12\n"; + } + } if (is_long_tabular) { - os << "\\begin{longtable}"; + if (is_xltabular) + os << "\\begin{xltabular}"; + else + os << "\\begin{longtable}"; switch (longtabular_alignment) { case LYX_LONGTABULAR_ALIGN_LEFT: os << "[l]"; break; case LYX_LONGTABULAR_ALIGN_CENTER: + os << "[c]"; break; case LYX_LONGTABULAR_ALIGN_RIGHT: os << "[r]"; break; } + if (is_xltabular) { + if (tabular_width.zero()) + os << "{" << from_ascii("\\columnwidth") << "}"; + else + os << "{" << from_ascii(tabular_width.asLatexString()) << "}"; + } } else { if (is_tabular_star) os << "\\begin{tabular*}{" << from_ascii(tabular_width.asLatexString()) << "}"; - else + else if (hasVarwidthColumn()) { + os << "\\begin{tabularx}{"; + if (tabular_width.zero()) + os << from_ascii("\\columnwidth") << "}"; + else + os << from_ascii(tabular_width.asLatexString()) << "}"; + } else os << "\\begin{tabular}"; switch (tabular_valignment) { case LYX_VALIGN_TOP: @@ -2756,8 +3319,8 @@ void Tabular::latex(otexstream & os, OutputParams const & runparams) const if (is_tabular_star) os << "@{\\extracolsep{\\fill}}"; - for (col_type c = 0; c < ncols(); ++c) { - if (columnLeftLine(c)) + for (auto const & c : columns) { + if ((bidi_rtl && columnRightLine(c)) || (!bidi_rtl && columnLeftLine(c))) os << '|'; if (!column_info[c].align_special.empty()) { os << column_info[c].align_special; @@ -2779,11 +3342,15 @@ void Tabular::latex(otexstream & os, OutputParams const & runparams) const case LYX_ALIGN_LAYOUT: case LYX_ALIGN_SPECIAL: break; - case LYX_ALIGN_DECIMAL: - os << ">{\\raggedleft}"; + case LYX_ALIGN_DECIMAL: { + if (bidi_rtl) + os << ">{\\raggedright}"; + else + os << ">{\\raggedleft}"; decimal = true; break; } + } char valign = 'p'; switch (column_info[c].valignment) { @@ -2821,6 +3388,44 @@ void Tabular::latex(otexstream & os, OutputParams const & runparams) const os << '{' << from_ascii(column_info[c].p_width.asLatexString()) << '}'; + } else if (column_info[c].varwidth) { + switch (column_info[c].alignment) { + case LYX_ALIGN_LEFT: + os << ">{\\raggedright\\arraybackslash}"; + break; + case LYX_ALIGN_RIGHT: + os << ">{\\raggedleft\\arraybackslash}"; + break; + case LYX_ALIGN_CENTER: + os << ">{\\centering\\arraybackslash}"; + break; + case LYX_ALIGN_NONE: + case LYX_ALIGN_BLOCK: + case LYX_ALIGN_LAYOUT: + case LYX_ALIGN_SPECIAL: + case LYX_ALIGN_DECIMAL: + 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; + } + os << "V{\\linewidth}"; } else { switch (column_info[c].alignment) { case LYX_ALIGN_LEFT: @@ -2838,20 +3443,22 @@ void Tabular::latex(otexstream & os, OutputParams const & runparams) const } } // end if else !column_info[i].p_width } // end if else !column_info[i].align_special - if (columnRightLine(c)) + if ((bidi_rtl && columnLeftLine(c)) || (!bidi_rtl && columnRightLine(c))) os << '|'; } os << "}\n"; - TeXLongtableHeaderFooter(os, runparams); + 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); + TeXRow(os, r, runparams, columns, logical_columns); if (is_long_tabular && row_info[r].newpage) os << "\\newpage\n"; } @@ -2861,17 +3468,30 @@ void Tabular::latex(otexstream & os, OutputParams const & runparams) const //+ the closing of the tabular + //+--------------------------------------------------------------------- - if (is_long_tabular) - os << "\\end{longtable}"; - else { + if (is_long_tabular) { + if (is_xltabular) + os << "\\end{xltabular}"; + else + os << "\\end{longtable}"; + } else { if (is_tabular_star) os << "\\end{tabular*}"; + else if (hasVarwidthColumn()) + os << "\\end{tabularx}"; else os << "\\end{tabular}"; } - if (rotate != 0) - os << breakln << "\\end{turn}"; + if (deactivate_chars) + // close the group + os << "\n\\endgroup\n"; + + if (rotate != 0) { + if (is_long_tabular) + os << breakln << "\\end{landscape}"; + else + os << breakln << "\\end{turn}"; + } if (!TexRow::isNone(pos)) os.texrow().start(pos); @@ -3029,26 +3649,26 @@ int Tabular::docbook(odocstream & os, OutputParams const & runparams) const } -docstring Tabular::xhtmlRow(XHTMLStream & xs, row_type row, +docstring Tabular::xhtmlRow(XMLStream & xs, row_type row, OutputParams const & runparams, bool header) const { docstring ret; string const celltag = header ? "th" : "td"; idx_type cell = getFirstCellInRow(row); - xs << html::StartTag("tr"); + xs << xml::StartTag("tr"); for (col_type c = 0; c < ncols(); ++c) { if (isPartOfMultiColumn(row, c) || isPartOfMultiRow(row, c)) continue; stringstream attr; - + Length const cwidth = column_info[c].p_width; if (!cwidth.zero()) { string const hwidth = cwidth.asHTMLString(); attr << "style =\"width: " << hwidth << ";\" "; } - + attr << "align='"; switch (getAlignment(cell)) { case LYX_ALIGN_LEFT: @@ -3080,17 +3700,17 @@ docstring Tabular::xhtmlRow(XHTMLStream & xs, row_type row, else if (isMultiRow(cell)) attr << " rowspan='" << rowSpan(cell) << "'"; - xs << html::StartTag(celltag, attr.str(), true) << html::CR(); + xs << xml::StartTag(celltag, attr.str(), true) << xml::CR(); ret += cellInset(cell)->xhtml(xs, runparams); - xs << html::EndTag(celltag) << html::CR(); + xs << xml::EndTag(celltag) << xml::CR(); ++cell; } - xs << html::EndTag("tr"); + xs << xml::EndTag("tr"); return ret; } -docstring Tabular::xhtml(XHTMLStream & xs, OutputParams const & runparams) const +docstring Tabular::xhtml(XMLStream & xs, OutputParams const & runparams) const { docstring ret; @@ -3108,20 +3728,20 @@ docstring Tabular::xhtml(XHTMLStream & xs, OutputParams const & runparams) const align = "right"; break; } - xs << html::StartTag("div", "class='longtable' style='text-align: " + align + ";'") - << html::CR(); + xs << xml::StartTag("div", "class='longtable' style='text-align: " + align + ";'") + << xml::CR(); // The caption flag wins over head/foot 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 + ";'") + << 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(); + xs << xml::EndTag("div") << xml::CR(); } } - xs << html::StartTag("table") << html::CR(); + xs << xml::StartTag("table") << xml::CR(); // output header info bool const havefirsthead = haveLTFirstHead(false); @@ -3130,7 +3750,7 @@ docstring Tabular::xhtml(XHTMLStream & xs, OutputParams const & runparams) const // in XHTML. this test accomplishes that. bool const havehead = !havefirsthead && haveLTHead(false); if (havehead || havefirsthead) { - xs << html::StartTag("thead") << html::CR(); + xs << xml::StartTag("thead") << xml::CR(); for (row_type r = 0; r < nrows(); ++r) { if (((havefirsthead && row_info[r].endfirsthead) || (havehead && row_info[r].endhead)) && @@ -3138,14 +3758,14 @@ docstring Tabular::xhtml(XHTMLStream & xs, OutputParams const & runparams) const ret += xhtmlRow(xs, r, runparams, true); } } - xs << html::EndTag("thead") << html::CR(); + xs << xml::EndTag("thead") << xml::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(); + xs << xml::StartTag("tfoot") << xml::CR(); for (row_type r = 0; r < nrows(); ++r) { if (((havelastfoot && row_info[r].endlastfoot) || (havefoot && row_info[r].endfoot)) && @@ -3153,21 +3773,21 @@ docstring Tabular::xhtml(XHTMLStream & xs, OutputParams const & runparams) const ret += xhtmlRow(xs, r, runparams); } } - xs << html::EndTag("tfoot") << html::CR(); + xs << xml::EndTag("tfoot") << xml::CR(); } - xs << html::StartTag("tbody") << html::CR(); + xs << xml::StartTag("tbody") << xml::CR(); for (row_type r = 0; r < nrows(); ++r) { if (isValidRow(r)) { ret += xhtmlRow(xs, r, runparams); } } - xs << html::EndTag("tbody") - << html::CR() - << html::EndTag("table") - << html::CR(); + xs << xml::EndTag("tbody") + << xml::CR() + << xml::EndTag("table") + << xml::CR(); if (is_long_tabular) - xs << html::EndTag("div") << html::CR(); + xs << xml::EndTag("div") << xml::CR(); return ret; } @@ -3414,31 +4034,58 @@ void Tabular::validate(LaTeXFeatures & features) const features.require("NeedTabularnewline"); if (use_booktabs) features.require("booktabs"); - if (is_long_tabular) - features.require("longtable"); + if (is_long_tabular && !hasVarwidthColumn()) { + if (tabular_width.zero()) + features.require("longtable"); + else + features.require("xltabular"); + } + if (rotate && is_long_tabular) + features.require("lscape"); if (needRotating()) features.require("rotating"); + if (hasVarwidthColumn()) { + if (is_long_tabular) + features.require("xltabular"); + else + features.require("tabularx"); + } for (idx_type cell = 0; cell < numberofcells; ++cell) { if (isMultiRow(cell)) features.require("multirow"); + if (getUsebox(cell) == BOX_VARWIDTH) + features.require("varwidth"); if (getVAlignment(cell) != LYX_VALIGN_TOP - || !getPWidth(cell).zero()) + || !getPWidth(cell).zero() + || isVTypeColumn(cellColumn(cell))) features.require("array"); + // Tell footnote that we need a savenote + // environment in non-long tables or + // longtable headers/footers + else if (!is_long_tabular && !features.inFloat()) + features.saveNoteEnv("tabular"); + else if (!isValidRow(cellRow(cell))) + features.saveNoteEnv("longtable"); + cellInset(cell)->validate(features); + features.saveNoteEnv(string()); } } -Tabular::BoxType Tabular::useParbox(idx_type cell) const +Tabular::BoxType Tabular::useBox(idx_type cell) const { ParagraphList const & parlist = cellInset(cell)->paragraphs(); + if (parlist.size() > 1) + return BOX_VARWIDTH; + 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 BOX_PARBOX; + if (cit->isNewline(i) || cit->layout().isEnvironment()) + return BOX_VARWIDTH; return BOX_NONE; } @@ -3452,13 +4099,13 @@ Tabular::BoxType Tabular::useParbox(idx_type cell) const InsetTableCell::InsetTableCell(Buffer * buf) : InsetText(buf, InsetText::PlainLayout), isFixedWidth(false), - contentAlign(LYX_ALIGN_CENTER) + isMultiColumn(false), isMultiRow(false), contentAlign(LYX_ALIGN_CENTER) {} bool InsetTableCell::forcePlainLayout(idx_type) const { - return !isFixedWidth; + return isMultiRow || (isMultiColumn && !isFixedWidth); } @@ -3470,7 +4117,7 @@ bool InsetTableCell::allowParagraphCustomization(idx_type) const bool InsetTableCell::forceLocalFontSwitch() const { - return isFixedWidth; + return true; } @@ -3479,6 +4126,9 @@ bool InsetTableCell::getStatus(Cursor & cur, FuncRequest const & cmd, { bool enabled = true; switch (cmd.action()) { + case LFUN_INSET_DISSOLVE: + enabled = false; + break; case LFUN_MATH_DISPLAY: if (!hasFixedWidth()) { enabled = false; @@ -3517,7 +4167,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); @@ -3525,6 +4175,33 @@ docstring InsetTableCell::xhtml(XHTMLStream & xs, OutputParams const & rp) const } +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 -= 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 + // to draw it partly out of view (bug 5890). + if (mi.base.textwidth < 1) + mi.base.textwidth = 1; + + // We tell metrics here not to expand on multiple pars + // This is the difference to InsetText::Metrics + if (hasFixedWidth()) + tm.metrics(mi, dim, mi.base.textwidth, false); + else + tm.metrics(mi, dim, 0, false); + mi.base.textwidth += horiz_offset; + dim.asc += topOffset(mi.base.bv); + dim.des += bottomOffset(mi.base.bv); + dim.wid += horiz_offset; +} + ///////////////////////////////////////////////////////////////////// // @@ -3665,15 +4342,17 @@ void InsetTabular::metrics(MetricsInfo & mi, Dimension & dim) const // multicolumn or multirow cell, but not first one continue; idx_type const cell = tabular.cellIndex(r, c); - Dimension dim; + Dimension dim0; MetricsInfo m = mi; Length const p_width = tabular.getPWidth(cell); if (!p_width.zero()) - m.base.textwidth = p_width.inPixels(mi.base); - tabular.cellInset(cell)->metrics(m, dim); - if (!p_width.zero()) - dim.wid = m.base.textwidth; - tabular.cellInfo(cell).width = dim.wid + 2 * WIDTH_OF_LINE + m.base.textwidth = mi.base.inPixels(p_width); + else if (tabular.column_info[c].varwidth) + m.base.textwidth = tabular.column_info[c].width; + tabular.cellInset(cell)->metrics(m, dim0); + if (!p_width.zero() || tabular.column_info[c].varwidth) + dim0.wid = m.base.textwidth; + tabular.cellInfo(cell).width = dim0.wid + 2 * WIDTH_OF_LINE + tabular.interColumnSpace(cell); // FIXME(?): do we need a second metrics call? @@ -3697,7 +4376,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); @@ -3710,43 +4389,45 @@ void InsetTabular::metrics(MetricsInfo & mi, Dimension & dim) const 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: + // = descent of text in last par + bottomOffset: int const lastpardes = tm.last().second->descent() - + TEXT_TO_INSET_OFFSET; + + bottomOffset(mi.base.bv); int offset = 0; switch (tabular.getVAlignment(cell)) { case Tabular::LYX_VALIGN_TOP: break; case Tabular::LYX_VALIGN_MIDDLE: - offset = -(dim.des - lastpardes)/2; + offset = -(dim0.des - lastpardes)/2; break; case Tabular::LYX_VALIGN_BOTTOM: - offset = -(dim.des - lastpardes); + offset = -(dim0.des - lastpardes); break; } tabular.cell_info[r][c].voffset = offset; - maxasc = max(maxasc, dim.asc - offset); - maxdes = max(maxdes, dim.des + offset); + maxasc = max(maxasc, dim0.asc - offset); + maxdes = max(maxdes, dim0.des + offset); } int const top_space = tabular.row_info[r].top_space_default ? - default_line_space : - tabular.row_info[r].top_space.inPixels(mi.base); + default_line_space : + mi.base.inPixels(tabular.row_info[r].top_space); tabular.setRowAscent(r, maxasc + ADD_TO_HEIGHT + top_space); int const bottom_space = tabular.row_info[r].bottom_space_default ? - default_line_space : - tabular.row_info[r].bottom_space.inPixels(mi.base); + default_line_space : + mi.base.inPixels(tabular.row_info[r].bottom_space); tabular.setRowDescent(r, maxdes + ADD_TO_HEIGHT + bottom_space); } - tabular.updateColumnWidths(); + // We need to recalculate the metrics after column width calculation + // with xtabular (possibly multiple times, so the call is recursive). + if (tabular.updateColumnWidths(mi) && tabular.hasVarwidthColumn()) + metrics(mi, dim); dim.asc = tabular.rowAscent(0) - tabular.offsetVAlignment(); dim.des = tabular.height() - dim.asc; dim.wid = tabular.width() + 2 * ADD_TO_TABULAR_WIDTH; } -bool InsetTabular::isCellSelected(Cursor & cur, row_type row, col_type col) - const +bool InsetTabular::isCellSelected(Cursor & cur, row_type row, col_type col) const { if (&cur.inset() == this && cur.selection()) { if (cur.selIsMultiCell()) { @@ -3790,6 +4471,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) { @@ -3806,6 +4490,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. @@ -3884,35 +4577,81 @@ 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, + Color const incol, bool drawline, bool heavy = false) +{ + 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, + Painter::thin_line); + pi.pain.line(x1 + lt, y1, x2 - rt, y2, pi.textColor(col), + drawline ? Painter::line_solid : Painter::line_onoffdash, + (heavy ? 2 : 1) * Painter::thin_line); + if (drawline && rt > 0) + pi.pain.line(x2 - rt, y1, x2, y2, pi.textColor(Color_tabularonoffline), + Painter::line_onoffdash, + Painter::thin_line); +} + +} + + void InsetTabular::drawCellLines(PainterInfo & pi, int x, int y, row_type row, idx_type cell) const { y -= tabular.rowAscent(row); int const w = tabular.cellWidth(cell); int const h = tabular.cellHeight(cell); - Color const linecolor = pi.textColor(Color_tabularline); - Color const gridcolor = pi.textColor(Color_tabularonoffline); + int lt = 0; + int rt = 0; + + 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 = InsetTableCell(*tabular.cellInset(cell)).paragraphs().front().lookupChange(0).color(); // Top bool drawline = tabular.topLine(cell) || (row > 0 && tabular.bottomLine(tabular.cellAbove(cell))); - pi.pain.line(x, y, x + w, y, - drawline ? linecolor : gridcolor, - drawline ? Painter::line_solid : Painter::line_onoffdash); + bool heavy = tabular.use_booktabs + && (row == 0 || (tabular.is_long_tabular && row == 1 && tabular.ltCaption(0))) + && tabular.rowTopLine(row); + if (tabular.topLineTrim(cell).first + || (row > 0 && tabular.bottomLineTrim(tabular.cellIndex(row - 1, col)).first)) + lt = 10; + 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, colour, drawline, heavy); // Bottom + lt = rt = 0; drawline = tabular.bottomLine(cell); - pi.pain.line(x, y + h, x + w, y + h, - drawline ? linecolor : gridcolor, - drawline ? Painter::line_solid : Painter::line_onoffdash); + row_type const lastrow = tabular.nrows() - 1; + // Consider multi-rows + row_type r = row; + while (r < lastrow && tabular.isMultiRow(tabular.cellIndex(r, col)) + && tabular.isPartOfMultiRow(r + 1, col)) + r++; + heavy = tabular.use_booktabs + && ((row == lastrow && tabular.rowBottomLine(row)) + || (r == lastrow && tabular.rowBottomLine(r))); + if (tabular.bottomLineTrim(cell).first) + lt = 10; + if (tabular.bottomLineTrim(cell).second) + rt = 10; + tabline(pi, x, y + h, x + w, y + h, lt, rt, colour, drawline, heavy); // Left - col_type const col = tabular.cellColumn(cell); drawline = tabular.leftLine(cell) || (col > 0 && tabular.rightLine(tabular.cellIndex(row, col - 1))); - pi.pain.line(x, y, x, y + h, - drawline ? linecolor : gridcolor, - drawline ? Painter::line_solid : Painter::line_onoffdash); + tabline(pi, x, y, x, y + h, 0, 0, colour, drawline); // Right x -= tabular.interColumnSpace(cell); @@ -3923,9 +4662,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))); - pi.pain.line(x + w, y, x + w, y + h, - drawline ? linecolor : gridcolor, - drawline ? Painter::line_solid : Painter::line_onoffdash); + tabline(pi, x + w, y, x + w, y + h, 0, 0, colour, drawline); } @@ -3955,7 +4692,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(); @@ -4435,6 +5172,18 @@ void InsetTabular::doDispatch(Cursor & cur, FuncRequest & cmd) case LFUN_PASTE: if (!tabularStackDirty()) { + // Check if we have plain text or HTML with rows/columns. + // and if so, pass over to LFUN_CLIPBOARD_PASTE + if (theClipboard().hasTextContents(Clipboard::AnyTextType) + && !theClipboard().hasTextContents(Clipboard::LyXTextType)) { + docstring const clip = + theClipboard().getAsText(Clipboard::AnyTextType); + 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; @@ -4445,6 +5194,8 @@ void InsetTabular::doDispatch(Cursor & cur, FuncRequest & cmd) } break; + case LFUN_CHANGE_ACCEPT: + case LFUN_CHANGE_REJECT: case LFUN_FONT_EMPH: case LFUN_FONT_BOLD: case LFUN_FONT_BOLDSYMBOL: @@ -4459,6 +5210,7 @@ void InsetTabular::doDispatch(Cursor & cur, FuncRequest & cmd) case LFUN_FONT_SIZE: case LFUN_FONT_UNDERLINE: case LFUN_FONT_STRIKEOUT: + case LFUN_FONT_CROSSOUT: case LFUN_FONT_UNDERUNDERLINE: case LFUN_FONT_UNDERWAVE: case LFUN_LANGUAGE: @@ -4467,14 +5219,55 @@ void InsetTabular::doDispatch(Cursor & cur, FuncRequest & cmd) case LFUN_WORD_CAPITALIZE: case LFUN_WORD_UPCASE: case LFUN_WORD_LOWCASE: - case LFUN_CHARS_TRANSPOSE: + case LFUN_CHARS_TRANSPOSE: { + bool const ct = (act == LFUN_CHANGE_ACCEPT || act == LFUN_CHANGE_REJECT); if (cur.selIsMultiCell()) { row_type rs, re; col_type cs, ce; getSelection(cur, rs, re, cs, ce); Cursor tmpcur = cur; for (row_type r = rs; r <= re; ++r) { + if (ct && cs == 0 && ce == tabular.ncols() - 1) { + // whole row selected + if (act == LFUN_CHANGE_ACCEPT) { + 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.deleted()) + tabular.row_info[r].change.setUnchanged(); + else if (tabular.row_info[r].change.inserted()) { + tabular.deleteRow(r, true); + --re; + continue; + } + } + } for (col_type c = cs; c <= ce; ++c) { + if (ct && rs == 0 && re == tabular.nrows() - 1) { + // whole col selected + if (act == LFUN_CHANGE_ACCEPT) { + 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.deleted()) + tabular.column_info[c].change.setUnchanged(); + else if (tabular.column_info[c].change.inserted()) { + tabular.deleteColumn(c, true); + --ce; + continue; + } + } + } // cursor follows cell: tmpcur.idx() = tabular.cellIndex(r, c); // select this cell only: @@ -4488,11 +5281,53 @@ void InsetTabular::doDispatch(Cursor & cur, FuncRequest & cmd) cell(tmpcur.idx())->dispatch(tmpcur, 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 { cell(cur.idx())->dispatch(cur, cmd); break; } + } + + case LFUN_CHANGE_NEXT: + 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.changed()) { + // select row + cur.idx() = tabular.getFirstCellInRow(tabular.cellRow(cur.idx())); + cur.pit() = 0; + cur.pos() = 0; + cur.resetAnchor(); + cur.idx() = tabular.getLastCellInRow(tabular.cellRow(cur.idx())); + cur.pit() = cur.lastpit(); + cur.pos() = cur.lastpos(); + cur.selection(true); + bvcur = cur; + rowselect_ = true; + } + 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; + cur.pos() = 0; + cur.resetAnchor(); + cur.idx() = tabular.cellIndex(tabular.nrows() - 1, tabular.cellColumn(cur.idx())); + cur.pit() = cur.lastpit(); + cur.pos() = cur.lastpos(); + cur.selection(true); + bvcur = cur; + colselect_ = true; + } + break; + } case LFUN_INSET_SETTINGS: // relay this lfun to Inset, not to the cell. @@ -4537,6 +5372,7 @@ bool InsetTabular::getFeatureStatus(Cursor & cur, string const & s, switch (action) { case Tabular::SET_PWIDTH: case Tabular::SET_MPWIDTH: + case Tabular::TOGGLE_VARWIDTH_COLUMN: case Tabular::SET_SPECIAL_COLUMN: case Tabular::SET_SPECIAL_MULTICOLUMN: case Tabular::APPEND_ROW: @@ -4552,7 +5388,7 @@ bool InsetTabular::getFeatureStatus(Cursor & cur, string const & s, return true; case Tabular::SET_TABULAR_WIDTH: - status.setEnabled(!tabular.rotate && !tabular.is_long_tabular + status.setEnabled(!tabular.rotate && tabular.tabular_valignment == Tabular::LYX_VALIGN_MIDDLE); break; @@ -4633,10 +5469,15 @@ bool InsetTabular::getFeatureStatus(Cursor & cur, string const & s, case Tabular::SET_ALL_LINES: case Tabular::UNSET_ALL_LINES: + case Tabular::SET_INNER_LINES: case Tabular::SET_BORDER_LINES: status.setEnabled(!tabular.ltCaption(tabular.cellRow(cur.idx()))); break; + case Tabular::RESET_FORMAL_DEFAULT: + status.setEnabled(tabular.use_booktabs); + break; + case Tabular::SET_LINE_TOP: case Tabular::SET_LINE_BOTTOM: status.setEnabled(!tabular.ltCaption(tabular.cellRow(cur.idx()))); @@ -4648,6 +5489,20 @@ bool InsetTabular::getFeatureStatus(Cursor & cur, string const & s, && !tabular.ltCaption(tabular.cellRow(cur.idx()))); break; + case Tabular::SET_LTRIM_TOP: + case Tabular::SET_RTRIM_TOP: + status.setEnabled(tabular.use_booktabs + && tabular.cellRow(cur.idx()) != 0 + && !tabular.ltCaption(tabular.cellRow(cur.idx()))); + break; + + case Tabular::SET_LTRIM_BOTTOM: + case Tabular::SET_RTRIM_BOTTOM: + status.setEnabled(tabular.use_booktabs + && tabular.cellRow(cur.idx()) != tabular.nrows() - 1 + && !tabular.ltCaption(tabular.cellRow(cur.idx()))); + break; + case Tabular::TOGGLE_LINE_TOP: status.setEnabled(!tabular.ltCaption(tabular.cellRow(cur.idx()))); status.setOnOff(tabular.topLine(cur.idx())); @@ -4670,17 +5525,43 @@ bool InsetTabular::getFeatureStatus(Cursor & cur, string const & s, status.setOnOff(tabular.rightLine(cur.idx())); break; + case Tabular::TOGGLE_LTRIM_TOP: + status.setEnabled(tabular.use_booktabs + && !tabular.ltCaption(tabular.cellRow(cur.idx()))); + status.setOnOff(tabular.topLineTrim(cur.idx()).first); + break; + + case Tabular::TOGGLE_RTRIM_TOP: + status.setEnabled(tabular.use_booktabs + && !tabular.ltCaption(tabular.cellRow(cur.idx()))); + status.setOnOff(tabular.topLineTrim(cur.idx()).second); + break; + + case Tabular::TOGGLE_LTRIM_BOTTOM: + status.setEnabled(tabular.use_booktabs + && !tabular.ltCaption(tabular.cellRow(cur.idx()))); + status.setOnOff(tabular.bottomLineTrim(cur.idx()).first); + break; + + case Tabular::TOGGLE_RTRIM_BOTTOM: + status.setEnabled(tabular.use_booktabs + && !tabular.ltCaption(tabular.cellRow(cur.idx()))); + status.setOnOff(tabular.bottomLineTrim(cur.idx()).second); + break; + // multirow cells only inherit the alignment of the column if the column has // no width, otherwise they are left-aligned // therefore allow always left but right and center only if there is no width case Tabular::M_ALIGN_LEFT: flag = false; + // fall through case Tabular::ALIGN_LEFT: status.setOnOff(tabular.getAlignment(cur.idx(), flag) == LYX_ALIGN_LEFT); break; case Tabular::M_ALIGN_RIGHT: flag = false; + // fall through case Tabular::ALIGN_RIGHT: status.setEnabled(!(tabular.isMultiRow(cur.idx()) && !tabular.getPWidth(cur.idx()).zero())); @@ -4689,6 +5570,7 @@ bool InsetTabular::getFeatureStatus(Cursor & cur, string const & s, case Tabular::M_ALIGN_CENTER: flag = false; + // fall through case Tabular::ALIGN_CENTER: status.setEnabled(!(tabular.isMultiRow(cur.idx()) && !tabular.getPWidth(cur.idx()).zero())); @@ -4709,6 +5591,7 @@ bool InsetTabular::getFeatureStatus(Cursor & cur, string const & s, case Tabular::M_VALIGN_TOP: flag = false; + // fall through case Tabular::VALIGN_TOP: status.setEnabled(!tabular.getPWidth(cur.idx()).zero() && !tabular.isMultiRow(cur.idx())); @@ -4718,6 +5601,7 @@ bool InsetTabular::getFeatureStatus(Cursor & cur, string const & s, case Tabular::M_VALIGN_BOTTOM: flag = false; + // fall through case Tabular::VALIGN_BOTTOM: status.setEnabled(!tabular.getPWidth(cur.idx()).zero() && !tabular.isMultiRow(cur.idx())); @@ -4727,6 +5611,7 @@ bool InsetTabular::getFeatureStatus(Cursor & cur, string const & s, case Tabular::M_VALIGN_MIDDLE: flag = false; + // fall through case Tabular::VALIGN_MIDDLE: status.setEnabled(!tabular.getPWidth(cur.idx()).zero() && !tabular.isMultiRow(cur.idx())); @@ -4930,7 +5815,7 @@ bool InsetTabular::getStatus(Cursor & cur, FuncRequest const & cmd, if (&cur.inset() != this) break; string action = cmd.getArg(0); - string arg = cmd.getLongArg(1); + string arg = cmd.getLongArg(1); return getFeatureStatus(cur, action, arg, status); } @@ -4974,6 +5859,7 @@ bool InsetTabular::getStatus(Cursor & cur, FuncRequest const & cmd, // disable these with multiple cells selected case LFUN_INSET_INSERT: case LFUN_TABULAR_INSERT: + case LFUN_TABULAR_STYLE_INSERT: case LFUN_FLEX_INSERT: case LFUN_FLOAT_INSERT: case LFUN_FLOAT_WIDE_INSERT: @@ -4998,6 +5884,37 @@ bool InsetTabular::getStatus(Cursor & cur, FuncRequest const & cmd, return cell(cur.idx())->getStatus(cur, cmd, status); } + case LFUN_CHANGE_ACCEPT: + case LFUN_CHANGE_REJECT: { + if (cur.selIsMultiCell()) { + row_type rs, re; + col_type cs, ce; + getSelection(cur, rs, re, cs, ce); + for (row_type r = rs; r <= re; ++r) { + 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.changed()) { + status.setEnabled(true); + return true; + } + } + } + } else { + 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.changed()) { + status.setEnabled(true); + return true; + } + } + return cell(cur.idx())->getStatus(cur, cmd, status); + } + // disable in non-fixed-width cells case LFUN_PARAGRAPH_BREAK: // multirow does not allow paragraph breaks @@ -5005,14 +5922,14 @@ bool InsetTabular::getStatus(Cursor & cur, FuncRequest const & cmd, status.setEnabled(false); return true; } - // Fall back - case LFUN_NEWLINE_INSERT: { - if (tabular.getPWidth(cur.idx()).zero()) { + // 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; - } else - return cell(cur.idx())->getStatus(cur, cmd, status); - } + } + return cell(cur.idx())->getStatus(cur, cmd, status); case LFUN_NEWPAGE_INSERT: status.setEnabled(false); @@ -5049,18 +5966,18 @@ bool InsetTabular::getStatus(Cursor & cur, FuncRequest const & cmd, } -Inset::DisplayType InsetTabular::display() const +Inset::RowFlags InsetTabular::rowFlags() const { if (tabular.is_long_tabular) { switch (tabular.longtabular_alignment) { case Tabular::LYX_LONGTABULAR_ALIGN_LEFT: - return AlignLeft; + return Display | AlignLeft; case Tabular::LYX_LONGTABULAR_ALIGN_CENTER: - return AlignCenter; + return Display; case Tabular::LYX_LONGTABULAR_ALIGN_RIGHT: - return AlignRight; + return Display | AlignRight; default: - return AlignCenter; + return Display; } } else return Inline; @@ -5110,7 +6027,7 @@ int InsetTabular::docbook(odocstream & os, OutputParams const & runparams) const } -docstring InsetTabular::xhtml(XHTMLStream & xs, OutputParams const & rp) const +docstring InsetTabular::xhtml(XMLStream & xs, OutputParams const & rp) const { return tabular.xhtml(xs, rp); } @@ -5437,6 +6354,7 @@ void InsetTabular::tabularFeatures(Cursor & cur, row_type sel_row_start; row_type sel_row_end; bool setLines = false; + bool setLinesInnerOnly = false; LyXAlignment setAlign = LYX_ALIGN_LEFT; Tabular::VAlignment setVAlign = Tabular::LYX_VALIGN_TOP; @@ -5498,10 +6416,12 @@ void InsetTabular::tabularFeatures(Cursor & cur, case Tabular::SET_PWIDTH: { Length const len(value); - tabular.setColumnPWidth(cur, cur.idx(), len); - if (len.zero() - && tabular.getAlignment(cur.idx(), true) == LYX_ALIGN_BLOCK) - tabularFeatures(cur, Tabular::ALIGN_CENTER, string()); + for (col_type c = sel_col_start; c <= sel_col_end; ++c) { + tabular.setColumnPWidth(cur, tabular.cellIndex(row, c), len); + if (len.zero() + && tabular.getAlignment(tabular.cellIndex(row, c), true) == LYX_ALIGN_BLOCK) + tabularFeatures(cur, Tabular::ALIGN_CENTER, string()); + } break; } @@ -5509,6 +6429,13 @@ void InsetTabular::tabularFeatures(Cursor & cur, tabular.setMColumnPWidth(cur, cur.idx(), Length(value)); break; + case Tabular::TOGGLE_VARWIDTH_COLUMN: { + bool const varwidth = value == "on"; + for (col_type c = sel_col_start; c <= sel_col_end; ++c) + tabular.toggleVarwidth(tabular.cellIndex(row, c), varwidth); + break; + } + case Tabular::SET_MROFFSET: tabular.setMROffset(cur, cur.idx(), Length(value)); break; @@ -5534,9 +6461,12 @@ void InsetTabular::tabularFeatures(Cursor & cur, case Tabular::DELETE_ROW: if (sel_row_end == tabular.nrows() - 1 && sel_row_start != 0) { - for (col_type c = 0; c < tabular.ncols(); c++) + for (col_type c = 0; c < tabular.ncols(); c++) { tabular.setBottomLine(tabular.cellIndex(sel_row_start - 1, c), tabular.bottomLine(tabular.cellIndex(sel_row_end, c))); + tabular.setBottomLineTrim(tabular.cellIndex(sel_row_start - 1, c), + tabular.bottomLineTrim(tabular.cellIndex(sel_row_end, c))); + } } for (row_type r = sel_row_start; r <= sel_row_end; ++r) @@ -5621,6 +6551,46 @@ void InsetTabular::tabularFeatures(Cursor & cur, break; } + case Tabular::SET_LTRIM_TOP: + case Tabular::TOGGLE_LTRIM_TOP: { + bool l = (feature == Tabular::SET_LTRIM_TOP) + ? (value == "true") : !tabular.topLineTrim(cur.idx()).first; + for (row_type r = sel_row_start; r <= sel_row_end; ++r) + for (col_type c = sel_col_start; c <= sel_col_end; ++c) + tabular.setTopLineLTrim(tabular.cellIndex(r, c), l); + break; + } + + case Tabular::SET_RTRIM_TOP: + case Tabular::TOGGLE_RTRIM_TOP: { + bool l = (feature == Tabular::SET_RTRIM_TOP) + ? (value == "true") : !tabular.topLineTrim(cur.idx()).second; + for (row_type r = sel_row_start; r <= sel_row_end; ++r) + for (col_type c = sel_col_start; c <= sel_col_end; ++c) + tabular.setTopLineRTrim(tabular.cellIndex(r, c), l); + break; + } + + case Tabular::SET_LTRIM_BOTTOM: + case Tabular::TOGGLE_LTRIM_BOTTOM: { + bool l = (feature == Tabular::SET_LTRIM_BOTTOM) + ? (value == "true") : !tabular.bottomLineTrim(cur.idx()).first; + for (row_type r = sel_row_start; r <= sel_row_end; ++r) + for (col_type c = sel_col_start; c <= sel_col_end; ++c) + tabular.setBottomLineLTrim(tabular.cellIndex(r, c), l); + break; + } + + case Tabular::SET_RTRIM_BOTTOM: + case Tabular::TOGGLE_RTRIM_BOTTOM: { + bool l = (feature == Tabular::SET_RTRIM_BOTTOM) + ? (value == "true") : !tabular.bottomLineTrim(cur.idx()).second; + for (row_type r = sel_row_start; r <= sel_row_end; ++r) + for (col_type c = sel_col_start; c <= sel_col_end; ++c) + tabular.setBottomLineRTrim(tabular.cellIndex(r, c), l); + break; + } + case Tabular::SET_LINE_LEFT: case Tabular::TOGGLE_LINE_LEFT: { bool lineSet = (feature == Tabular::SET_LINE_LEFT) @@ -5659,6 +6629,7 @@ void InsetTabular::tabularFeatures(Cursor & cur, case Tabular::M_VALIGN_BOTTOM: case Tabular::M_VALIGN_MIDDLE: flag = false; + // fall through case Tabular::VALIGN_TOP: case Tabular::VALIGN_BOTTOM: case Tabular::VALIGN_MIDDLE: @@ -5672,7 +6643,7 @@ void InsetTabular::tabularFeatures(Cursor & cur, // just multicol for one single cell // check whether we are completely in a multicol if (!tabular.isMultiColumn(cur.idx())) - tabular.setMultiColumn(cur.idx(), 1, + tabular.setMultiColumn(cur, cur.idx(), 1, tabular.rightLine(cur.idx())); break; } @@ -5681,7 +6652,7 @@ void InsetTabular::tabularFeatures(Cursor & cur, idx_type const s_start = cur.selBegin().idx(); row_type const col_start = tabular.cellColumn(s_start); row_type const col_end = tabular.cellColumn(cur.selEnd().idx()); - cur.idx() = tabular.setMultiColumn(s_start, col_end - col_start + 1, + cur.idx() = tabular.setMultiColumn(cur, s_start, col_end - col_start + 1, tabular.rightLine(cur.selEnd().idx())); cur.pit() = 0; cur.pos() = 0; @@ -5727,7 +6698,7 @@ void InsetTabular::tabularFeatures(Cursor & cur, // just multirow for one single cell // check whether we are completely in a multirow if (!tabular.isMultiRow(cur.idx())) - tabular.setMultiRow(cur.idx(), 1, + tabular.setMultiRow(cur, cur.idx(), 1, tabular.bottomLine(cur.idx()), tabular.getAlignment(cur.idx())); break; @@ -5737,7 +6708,7 @@ void InsetTabular::tabularFeatures(Cursor & cur, idx_type const s_start = cur.selBegin().idx(); row_type const row_start = tabular.cellRow(s_start); row_type const row_end = tabular.cellRow(cur.selEnd().idx()); - cur.idx() = tabular.setMultiRow(s_start, row_end - row_start + 1, + cur.idx() = tabular.setMultiRow(cur, s_start, row_end - row_start + 1, tabular.bottomLine(cur.selEnd().idx()), tabular.getAlignment(cur.selEnd().idx())); cur.pit() = 0; @@ -5779,17 +6750,38 @@ void InsetTabular::tabularFeatures(Cursor & cur, break; } + case Tabular::SET_INNER_LINES: + setLinesInnerOnly = true; + // fall through case Tabular::SET_ALL_LINES: 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); - tabular.setTopLine(cell, setLines); - tabular.setBottomLine(cell, setLines); - tabular.setRightLine(cell, setLines); - tabular.setLeftLine(cell, setLines); + 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); + } + break; + + case Tabular::RESET_FORMAL_DEFAULT: + for (row_type r = 0; r < tabular.nrows(); ++r) { + bool const head_or_foot = r == 0 || r == tabular.nrows() - 1; + for (col_type c = 0; c < tabular.ncols(); ++c) { + idx_type const cell = tabular.cellIndex(r, c); + tabular.setTopLine(cell, head_or_foot); + tabular.setBottomLine(cell, head_or_foot); } + } break; case Tabular::SET_BORDER_LINES: @@ -5863,8 +6855,6 @@ void InsetTabular::tabularFeatures(Cursor & cur, tabular.longtabular_alignment = Tabular::LYX_LONGTABULAR_ALIGN_RIGHT; break; - - case Tabular::SET_ROTATE_CELL: for (row_type r = sel_row_start; r <= sel_row_end; ++r) for (col_type c = sel_col_start; c <= sel_col_end; ++c) @@ -5905,6 +6895,7 @@ void InsetTabular::tabularFeatures(Cursor & cur, case Tabular::UNSET_LTFIRSTHEAD: flag = false; + // fall through case Tabular::SET_LTFIRSTHEAD: tabular.getRowOfLTFirstHead(row, ltt); checkLongtableSpecial(ltt, value, flag); @@ -5913,6 +6904,7 @@ void InsetTabular::tabularFeatures(Cursor & cur, case Tabular::UNSET_LTHEAD: flag = false; + // fall through case Tabular::SET_LTHEAD: tabular.getRowOfLTHead(row, ltt); checkLongtableSpecial(ltt, value, flag); @@ -5921,6 +6913,7 @@ void InsetTabular::tabularFeatures(Cursor & cur, case Tabular::UNSET_LTFOOT: flag = false; + // fall through case Tabular::SET_LTFOOT: tabular.getRowOfLTFoot(row, ltt); checkLongtableSpecial(ltt, value, flag); @@ -5929,6 +6922,7 @@ void InsetTabular::tabularFeatures(Cursor & cur, case Tabular::UNSET_LTLASTFOOT: flag = false; + // fall through case Tabular::SET_LTLASTFOOT: tabular.getRowOfLTLastFoot(row, ltt); checkLongtableSpecial(ltt, value, flag); @@ -5937,6 +6931,7 @@ void InsetTabular::tabularFeatures(Cursor & cur, case Tabular::UNSET_LTNEWPAGE: flag = false; + // fall through case Tabular::SET_LTNEWPAGE: tabular.setLTNewPage(row, flag); break; @@ -5944,7 +6939,7 @@ void InsetTabular::tabularFeatures(Cursor & cur, case Tabular::SET_LTCAPTION: { if (tabular.ltCaption(row)) break; - cur.idx() = tabular.setLTCaption(row, true); + cur.idx() = tabular.setLTCaption(cur, row, true); cur.pit() = 0; cur.pos() = 0; cur.selection(false); @@ -5960,7 +6955,7 @@ void InsetTabular::tabularFeatures(Cursor & cur, case Tabular::UNSET_LTCAPTION: { if (!tabular.ltCaption(row)) break; - cur.idx() = tabular.setLTCaption(row, false); + cur.idx() = tabular.setLTCaption(cur, row, false); cur.pit() = 0; cur.pos() = 0; cur.selection(false); @@ -6068,18 +7063,18 @@ 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()); @@ -6135,10 +7130,14 @@ 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 ? + if (!lyxrc.ct_markup_copied) { + // do not paste deleted text + inset->acceptChanges(); + inset->setChange(Change(buffer().params().track_changes ? Change::INSERTED : Change::UNCHANGED)); + } cur.pos() = 0; + cur.pit() = 0; } } return true; @@ -6230,6 +7229,20 @@ Text * InsetTabular::getText(int idx) const } +bool InsetTabular::isChanged() const +{ + for (idx_type idx = 0; idx < nargs(); ++idx) { + if (cell(idx)->isChanged()) + return true; + if (tabular.row_info[tabular.cellRow(idx)].change.changed()) + return true; + if (tabular.column_info[tabular.cellColumn(idx)].change.changed()) + return true; + } + return false; +} + + void InsetTabular::setChange(Change const & change) { for (idx_type idx = 0; idx < nargs(); ++idx) @@ -6241,6 +7254,19 @@ 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.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.inserted()) + tabular.column_info[col].change.setUnchanged(); + else if (tabular.column_info[col].change.deleted()) + tabular.deleteColumn(col, true); + } + tabular.updateIndexes(); } @@ -6248,6 +7274,19 @@ 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.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.deleted()) + tabular.column_info[col].change.setUnchanged(); + else if (tabular.column_info[col].change.inserted()) + tabular.deleteColumn(col, true); + } + tabular.updateIndexes(); } @@ -6259,7 +7298,7 @@ bool InsetTabular::allowParagraphCustomization(idx_type cell) const bool InsetTabular::forcePlainLayout(idx_type cell) const { - return !tabular.getPWidth(cell).zero(); + return tabular.isMultiColumn(cell) && !tabular.getPWidth(cell).zero(); } @@ -6307,20 +7346,26 @@ bool InsetTabular::insertPlaintextString(BufferView & bv, docstring const & buf, } size_t op = 0; - idx_type const cells = loctab->numberofcells; + idx_type cells = loctab->numberofcells; p = 0; cols = ocol; rows = loctab->nrows(); - col_type const columns = loctab->ncols(); + col_type columns = loctab->ncols(); - while (cell < cells && p < len && row < rows && + while (p < len && (p = buf.find_first_of(from_ascii("\t\n"), p)) != docstring::npos) { if (p >= len) break; switch (buf[p]) { case '\t': - // we can only set this if we are not too far right + // append column if necessary + if (cols == columns) { + loctab->appendColumn(cols - 1); + columns = loctab->ncols(); + cells = loctab->numberofcells; + ++cell; + } if (cols < columns) { shared_ptr inset = loctab->cellInset(cell); Font const font = bv.textMetrics(&inset->text()). @@ -6342,6 +7387,12 @@ bool InsetTabular::insertPlaintextString(BufferView & bv, docstring const & buf, } cols = ocol; ++row; + // append row if necessary + if (row == rows && p < len - 1) { + loctab->appendRow(row - 1); + rows = loctab->nrows(); + cells = loctab->numberofcells; + } if (row < rows) cell = loctab->cellIndex(row, cols); break;