/**************************************************************************** | |
** | |
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). | |
** All rights reserved. | |
** Contact: Nokia Corporation (qt-info@nokia.com) | |
** | |
** This file is part of the QtGui module of the Qt Toolkit. | |
** | |
** $QT_BEGIN_LICENSE:LGPL$ | |
** GNU Lesser General Public License Usage | |
** This file may be used under the terms of the GNU Lesser General Public | |
** License version 2.1 as published by the Free Software Foundation and | |
** appearing in the file LICENSE.LGPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU Lesser | |
** General Public License version 2.1 requirements will be met: | |
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. | |
** | |
** In addition, as a special exception, Nokia gives you certain additional | |
** rights. These rights are described in the Nokia Qt LGPL Exception | |
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. | |
** | |
** GNU General Public License Usage | |
** Alternatively, this file may be used under the terms of the GNU General | |
** Public License version 3.0 as published by the Free Software Foundation | |
** and appearing in the file LICENSE.GPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU General | |
** Public License version 3.0 requirements will be met: | |
** http://www.gnu.org/copyleft/gpl.html. | |
** | |
** Other Usage | |
** Alternatively, this file may be used in accordance with the terms and | |
** conditions contained in a signed written agreement between you and Nokia. | |
** | |
** | |
** | |
** | |
** | |
** $QT_END_LICENSE$ | |
** | |
****************************************************************************/ | |
#include "qtexttable.h" | |
#include "qtextcursor.h" | |
#include "qtextformat.h" | |
#include <qdebug.h> | |
#include "qtexttable_p.h" | |
#include "qvarlengtharray.h" | |
#include "private/qfunctions_p.h" | |
#include <stdlib.h> | |
QT_BEGIN_NAMESPACE | |
/*! | |
\class QTextTableCell | |
\reentrant | |
\brief The QTextTableCell class represents the properties of a | |
cell in a QTextTable. | |
\ingroup richtext-processing | |
Table cells are pieces of document structure that belong to a table. | |
The table orders cells into particular rows and columns; cells can | |
also span multiple columns and rows. | |
Cells are usually created when a table is inserted into a document with | |
QTextCursor::insertTable(), but they are also created and destroyed when | |
a table is resized. | |
Cells contain information about their location in a table; you can | |
obtain the row() and column() numbers of a cell, and its rowSpan() | |
and columnSpan(). | |
The format() of a cell describes the default character format of its | |
contents. The firstCursorPosition() and lastCursorPosition() functions | |
are used to obtain the extent of the cell in the document. | |
\sa QTextTable QTextTableFormat | |
*/ | |
/*! | |
\fn QTextTableCell::QTextTableCell() | |
Constructs an invalid table cell. | |
\sa isValid() | |
*/ | |
/*! | |
\fn QTextTableCell::QTextTableCell(const QTextTableCell &other) | |
Copy constructor. Creates a new QTextTableCell object based on the | |
\a other cell. | |
*/ | |
/*! | |
\fn QTextTableCell& QTextTableCell::operator=(const QTextTableCell &other) | |
Assigns the \a other table cell to this table cell. | |
*/ | |
/*! | |
\since 4.2 | |
Sets the cell's character format to \a format. This can for example be used to change | |
the background color of the entire cell: | |
QTextTableCell cell = table->cellAt(2, 3); | |
QTextCharFormat format = cell.format(); | |
format.setBackground(Qt::blue); | |
cell.setFormat(format); | |
Note that the cell's row or column span cannot be changed through this function. You have | |
to use QTextTable::mergeCells and QTextTable::splitCell instead. | |
\sa format() | |
*/ | |
void QTextTableCell::setFormat(const QTextCharFormat &format) | |
{ | |
QTextCharFormat fmt = format; | |
fmt.clearProperty(QTextFormat::ObjectIndex); | |
fmt.setObjectType(QTextFormat::TableCellObject); | |
QTextDocumentPrivate *p = table->docHandle(); | |
QTextDocumentPrivate::FragmentIterator frag(&p->fragmentMap(), fragment); | |
QTextFormatCollection *c = p->formatCollection(); | |
QTextCharFormat oldFormat = c->charFormat(frag->format); | |
fmt.setTableCellRowSpan(oldFormat.tableCellRowSpan()); | |
fmt.setTableCellColumnSpan(oldFormat.tableCellColumnSpan()); | |
p->setCharFormat(frag.position(), 1, fmt, QTextDocumentPrivate::SetFormatAndPreserveObjectIndices); | |
} | |
/*! | |
Returns the cell's character format. | |
*/ | |
QTextCharFormat QTextTableCell::format() const | |
{ | |
QTextDocumentPrivate *p = table->docHandle(); | |
QTextFormatCollection *c = p->formatCollection(); | |
QTextCharFormat fmt = c->charFormat(tableCellFormatIndex()); | |
fmt.setObjectType(QTextFormat::TableCellObject); | |
return fmt; | |
} | |
/*! | |
\since 4.5 | |
Returns the index of the tableCell's format in the document's internal list of formats. | |
\sa QTextDocument::allFormats() | |
*/ | |
int QTextTableCell::tableCellFormatIndex() const | |
{ | |
QTextDocumentPrivate *p = table->docHandle(); | |
return QTextDocumentPrivate::FragmentIterator(&p->fragmentMap(), fragment)->format; | |
} | |
/*! | |
Returns the number of the row in the table that contains this cell. | |
\sa column() | |
*/ | |
int QTextTableCell::row() const | |
{ | |
const QTextTablePrivate *tp = table->d_func(); | |
if (tp->dirty) | |
tp->update(); | |
int idx = tp->findCellIndex(fragment); | |
if (idx == -1) | |
return idx; | |
return tp->cellIndices.at(idx) / tp->nCols; | |
} | |
/*! | |
Returns the number of the column in the table that contains this cell. | |
\sa row() | |
*/ | |
int QTextTableCell::column() const | |
{ | |
const QTextTablePrivate *tp = table->d_func(); | |
if (tp->dirty) | |
tp->update(); | |
int idx = tp->findCellIndex(fragment); | |
if (idx == -1) | |
return idx; | |
return tp->cellIndices.at(idx) % tp->nCols; | |
} | |
/*! | |
Returns the number of rows this cell spans. The default is 1. | |
\sa columnSpan() | |
*/ | |
int QTextTableCell::rowSpan() const | |
{ | |
return format().tableCellRowSpan(); | |
} | |
/*! | |
Returns the number of columns this cell spans. The default is 1. | |
\sa rowSpan() | |
*/ | |
int QTextTableCell::columnSpan() const | |
{ | |
return format().tableCellColumnSpan(); | |
} | |
/*! | |
\fn bool QTextTableCell::isValid() const | |
Returns true if this is a valid table cell; otherwise returns | |
false. | |
*/ | |
/*! | |
Returns the first valid cursor position in this cell. | |
\sa lastCursorPosition() | |
*/ | |
QTextCursor QTextTableCell::firstCursorPosition() const | |
{ | |
return QTextCursor(table->d_func()->pieceTable, firstPosition()); | |
} | |
/*! | |
Returns the last valid cursor position in this cell. | |
\sa firstCursorPosition() | |
*/ | |
QTextCursor QTextTableCell::lastCursorPosition() const | |
{ | |
return QTextCursor(table->d_func()->pieceTable, lastPosition()); | |
} | |
/*! | |
\internal | |
Returns the first valid position in the document occupied by this cell. | |
*/ | |
int QTextTableCell::firstPosition() const | |
{ | |
QTextDocumentPrivate *p = table->docHandle(); | |
return p->fragmentMap().position(fragment) + 1; | |
} | |
/*! | |
\internal | |
Returns the last valid position in the document occupied by this cell. | |
*/ | |
int QTextTableCell::lastPosition() const | |
{ | |
QTextDocumentPrivate *p = table->docHandle(); | |
const QTextTablePrivate *td = table->d_func(); | |
int index = table->d_func()->findCellIndex(fragment); | |
int f; | |
if (index != -1) | |
f = td->cells.value(index + 1, td->fragment_end); | |
else | |
f = td->fragment_end; | |
return p->fragmentMap().position(f); | |
} | |
/*! | |
Returns a frame iterator pointing to the beginning of the table's cell. | |
\sa end() | |
*/ | |
QTextFrame::iterator QTextTableCell::begin() const | |
{ | |
QTextDocumentPrivate *p = table->docHandle(); | |
int b = p->blockMap().findNode(firstPosition()); | |
int e = p->blockMap().findNode(lastPosition()+1); | |
return QTextFrame::iterator(const_cast<QTextTable *>(table), b, b, e); | |
} | |
/*! | |
Returns a frame iterator pointing to the end of the table's cell. | |
\sa begin() | |
*/ | |
QTextFrame::iterator QTextTableCell::end() const | |
{ | |
QTextDocumentPrivate *p = table->docHandle(); | |
int b = p->blockMap().findNode(firstPosition()); | |
int e = p->blockMap().findNode(lastPosition()+1); | |
return QTextFrame::iterator(const_cast<QTextTable *>(table), e, b, e); | |
} | |
/*! | |
\fn QTextCursor QTextTableCell::operator==(const QTextTableCell &other) const | |
Returns true if this cell object and the \a other cell object | |
describe the same cell; otherwise returns false. | |
*/ | |
/*! | |
\fn QTextCursor QTextTableCell::operator!=(const QTextTableCell &other) const | |
Returns true if this cell object and the \a other cell object | |
describe different cells; otherwise returns false. | |
*/ | |
/*! | |
\fn QTextTableCell::~QTextTableCell() | |
Destroys the table cell. | |
*/ | |
QTextTablePrivate::~QTextTablePrivate() | |
{ | |
if (grid) | |
free(grid); | |
} | |
QTextTable *QTextTablePrivate::createTable(QTextDocumentPrivate *pieceTable, int pos, int rows, int cols, const QTextTableFormat &tableFormat) | |
{ | |
QTextTableFormat fmt = tableFormat; | |
fmt.setColumns(cols); | |
QTextTable *table = qobject_cast<QTextTable *>(pieceTable->createObject(fmt)); | |
Q_ASSERT(table); | |
pieceTable->beginEditBlock(); | |
// qDebug("---> createTable: rows=%d, cols=%d at %d", rows, cols, pos); | |
// add block after table | |
QTextCharFormat charFmt; | |
charFmt.setObjectIndex(table->objectIndex()); | |
charFmt.setObjectType(QTextFormat::TableCellObject); | |
int charIdx = pieceTable->formatCollection()->indexForFormat(charFmt); | |
int cellIdx = pieceTable->formatCollection()->indexForFormat(QTextBlockFormat()); | |
QTextTablePrivate *d = table->d_func(); | |
d->blockFragmentUpdates = true; | |
d->fragment_start = pieceTable->insertBlock(QTextBeginningOfFrame, pos, cellIdx, charIdx); | |
d->cells.append(d->fragment_start); | |
++pos; | |
for (int i = 1; i < rows*cols; ++i) { | |
d->cells.append(pieceTable->insertBlock(QTextBeginningOfFrame, pos, cellIdx, charIdx)); | |
// qDebug(" addCell at %d", pos); | |
++pos; | |
} | |
d->fragment_end = pieceTable->insertBlock(QTextEndOfFrame, pos, cellIdx, charIdx); | |
// qDebug(" addEOR at %d", pos); | |
++pos; | |
d->blockFragmentUpdates = false; | |
d->dirty = true; | |
pieceTable->endEditBlock(); | |
return table; | |
} | |
struct QFragmentFindHelper | |
{ | |
inline QFragmentFindHelper(int _pos, const QTextDocumentPrivate::FragmentMap &map) | |
: pos(_pos), fragmentMap(map) {} | |
uint pos; | |
const QTextDocumentPrivate::FragmentMap &fragmentMap; | |
}; | |
Q_STATIC_GLOBAL_INLINE_OPERATOR bool operator<(int fragment, const QFragmentFindHelper &helper) | |
{ | |
return helper.fragmentMap.position(fragment) < helper.pos; | |
} | |
Q_STATIC_GLOBAL_INLINE_OPERATOR bool operator<(const QFragmentFindHelper &helper, int fragment) | |
{ | |
return helper.pos < helper.fragmentMap.position(fragment); | |
} | |
int QTextTablePrivate::findCellIndex(int fragment) const | |
{ | |
QFragmentFindHelper helper(pieceTable->fragmentMap().position(fragment), | |
pieceTable->fragmentMap()); | |
QList<int>::ConstIterator it = qBinaryFind(cells.begin(), cells.end(), helper); | |
if (it == cells.end()) | |
return -1; | |
return it - cells.begin(); | |
} | |
void QTextTablePrivate::fragmentAdded(const QChar &type, uint fragment) | |
{ | |
dirty = true; | |
if (blockFragmentUpdates) | |
return; | |
if (type == QTextBeginningOfFrame) { | |
Q_ASSERT(cells.indexOf(fragment) == -1); | |
const uint pos = pieceTable->fragmentMap().position(fragment); | |
QFragmentFindHelper helper(pos, pieceTable->fragmentMap()); | |
QList<int>::Iterator it = qLowerBound(cells.begin(), cells.end(), helper); | |
cells.insert(it, fragment); | |
if (!fragment_start || pos < pieceTable->fragmentMap().position(fragment_start)) | |
fragment_start = fragment; | |
return; | |
} | |
QTextFramePrivate::fragmentAdded(type, fragment); | |
} | |
void QTextTablePrivate::fragmentRemoved(const QChar &type, uint fragment) | |
{ | |
dirty = true; | |
if (blockFragmentUpdates) | |
return; | |
if (type == QTextBeginningOfFrame) { | |
Q_ASSERT(cells.indexOf(fragment) != -1); | |
cells.removeAll(fragment); | |
if (fragment_start == fragment && cells.size()) { | |
fragment_start = cells.at(0); | |
} | |
if (fragment_start != fragment) | |
return; | |
} | |
QTextFramePrivate::fragmentRemoved(type, fragment); | |
} | |
/*! | |
/fn void QTextTablePrivate::update() const | |
This function is usually called when the table is "dirty". | |
It seems to update all kind of table information. | |
*/ | |
void QTextTablePrivate::update() const | |
{ | |
Q_Q(const QTextTable); | |
nCols = q->format().columns(); | |
nRows = (cells.size() + nCols-1)/nCols; | |
// qDebug(">>>> QTextTablePrivate::update, nRows=%d, nCols=%d", nRows, nCols); | |
grid = q_check_ptr((int *)realloc(grid, nRows*nCols*sizeof(int))); | |
memset(grid, 0, nRows*nCols*sizeof(int)); | |
QTextDocumentPrivate *p = pieceTable; | |
QTextFormatCollection *c = p->formatCollection(); | |
cellIndices.resize(cells.size()); | |
int cell = 0; | |
for (int i = 0; i < cells.size(); ++i) { | |
int fragment = cells.at(i); | |
QTextCharFormat fmt = c->charFormat(QTextDocumentPrivate::FragmentIterator(&p->fragmentMap(), fragment)->format); | |
int rowspan = fmt.tableCellRowSpan(); | |
int colspan = fmt.tableCellColumnSpan(); | |
// skip taken cells | |
while (cell < nRows*nCols && grid[cell]) | |
++cell; | |
int r = cell/nCols; | |
int c = cell%nCols; | |
cellIndices[i] = cell; | |
if (r + rowspan > nRows) { | |
grid = q_check_ptr((int *)realloc(grid, sizeof(int)*(r + rowspan)*nCols)); | |
memset(grid + (nRows*nCols), 0, sizeof(int)*(r+rowspan-nRows)*nCols); | |
nRows = r + rowspan; | |
} | |
Q_ASSERT(c + colspan <= nCols); | |
for (int ii = 0; ii < rowspan; ++ii) { | |
for (int jj = 0; jj < colspan; ++jj) { | |
Q_ASSERT(grid[(r+ii)*nCols + c+jj] == 0); | |
grid[(r+ii)*nCols + c+jj] = fragment; | |
// qDebug(" setting cell %d span=%d/%d at %d/%d", fragment, rowspan, colspan, r+ii, c+jj); | |
} | |
} | |
} | |
// qDebug("<<<< end: nRows=%d, nCols=%d", nRows, nCols); | |
dirty = false; | |
} | |
/*! | |
\class QTextTable | |
\reentrant | |
\brief The QTextTable class represents a table in a QTextDocument. | |
\ingroup richtext-processing | |
A table is a group of cells ordered into rows and columns. Each table | |
contains at least one row and one column. Each cell contains a block, and | |
is surrounded by a frame. | |
Tables are usually created and inserted into a document with the | |
QTextCursor::insertTable() function. | |
For example, we can insert a table with three rows and two columns at the | |
current cursor position in an editor using the following lines of code: | |
\snippet doc/src/snippets/textdocument-tables/mainwindow.cpp 1 | |
\codeline | |
\snippet doc/src/snippets/textdocument-tables/mainwindow.cpp 3 | |
The table format is either defined when the table is created or changed | |
later with setFormat(). | |
The table currently being edited by the cursor is found with | |
QTextCursor::currentTable(). This allows its format or dimensions to be | |
changed after it has been inserted into a document. | |
A table's size can be changed with resize(), or by using | |
insertRows(), insertColumns(), removeRows(), or removeColumns(). | |
Use cellAt() to retrieve table cells. | |
The starting and ending positions of table rows can be found by moving | |
a cursor within a table, and using the rowStart() and rowEnd() functions | |
to obtain cursors at the start and end of each row. | |
Rows and columns within a QTextTable can be merged and split using | |
the mergeCells() and splitCell() functions. However, only cells that span multiple | |
rows or columns can be split. (Merging or splitting does not increase or decrease | |
the number of rows and columns.) | |
Note that if you have merged multiple columns and rows into one cell, you will not | |
be able to split the merged cell into new cells spanning over more than one row | |
or column. To be able to split cells spanning over several rows and columns you | |
need to do this over several iterations. | |
\table 80% | |
\row | |
\o \inlineimage texttable-split.png Original Table | |
\o Suppose we have a 2x3 table of names and addresses. To merge both | |
columns in the first row we invoke mergeCells() with \a row = 0, | |
\a column = 0, \a numRows = 1 and \a numColumns = 2. | |
\snippet doc/src/snippets/textdocument-texttable/main.cpp 0 | |
\row | |
\o \inlineimage texttable-merge.png | |
\o This gives us the following table. To split the first row of the table | |
back into two cells, we invoke the splitCell() function with \a numRows | |
and \a numCols = 1. | |
\snippet doc/src/snippets/textdocument-texttable/main.cpp 1 | |
\row | |
\o \inlineimage texttable-split.png Split Table | |
\o This results in the original table. | |
\endtable | |
\sa QTextTableFormat | |
*/ | |
/*! \internal | |
*/ | |
QTextTable::QTextTable(QTextDocument *doc) | |
: QTextFrame(*new QTextTablePrivate(doc), doc) | |
{ | |
} | |
/*! \internal | |
Destroys the table. | |
*/ | |
QTextTable::~QTextTable() | |
{ | |
} | |
/*! | |
\fn QTextTableCell QTextTable::cellAt(int row, int column) const | |
Returns the table cell at the given \a row and \a column in the table. | |
\sa columns() rows() | |
*/ | |
QTextTableCell QTextTable::cellAt(int row, int col) const | |
{ | |
Q_D(const QTextTable); | |
if (d->dirty) | |
d->update(); | |
if (row < 0 || row >= d->nRows || col < 0 || col >= d->nCols) | |
return QTextTableCell(); | |
return QTextTableCell(this, d->grid[row*d->nCols + col]); | |
} | |
/*! | |
\overload | |
Returns the table cell that contains the character at the given \a position | |
in the document. | |
*/ | |
QTextTableCell QTextTable::cellAt(int position) const | |
{ | |
Q_D(const QTextTable); | |
if (d->dirty) | |
d->update(); | |
uint pos = (uint)position; | |
const QTextDocumentPrivate::FragmentMap &map = d->pieceTable->fragmentMap(); | |
if (position < 0 || map.position(d->fragment_start) >= pos || map.position(d->fragment_end) < pos) | |
return QTextTableCell(); | |
QFragmentFindHelper helper(position, map); | |
QList<int>::ConstIterator it = qLowerBound(d->cells.begin(), d->cells.end(), helper); | |
if (it != d->cells.begin()) | |
--it; | |
return QTextTableCell(this, *it); | |
} | |
/*! | |
\fn QTextTableCell QTextTable::cellAt(const QTextCursor &cursor) const | |
\overload | |
Returns the table cell containing the given \a cursor. | |
*/ | |
QTextTableCell QTextTable::cellAt(const QTextCursor &c) const | |
{ | |
return cellAt(c.position()); | |
} | |
/*! | |
\fn void QTextTable::resize(int rows, int columns) | |
Resizes the table to contain the required number of \a rows and \a columns. | |
\sa insertRows() insertColumns() removeRows() removeColumns() | |
*/ | |
void QTextTable::resize(int rows, int cols) | |
{ | |
Q_D(QTextTable); | |
if (d->dirty) | |
d->update(); | |
int nRows = this->rows(); | |
int nCols = this->columns(); | |
if (rows == nRows && cols == nCols) | |
return; | |
d->pieceTable->beginEditBlock(); | |
if (nCols < cols) | |
insertColumns(nCols, cols - nCols); | |
else if (nCols > cols) | |
removeColumns(cols, nCols - cols); | |
if (nRows < rows) | |
insertRows(nRows, rows-nRows); | |
else if (nRows > rows) | |
removeRows(rows, nRows-rows); | |
d->pieceTable->endEditBlock(); | |
} | |
/*! | |
\fn void QTextTable::insertRows(int index, int rows) | |
Inserts a number of \a rows before the row with the specified \a index. | |
\sa resize() insertColumns() removeRows() removeColumns() appendRows() appendColumns() | |
*/ | |
void QTextTable::insertRows(int pos, int num) | |
{ | |
Q_D(QTextTable); | |
if (num <= 0) | |
return; | |
if (d->dirty) | |
d->update(); | |
if (pos > d->nRows || pos < 0) | |
pos = d->nRows; | |
// qDebug() << "-------- insertRows" << pos << num; | |
QTextDocumentPrivate *p = d->pieceTable; | |
QTextFormatCollection *c = p->formatCollection(); | |
p->beginEditBlock(); | |
int extended = 0; | |
int insert_before = 0; | |
if (pos > 0 && pos < d->nRows) { | |
for (int i = 0; i < d->nCols; ++i) { | |
int cell = d->grid[pos*d->nCols + i]; | |
if (cell == d->grid[(pos-1)*d->nCols+i]) { | |
// cell spans the insertion place, extend it | |
QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), cell); | |
QTextCharFormat fmt = c->charFormat(it->format); | |
fmt.setTableCellRowSpan(fmt.tableCellRowSpan() + num); | |
p->setCharFormat(it.position(), 1, fmt); | |
extended++; | |
} else if (!insert_before) { | |
insert_before = cell; | |
} | |
} | |
} else { | |
insert_before = (pos == 0 ? d->grid[0] : d->fragment_end); | |
} | |
if (extended < d->nCols) { | |
Q_ASSERT(insert_before); | |
QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), insert_before); | |
QTextCharFormat fmt = c->charFormat(it->format); | |
fmt.setTableCellRowSpan(1); | |
fmt.setTableCellColumnSpan(1); | |
Q_ASSERT(fmt.objectIndex() == objectIndex()); | |
int pos = it.position(); | |
int cfmt = p->formatCollection()->indexForFormat(fmt); | |
int bfmt = p->formatCollection()->indexForFormat(QTextBlockFormat()); | |
// qDebug("inserting %d cells, nCols=%d extended=%d", num*(d->nCols-extended), d->nCols, extended); | |
for (int i = 0; i < num*(d->nCols-extended); ++i) | |
p->insertBlock(QTextBeginningOfFrame, pos, bfmt, cfmt, QTextUndoCommand::MoveCursor); | |
} | |
// qDebug() << "-------- end insertRows" << pos << num; | |
p->endEditBlock(); | |
} | |
/*! | |
\fn void QTextTable::insertColumns(int index, int columns) | |
Inserts a number of \a columns before the column with the specified \a index. | |
\sa insertRows() resize() removeRows() removeColumns() appendRows() appendColumns() | |
*/ | |
void QTextTable::insertColumns(int pos, int num) | |
{ | |
Q_D(QTextTable); | |
if (num <= 0) | |
return; | |
if (d->dirty) | |
d->update(); | |
if (pos > d->nCols || pos < 0) | |
pos = d->nCols; | |
// qDebug() << "-------- insertCols" << pos << num; | |
QTextDocumentPrivate *p = d->pieceTable; | |
QTextFormatCollection *c = p->formatCollection(); | |
p->beginEditBlock(); | |
QList<int> extendedSpans; | |
for (int i = 0; i < d->nRows; ++i) { | |
int cell; | |
if (i == d->nRows - 1 && pos == d->nCols) | |
cell = d->fragment_end; | |
else | |
cell = d->grid[i*d->nCols + pos]; | |
if (pos > 0 && pos < d->nCols && cell == d->grid[i*d->nCols + pos - 1]) { | |
// cell spans the insertion place, extend it | |
if (!extendedSpans.contains(cell)) { | |
QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), cell); | |
QTextCharFormat fmt = c->charFormat(it->format); | |
fmt.setTableCellColumnSpan(fmt.tableCellColumnSpan() + num); | |
p->setCharFormat(it.position(), 1, fmt); | |
d->dirty = true; | |
extendedSpans << cell; | |
} | |
} else { | |
/* If the next cell is spanned from the row above, we need to find the right position | |
to insert to */ | |
if (i > 0 && pos < d->nCols && cell == d->grid[(i-1) * d->nCols + pos]) { | |
int gridIndex = i*d->nCols + pos; | |
const int gridEnd = d->nRows * d->nCols - 1; | |
while (gridIndex < gridEnd && cell == d->grid[gridIndex]) { | |
++gridIndex; | |
} | |
if (gridIndex == gridEnd) | |
cell = d->fragment_end; | |
else | |
cell = d->grid[gridIndex]; | |
} | |
QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), cell); | |
QTextCharFormat fmt = c->charFormat(it->format); | |
fmt.setTableCellRowSpan(1); | |
fmt.setTableCellColumnSpan(1); | |
Q_ASSERT(fmt.objectIndex() == objectIndex()); | |
int position = it.position(); | |
int cfmt = p->formatCollection()->indexForFormat(fmt); | |
int bfmt = p->formatCollection()->indexForFormat(QTextBlockFormat()); | |
for (int i = 0; i < num; ++i) | |
p->insertBlock(QTextBeginningOfFrame, position, bfmt, cfmt, QTextUndoCommand::MoveCursor); | |
} | |
} | |
QTextTableFormat tfmt = format(); | |
tfmt.setColumns(tfmt.columns()+num); | |
QVector<QTextLength> columnWidths = tfmt.columnWidthConstraints(); | |
if (! columnWidths.isEmpty()) { | |
for (int i = num; i > 0; --i) | |
columnWidths.insert(pos, columnWidths[qMax(0, pos-1)]); | |
} | |
tfmt.setColumnWidthConstraints (columnWidths); | |
QTextObject::setFormat(tfmt); | |
// qDebug() << "-------- end insertCols" << pos << num; | |
p->endEditBlock(); | |
} | |
/*! | |
\since 4.5 | |
Appends \a count rows at the bottom of the table. | |
\sa insertColumns() insertRows() resize() removeRows() removeColumns() appendColumns() | |
*/ | |
void QTextTable::appendRows(int count) | |
{ | |
insertRows(rows(), count); | |
} | |
/*! | |
\since 4.5 | |
Appends \a count columns at the right side of the table. | |
\sa insertColumns() insertRows() resize() removeRows() removeColumns() appendRows() | |
*/ | |
void QTextTable::appendColumns(int count) | |
{ | |
insertColumns(columns(), count); | |
} | |
/*! | |
\fn void QTextTable::removeRows(int index, int rows) | |
Removes a number of \a rows starting with the row at the specified \a index. | |
\sa insertRows(), insertColumns(), resize(), removeColumns() appendRows() appendColumns() | |
*/ | |
void QTextTable::removeRows(int pos, int num) | |
{ | |
Q_D(QTextTable); | |
// qDebug() << "-------- removeRows" << pos << num; | |
if (num <= 0 || pos < 0) | |
return; | |
if (d->dirty) | |
d->update(); | |
if (pos >= d->nRows) | |
return; | |
if (pos+num > d->nRows) | |
num = d->nRows - pos; | |
QTextDocumentPrivate *p = d->pieceTable; | |
QTextFormatCollection *collection = p->formatCollection(); | |
p->beginEditBlock(); | |
// delete whole table? | |
if (pos == 0 && num == d->nRows) { | |
const int pos = p->fragmentMap().position(d->fragment_start); | |
p->remove(pos, p->fragmentMap().position(d->fragment_end) - pos + 1); | |
p->endEditBlock(); | |
return; | |
} | |
p->aboutToRemoveCell(cellAt(pos, 0).firstPosition(), cellAt(pos + num - 1, d->nCols - 1).lastPosition()); | |
QList<int> touchedCells; | |
for (int r = pos; r < pos + num; ++r) { | |
for (int c = 0; c < d->nCols; ++c) { | |
int cell = d->grid[r*d->nCols + c]; | |
if (touchedCells.contains(cell)) | |
continue; | |
touchedCells << cell; | |
QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), cell); | |
QTextCharFormat fmt = collection->charFormat(it->format); | |
int span = fmt.tableCellRowSpan(); | |
if (span > 1) { | |
fmt.setTableCellRowSpan(span - 1); | |
p->setCharFormat(it.position(), 1, fmt); | |
} else { | |
// remove cell | |
int index = d->cells.indexOf(cell) + 1; | |
int f_end = index < d->cells.size() ? d->cells.at(index) : d->fragment_end; | |
p->remove(it.position(), p->fragmentMap().position(f_end) - it.position()); | |
} | |
} | |
} | |
p->endEditBlock(); | |
// qDebug() << "-------- end removeRows" << pos << num; | |
} | |
/*! | |
\fn void QTextTable::removeColumns(int index, int columns) | |
Removes a number of \a columns starting with the column at the specified | |
\a index. | |
\sa insertRows() insertColumns() removeRows() resize() appendRows() appendColumns() | |
*/ | |
void QTextTable::removeColumns(int pos, int num) | |
{ | |
Q_D(QTextTable); | |
// qDebug() << "-------- removeCols" << pos << num; | |
if (num <= 0 || pos < 0) | |
return; | |
if (d->dirty) | |
d->update(); | |
if (pos >= d->nCols) | |
return; | |
if (pos + num > d->nCols) | |
pos = d->nCols - num; | |
QTextDocumentPrivate *p = d->pieceTable; | |
QTextFormatCollection *collection = p->formatCollection(); | |
p->beginEditBlock(); | |
// delete whole table? | |
if (pos == 0 && num == d->nCols) { | |
const int pos = p->fragmentMap().position(d->fragment_start); | |
p->remove(pos, p->fragmentMap().position(d->fragment_end) - pos + 1); | |
p->endEditBlock(); | |
return; | |
} | |
p->aboutToRemoveCell(cellAt(0, pos).firstPosition(), cellAt(d->nRows - 1, pos + num - 1).lastPosition()); | |
QList<int> touchedCells; | |
for (int r = 0; r < d->nRows; ++r) { | |
for (int c = pos; c < pos + num; ++c) { | |
int cell = d->grid[r*d->nCols + c]; | |
QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), cell); | |
QTextCharFormat fmt = collection->charFormat(it->format); | |
int span = fmt.tableCellColumnSpan(); | |
if (touchedCells.contains(cell) && span <= 1) | |
continue; | |
touchedCells << cell; | |
if (span > 1) { | |
fmt.setTableCellColumnSpan(span - 1); | |
p->setCharFormat(it.position(), 1, fmt); | |
} else { | |
// remove cell | |
int index = d->cells.indexOf(cell) + 1; | |
int f_end = index < d->cells.size() ? d->cells.at(index) : d->fragment_end; | |
p->remove(it.position(), p->fragmentMap().position(f_end) - it.position()); | |
} | |
} | |
} | |
QTextTableFormat tfmt = format(); | |
tfmt.setColumns(tfmt.columns()-num); | |
QVector<QTextLength> columnWidths = tfmt.columnWidthConstraints(); | |
if (columnWidths.count() > pos) { | |
columnWidths.remove(pos, num); | |
tfmt.setColumnWidthConstraints (columnWidths); | |
} | |
QTextObject::setFormat(tfmt); | |
p->endEditBlock(); | |
// qDebug() << "-------- end removeCols" << pos << num; | |
} | |
/*! | |
\since 4.1 | |
Merges the cell at the specified \a row and \a column with the adjacent cells | |
into one cell. The new cell will span \a numRows rows and \a numCols columns. | |
If \a numRows or \a numCols is less than the current number of rows or columns | |
the cell spans then this method does nothing. | |
\sa splitCell() | |
*/ | |
void QTextTable::mergeCells(int row, int column, int numRows, int numCols) | |
{ | |
Q_D(QTextTable); | |
if (d->dirty) | |
d->update(); | |
QTextDocumentPrivate *p = d->pieceTable; | |
QTextFormatCollection *fc = p->formatCollection(); | |
const QTextTableCell cell = cellAt(row, column); | |
if (!cell.isValid() || row != cell.row() || column != cell.column()) | |
return; | |
QTextCharFormat fmt = cell.format(); | |
const int rowSpan = fmt.tableCellRowSpan(); | |
const int colSpan = fmt.tableCellColumnSpan(); | |
numRows = qMin(numRows, rows() - cell.row()); | |
numCols = qMin(numCols, columns() - cell.column()); | |
// nothing to merge? | |
if (numRows < rowSpan || numCols < colSpan) | |
return; | |
// check the edges of the merge rect to make sure no cell spans the edge | |
for (int r = row; r < row + numRows; ++r) { | |
if (cellAt(r, column) == cellAt(r, column - 1)) | |
return; | |
if (cellAt(r, column + numCols) == cellAt(r, column + numCols - 1)) | |
return; | |
} | |
for (int c = column; c < column + numCols; ++c) { | |
if (cellAt(row, c) == cellAt(row - 1, c)) | |
return; | |
if (cellAt(row + numRows, c) == cellAt(row + numRows - 1, c)) | |
return; | |
} | |
p->beginEditBlock(); | |
const int origCellPosition = cell.firstPosition() - 1; | |
const int cellFragment = d->grid[row * d->nCols + column]; | |
// find the position at which to insert the contents of the merged cells | |
QFragmentFindHelper helper(origCellPosition, p->fragmentMap()); | |
QList<int>::Iterator it = qBinaryFind(d->cells.begin(), d->cells.end(), helper); | |
Q_ASSERT(it != d->cells.end()); | |
Q_ASSERT(*it == cellFragment); | |
const int insertCellIndex = it - d->cells.begin(); | |
int insertFragment = d->cells.value(insertCellIndex + 1, d->fragment_end); | |
uint insertPos = p->fragmentMap().position(insertFragment); | |
d->blockFragmentUpdates = true; | |
bool rowHasText = cell.firstCursorPosition().block().length(); | |
bool needsParagraph = rowHasText && colSpan == numCols; | |
// find all cells that will be erased by the merge | |
for (int r = row; r < row + numRows; ++r) { | |
int firstColumn = r < row + rowSpan ? column + colSpan : column; | |
// don't recompute the cell index for the first row | |
int firstCellIndex = r == row ? insertCellIndex + 1 : -1; | |
int cellIndex = firstCellIndex; | |
for (int c = firstColumn; c < column + numCols; ++c) { | |
const int fragment = d->grid[r * d->nCols + c]; | |
// already handled? | |
if (fragment == cellFragment) | |
continue; | |
QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), fragment); | |
uint pos = it.position(); | |
if (firstCellIndex == -1) { | |
QFragmentFindHelper helper(pos, p->fragmentMap()); | |
QList<int>::Iterator it = qBinaryFind(d->cells.begin(), d->cells.end(), helper); | |
Q_ASSERT(it != d->cells.end()); | |
Q_ASSERT(*it == fragment); | |
firstCellIndex = cellIndex = it - d->cells.begin(); | |
} | |
++cellIndex; | |
QTextCharFormat fmt = fc->charFormat(it->format); | |
const int cellRowSpan = fmt.tableCellRowSpan(); | |
const int cellColSpan = fmt.tableCellColumnSpan(); | |
// update the grid for this cell | |
for (int i = r; i < r + cellRowSpan; ++i) | |
for (int j = c; j < c + cellColSpan; ++j) | |
d->grid[i * d->nCols + j] = cellFragment; | |
// erase the cell marker | |
p->remove(pos, 1); | |
const int nextFragment = d->cells.value(cellIndex, d->fragment_end); | |
const uint nextPos = p->fragmentMap().position(nextFragment); | |
Q_ASSERT(nextPos >= pos); | |
// merge the contents of the cell (if not empty) | |
if (nextPos > pos) { | |
if (needsParagraph) { | |
needsParagraph = false; | |
QTextCursor(p, insertPos++).insertBlock(); | |
p->move(pos + 1, insertPos, nextPos - pos); | |
} else if (rowHasText) { | |
QTextCursor(p, insertPos++).insertText(QLatin1String(" ")); | |
p->move(pos + 1, insertPos, nextPos - pos); | |
} else { | |
p->move(pos, insertPos, nextPos - pos); | |
} | |
insertPos += nextPos - pos; | |
rowHasText = true; | |
} | |
} | |
if (rowHasText) { | |
needsParagraph = true; | |
rowHasText = false; | |
} | |
// erase cells from last row | |
if (firstCellIndex >= 0) { | |
d->cellIndices.remove(firstCellIndex, cellIndex - firstCellIndex); | |
d->cells.erase(d->cells.begin() + firstCellIndex, d->cells.begin() + cellIndex); | |
} | |
} | |
d->fragment_start = d->cells.first(); | |
fmt.setTableCellRowSpan(numRows); | |
fmt.setTableCellColumnSpan(numCols); | |
p->setCharFormat(origCellPosition, 1, fmt); | |
d->blockFragmentUpdates = false; | |
d->dirty = false; | |
p->endEditBlock(); | |
} | |
/*! | |
\overload | |
\since 4.1 | |
Merges the cells selected by the provided \a cursor. | |
\sa splitCell() | |
*/ | |
void QTextTable::mergeCells(const QTextCursor &cursor) | |
{ | |
if (!cursor.hasComplexSelection()) | |
return; | |
int firstRow, numRows, firstColumn, numColumns; | |
cursor.selectedTableCells(&firstRow, &numRows, &firstColumn, &numColumns); | |
mergeCells(firstRow, firstColumn, numRows, numColumns); | |
} | |
/*! | |
\since 4.1 | |
Splits the specified cell at \a row and \a column into an array of multiple | |
cells with dimensions specified by \a numRows and \a numCols. | |
\note It is only possible to split cells that span multiple rows or columns, such as rows | |
that have been merged using mergeCells(). | |
\sa mergeCells() | |
*/ | |
void QTextTable::splitCell(int row, int column, int numRows, int numCols) | |
{ | |
Q_D(QTextTable); | |
if (d->dirty) | |
d->update(); | |
QTextDocumentPrivate *p = d->pieceTable; | |
QTextFormatCollection *c = p->formatCollection(); | |
const QTextTableCell cell = cellAt(row, column); | |
if (!cell.isValid()) | |
return; | |
row = cell.row(); | |
column = cell.column(); | |
QTextCharFormat fmt = cell.format(); | |
const int rowSpan = fmt.tableCellRowSpan(); | |
const int colSpan = fmt.tableCellColumnSpan(); | |
// nothing to split? | |
if (numRows > rowSpan || numCols > colSpan) | |
return; | |
p->beginEditBlock(); | |
const int origCellPosition = cell.firstPosition() - 1; | |
QVarLengthArray<int> rowPositions(rowSpan); | |
rowPositions[0] = cell.lastPosition(); | |
for (int r = row + 1; r < row + rowSpan; ++r) { | |
// find the cell before which to insert the new cell markers | |
int gridIndex = r * d->nCols + column; | |
QVector<int>::iterator it = qUpperBound(d->cellIndices.begin(), d->cellIndices.end(), gridIndex); | |
int cellIndex = it - d->cellIndices.begin(); | |
int fragment = d->cells.value(cellIndex, d->fragment_end); | |
rowPositions[r - row] = p->fragmentMap().position(fragment); | |
} | |
fmt.setTableCellColumnSpan(1); | |
fmt.setTableCellRowSpan(1); | |
const int fmtIndex = c->indexForFormat(fmt); | |
const int blockIndex = p->blockMap().find(cell.lastPosition())->format; | |
int insertAdjustement = 0; | |
for (int i = 0; i < numRows; ++i) { | |
for (int c = 0; c < colSpan - numCols; ++c) | |
p->insertBlock(QTextBeginningOfFrame, rowPositions[i] + insertAdjustement + c, blockIndex, fmtIndex); | |
insertAdjustement += colSpan - numCols; | |
} | |
for (int i = numRows; i < rowSpan; ++i) { | |
for (int c = 0; c < colSpan; ++c) | |
p->insertBlock(QTextBeginningOfFrame, rowPositions[i] + insertAdjustement + c, blockIndex, fmtIndex); | |
insertAdjustement += colSpan; | |
} | |
fmt.setTableCellRowSpan(numRows); | |
fmt.setTableCellColumnSpan(numCols); | |
p->setCharFormat(origCellPosition, 1, fmt); | |
p->endEditBlock(); | |
} | |
/*! | |
Returns the number of rows in the table. | |
\sa columns() | |
*/ | |
int QTextTable::rows() const | |
{ | |
Q_D(const QTextTable); | |
if (d->dirty) | |
d->update(); | |
return d->nRows; | |
} | |
/*! | |
Returns the number of columns in the table. | |
\sa rows() | |
*/ | |
int QTextTable::columns() const | |
{ | |
Q_D(const QTextTable); | |
if (d->dirty) | |
d->update(); | |
return d->nCols; | |
} | |
#if 0 | |
void QTextTable::mergeCells(const QTextCursor &selection) | |
{ | |
} | |
#endif | |
/*! | |
\fn QTextCursor QTextTable::rowStart(const QTextCursor &cursor) const | |
Returns a cursor pointing to the start of the row that contains the | |
given \a cursor. | |
\sa rowEnd() | |
*/ | |
QTextCursor QTextTable::rowStart(const QTextCursor &c) const | |
{ | |
Q_D(const QTextTable); | |
QTextTableCell cell = cellAt(c); | |
if (!cell.isValid()) | |
return QTextCursor(); | |
int row = cell.row(); | |
QTextDocumentPrivate *p = d->pieceTable; | |
QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), d->grid[row*d->nCols]); | |
return QTextCursor(p, it.position()); | |
} | |
/*! | |
\fn QTextCursor QTextTable::rowEnd(const QTextCursor &cursor) const | |
Returns a cursor pointing to the end of the row that contains the given | |
\a cursor. | |
\sa rowStart() | |
*/ | |
QTextCursor QTextTable::rowEnd(const QTextCursor &c) const | |
{ | |
Q_D(const QTextTable); | |
QTextTableCell cell = cellAt(c); | |
if (!cell.isValid()) | |
return QTextCursor(); | |
int row = cell.row() + 1; | |
int fragment = row < d->nRows ? d->grid[row*d->nCols] : d->fragment_end; | |
QTextDocumentPrivate *p = d->pieceTable; | |
QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), fragment); | |
return QTextCursor(p, it.position() - 1); | |
} | |
/*! | |
\fn void QTextTable::setFormat(const QTextTableFormat &format) | |
Sets the table's \a format. | |
\sa format() | |
*/ | |
void QTextTable::setFormat(const QTextTableFormat &format) | |
{ | |
QTextTableFormat fmt = format; | |
// don't try to change the number of table columns from here | |
fmt.setColumns(columns()); | |
QTextObject::setFormat(fmt); | |
} | |
/*! | |
\fn QTextTableFormat QTextTable::format() const | |
Returns the table's format. | |
\sa setFormat() | |
*/ | |
QT_END_NAMESPACE |