X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2Finsets%2FInsetTabular.cpp;h=69f9e73e59f0f0f11e125f2c80e4ba5449048455;hb=8124e6c02ea1fd6779bb6c47ffe2bca2c8bd2d97;hp=181e68cb7c56bc80b6df31e33cd84f1281577b59;hpb=9cb98136ceeb3fbda70aa5beea4cf3f5ea26a36c;p=lyx.git diff --git a/src/insets/InsetTabular.cpp b/src/insets/InsetTabular.cpp index 181e68cb7c..69f9e73e59 100644 --- a/src/insets/InsetTabular.cpp +++ b/src/insets/InsetTabular.cpp @@ -57,6 +57,7 @@ #include "frontends/Painter.h" #include "frontends/Selection.h" +#include "support/Changer.h" #include "support/convert.h" #include "support/debug.h" #include "support/docstream.h" @@ -163,6 +164,7 @@ TabularFeature tabularFeature[] = { Tabular::UNSET_MULTIROW, "unset-multirow", false }, { Tabular::SET_MROFFSET, "set-mroffset", true }, { Tabular::SET_ALL_LINES, "set-all-lines", false }, + { Tabular::TOGGLE_ALL_LINES, "toggle-all-lines", false }, { Tabular::RESET_FORMAL_DEFAULT, "reset-formal-default", false }, { Tabular::UNSET_ALL_LINES, "unset-all-lines", false }, { Tabular::TOGGLE_LONGTABULAR, "toggle-longtabular", false }, @@ -200,6 +202,7 @@ TabularFeature tabularFeature[] = { Tabular::SET_BOTTOM_SPACE, "set-bottom-space", true }, { Tabular::SET_INTERLINE_SPACE, "set-interline-space", true }, { Tabular::SET_BORDER_LINES, "set-border-lines", false }, + { Tabular::TOGGLE_BORDER_LINES, "toggle-border-lines", false }, { Tabular::TABULAR_VALIGN_TOP, "tabular-valign-top", false}, { Tabular::TABULAR_VALIGN_MIDDLE, "tabular-valign-middle", false}, { Tabular::TABULAR_VALIGN_BOTTOM, "tabular-valign-bottom", false}, @@ -209,6 +212,7 @@ TabularFeature tabularFeature[] = { Tabular::SET_DECIMAL_POINT, "set-decimal-point", true }, { Tabular::SET_TABULAR_WIDTH, "set-tabular-width", true }, { Tabular::SET_INNER_LINES, "set-inner-lines", false }, + { Tabular::TOGGLE_INNER_LINES, "toggle-inner-lines", false }, { Tabular::LAST_ACTION, "", false } }; @@ -616,7 +620,7 @@ DocIterator separatorPos(InsetTableCell const * cell, docstring const & align_d) InsetTableCell splitCell(InsetTableCell & head, docstring const & align_d, bool & hassep) { - InsetTableCell tail = InsetTableCell(head); + InsetTableCell tail = head; DocIterator const dit = separatorPos(&head, align_d); hassep = static_cast(dit); if (hassep) { @@ -811,7 +815,7 @@ void Tabular::deleteRow(row_type const row, bool const force) if (row + 1 < nrows() && cell_info[row][c].multirow == CELL_BEGIN_OF_MULTIROW && cell_info[row + 1][c].multirow == CELL_PART_OF_MULTIROW) { - cell_info[row + 1][c].multirow = CELL_BEGIN_OF_MULTIROW; + cell_info[row + 1][c] = cell_info[row][c]; } } if (ct) @@ -838,13 +842,13 @@ void Tabular::appendRow(row_type row) void Tabular::insertRow(row_type const row, bool copy) { - row_info.insert(row_info.begin() + row + 1, RowData(row_info[row])); + row_info.insert(row_info.begin() + row + 1, row_info[row]); cell_info.insert(cell_info.begin() + row + 1, cell_vector(0, CellData(buffer_))); for (col_type c = 0; c < ncols(); ++c) { cell_info[row + 1].insert(cell_info[row + 1].begin() + c, - copy ? CellData(cell_info[row][c]) : CellData(buffer_)); + copy ? cell_info[row][c] : CellData(buffer_)); if (cell_info[row][c].multirow == CELL_BEGIN_OF_MULTIROW) cell_info[row + 1][c].multirow = CELL_PART_OF_MULTIROW; } @@ -871,49 +875,92 @@ void Tabular::insertRow(row_type const row, bool copy) } -void Tabular::moveColumn(col_type col, ColDirection direction) +void Tabular::moveColumn(col_type col_start, col_type col_end, + ColDirection direction) { - if (direction == Tabular::LEFT) - col = col - 1; - - std::swap(column_info[col], column_info[col + 1]); - - for (row_type r = 0; r < nrows(); ++r) { - std::swap(cell_info[r][col], cell_info[r][col + 1]); - std::swap(cell_info[r][col].left_line, cell_info[r][col + 1].left_line); - std::swap(cell_info[r][col].right_line, cell_info[r][col + 1].right_line); - - idx_type const i = cellIndex(r, col); - idx_type const j = cellIndex(r, col + 1); - if (buffer().params().track_changes) { - cellInfo(i).inset->setChange(Change(Change::INSERTED)); - cellInfo(j).inset->setChange(Change(Change::INSERTED)); + if (direction == Tabular::LEFT) { + for (col_type col = col_start; col <= col_end; ++col) { + std::swap(column_info[col - 1], column_info[col]); + for (row_type r = 0; r < nrows(); ++r) { + std::swap(cell_info[r][col - 1], cell_info[r][col]); + std::swap(cell_info[r][col - 1].left_line, cell_info[r][col].left_line); + std::swap(cell_info[r][col - 1].right_line, cell_info[r][col].right_line); + + if (buffer().params().track_changes) { + idx_type const i = cellIndex(r, col - 1); + idx_type const j = cellIndex(r, col); + cellInfo(i).inset->setChange(Change(Change::INSERTED)); + cellInfo(j).inset->setChange(Change(Change::INSERTED)); + } + } + updateIndexes(); + if (col == ncols()) + break; + } + } else { + for (col_type col = col_end; col >= col_start; --col) { + std::swap(column_info[col], column_info[col + 1]); + for (row_type r = 0; r < nrows(); ++r) { + std::swap(cell_info[r][col], cell_info[r][col + 1]); + std::swap(cell_info[r][col].left_line, cell_info[r][col + 1].left_line); + std::swap(cell_info[r][col].right_line, cell_info[r][col + 1].right_line); + + if (buffer().params().track_changes) { + idx_type const i = cellIndex(r, col); + idx_type const j = cellIndex(r, col + 1); + cellInfo(i).inset->setChange(Change(Change::INSERTED)); + cellInfo(j).inset->setChange(Change(Change::INSERTED)); + } + } + updateIndexes(); + if (col == 0) + break; } } - updateIndexes(); } -void Tabular::moveRow(row_type row, RowDirection direction) +void Tabular::moveRow(row_type row_start, row_type row_end, RowDirection direction) { - if (direction == Tabular::UP) - row = row - 1; - - std::swap(row_info[row], row_info[row + 1]); - - for (col_type c = 0; c < ncols(); ++c) { - std::swap(cell_info[row][c], cell_info[row + 1][c]); - std::swap(cell_info[row][c].top_line, cell_info[row + 1][c].top_line); - std::swap(cell_info[row][c].bottom_line, cell_info[row + 1][c].bottom_line); - - idx_type const i = cellIndex(row, c); - idx_type const j = cellIndex(row + 1, c); - if (buffer().params().track_changes) { - cellInfo(i).inset->setChange(Change(Change::INSERTED)); - cellInfo(j).inset->setChange(Change(Change::INSERTED)); + if (direction == Tabular::UP) { + for (row_type row = row_start; row <= row_end; ++row) { + std::swap(row_info[row - 1], row_info[row]); + for (col_type c = 0; c < ncols(); ++c) { + std::swap(cell_info[row - 1][c], cell_info[row][c]); + std::swap(cell_info[row - 1][c].top_line, cell_info[row][c].top_line); + std::swap(cell_info[row - 1][c].bottom_line, cell_info[row][c].bottom_line); + + idx_type const i = cellIndex(row - 1, c); + idx_type const j = cellIndex(row, c); + if (buffer().params().track_changes) { + cellInfo(i).inset->setChange(Change(Change::INSERTED)); + cellInfo(j).inset->setChange(Change(Change::INSERTED)); + } + } + updateIndexes(); + if (row == nrows()) + break; + } + } else { + for (row_type row = row_end; row >= row_start; --row) { + std::swap(row_info[row], row_info[row + 1]); + for (col_type c = 0; c < ncols(); ++c) { + std::swap(cell_info[row][c], cell_info[row + 1][c]); + std::swap(cell_info[row][c].top_line, cell_info[row + 1][c].top_line); + std::swap(cell_info[row][c].bottom_line, cell_info[row + 1][c].bottom_line); + + idx_type const i = cellIndex(row, c); + idx_type const j = cellIndex(row + 1, c); + if (buffer().params().track_changes) { + cellInfo(i).inset->setChange(Change(Change::INSERTED)); + cellInfo(j).inset->setChange(Change(Change::INSERTED)); + } + } + updateIndexes(); + if (row == 0) + break; } } - updateIndexes(); } @@ -930,7 +977,7 @@ void Tabular::deleteColumn(col_type const col, bool const force) if (col + 1 < ncols() && cell_info[r][col].multicolumn == CELL_BEGIN_OF_MULTICOLUMN && cell_info[r][col + 1].multicolumn == CELL_PART_OF_MULTICOLUMN) { - cell_info[r][col + 1].multicolumn = CELL_BEGIN_OF_MULTICOLUMN; + cell_info[r][col + 1] = cell_info[r][col]; } if (!ct) cell_info[r].erase(cell_info[r].begin() + col); @@ -958,11 +1005,11 @@ void Tabular::appendColumn(col_type col) void Tabular::insertColumn(col_type const col, bool copy) { bool const ct = buffer().params().track_changes; - column_info.insert(column_info.begin() + col + 1, ColumnData(column_info[col])); + column_info.insert(column_info.begin() + col + 1, column_info[col]); for (row_type r = 0; r < nrows(); ++r) { cell_info[r].insert(cell_info[r].begin() + col + 1, - copy ? CellData(cell_info[r][col]) : CellData(buffer_)); + copy ? cell_info[r][col] : CellData(buffer_)); if (cell_info[r][col].multicolumn == CELL_BEGIN_OF_MULTICOLUMN) cell_info[r][col + 1].multicolumn = CELL_PART_OF_MULTICOLUMN; } @@ -1077,6 +1124,76 @@ bool Tabular::rightLine(idx_type cell, bool const ignore_bt) const } +bool Tabular::outsideBorders( + row_type const sel_row_start, row_type const sel_row_end, + col_type const sel_col_start, col_type const sel_col_end) const +{ + if (!use_booktabs) + for (row_type r = sel_row_start; r <= sel_row_end; ++r) { + if (!leftLine(cellIndex(r, sel_col_start)) + || !rightLine(cellIndex(r, sel_col_end))) + return false; + } + for (col_type c = sel_col_start; c <= sel_col_end; ++c) { + if (!topLine(cellIndex(sel_row_start, c)) + || !bottomLine(cellIndex(sel_row_end, c))) + return false; + } + return true; +} + + +bool Tabular::innerBorders( + row_type const sel_row_start, row_type const sel_row_end, + col_type const sel_col_start, col_type const sel_col_end) const +{ + // Single cell has no inner borders + if (sel_row_start == sel_row_end && sel_col_start == sel_col_end) + return false; + for (row_type r = sel_row_start; r <= sel_row_end; ++r) + for (col_type c = sel_col_start; c <= sel_col_end; ++c) { + idx_type const cell = cellIndex(r, c); + if ((r != sel_row_start && !topLine(cell) + && cell_info[r][c].multirow != CELL_PART_OF_MULTIROW) + || (!use_booktabs + && c != sel_col_start && !leftLine(cell) + && cell_info[r][c].multicolumn != CELL_PART_OF_MULTICOLUMN)) + return false; + } + return true; +} + + +void Tabular::setLines( + row_type const sel_row_start, row_type const sel_row_end, + col_type const sel_col_start, col_type const sel_col_end, + bool setLinesInnerOnly, bool setLines) +{ + for (row_type r = sel_row_start; r <= sel_row_end; ++r) + for (col_type c = sel_col_start; c <= sel_col_end; ++c) { + idx_type const cell = cellIndex(r, c); + if (!(setLinesInnerOnly && r == sel_row_start) + // for multirows, cell is taken care of at beginning + && cell_info[r][c].multirow != CELL_PART_OF_MULTIROW) + setTopLine(cell, setLines); + if (!(setLinesInnerOnly && r == sel_row_end) + && (r == sel_row_end || (!setLines + // for multirows, cell is taken care of at the last part + && cell_info[r + 1][c].multirow != CELL_PART_OF_MULTIROW))) + setBottomLine(cell, setLines); + if (!(setLinesInnerOnly && c == sel_col_start) + // for multicolumns, cell is taken care of at beginning + && cell_info[r][c].multicolumn != CELL_PART_OF_MULTICOLUMN) + setLeftLine(cell, setLines); + if (!(setLinesInnerOnly && c == sel_col_end) + && (c == sel_col_end || (!setLines + // for multicolumns, cell is taken care of at the last part + && cell_info[r][c + 1].multicolumn != CELL_PART_OF_MULTICOLUMN))) + setRightLine(cell, setLines); + } +} + + pair Tabular::topLineTrim(idx_type const cell) const { if (!use_booktabs) @@ -1310,36 +1427,18 @@ void Tabular::setVAlignment(idx_type cell, VAlignment align, namespace { /** - * Allow line and paragraph breaks for fixed width multicol/multirow cells + * Allow line and paragraph breaks for fixed width multirow cells * or disallow them, merge cell paragraphs and reset layout to standard * for variable width multicol cells. */ void toggleFixedWidth(Cursor & cur, InsetTableCell * inset, - bool const fixedWidth, bool const multicol, - bool const multirow) + bool const fixedWidth, bool const multirow) { inset->toggleFixedWidth(fixedWidth); - if (!multirow && (fixedWidth || !multicol)) + if (!multirow) return; - // merge all paragraphs to one BufferParams const & bp = cur.bv().buffer().params(); - while (inset->paragraphs().size() > 1) - mergeParagraph(bp, inset->paragraphs(), 0); - - // This is relevant for multirows - if (fixedWidth) - return; - - // remove newlines - ParagraphList::iterator pit = inset->paragraphs().begin(); - for (; pit != inset->paragraphs().end(); ++pit) { - for (pos_type j = 0; j != pit->size(); ++j) { - if (pit->isNewline(j)) - pit->eraseChar(j, bp.track_changes); - } - } - // reset layout cur.push(*inset); // undo information has already been recorded @@ -1366,10 +1465,10 @@ void Tabular::setColumnPWidth(Cursor & cur, idx_type cell, idx_type const cidx = cellIndex(r, c); // because of multicolumns toggleFixedWidth(cur, cellInset(cidx).get(), - !getPWidth(cidx).zero(), isMultiColumn(cidx), - isMultiRow(cidx)); + !getPWidth(cidx).zero(), isMultiRow(cidx)); if (isMultiRow(cidx)) - setAlignment(cidx, LYX_ALIGN_LEFT, false); + setAlignment(cidx, column_info[c].alignment, + !column_info[c].p_width.zero()); } // cur can become invalid after paragraphs were merged cur.fixIfBroken(); @@ -1394,7 +1493,7 @@ bool Tabular::setMColumnPWidth(Cursor & cur, idx_type cell, cellInfo(cell).p_width = width; toggleFixedWidth(cur, cellInset(cell).get(), !width.zero(), - isMultiColumn(cell), isMultiRow(cell)); + isMultiRow(cell)); // cur can become invalid after paragraphs were merged cur.fixIfBroken(); return true; @@ -1404,6 +1503,7 @@ bool Tabular::setMColumnPWidth(Cursor & cur, idx_type cell, bool Tabular::toggleVarwidth(idx_type cell, bool const varwidth) { column_info[cellColumn(cell)].varwidth = varwidth; + cellInset(cell).get()->toggleVarWidth(varwidth); return true; } @@ -1984,7 +2084,9 @@ bool Tabular::isVTypeColumn(col_type c) const { for (row_type r = 0; r < nrows(); ++r) { idx_type idx = cellIndex(r, c); - if (getRotateCell(idx) == 0 && useBox(idx) == BOX_VARWIDTH) + if (getRotateCell(idx) == 0 && useBox(idx) == BOX_VARWIDTH + && getAlignment(idx) == LYX_ALIGN_LEFT + && getVAlignment(idx) == LYX_VALIGN_TOP) return true; } return false; @@ -2021,8 +2123,7 @@ idx_type Tabular::setMultiColumn(Cursor & cur, idx_type cell, idx_type number, // non-fixed width multicolumns cannot have multiple paragraphs if (getPWidth(cell).zero()) { toggleFixedWidth(cur, cellInset(cell).get(), - !getPWidth(cell).zero(), isMultiColumn(cell), - isMultiRow(cell)); + !getPWidth(cell).zero(), isMultiRow(cell)); // cur can become invalid after paragraphs were merged cur.fixIfBroken(); } @@ -2081,7 +2182,7 @@ idx_type Tabular::setMultiRow(Cursor & cur, idx_type cell, idx_type number, if (getPWidth(cell).zero()) { toggleFixedWidth(cur, cellInset(cell).get(), !getPWidth(cell).zero(), - isMultiColumn(cell), isMultiRow(cell)); + isMultiRow(cell)); // cur can become invalid after paragraphs were merged cur.fixIfBroken(); } @@ -2577,7 +2678,7 @@ void Tabular::TeXTopHLine(otexstream & os, row_type row, list columns, break; } - for (col_type j = cstart ; j < c ; ++j) + for (col_type j = cstart ; j <= c ; ++j) if (column_info[j].alignment == LYX_ALIGN_DECIMAL) ++offset; col_type lastcol = (*it1 == *it2) ? c + 1 + offset : columns.size() - c + offset; @@ -2713,7 +2814,7 @@ void Tabular::TeXBottomHLine(otexstream & os, row_type row, list colum break; } - for (col_type j = cstart ; j < c ; ++j) + for (col_type j = cstart ; j <= c ; ++j) if (column_info[j].alignment == LYX_ALIGN_DECIMAL) ++offset; col_type lastcol = (*it1 == *it2) ? c + 1 + offset : columns.size() - c + offset; @@ -2825,16 +2926,21 @@ void Tabular::TeXCellPreamble(otexstream & os, idx_type cell, << from_ascii(getPWidth(cell).asLatexString()) << '}'; } else { - switch (align) { - case LYX_ALIGN_LEFT: - os << 'l'; - break; - case LYX_ALIGN_RIGHT: - os << 'r'; - break; - default: - os << 'c'; - break; + if ((getRotateCell(cell) == 0 && useBox(cell) == BOX_VARWIDTH + && align == LYX_ALIGN_LEFT)) + os << "V{\\linewidth}"; + else { + switch (align) { + case LYX_ALIGN_LEFT: + os << 'l'; + break; + case LYX_ALIGN_RIGHT: + os << 'r'; + break; + default: + os << 'c'; + break; + } } } // end if else !getPWidth } // end if else !cellinfo_of_cell @@ -2852,9 +2958,14 @@ void Tabular::TeXCellPreamble(otexstream & os, idx_type cell, os << "\\multirow{" << rowSpan(cell) << "}{"; if (!getPWidth(cell).zero()) os << from_ascii(getPWidth(cell).asLatexString()); - else - // we need to set a default value - os << "*"; + else { + if (column_info[c].varwidth) + // this inherits varwidth size + os << "="; + else + // we need to set a default value + os << "*"; + } os << "}"; if (!getMROffset(cell).zero()) os << "[" << from_ascii(getMROffset(cell).asLatexString()) << "]"; @@ -2894,8 +3005,10 @@ void Tabular::TeXCellPreamble(otexstream & os, idx_type cell, } os << "]{" << from_ascii(getPWidth(cell).asLatexString()) << "}\n"; - } else if (getRotateCell(cell) != 0 && getUsebox(cell) == BOX_VARWIDTH) { - os << "\\begin{varwidth}["; + } else if (getUsebox(cell) == BOX_VARWIDTH + && (getRotateCell(cell) != 0 || align != LYX_ALIGN_LEFT + || valign != LYX_VALIGN_TOP || hasNewlines(cell))) { + os << "\\begin{cellvarwidth}["; switch (valign) { case LYX_VALIGN_TOP: os << 't'; @@ -2906,9 +3019,26 @@ void Tabular::TeXCellPreamble(otexstream & os, idx_type cell, case LYX_VALIGN_BOTTOM: os << 'b'; break; + } + os << "]\n"; + switch (align) { + case LYX_ALIGN_RIGHT: + os << "\\raggedleft\n"; + break; + case LYX_ALIGN_CENTER: + os << "\\centering\n"; + break; + case LYX_ALIGN_LEFT: + //os << "\\narrowragged\n"; + break; + case LYX_ALIGN_BLOCK: + case LYX_ALIGN_DECIMAL: + case LYX_ALIGN_NONE: + case LYX_ALIGN_LAYOUT: + case LYX_ALIGN_SPECIAL: + break; + } } - os << "]{\\linewidth}\n"; -} } @@ -2924,8 +3054,10 @@ void Tabular::TeXCellPostamble(otexstream & os, idx_type cell, os << '}'; else if (getUsebox(cell) == BOX_MINIPAGE) os << breakln << "\\end{minipage}"; - else if (getRotateCell(cell) != 0 && getUsebox(cell) == BOX_VARWIDTH) - os << breakln << "\\end{varwidth}"; + else if (getUsebox(cell) == BOX_VARWIDTH + && (getRotateCell(cell) != 0 || getAlignment(cell) != LYX_ALIGN_LEFT + || getVAlignment(cell) != LYX_VALIGN_TOP || hasNewlines(cell))) + os << breakln << "\\end{cellvarwidth}"; if (getRotateCell(cell) != 0) os << breakln << "\\end{turn}"; if (ismultirow) @@ -3076,7 +3208,7 @@ void Tabular::TeXRow(otexstream & os, row_type row, bool rtl = par.isRTL(buffer().params()) && !par.empty() && getPWidth(cell).zero() - && !runparams.use_polyglossia; + && !runparams.isFullUnicode(); if (rtl) { string const lang = @@ -3140,6 +3272,8 @@ void Tabular::TeXRow(otexstream & os, row_type row, } else if (!isPartOfMultiRow(row, c)) { if (!runparams.nice) os.texrow().start(par.id(), 0); + if (isMultiRow(cell) && !LaTeXFeatures::isAvailableAtLeastFrom("multirow", 2021, 1, 29)) + newrp.isNonLong = true; inset->latex(os, newrp); } @@ -3404,26 +3538,9 @@ void Tabular::latex(otexstream & os, OutputParams const & runparams) const break; } os << 'X'; - } else if (isVTypeColumn(c)) { - switch (column_info[c].alignment) { - case LYX_ALIGN_LEFT: - os << ">{\\raggedright}"; - break; - case LYX_ALIGN_RIGHT: - os << ">{\\raggedleft}"; - break; - case LYX_ALIGN_CENTER: - os << ">{\\centering}"; - break; - case LYX_ALIGN_NONE: - case LYX_ALIGN_BLOCK: - case LYX_ALIGN_LAYOUT: - case LYX_ALIGN_SPECIAL: - case LYX_ALIGN_DECIMAL: - break; - } + } else if (isVTypeColumn(c)) os << "V{\\linewidth}"; - } else { + else { switch (column_info[c].alignment) { case LYX_ALIGN_LEFT: os << 'l'; @@ -3495,314 +3612,361 @@ void Tabular::latex(otexstream & os, OutputParams const & runparams) const } -void Tabular::docbookRow(XMLStream & xs, row_type row, - OutputParams const & runparams, bool header) const +std::string Tabular::getVAlignAsXmlAttribute(idx_type cell) const { - switch (buffer().params().docbook_table_output) { - case BufferParams::HTMLTable: - docbookRowAsHTML(xs, row, runparams, header); - break; - case BufferParams::CALSTable: - docbookRowAsCALS(xs, row, runparams); - break; + switch (getVAlignment(cell)) { + case LYX_VALIGN_TOP: + return "valign='top'"; + case LYX_VALIGN_BOTTOM: + return "valign='bottom'"; + case LYX_VALIGN_MIDDLE: + return "valign='middle'"; + default: + // This case only silences a compiler warning, as all the cases are covered above. + return ""; } } -void Tabular::docbookRowAsHTML(XMLStream & xs, row_type row, - OutputParams const & runparams, bool header) const +std::string Tabular::getVAlignAsCssAttribute(idx_type cell) const { - string const celltag = header ? "th" : "td"; - idx_type cell = getFirstCellInRow(row); + switch (getVAlignment(cell)) { + case LYX_VALIGN_TOP: + return "vertical-align: top"; + case LYX_VALIGN_BOTTOM: + return "vertical-align: bottom"; + case LYX_VALIGN_MIDDLE: + return "vertical-align: middle"; + default: + // This case only silences a compiler warning, as all the cases are covered above. + return ""; + } +} - xs << xml::StartTag("tr"); - xs << xml::CR(); - for (col_type c = 0; c < ncols(); ++c) { - if (isPartOfMultiColumn(row, c) || isPartOfMultiRow(row, c)) - continue; - stringstream attr; +std::string Tabular::getHAlignAsXmlAttribute(idx_type cell) const +{ + switch (getAlignment(cell)) { + case LYX_ALIGN_LEFT: + return "align='left'"; + case LYX_ALIGN_RIGHT: + return "align='right'"; + case LYX_ALIGN_BLOCK: + return "align='justify'"; + case LYX_ALIGN_DECIMAL: { + Language const *lang = buffer().paragraphs().front().getParLanguage(buffer().params()); + return "align='char' char='" + to_utf8(lang->decimalSeparator()) + "'"; + } + default: + return "align='center'"; + } +} - 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_BLOCK: - attr << "justify"; - break; - case LYX_ALIGN_DECIMAL: { - Language const *tlang = buffer().paragraphs().front().getParLanguage(buffer().params()); - attr << "char' char='" << to_utf8(tlang->decimalSeparator()); +std::string Tabular::getHAlignAsCssAttribute(idx_type cell) const +{ + switch (getAlignment(cell)) { + case LYX_ALIGN_LEFT: + return "text-align: left"; + case LYX_ALIGN_RIGHT: + return "text-align: right"; + case LYX_ALIGN_BLOCK: + return "text-align: justify"; + case LYX_ALIGN_DECIMAL: { + // In theory, character-level alignment is supported for CSS4, but it's + // experimental. + // https://www.w3.org/TR/css-text-4/#character-alignment + Language const *lang = buffer().paragraphs().front().getParLanguage(buffer().params()); + return "text-align: \"" + to_utf8(lang->decimalSeparator()) + "\""; + } + default: + return "text-align: center"; + } +} + + +Tabular::XmlRowWiseBorders Tabular::computeXmlBorders(row_type row) const +{ + Tabular::XmlRowWiseBorders borders; + + // Determine whether borders are required. + for (col_type c = 0; c < ncols(); ++c) { + if (row < nrows() - 1) { + if (!bottomLine(cellIndex(row, c)) + || !topLine(cellIndex(row + 1, c))) { + borders.completeBorder = false; + } + if (!bottomLine(cellIndex(row, c)) + && !topLine(cellIndex(row + 1, c))) { + borders.completeBorderBelow = false; + } + } else if (row == nrows() - 1 && !bottomLine(cellIndex(row, c))) { + borders.completeBorderBelow = false; } - break; - case LYX_ALIGN_LEFT: - attr << "left"; - break; - case LYX_ALIGN_RIGHT: - attr << "right"; - break; - default: - attr << "center"; - break; + + if ((row > 0 && !bottomLine(cellIndex(row - 1, c)) && !topLine(cellIndex(row, c))) || + (row == 0 && !topLine(cellIndex(row, c)))) { + borders.completeBorderAbove = false; } - attr << "'"; - attr << " valign='"; - switch (getVAlignment(cell)) { - case LYX_VALIGN_TOP: - attr << "top"; - break; - case LYX_VALIGN_BOTTOM: - attr << "bottom"; - break; - case LYX_VALIGN_MIDDLE: - attr << "middle"; + } + + // Size of booktabs borders. + if (use_booktabs) { + if (borders.completeBorderAbove) + borders.borderTopWidth = row == 0 ? 2 : 1.5; + if (borders.completeBorderBelow) { + borders.borderBottomWidth = row == nrows() - 1 ? 2 : 1.5; + borders.borderBottomWidthComplete = 3 * borders.borderBottomWidth; } - attr << "'"; + } - if (isMultiColumn(cell)) - attr << " colspan='" << columnSpan(cell) << "'"; - else if (isMultiRow(cell)) - attr << " rowspan='" << rowSpan(cell) << "'"; + return borders; +} - xs << xml::StartTag(celltag, attr.str(), true); - cellInset(cell)->docbook(xs, runparams); - xs << xml::EndTag(celltag); - xs << xml::CR(); - ++cell; + +std::vector Tabular::computeCssStylePerCell(row_type row, col_type col, idx_type cell) const +{ + std::vector styles; + + // Fixed width. + Length const col_width = column_info[col].p_width; + if (!col_width.zero()) + styles.emplace_back("width: " + col_width.asHTMLString()); + + // Borders and booktabs. + const Tabular::XmlRowWiseBorders borders = computeXmlBorders(row); + + if (bottomLine(cell)) { + if (row < nrows() - 1 && borders.completeBorder) + styles.emplace_back("border-bottom: " + to_string(borders.borderBottomWidthComplete) + "px double"); + else + styles.emplace_back("border-bottom: " + to_string(borders.borderBottomWidth) + "px solid"); } - xs << xml::EndTag("tr"); - xs << xml::CR(); + if (rightLine(cell)) { + if (col < ncols() - 1 && leftLine(cell + 1)) + styles.emplace_back("border-right: 3px double"); + else + styles.emplace_back("border-right: 1px solid"); + } + if (leftLine(cell)) + styles.emplace_back("border-left: 1px solid"); + if (topLine(cell)) + styles.emplace_back("border-top: " + to_string(borders.borderTopWidth) + "px solid"); + + return styles; } -void Tabular::docbookRowAsCALS(XMLStream & xs, row_type row, - OutputParams const & runparams) const +docstring Tabular::xmlRow(XMLStream & xs, const row_type row, OutputParams const & runparams, + const bool header, const XmlOutputFormat output_format, BufferParams::TableOutput docbook_table_output) const { + docstring ret; + const bool is_xhtml_table = output_format == XmlOutputFormat::XHTML || + docbook_table_output == BufferParams::TableOutput::HTMLTable; + const bool is_cals_table = output_format == XmlOutputFormat::DOCBOOK && + docbook_table_output == BufferParams::TableOutput::CALSTable; + + std::string const row_tag = is_xhtml_table ? "tr" : "row"; + std::string const cell_tag = is_xhtml_table ? (header ? "th" : "td") : "entry"; + Tabular::XmlRowWiseBorders const borders = computeXmlBorders(row); idx_type cell = getFirstCellInRow(row); - xs << xml::StartTag("row"); + std::string row_attr; + bool cals_row_has_rowsep = false; // TODO: is this required? Is it possible that only a/several cells request a row separator, but not the complete row? + // CALS only: all cases where there should be a line *below* this row. + if (is_cals_table && (row_info[row].bottom_space_default || bottomLine(cell))) { + if (borders.completeBorderBelow) { + row_attr = "rowsep='1'"; + cals_row_has_rowsep = true; + } + } + + xs << xml::StartTag(row_tag, row_attr); xs << xml::CR(); - for (col_type c = 0; c < ncols(); ++c) { + for (col_type c = 0; c < ncols(); ++c, ++cell) { if (isPartOfMultiColumn(row, c) || isPartOfMultiRow(row, c)) continue; - stringstream attr; + stringstream attr; // Tag-level attributes, both for HTML and CALS. + stringstream style; // Tag-level CSS, only for HTML tables output in HTML. - attr << "align='"; - switch (getAlignment(cell)) { - case LYX_ALIGN_BLOCK: - attr << "justify"; - break; - case LYX_ALIGN_DECIMAL: { - Language const *tlang = buffer().paragraphs().front().getParLanguage(buffer().params()); - attr << "char' char='" << to_utf8(tlang->decimalSeparator()); + if (is_cals_table) { + if (!cals_row_has_rowsep && bottomLine(cell)) + attr << "rowsep='1' "; } - break; - case LYX_ALIGN_LEFT: - attr << "left"; - break; - case LYX_ALIGN_RIGHT: - attr << "right"; - break; - default: - attr << "center"; - break; - } - attr << "'"; - attr << " valign='"; - switch (getVAlignment(cell)) { - case LYX_VALIGN_TOP: - attr << "top"; - break; - case LYX_VALIGN_BOTTOM: - attr << "bottom"; - break; - case LYX_VALIGN_MIDDLE: - attr << "middle"; + if (output_format == XmlOutputFormat::XHTML) { + // In HTML5, prefer to use CSS instead of attributes for alignment + // (align and valign). + style << getHAlignAsCssAttribute(cell) << "; " + << getVAlignAsCssAttribute(cell); + } else { + // In DocBook, both for HTML and CALS tables, stick to attributes. + attr << getHAlignAsXmlAttribute(cell) << " " + << getVAlignAsXmlAttribute(cell); + } + + if (is_xhtml_table) { + if (isMultiColumn(cell)) + attr << " colspan='" << columnSpan(cell) << "'"; + else if (isMultiRow(cell)) + attr << " rowspan='" << rowSpan(cell) << "'"; + } else if (is_cals_table) { + if (isMultiColumn(cell)) + attr << " namest='c" << c << " nameend='c" << (c + columnSpan(cell)) << "'"; + else if (isMultiRow(cell)) + attr << " morerows='" << rowSpan(cell) << "'"; + else + attr << " colname='c" << (c + 1) << "'"; // CALS column numbering starts at 1. } - attr << "'"; - if (isMultiColumn(cell)) - attr << " colspan='" << columnSpan(cell) << "'"; - else if (isMultiRow(cell)) - attr << " rowspan='" << rowSpan(cell) << "'"; - else - attr << " colname='c" << (c + 1) << "'"; // Column numbering starts at 1. + // Final step: prepend the CSS style. + std::string attr_str = attr.str(); + if (is_xhtml_table) { + const std::vector styles = computeCssStylePerCell(row, c, cell); - // All cases where there should be a line *below* this row. - if (row_info[row].bottom_space_default) - attr << " rowsep='1'"; + std::string attr_str_prefix = "style='" + style.str(); + if (!styles.empty()) + attr_str_prefix += "; "; + for (auto it = styles.begin(); it != styles.end(); ++it) { + attr_str_prefix += *it; + if (it != styles.end() - 1) + attr_str_prefix += "; "; + } + attr_str_prefix += "' "; + + attr_str.insert(0, attr_str_prefix); + } - xs << xml::StartTag("entry", attr.str(), true); - cellInset(cell)->docbook(xs, runparams); - xs << xml::EndTag("entry"); + // Render the cell as either XHTML or DocBook. + xs << xml::StartTag(cell_tag, attr_str, true); + if (output_format == XmlOutputFormat::XHTML) { + ret += cellInset(cell)->xhtml(xs, runparams); + } else if (output_format == XmlOutputFormat::DOCBOOK) { + // DocBook: no return value for this function. + OutputParams rp = runparams; + rp.docbook_in_par = false; + rp.docbook_force_pars = true; + cellInset(cell)->docbook(xs, rp); + } + xs << xml::EndTag(cell_tag); xs << xml::CR(); - ++cell; } - xs << xml::EndTag("row"); + xs << xml::EndTag(row_tag); xs << xml::CR(); + + return ret; } -void Tabular::docbook(XMLStream & xs, OutputParams const & runparams) const +void Tabular::xmlHeader(XMLStream & xs, OutputParams const & runparams, const XmlOutputFormat output_format) const { - docstring ret; - - // Some tables are inline. Likely limitation: cannot output a table within a table; is that really a limitation? - if (!runparams.docbook_in_table) { // Check on the *outer* set of parameters, so that the table can be closed - // properly at the end of this function. - xs << xml::StartTag("informaltable"); - xs << xml::CR(); - } - - // "Formal" tables have a title and use the tag ; the distinction with is done outside. - // HTML has the caption first with titles forbidden, and CALS has a title first. - if (haveLTCaption()) { - std::string tag = ((buffer().params().docbook_table_output) == BufferParams::HTMLTable) ? "caption" : "title"; - - xs << xml::StartTag(tag); - for (row_type r = 0; r < nrows(); ++r) - if (row_info[r].caption) - docbookRow(xs, r, runparams); - xs << xml::EndTag(tag); - xs << xml::CR(); - } - - // CALS header: describe all columns in this table. For names, take 'c' then the ID of the column. - // Start at one, as is customary with CALS! - if (buffer().params().docbook_table_output == BufferParams::CALSTable) { - for (col_type c = 0; c < ncols(); ++c) { - std::stringstream attr; - attr << "colnum='" << (c + 1) << "' "; - attr << "colname='c" << (c + 1) << "' "; - Length const cwidth = column_info[c].p_width; - if (!cwidth.zero()) - attr << "colwidth='" << cwidth.asHTMLString() << "' "; - attr << "rowheader='norowheader'"; // Last attribute, hence no space at the end. - - xs << xml::CompTag("colspec", attr.str()); - xs << xml::CR(); - } - } - // Output the header of the table. For both HTML and CALS, this is surrounded by a thead. - bool const havefirsthead = haveLTFirstHead(false); + bool const have_first_head = haveLTFirstHead(false); // if we have a first head, then we are going to ignore the // headers for the additional pages, since there aren't any - // in DocBook. this test accomplishes that. - bool const havehead = !havefirsthead && haveLTHead(false); - if (havehead || havefirsthead) { + // in HTML or DocBook. + bool const have_head = !have_first_head && haveLTHead(false); + + if (have_head || have_first_head) { xs << xml::StartTag("thead") << xml::CR(); for (row_type r = 0; r < nrows(); ++r) { - if (((havefirsthead && row_info[r].endfirsthead) || - (havehead && row_info[r].endhead)) && + if (((have_first_head && row_info[r].endfirsthead) || + (have_head && row_info[r].endhead)) && !row_info[r].caption) { - docbookRow(xs, r, runparams, true); // TODO: HTML vs CALS + xmlRow(xs, r, runparams, true, output_format, buffer().params().docbook_table_output); } } xs << xml::EndTag("thead"); xs << xml::CR(); } +} + +void Tabular::xmlFooter(XMLStream & xs, OutputParams const & runparams, const XmlOutputFormat output_format) const +{ // Output the footer of the table. For both HTML and CALS, this is surrounded by a tfoot and output just after // the header (and before the body). - bool const havelastfoot = haveLTLastFoot(false); - // as before. - bool const havefoot = !havelastfoot && haveLTFoot(false); - if (havefoot || havelastfoot) { + bool const have_last_foot = haveLTLastFoot(false); + bool const have_foot = !have_last_foot && haveLTFoot(false); + + if (have_foot || have_last_foot) { xs << xml::StartTag("tfoot") << xml::CR(); for (row_type r = 0; r < nrows(); ++r) { - if (((havelastfoot && row_info[r].endlastfoot) || - (havefoot && row_info[r].endfoot)) && + if (((have_last_foot && row_info[r].endlastfoot) || + (have_foot && row_info[r].endfoot)) && !row_info[r].caption) { - docbookRow(xs, r, runparams); // TODO: HTML vs CALS + xmlRow(xs, r, runparams, false, output_format, buffer().params().docbook_table_output); } } xs << xml::EndTag("tfoot"); xs << xml::CR(); } +} + +void Tabular::xmlBody(XMLStream & xs, OutputParams const & runparams, const XmlOutputFormat output_format) const +{ // Output the main part of the table. The tbody container is mandatory for CALS, but optional for HTML (only if // there is no header and no footer). It never hurts to have it, though. xs << xml::StartTag("tbody"); xs << xml::CR(); for (row_type r = 0; r < nrows(); ++r) if (isValidRow(r)) - docbookRow(xs, r, runparams); + xmlRow(xs, r, runparams, false, output_format, buffer().params().docbook_table_output); xs << xml::EndTag("tbody"); xs << xml::CR(); - - // If this method started the table tag, also make it close it. - if (!runparams.docbook_in_table) { - xs << xml::EndTag("informaltable"); - xs << xml::CR(); - } } -docstring Tabular::xhtmlRow(XMLStream & xs, row_type row, - OutputParams const & runparams, bool header) const +void Tabular::docbook(XMLStream & xs, OutputParams const & runparams) const { - docstring ret; - string const celltag = header ? "th" : "td"; - idx_type cell = getFirstCellInRow(row); + // Some tables are inline. Likely limitation: cannot output a table within a table; is that really a limitation? + if (!runparams.docbook_in_table) { // Check on the *outer* set of parameters, so that the table can be closed + // properly at the end of this function. + xs << xml::StartTag("informaltable"); + xs << xml::CR(); + } - xs << xml::StartTag("tr"); - for (col_type c = 0; c < ncols(); ++c) { - if (isPartOfMultiColumn(row, c) || isPartOfMultiRow(row, c)) - continue; + // "Formal" tables have a title and use the tag
; the distinction with is done outside. + // HTML has the caption first with titles forbidden, and CALS has a title first. + if (haveLTCaption()) { + std::string caption_tag = ((buffer().params().docbook_table_output) == BufferParams::HTMLTable) ? "caption" : "title"; - stringstream attr; + xs << xml::StartTag(caption_tag); + for (row_type r = 0; r < nrows(); ++r) + if (row_info[r].caption) + xmlRow(xs, r, runparams, false, XmlOutputFormat::DOCBOOK, buffer().params().docbook_table_output); + xs << xml::EndTag(caption_tag); + xs << xml::CR(); + } - Length const cwidth = column_info[c].p_width; - if (!cwidth.zero()) { - string const hwidth = cwidth.asHTMLString(); - attr << "style =\"width: " << hwidth << ";\" "; - } + // CALS header: describe all columns in this table. For names, take 'c' then the ID of the column. + // Start at one, as is customary with CALS! + if (buffer().params().docbook_table_output == BufferParams::CALSTable) { + for (col_type c = 0; c < ncols(); ++c) { + std::stringstream attr; + attr << "colnum='" << (c + 1) << "' "; + attr << "colname='c" << (c + 1) << "' "; + Length const cwidth = column_info[c].p_width; + if (!cwidth.zero()) + attr << "colwidth='" << cwidth.asHTMLString() << "' "; + attr << "rowheader='norowheader'"; // Last attribute, hence no space at the end. - attr << "align='"; - switch (getAlignment(cell)) { - case LYX_ALIGN_LEFT: - attr << "left"; - break; - case LYX_ALIGN_RIGHT: - attr << "right"; - break; - default: - attr << "center"; - break; - } - attr << "'"; - attr << " valign='"; - switch (getVAlignment(cell)) { - case LYX_VALIGN_TOP: - attr << "top"; - break; - case LYX_VALIGN_BOTTOM: - attr << "bottom"; - break; - case LYX_VALIGN_MIDDLE: - attr << "middle"; + xs << xml::CompTag("colspec", attr.str()); + xs << xml::CR(); } - attr << "'"; + } - if (isMultiColumn(cell)) - attr << " colspan='" << columnSpan(cell) << "'"; - else if (isMultiRow(cell)) - attr << " rowspan='" << rowSpan(cell) << "'"; + xmlHeader(xs, runparams, XmlOutputFormat::DOCBOOK); + xmlFooter(xs, runparams, XmlOutputFormat::DOCBOOK); + xmlBody(xs, runparams, XmlOutputFormat::DOCBOOK); - xs << xml::StartTag(celltag, attr.str(), true) << xml::CR(); - ret += cellInset(cell)->xhtml(xs, runparams); - xs << xml::EndTag(celltag) << xml::CR(); - ++cell; + // If this method started the table tag, also make it close it. + if (!runparams.docbook_in_table) { + xs << xml::EndTag("informaltable"); + xs << xml::CR(); } - xs << xml::EndTag("tr"); - return ret; } @@ -3811,7 +3975,7 @@ docstring Tabular::xhtml(XMLStream & xs, OutputParams const & runparams) const docstring ret; if (is_long_tabular) { - // we'll wrap it in a div, so as to deal with alignment + // We'll wrap it in a div to deal with alignment. string align; switch (longtabular_alignment) { case LYX_LONGTABULAR_ALIGN_LEFT: @@ -3826,13 +3990,14 @@ docstring Tabular::xhtml(XMLStream & xs, OutputParams const & runparams) const } xs << xml::StartTag("div", "class='longtable' style='text-align: " + align + ";'"); xs << xml::CR(); - // The caption flag wins over head/foot + + // The caption flag is output before header/footer. if (haveLTCaption()) { xs << xml::StartTag("div", "class='longtable-caption' style='text-align: " + align + ";'"); xs << xml::CR(); for (row_type r = 0; r < nrows(); ++r) if (row_info[r].caption) - ret += xhtmlRow(xs, r, runparams); + ret += xmlRow(xs, r, runparams, false, XmlOutputFormat::XHTML); xs << xml::EndTag("div"); xs << xml::CR(); } @@ -3841,48 +4006,10 @@ docstring Tabular::xhtml(XMLStream & xs, OutputParams const & runparams) const xs << xml::StartTag("table"); xs << xml::CR(); - // output header info - bool const havefirsthead = haveLTFirstHead(false); - // if we have a first head, then we are going to ignore the - // headers for the additional pages, since there aren't any - // in XHTML. this test accomplishes that. - bool const havehead = !havefirsthead && haveLTHead(false); - if (havehead || havefirsthead) { - xs << xml::StartTag("thead"); - xs << xml::CR(); - for (row_type r = 0; r < nrows(); ++r) { - if (((havefirsthead && row_info[r].endfirsthead) || - (havehead && row_info[r].endhead)) && - !row_info[r].caption) { - ret += xhtmlRow(xs, r, runparams, true); - } - } - xs << xml::EndTag("thead"); - xs << xml::CR(); - } - // output footer info - bool const havelastfoot = haveLTLastFoot(false); - // as before. - bool const havefoot = !havelastfoot && haveLTFoot(false); - if (havefoot || havelastfoot) { - 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)) && - !row_info[r].caption) { - ret += xhtmlRow(xs, r, runparams); - } - } - xs << xml::EndTag("tfoot"); - xs << xml::CR(); - } + xmlHeader(xs, runparams, XmlOutputFormat::XHTML); + xmlFooter(xs, runparams, XmlOutputFormat::XHTML); + xmlBody(xs, runparams, XmlOutputFormat::XHTML); - xs << xml::StartTag("tbody") << xml::CR(); - for (row_type r = 0; r < nrows(); ++r) - if (isValidRow(r)) - ret += xhtmlRow(xs, r, runparams); - xs << xml::EndTag("tbody"); - xs << xml::CR(); xs << xml::EndTag("table"); xs << xml::CR(); if (is_long_tabular) { @@ -4154,8 +4281,10 @@ void Tabular::validate(LaTeXFeatures & features) const for (idx_type cell = 0; cell < numberofcells; ++cell) { if (isMultiRow(cell)) features.require("multirow"); - if (getUsebox(cell) == BOX_VARWIDTH) + if (getUsebox(cell) == BOX_VARWIDTH) { features.require("varwidth"); + features.require("cellvarwidth"); + } if (getVAlignment(cell) != LYX_VALIGN_TOP || !getPWidth(cell).zero() || isVTypeColumn(cellColumn(cell))) @@ -4192,6 +4321,21 @@ Tabular::BoxType Tabular::useBox(idx_type cell) const } +bool Tabular::hasNewlines(idx_type cell) const +{ + ParagraphList const & parlist = cellInset(cell)->paragraphs(); + ParagraphList::const_iterator cit = parlist.begin(); + ParagraphList::const_iterator end = parlist.end(); + + for (; cit != end; ++cit) + for (int i = 0; i < cit->size(); ++i) + if (cit->isNewline(i)) + return true; + + return false; +} + + ///////////////////////////////////////////////////////////////////// // // InsetTableCell @@ -4199,16 +4343,10 @@ Tabular::BoxType Tabular::useBox(idx_type cell) const ///////////////////////////////////////////////////////////////////// InsetTableCell::InsetTableCell(Buffer * buf) - : InsetText(buf, InsetText::PlainLayout), isFixedWidth(false), + : InsetText(buf, InsetText::PlainLayout), isFixedWidth(false), isVarwidth(false), isMultiColumn(false), isMultiRow(false), contentAlign(LYX_ALIGN_CENTER) {} -bool InsetTableCell::forcePlainLayout(idx_type) const -{ - return isMultiRow || (isMultiColumn && !isFixedWidth); -} - - bool InsetTableCell::allowParagraphCustomization(idx_type) const { return isFixedWidth; @@ -4226,6 +4364,7 @@ bool InsetTableCell::getStatus(Cursor & cur, FuncRequest const & cmd, { bool enabled = true; switch (cmd.action()) { + case LFUN_INSET_SPLIT: case LFUN_INSET_DISSOLVE: enabled = false; break; @@ -4298,10 +4437,11 @@ void InsetTableCell::metrics(MetricsInfo & mi, Dimension & dim) const // We tell metrics here not to expand on multiple pars // This is the difference to InsetText::Metrics + Changer changetight = changeVar(mi.tight_insets, true); if (hasFixedWidth()) - tm.metrics(mi, dim, mi.base.textwidth, false); + tm.metrics(mi, dim, mi.base.textwidth); else - tm.metrics(mi, dim, 0, false); + tm.metrics(mi, dim, 0); mi.base.textwidth += horiz_offset; dim.asc += topOffset(mi.base.bv); dim.des += bottomOffset(mi.base.bv); @@ -4480,7 +4620,7 @@ void InsetTabular::metrics(MetricsInfo & mi, Dimension & dim) const // determine horizontal offset because of decimal align (if necessary) int decimal_width = 0; if (tabular.getAlignment(cell) == LYX_ALIGN_DECIMAL) { - InsetTableCell tail = InsetTableCell(*tabular.cellInset(cell)); + InsetTableCell tail = *tabular.cellInset(cell); tail.setBuffer(tabular.buffer()); // we need to set macrocontext position everywhere // otherwise we crash with nested insets (e.g. footnotes) @@ -4506,9 +4646,10 @@ void InsetTabular::metrics(MetricsInfo & mi, Dimension & dim) const tabular.cell_info[r][c].decimal_hoffset = tm.width() - decimal_width; tabular.cell_info[r][c].decimal_width = decimal_width; - // with LYX_VALIGN_BOTTOM the descent is relative to the last par - // = descent of text in last par + bottomOffset: - int const lastpardes = tm.last().second->descent() + // with LYX_VALIGN_BOTTOM the descent is relative to the last + // row of the last par (note that the par might have multile rows!) + // = descent of text in last row + bottomOffset: + int const lastpardes = tm.last().second->rows().back().descent() + bottomOffset(mi.base.bv); int offset = 0; switch (tabular.getVAlignment(cell)) { @@ -4732,7 +4873,7 @@ void InsetTabular::drawCellLines(PainterInfo & pi, int x, int y, 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(); + colour = tabular.cellInset(cell)->paragraphs().front().lookupChange(0).color(); // Top bool drawline = tabular.topLine(cell) @@ -4968,7 +5109,9 @@ void InsetTabular::doDispatch(Cursor & cur, FuncRequest & cmd) if (bvcur.selIsMultiCell()) { bvcur.pit() = bvcur.lastpit(); bvcur.pos() = bvcur.lastpos(); - } + } else + // Let InsetTableCell do it + cell(cur.idx())->dispatch(cur, cmd); } break; @@ -5218,6 +5361,13 @@ void InsetTabular::doDispatch(Cursor & cur, FuncRequest & cmd) } break; + case LFUN_COPY: + if (cur.selIsMultiCell()) + copySelection(cur); + else + cell(cur.idx())->dispatch(cur, cmd); + break; + case LFUN_CUT: if (cur.selIsMultiCell()) { if (copySelection(cur)) { @@ -5253,16 +5403,6 @@ void InsetTabular::doDispatch(Cursor & cur, FuncRequest & cmd) cell(cur.idx())->dispatch(cur, cmd); break; - case LFUN_COPY: - if (!cur.selection()) - break; - if (cur.selIsMultiCell()) { - cur.finishUndo(); - copySelection(cur); - } else - cell(cur.idx())->dispatch(cur, cmd); - break; - case LFUN_CLIPBOARD_PASTE: case LFUN_PRIMARY_SELECTION_PASTE: { docstring const clip = (act == LFUN_CLIPBOARD_PASTE) ? @@ -5277,8 +5417,7 @@ void InsetTabular::doDispatch(Cursor & cur, FuncRequest & cmd) if (insertPlaintextString(cur.bv(), clip, false)) { // content has been replaced, // so cursor might be invalid - cur.pos() = cur.lastpos(); - cur.pit() = cur.lastpit(); + cur.fixIfBroken(); bvcur.setCursor(cur); break; } @@ -5302,7 +5441,8 @@ void InsetTabular::doDispatch(Cursor & cur, FuncRequest & cmd) break; } } - else if (theClipboard().hasTextContents(Clipboard::LyXTextType)) { + else if (!theClipboard().isInternal() + && theClipboard().hasTextContents(Clipboard::LyXTextType)) { // This might be tabular data from another LyX instance. Check! docstring const clip = theClipboard().getAsText(Clipboard::PlainTextType); @@ -5324,6 +5464,7 @@ void InsetTabular::doDispatch(Cursor & cur, FuncRequest & cmd) case LFUN_CHANGE_ACCEPT: case LFUN_CHANGE_REJECT: + case LFUN_FONT_DEFAULT: case LFUN_FONT_EMPH: case LFUN_FONT_BOLD: case LFUN_FONT_BOLDSYMBOL: @@ -5505,8 +5646,6 @@ bool InsetTabular::getFeatureStatus(Cursor & cur, string const & s, case Tabular::SET_SPECIAL_MULTICOLUMN: case Tabular::APPEND_ROW: case Tabular::APPEND_COLUMN: - case Tabular::DELETE_ROW: - case Tabular::DELETE_COLUMN: case Tabular::COPY_ROW: case Tabular::COPY_COLUMN: case Tabular::SET_TOP_SPACE: @@ -5515,6 +5654,13 @@ bool InsetTabular::getFeatureStatus(Cursor & cur, string const & s, status.clear(); return true; + case Tabular::DELETE_ROW: + status.setEnabled(tabular.nrows() > 1); + break; + case Tabular::DELETE_COLUMN: + status.setEnabled(tabular.ncols() > 1); + break; + case Tabular::SET_TABULAR_WIDTH: status.setEnabled(!tabular.rotate && tabular.tabular_valignment == Tabular::LYX_VALIGN_MIDDLE); @@ -5524,43 +5670,57 @@ bool InsetTabular::getFeatureStatus(Cursor & cur, string const & s, case Tabular::MOVE_COLUMN_LEFT: case Tabular::MOVE_ROW_DOWN: case Tabular::MOVE_ROW_UP: { - if (cur.selection()) { - status.message(_("Selections not supported.")); - status.setEnabled(false); - break; + row_type rs, re; + col_type cs, ce; + if (cur.selIsMultiCell()) + getSelection(cur, rs, re, cs, ce); + else { + rs = tabular.cellRow(cur.idx()); + re = rs; + cs = tabular.cellColumn(cur.idx()); + ce = cs; } - - if ((action == Tabular::MOVE_COLUMN_RIGHT && - tabular.ncols() == tabular.cellColumn(cur.idx()) + 1) || - (action == Tabular::MOVE_COLUMN_LEFT && - tabular.cellColumn(cur.idx()) == 0) || - (action == Tabular::MOVE_ROW_DOWN && - tabular.nrows() == tabular.cellRow(cur.idx()) + 1) || - (action == Tabular::MOVE_ROW_UP && - tabular.cellRow(cur.idx()) == 0)) { + if ((action == Tabular::MOVE_COLUMN_RIGHT + && tabular.ncols() == ce + 1) + || (action == Tabular::MOVE_COLUMN_LEFT && cs == 0) + || (action == Tabular::MOVE_ROW_DOWN + && tabular.nrows() == re + 1) + || (action == Tabular::MOVE_ROW_UP && rs == 0)) { status.setEnabled(false); break; } - if (action == Tabular::MOVE_COLUMN_RIGHT || - action == Tabular::MOVE_COLUMN_LEFT) { - if (tabular.hasMultiColumn(tabular.cellColumn(cur.idx())) || - tabular.hasMultiColumn(tabular.cellColumn(cur.idx()) + - (action == Tabular::MOVE_COLUMN_RIGHT ? 1 : -1))) { - status.message(_("Multi-column in current or" - " destination column.")); + if (action == Tabular::MOVE_COLUMN_RIGHT + || action == Tabular::MOVE_COLUMN_LEFT) { + bool has_multicol = (action == Tabular::MOVE_COLUMN_RIGHT) + ? tabular.hasMultiColumn(ce + 1) + : tabular.hasMultiColumn(cs - 1); + for (col_type c = cs; c <= ce; ++c) { + if (tabular.hasMultiColumn(c)) { + has_multicol = true; + break; + } + } + if (has_multicol) { + status.message(_("Column movement not supported with multi-columns.")); status.setEnabled(false); break; } } - if (action == Tabular::MOVE_ROW_DOWN || - action == Tabular::MOVE_ROW_UP) { - if (tabular.hasMultiRow(tabular.cellRow(cur.idx())) || - tabular.hasMultiRow(tabular.cellRow(cur.idx()) + - (action == Tabular::MOVE_ROW_DOWN ? 1 : -1))) { - status.message(_("Multi-row in current or" - " destination row.")); + if (action == Tabular::MOVE_ROW_DOWN + || action == Tabular::MOVE_ROW_UP) { + bool has_multirow = (action == Tabular::MOVE_ROW_DOWN) + ? tabular.hasMultiRow(re + 1) + : tabular.hasMultiRow(rs - 1); + for (row_type r = rs; r <= re; ++r) { + if (tabular.hasMultiRow(r)) { + has_multirow = true; + break; + } + } + if (has_multirow) { + status.message(_("Row movement not supported with multi-rows.")); status.setEnabled(false); break; } @@ -5595,9 +5755,28 @@ bool InsetTabular::getFeatureStatus(Cursor & cur, string const & s, status.setOnOff(tabular.isMultiRow(cur.idx())); break; + case Tabular::TOGGLE_INNER_LINES: + status.setOnOff(tabular.innerBorders(sel_row_start, sel_row_end, + sel_col_start, sel_col_end)); + status.setEnabled(!tabular.ltCaption(tabular.cellRow(cur.idx()))); + break; + case Tabular::TOGGLE_ALL_LINES: + status.setOnOff(tabular.innerBorders(sel_row_start, sel_row_end, + sel_col_start, sel_col_end) + && tabular.outsideBorders(sel_row_start, sel_row_end, + sel_col_start, sel_col_end)); + status.setEnabled(!tabular.ltCaption(tabular.cellRow(cur.idx()))); + break; case Tabular::SET_ALL_LINES: case Tabular::UNSET_ALL_LINES: case Tabular::SET_INNER_LINES: + status.setEnabled(!tabular.ltCaption(tabular.cellRow(cur.idx()))); + break; + + case Tabular::TOGGLE_BORDER_LINES: + status.setOnOff(tabular.outsideBorders(sel_row_start, sel_row_end, + sel_col_start, sel_col_end)); + // fall through case Tabular::SET_BORDER_LINES: status.setEnabled(!tabular.ltCaption(tabular.cellRow(cur.idx()))); break; @@ -5721,8 +5900,8 @@ bool InsetTabular::getFeatureStatus(Cursor & cur, string const & s, flag = false; // fall through case Tabular::VALIGN_TOP: - status.setEnabled(!tabular.getPWidth(cur.idx()).zero() - && !tabular.isMultiRow(cur.idx())); + status.setEnabled((!tabular.getPWidth(cur.idx()).zero() + || tabular.getUsebox(cur.idx()) == Tabular::BOX_VARWIDTH)); status.setOnOff( tabular.getVAlignment(cur.idx(), flag) == Tabular::LYX_VALIGN_TOP); break; @@ -5731,8 +5910,8 @@ bool InsetTabular::getFeatureStatus(Cursor & cur, string const & s, flag = false; // fall through case Tabular::VALIGN_BOTTOM: - status.setEnabled(!tabular.getPWidth(cur.idx()).zero() - && !tabular.isMultiRow(cur.idx())); + status.setEnabled((!tabular.getPWidth(cur.idx()).zero() + || tabular.getUsebox(cur.idx()) == Tabular::BOX_VARWIDTH)); status.setOnOff( tabular.getVAlignment(cur.idx(), flag) == Tabular::LYX_VALIGN_BOTTOM); break; @@ -5741,8 +5920,8 @@ bool InsetTabular::getFeatureStatus(Cursor & cur, string const & s, flag = false; // fall through case Tabular::VALIGN_MIDDLE: - status.setEnabled(!tabular.getPWidth(cur.idx()).zero() - && !tabular.isMultiRow(cur.idx())); + status.setEnabled((!tabular.getPWidth(cur.idx()).zero() + || tabular.getUsebox(cur.idx()) == Tabular::BOX_VARWIDTH)); status.setOnOff( tabular.getVAlignment(cur.idx(), flag) == Tabular::LYX_VALIGN_MIDDLE); break; @@ -5961,7 +6140,7 @@ bool InsetTabular::getStatus(Cursor & cur, FuncRequest const & cmd, } // check if there is already a caption bool have_caption = false; - InsetTableCell itc = InsetTableCell(*tabular.cellInset(cur.idx())); + InsetTableCell itc = *tabular.cellInset(cur.idx()); ParagraphList::const_iterator pit = itc.paragraphs().begin(); ParagraphList::const_iterator pend = itc.paragraphs().end(); for (; pit != pend; ++pit) { @@ -6043,20 +6222,8 @@ bool InsetTabular::getStatus(Cursor & cur, FuncRequest const & cmd, return cell(cur.idx())->getStatus(cur, cmd, status); } - // disable in non-fixed-width cells case LFUN_PARAGRAPH_BREAK: - // multirow does not allow paragraph breaks - if (tabular.isMultiRow(cur.idx())) { - status.setEnabled(false); - return true; - } - // fall through case LFUN_NEWLINE_INSERT: - if ((tabular.isMultiColumn(cur.idx()) || tabular.isMultiRow(cur.idx())) - && tabular.getPWidth(cur.idx()).zero()) { - status.setEnabled(false); - return true; - } return cell(cur.idx())->getStatus(cur, cmd, status); case LFUN_NEWPAGE_INSERT: @@ -6094,21 +6261,21 @@ bool InsetTabular::getStatus(Cursor & cur, FuncRequest const & cmd, } -Inset::RowFlags InsetTabular::rowFlags() const +int InsetTabular::rowFlags() const { - if (tabular.is_long_tabular) { - switch (tabular.longtabular_alignment) { - case Tabular::LYX_LONGTABULAR_ALIGN_LEFT: - return Display | AlignLeft; - case Tabular::LYX_LONGTABULAR_ALIGN_CENTER: - return Display; - case Tabular::LYX_LONGTABULAR_ALIGN_RIGHT: - return Display | AlignRight; - default: - return Display; - } - } else - return Inline; + if (tabular.is_long_tabular) { + switch (tabular.longtabular_alignment) { + case Tabular::LYX_LONGTABULAR_ALIGN_LEFT: + return Display | AlignLeft; + case Tabular::LYX_LONGTABULAR_ALIGN_CENTER: + return Display; + case Tabular::LYX_LONGTABULAR_ALIGN_RIGHT: + return Display | AlignRight; + default: + return Display; + } + } else + return Inline; } @@ -6461,7 +6628,7 @@ void InsetTabular::tabularFeatures(Cursor & cur, row_type sel_row_start; row_type sel_row_end; bool setLines = false; - bool setLinesInnerOnly = false; + bool toggle = false; LyXAlignment setAlign = LYX_ALIGN_LEFT; Tabular::VAlignment setVAlign = Tabular::LYX_VALIGN_TOP; @@ -6619,23 +6786,51 @@ void InsetTabular::tabularFeatures(Cursor & cur, break; case Tabular::MOVE_COLUMN_RIGHT: - tabular.moveColumn(column, Tabular::RIGHT); - cur.idx() = tabular.cellIndex(row, column + 1); + tabular.moveColumn(sel_col_start, sel_col_end, Tabular::RIGHT); + if (cur.selection()) { + cur.selection(false); + cur.idx() = tabular.cellIndex(sel_row_start, sel_col_start + 1); + cur.resetAnchor(); + cur.idx() = tabular.cellIndex(sel_row_end, sel_col_end + 1); + cur.setSelection(); + } else + cur.idx() = tabular.cellIndex(row, column + 1); break; case Tabular::MOVE_COLUMN_LEFT: - tabular.moveColumn(column, Tabular::LEFT); - cur.idx() = tabular.cellIndex(row, column - 1); + tabular.moveColumn(sel_col_start, sel_col_end, Tabular::LEFT); + if (cur.selection()) { + cur.selection(false); + cur.idx() = tabular.cellIndex(sel_row_start, sel_col_start - 1); + cur.resetAnchor(); + cur.idx() = tabular.cellIndex(sel_row_end, sel_col_end - 1); + cur.setSelection(); + } else + cur.idx() = tabular.cellIndex(row, column - 1); break; case Tabular::MOVE_ROW_DOWN: - tabular.moveRow(row, Tabular::DOWN); - cur.idx() = tabular.cellIndex(row + 1, column); + tabular.moveRow(sel_row_start, sel_row_end, Tabular::DOWN); + if (cur.selection()) { + cur.selection(false); + cur.idx() = tabular.cellIndex(sel_row_start + 1, sel_col_start); + cur.resetAnchor(); + cur.idx() = tabular.cellIndex(sel_row_end + 1, sel_col_end); + cur.setSelection(); + } else + cur.idx() = tabular.cellIndex(row + 1, column); break; case Tabular::MOVE_ROW_UP: - tabular.moveRow(row, Tabular::UP); - cur.idx() = tabular.cellIndex(row - 1, column); + tabular.moveRow(sel_row_start, sel_row_end, Tabular::UP); + if (cur.selection()) { + cur.selection(false); + cur.idx() = tabular.cellIndex(sel_row_start - 1, sel_col_start); + cur.resetAnchor(); + cur.idx() = tabular.cellIndex(sel_row_end - 1, sel_col_end); + cur.setSelection(); + } else + cur.idx() = tabular.cellIndex(row - 1, column); break; case Tabular::SET_LINE_TOP: @@ -6857,27 +7052,36 @@ void InsetTabular::tabularFeatures(Cursor & cur, break; } + case Tabular::TOGGLE_INNER_LINES: + toggle = true; + // fall through case Tabular::SET_INNER_LINES: - setLinesInnerOnly = true; + if (toggle) + setLines = !tabular.innerBorders(sel_row_start, sel_row_end, + sel_col_start, sel_col_end); + else + setLines = true; + tabular.setLines(sel_row_start, sel_row_end, + sel_col_start, sel_col_end, + true, setLines); + break; + + case Tabular::TOGGLE_ALL_LINES: + toggle = true; // fall through case Tabular::SET_ALL_LINES: - setLines = true; + if (toggle) + setLines = !tabular.innerBorders(sel_row_start, sel_row_end, + sel_col_start, sel_col_end) + || !tabular.outsideBorders(sel_row_start, sel_row_end, + sel_col_start, sel_col_end); + else + setLines = true; // fall through case Tabular::UNSET_ALL_LINES: - for (row_type r = sel_row_start; r <= sel_row_end; ++r) - for (col_type c = sel_col_start; c <= sel_col_end; ++c) { - idx_type const cell = tabular.cellIndex(r, c); - if (!setLinesInnerOnly || r != sel_row_start) - tabular.setTopLine(cell, setLines); - if ((!setLinesInnerOnly || r != sel_row_end) - && (!setLines || r == sel_row_end)) - tabular.setBottomLine(cell, setLines); - if ((!setLinesInnerOnly || c != sel_col_end) - && (!setLines || c == sel_col_end)) - tabular.setRightLine(cell, setLines); - if ((!setLinesInnerOnly || c != sel_col_start)) - tabular.setLeftLine(cell, setLines); - } + tabular.setLines(sel_row_start, sel_row_end, + sel_col_start, sel_col_end, + false, setLines); break; case Tabular::RESET_FORMAL_DEFAULT: @@ -6891,16 +7095,24 @@ void InsetTabular::tabularFeatures(Cursor & cur, } break; - case Tabular::SET_BORDER_LINES: + case Tabular::TOGGLE_BORDER_LINES: + toggle = true; + // fall through + case Tabular::SET_BORDER_LINES: { + bool const border = toggle && + tabular.outsideBorders(sel_row_start, sel_row_end, + sel_col_start, sel_col_end) + ? false : true; for (row_type r = sel_row_start; r <= sel_row_end; ++r) { - tabular.setLeftLine(tabular.cellIndex(r, sel_col_start), true); - tabular.setRightLine(tabular.cellIndex(r, sel_col_end), true); + tabular.setLeftLine(tabular.cellIndex(r, sel_col_start), border); + tabular.setRightLine(tabular.cellIndex(r, sel_col_end), border); } for (col_type c = sel_col_start; c <= sel_col_end; ++c) { - tabular.setTopLine(tabular.cellIndex(sel_row_start, c), true); - tabular.setBottomLine(tabular.cellIndex(sel_row_end, c), true); + tabular.setTopLine(tabular.cellIndex(sel_row_start, c), border); + tabular.setBottomLine(tabular.cellIndex(sel_row_end, c), border); } break; + } case Tabular::TOGGLE_LONGTABULAR: if (tabular.is_long_tabular) @@ -7240,12 +7452,15 @@ bool InsetTabular::pasteClipboard(Cursor & cur) // FIXME?: why do we need to do this explicitly? (EL) tabular.cellInset(r2, c2)->setBuffer(tabular.buffer()); - if (!lyxrc.ct_markup_copied) { - // do not paste deleted text - inset->acceptChanges(); + if (lyxrc.ct_markup_copied) { + // Only change to inserted if ct is active, + // otherwise leave markup as is + if (buffer().params().track_changes) + inset->setChange(Change(Change::INSERTED)); + } else + // Resolve all markup to inserted or unchanged inset->setChange(Change(buffer().params().track_changes ? - Change::INSERTED : Change::UNCHANGED)); - } + Change::INSERTED : Change::UNCHANGED)); cur.pos() = 0; cur.pit() = 0; } @@ -7301,10 +7516,15 @@ docstring InsetTabular::asString(idx_type stidx, idx_type enidx, { LASSERT(stidx <= enidx, return docstring()); docstring retval; - col_type const col1 = tabular.cellColumn(stidx); - col_type const col2 = tabular.cellColumn(enidx); - row_type const row1 = tabular.cellRow(stidx); - row_type const row2 = tabular.cellRow(enidx); + col_type col1 = tabular.cellColumn(stidx); + col_type col2 = tabular.cellColumn(enidx); + row_type row1 = tabular.cellRow(stidx); + row_type row2 = tabular.cellRow(enidx); + // stidx might be in a later column or row than enidx + if (col1 > col2) + swap(col1, col2); + if (row1 > row2) + swap(row1, row2); bool first = true; for (col_type col = col1; col <= col2; col++) for (row_type row = row1; row <= row2; row++) { @@ -7322,10 +7542,15 @@ ParagraphList InsetTabular::asParList(idx_type stidx, idx_type enidx) { LASSERT(stidx <= enidx, return ParagraphList()); ParagraphList retval; - col_type const col1 = tabular.cellColumn(stidx); - col_type const col2 = tabular.cellColumn(enidx); - row_type const row1 = tabular.cellRow(stidx); - row_type const row2 = tabular.cellRow(enidx); + col_type col1 = tabular.cellColumn(stidx); + col_type col2 = tabular.cellColumn(enidx); + row_type row1 = tabular.cellRow(stidx); + row_type row2 = tabular.cellRow(enidx); + // stidx might be in a later column or row than enidx + if (col1 > col2) + swap(col1, col2); + if (row1 > row2) + swap(row1, row2); for (col_type col = col1; col <= col2; col++) for (row_type row = row1; row <= row2; row++) for (auto const & par : tabular.cellInset(row, col)->paragraphs()) @@ -7424,12 +7649,6 @@ bool InsetTabular::allowParagraphCustomization(idx_type cell) const } -bool InsetTabular::forcePlainLayout(idx_type cell) const -{ - return tabular.isMultiColumn(cell) && !tabular.getPWidth(cell).zero(); -} - - bool InsetTabular::insertPlaintextString(BufferView & bv, docstring const & buf, bool usePaste) { @@ -7583,7 +7802,8 @@ bool InsetTabular::automaticPopupCompletion() const bool InsetTabular::showCompletionCursor() const { - return lyxrc.completion_cursor_text; + return lyxrc.completion_cursor_text && + (lyxrc.completion_inline_text || lyxrc.completion_popup_text); } @@ -7601,12 +7821,12 @@ docstring InsetTabular::completionPrefix(Cursor const & cur) const } -bool InsetTabular::insertCompletion(Cursor & cur, docstring const & s, bool finished) +bool InsetTabular::insertCompletion(Cursor & cur, docstring const & s, bool /*finished*/) { if (!completionSupported(cur)) return false; - return cur.text()->insertCompletion(cur, s, finished); + return cur.text()->insertCompletion(cur, s); }