Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qtexttable.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qtexttable.h"
5#include "qtextcursor.h"
6#include "qtextformat.h"
7#include <qdebug.h>
9#include "qtexttable_p.h"
11
12#include <algorithm>
13#include <stdlib.h>
14
16
17using namespace Qt::StringLiterals;
18
19/*!
20 \class QTextTableCell
21 \reentrant
22
23 \brief The QTextTableCell class represents the properties of a
24 cell in a QTextTable.
25 \inmodule QtGui
26
27 \ingroup richtext-processing
28
29 Table cells are pieces of document structure that belong to a table.
30 The table orders cells into particular rows and columns; cells can
31 also span multiple columns and rows.
32
33 Cells are usually created when a table is inserted into a document with
34 QTextCursor::insertTable(), but they are also created and destroyed when
35 a table is resized.
36
37 Cells contain information about their location in a table; you can
38 obtain the row() and column() numbers of a cell, and its rowSpan()
39 and columnSpan().
40
41 The format() of a cell describes the default character format of its
42 contents. The firstCursorPosition() and lastCursorPosition() functions
43 are used to obtain the extent of the cell in the document.
44
45 \sa QTextTable, QTextTableFormat
46*/
47
48/*!
49 \fn QTextTableCell::QTextTableCell()
50
51 Constructs an invalid table cell.
52
53 \sa isValid()
54*/
55
56/*!
57 \fn QTextTableCell::QTextTableCell(const QTextTableCell &other)
58
59 Copy constructor. Creates a new QTextTableCell object based on the
60 \a other cell.
61*/
62
63/*!
64 \fn QTextTableCell& QTextTableCell::operator=(const QTextTableCell &other)
65
66 Assigns the \a other table cell to this table cell.
67*/
68
69/*!
70 \since 4.2
71
72 Sets the cell's character format to \a format. This can for example be used to change
73 the background color of the entire cell:
74
75 \code
76 QTextTableCell cell = table->cellAt(2, 3);
77 QTextCharFormat format = cell.format();
78 format.setBackground(Qt::blue);
79 cell.setFormat(format);
80 \endcode
81
82 Note that the cell's row or column span cannot be changed through this function. You have
83 to use QTextTable::mergeCells and QTextTable::splitCell instead.
84
85 \sa format()
86*/
87void QTextTableCell::setFormat(const QTextCharFormat &format)
88{
89 QTextCharFormat fmt = format;
90 fmt.clearProperty(QTextFormat::ObjectIndex);
91 fmt.setObjectType(QTextFormat::TableCellObject);
92 QTextDocumentPrivate *p = const_cast<QTextDocumentPrivate *>(QTextDocumentPrivate::get(table));
93 QTextDocumentPrivate::FragmentIterator frag(&p->fragmentMap(), fragment);
94
95 QTextFormatCollection *c = p->formatCollection();
96 QTextCharFormat oldFormat = c->charFormat(frag->format);
97 fmt.setTableCellRowSpan(oldFormat.tableCellRowSpan());
98 fmt.setTableCellColumnSpan(oldFormat.tableCellColumnSpan());
99
100 p->setCharFormat(frag.position(), 1, fmt, QTextDocumentPrivate::SetFormatAndPreserveObjectIndices);
101}
102
103/*!
104 Returns the cell's character format.
105*/
106QTextCharFormat QTextTableCell::format() const
107{
108 const QTextDocumentPrivate *p = QTextDocumentPrivate::get(table);
109 const QTextFormatCollection *c = p->formatCollection();
110
111 QTextCharFormat fmt = c->charFormat(tableCellFormatIndex());
112 fmt.setObjectType(QTextFormat::TableCellObject);
113 return fmt;
114}
115
116/*!
117 \since 4.5
118
119 Returns the index of the tableCell's format in the document's internal list of formats.
120
121 \sa QTextDocument::allFormats()
122*/
123int QTextTableCell::tableCellFormatIndex() const
124{
125 const QTextDocumentPrivate *p = QTextDocumentPrivate::get(table);
126 return QTextDocumentPrivate::FragmentIterator(&p->fragmentMap(), fragment)->format;
127}
128
129/*!
130 Returns the number of the row in the table that contains this cell.
131
132 \sa column()
133*/
134int QTextTableCell::row() const
135{
136 const QTextTablePrivate *tp = table->d_func();
137 if (tp->dirty)
138 tp->update();
139
140 int idx = tp->findCellIndex(fragment);
141 if (idx == -1)
142 return idx;
143 return tp->cellIndices.at(idx) / tp->nCols;
144}
145
146/*!
147 Returns the number of the column in the table that contains this cell.
148
149 \sa row()
150*/
151int QTextTableCell::column() const
152{
153 const QTextTablePrivate *tp = table->d_func();
154 if (tp->dirty)
155 tp->update();
156
157 int idx = tp->findCellIndex(fragment);
158 if (idx == -1)
159 return idx;
160 return tp->cellIndices.at(idx) % tp->nCols;
161}
162
163/*!
164 Returns the number of rows this cell spans. The default is 1.
165
166 \sa columnSpan()
167*/
168int QTextTableCell::rowSpan() const
169{
170 return format().tableCellRowSpan();
171}
172
173/*!
174 Returns the number of columns this cell spans. The default is 1.
175
176 \sa rowSpan()
177*/
178int QTextTableCell::columnSpan() const
179{
180 return format().tableCellColumnSpan();
181}
182
183/*!
184 \fn bool QTextTableCell::isValid() const
185
186 Returns \c true if this is a valid table cell; otherwise returns
187 false.
188*/
189
190
191/*!
192 Returns the first valid cursor position in this cell.
193
194 \sa lastCursorPosition()
195*/
196QTextCursor QTextTableCell::firstCursorPosition() const
197{
198 return QTextCursorPrivate::fromPosition(table->d_func()->pieceTable, firstPosition());
199}
200
201/*!
202 Returns the last valid cursor position in this cell.
203
204 \sa firstCursorPosition()
205*/
206QTextCursor QTextTableCell::lastCursorPosition() const
207{
208 return QTextCursorPrivate::fromPosition(table->d_func()->pieceTable, lastPosition());
209}
210
211
212/*!
213 \internal
214
215 Returns the first valid position in the document occupied by this cell.
216*/
217int QTextTableCell::firstPosition() const
218{
219 const QTextDocumentPrivate *p = QTextDocumentPrivate::get(table);
220 return p->fragmentMap().position(fragment) + 1;
221}
222
223/*!
224 \internal
225
226 Returns the last valid position in the document occupied by this cell.
227*/
228int QTextTableCell::lastPosition() const
229{
230 const QTextDocumentPrivate *p = QTextDocumentPrivate::get(table);
231 const QTextTablePrivate *td = table->d_func();
232 int index = table->d_func()->findCellIndex(fragment);
233 int f;
234 if (index != -1)
235 f = td->cells.value(index + 1, td->fragment_end);
236 else
237 f = td->fragment_end;
238 return p->fragmentMap().position(f);
239}
240
241
242/*!
243 Returns a frame iterator pointing to the beginning of the table's cell.
244
245 \sa end()
246*/
247QTextFrame::iterator QTextTableCell::begin() const
248{
249 const QTextDocumentPrivate *p = QTextDocumentPrivate::get(table);
250 int b = p->blockMap().findNode(firstPosition());
251 int e = p->blockMap().findNode(lastPosition()+1);
252 return QTextFrame::iterator(const_cast<QTextTable *>(table), b, b, e);
253}
254
255/*!
256 Returns a frame iterator pointing to the end of the table's cell.
257
258 \sa begin()
259*/
260QTextFrame::iterator QTextTableCell::end() const
261{
262 const QTextDocumentPrivate *p = QTextDocumentPrivate::get(table);
263 int b = p->blockMap().findNode(firstPosition());
264 int e = p->blockMap().findNode(lastPosition()+1);
265 return QTextFrame::iterator(const_cast<QTextTable *>(table), e, b, e);
266}
267
268
269/*!
270 \fn QTextCursor QTextTableCell::operator==(const QTextTableCell &other) const
271
272 Returns \c true if this cell object and the \a other cell object
273 describe the same cell; otherwise returns \c false.
274*/
275
276/*!
277 \fn QTextCursor QTextTableCell::operator!=(const QTextTableCell &other) const
278
279 Returns \c true if this cell object and the \a other cell object
280 describe different cells; otherwise returns \c false.
281*/
282
283/*!
284 \fn QTextTableCell::~QTextTableCell()
285
286 Destroys the table cell.
287*/
288
289QTextTable *QTextTablePrivate::createTable(QTextDocumentPrivate *pieceTable, int pos, int rows, int cols, const QTextTableFormat &tableFormat)
290{
291 QTextTableFormat fmt = tableFormat;
292 fmt.setColumns(cols);
293 QTextTable *table = qobject_cast<QTextTable *>(pieceTable->createObject(fmt));
294 Q_ASSERT(table);
295
296 pieceTable->beginEditBlock();
297
298// qDebug("---> createTable: rows=%d, cols=%d at %d", rows, cols, pos);
299 // add block after table
300 QTextCharFormat charFmt;
301 charFmt.setObjectIndex(table->objectIndex());
302 charFmt.setObjectType(QTextFormat::TableCellObject);
303
304
305 int charIdx = pieceTable->formatCollection()->indexForFormat(charFmt);
306 int cellIdx = pieceTable->formatCollection()->indexForFormat(QTextBlockFormat());
307
308 QTextTablePrivate *d = table->d_func();
309 d->blockFragmentUpdates = true;
310
311 d->fragment_start = pieceTable->insertBlock(QTextBeginningOfFrame, pos, cellIdx, charIdx);
312 d->cells.append(d->fragment_start);
313 ++pos;
314
315 for (int i = 1; i < rows*cols; ++i) {
316 d->cells.append(pieceTable->insertBlock(QTextBeginningOfFrame, pos, cellIdx, charIdx));
317// qDebug(" addCell at %d", pos);
318 ++pos;
319 }
320
321 d->fragment_end = pieceTable->insertBlock(QTextEndOfFrame, pos, cellIdx, charIdx);
322// qDebug(" addEOR at %d", pos);
323 ++pos;
324
325 d->blockFragmentUpdates = false;
326 d->dirty = true;
327
328 pieceTable->endEditBlock();
329
330 return table;
331}
332
334{
335 inline QFragmentFindHelper(int _pos, const QTextDocumentPrivate::FragmentMap &map)
336 : pos(_pos), fragmentMap(map) {}
337 uint pos;
339};
340
341static inline bool operator<(int fragment, const QFragmentFindHelper &helper)
342{
343 return helper.fragmentMap.position(fragment) < helper.pos;
344}
345
346static inline bool operator<(const QFragmentFindHelper &helper, int fragment)
347{
348 return helper.pos < helper.fragmentMap.position(fragment);
349}
350
351int QTextTablePrivate::findCellIndex(int fragment) const
352{
353 QFragmentFindHelper helper(pieceTable->fragmentMap().position(fragment),
354 pieceTable->fragmentMap());
355 const auto it = std::lower_bound(cells.constBegin(), cells.constEnd(), helper);
356 if ((it == cells.constEnd()) || (helper < *it))
357 return -1;
358 return it - cells.constBegin();
359}
360
361void QTextTablePrivate::fragmentAdded(QChar type, uint fragment)
362{
363 dirty = true;
364 if (blockFragmentUpdates)
365 return;
366 if (type == QTextBeginningOfFrame) {
367 Q_ASSERT(cells.indexOf(int(fragment)) == -1);
368 const uint pos = pieceTable->fragmentMap().position(fragment);
369 QFragmentFindHelper helper(pos, pieceTable->fragmentMap());
370 auto it = std::lower_bound(cells.begin(), cells.end(), helper);
371 cells.insert(it, fragment);
372 if (!fragment_start || pos < pieceTable->fragmentMap().position(fragment_start))
373 fragment_start = fragment;
374 return;
375 }
376 QTextFramePrivate::fragmentAdded(type, fragment);
377}
378
379void QTextTablePrivate::fragmentRemoved(QChar type, uint fragment)
380{
381 dirty = true;
382 if (blockFragmentUpdates)
383 return;
384 if (type == QTextBeginningOfFrame) {
385 Q_ASSERT(cells.indexOf(int(fragment)) != -1);
386 cells.removeAll(int(fragment));
387 if (fragment_start == fragment && cells.size()) {
388 fragment_start = cells.at(0);
389 }
390 if (fragment_start != fragment)
391 return;
392 }
393 QTextFramePrivate::fragmentRemoved(type, fragment);
394}
395
396/*!
397 \internal
398 This function is usually called when the table is "dirty".
399 It seems to update all kind of table information.
400*/
401void QTextTablePrivate::update() const
402{
403 Q_Q(const QTextTable);
404 nCols = q->format().columns();
405 nRows = (cells.size() + nCols-1)/nCols;
406// qDebug(">>>> QTextTablePrivate::update, nRows=%d, nCols=%d", nRows, nCols);
407
408 grid.assign(nRows * nCols, 0);
409
410 QTextDocumentPrivate *p = pieceTable;
411 QTextFormatCollection *c = p->formatCollection();
412
413 cellIndices.resize(cells.size());
414
415 int cell = 0;
416 for (int i = 0; i < cells.size(); ++i) {
417 int fragment = cells.at(i);
418 QTextCharFormat fmt = c->charFormat(QTextDocumentPrivate::FragmentIterator(&p->fragmentMap(), fragment)->format);
419 int rowspan = fmt.tableCellRowSpan();
420 int colspan = fmt.tableCellColumnSpan();
421
422 // skip taken cells
423 while (cell < nRows*nCols && grid[cell])
424 ++cell;
425
426 int r = cell/nCols;
427 int c = cell%nCols;
428 cellIndices[i] = cell;
429
430 if (r + rowspan > nRows) {
431 grid.resize((r + rowspan) * nCols, 0);
432 nRows = r + rowspan;
433 }
434
435 Q_ASSERT(c + colspan <= nCols);
436 for (int ii = 0; ii < rowspan; ++ii) {
437 for (int jj = 0; jj < colspan; ++jj) {
438 Q_ASSERT(grid[(r+ii)*nCols + c+jj] == 0);
439 grid[(r+ii)*nCols + c+jj] = fragment;
440// qDebug(" setting cell %d span=%d/%d at %d/%d", fragment, rowspan, colspan, r+ii, c+jj);
441 }
442 }
443 }
444// qDebug("<<<< end: nRows=%d, nCols=%d", nRows, nCols);
445
446 dirty = false;
447}
448
449
450
451
452
453/*!
454 \class QTextTable
455 \reentrant
456
457 \brief The QTextTable class represents a table in a QTextDocument.
458 \inmodule QtGui
459
460 \ingroup richtext-processing
461
462 A table is a group of cells ordered into rows and columns. Each table
463 contains at least one row and one column. Each cell contains a block, and
464 is surrounded by a frame.
465
466 Tables are usually created and inserted into a document with the
467 QTextCursor::insertTable() function.
468 For example, we can insert a table with three rows and two columns at the
469 current cursor position in an editor using the following lines of code:
470
471 \snippet textdocument-tables/mainwindow.cpp 1
472 \codeline
473 \snippet textdocument-tables/mainwindow.cpp 3
474
475 The table format is either defined when the table is created or changed
476 later with setFormat().
477
478 The table currently being edited by the cursor is found with
479 QTextCursor::currentTable(). This allows its format or dimensions to be
480 changed after it has been inserted into a document.
481
482 A table's size can be changed with resize(), or by using
483 insertRows(), insertColumns(), removeRows(), or removeColumns().
484 Use cellAt() to retrieve table cells.
485
486 The starting and ending positions of table rows can be found by moving
487 a cursor within a table, and using the rowStart() and rowEnd() functions
488 to obtain cursors at the start and end of each row.
489
490 Rows and columns within a QTextTable can be merged and split using
491 the mergeCells() and splitCell() functions. However, only cells that span multiple
492 rows or columns can be split. (Merging or splitting does not increase or decrease
493 the number of rows and columns.)
494
495 Note that if you have merged multiple columns and rows into one cell, you will not
496 be able to split the merged cell into new cells spanning over more than one row
497 or column. To be able to split cells spanning over several rows and columns you
498 need to do this over several iterations.
499
500 \table 80%
501 \row
502 \li \inlineimage texttable-split.png Original Table
503 \li Suppose we have a 2x3 table of names and addresses. To merge both
504 columns in the first row we invoke mergeCells() with \a row = 0,
505 \a column = 0, \a numRows = 1 and \a numColumns = 2.
506 \snippet textdocument-texttable/main.cpp 0
507
508 \row
509 \li \inlineimage texttable-merge.png
510 \li This gives us the following table. To split the first row of the table
511 back into two cells, we invoke the splitCell() function with \a numRows
512 and \a numCols = 1.
513 \snippet textdocument-texttable/main.cpp 1
514
515 \row
516 \li \inlineimage texttable-split.png Split Table
517 \li This results in the original table.
518 \endtable
519
520 \sa QTextTableFormat
521*/
522
523/*! \internal
524 */
525QTextTable::QTextTable(QTextDocument *doc)
526 : QTextFrame(*new QTextTablePrivate(doc), doc)
527{
528}
529
530/*! \internal
531
532Destroys the table.
533 */
534QTextTable::~QTextTable()
535{
536}
537
538
539/*!
540 \fn QTextTableCell QTextTable::cellAt(int row, int column) const
541
542 Returns the table cell at the given \a row and \a column in the table.
543
544 \sa columns(), rows()
545*/
546QTextTableCell QTextTable::cellAt(int row, int col) const
547{
548 Q_D(const QTextTable);
549 if (d->dirty)
550 d->update();
551
552 if (row < 0 || row >= d->nRows || col < 0 || col >= d->nCols)
553 return QTextTableCell();
554
555 return QTextTableCell(this, d->grid[row*d->nCols + col]);
556}
557
558/*!
559 \overload
560
561 Returns the table cell that contains the character at the given \a position
562 in the document.
563*/
564QTextTableCell QTextTable::cellAt(int position) const
565{
566 Q_D(const QTextTable);
567 if (d->dirty)
568 d->update();
569
570 uint pos = (uint)position;
571 const QTextDocumentPrivate::FragmentMap &map = d->pieceTable->fragmentMap();
572 if (position < 0 || map.position(d->fragment_start) >= pos || map.position(d->fragment_end) < pos)
573 return QTextTableCell();
574
575 QFragmentFindHelper helper(position, map);
576 auto it = std::lower_bound(d->cells.begin(), d->cells.end(), helper);
577 if (it != d->cells.begin())
578 --it;
579
580 return QTextTableCell(this, *it);
581}
582
583/*!
584 \fn QTextTableCell QTextTable::cellAt(const QTextCursor &cursor) const
585
586 \overload
587
588 Returns the table cell containing the given \a cursor.
589*/
590QTextTableCell QTextTable::cellAt(const QTextCursor &c) const
591{
592 return cellAt(c.position());
593}
594
595/*!
596 \fn void QTextTable::resize(int rows, int columns)
597
598 Resizes the table to contain the required number of \a rows and \a columns.
599
600 \sa insertRows(), insertColumns(), removeRows(), removeColumns()
601*/
602void QTextTable::resize(int rows, int cols)
603{
604 Q_D(QTextTable);
605 if (d->dirty)
606 d->update();
607
608 int nRows = this->rows();
609 int nCols = this->columns();
610
611 if (rows == nRows && cols == nCols)
612 return;
613
614 d->pieceTable->beginEditBlock();
615
616 if (nCols < cols)
617 insertColumns(nCols, cols - nCols);
618 else if (nCols > cols)
619 removeColumns(cols, nCols - cols);
620
621 if (nRows < rows)
622 insertRows(nRows, rows-nRows);
623 else if (nRows > rows)
624 removeRows(rows, nRows-rows);
625
626 d->pieceTable->endEditBlock();
627}
628
629/*!
630 \fn void QTextTable::insertRows(int index, int rows)
631
632 Inserts a number of \a rows before the row with the specified \a index.
633
634 \sa resize(), insertColumns(), removeRows(), removeColumns(), appendRows(), appendColumns()
635*/
636void QTextTable::insertRows(int pos, int num)
637{
638 Q_D(QTextTable);
639 if (num <= 0)
640 return;
641
642 if (d->dirty)
643 d->update();
644
645 if (pos > d->nRows || pos < 0)
646 pos = d->nRows;
647
648// qDebug() << "-------- insertRows" << pos << num;
649 QTextDocumentPrivate *p = d->pieceTable;
650 QTextFormatCollection *c = p->formatCollection();
651 p->beginEditBlock();
652
653 int extended = 0;
654 int insert_before = 0;
655 if (pos > 0 && pos < d->nRows) {
656 int lastCell = -1;
657 for (int i = 0; i < d->nCols; ++i) {
658 int cell = d->grid[pos*d->nCols + i];
659 if (cell == d->grid[(pos-1)*d->nCols+i]) {
660 // cell spans the insertion place, extend it
661 if (cell != lastCell) {
662 QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), cell);
663 QTextCharFormat fmt = c->charFormat(it->format);
664 fmt.setTableCellRowSpan(fmt.tableCellRowSpan() + num);
665 p->setCharFormat(it.position(), 1, fmt);
666 }
667 extended++;
668 } else if (!insert_before) {
669 insert_before = cell;
670 }
671 lastCell = cell;
672 }
673 } else {
674 insert_before = (pos == 0 ? d->grid[0] : d->fragment_end);
675 }
676 if (extended < d->nCols) {
677 Q_ASSERT(insert_before);
678 QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), insert_before);
679 QTextCharFormat fmt = c->charFormat(it->format);
680 fmt.setTableCellRowSpan(1);
681 fmt.setTableCellColumnSpan(1);
682 Q_ASSERT(fmt.objectIndex() == objectIndex());
683 int pos = it.position();
684 int cfmt = p->formatCollection()->indexForFormat(fmt);
685 int bfmt = p->formatCollection()->indexForFormat(QTextBlockFormat());
686// qDebug("inserting %d cells, nCols=%d extended=%d", num*(d->nCols-extended), d->nCols, extended);
687 for (int i = 0; i < num*(d->nCols-extended); ++i)
688 p->insertBlock(QTextBeginningOfFrame, pos, bfmt, cfmt, QTextUndoCommand::MoveCursor);
689 }
690
691// qDebug() << "-------- end insertRows" << pos << num;
692 p->endEditBlock();
693}
694
695/*!
696 \fn void QTextTable::insertColumns(int index, int columns)
697
698 Inserts a number of \a columns before the column with the specified \a index.
699
700 \sa insertRows(), resize(), removeRows(), removeColumns(), appendRows(), appendColumns()
701*/
702void QTextTable::insertColumns(int pos, int num)
703{
704 Q_D(QTextTable);
705 if (num <= 0)
706 return;
707
708 if (d->dirty)
709 d->update();
710
711 if (pos > d->nCols || pos < 0)
712 pos = d->nCols;
713
714// qDebug() << "-------- insertCols" << pos << num;
715 QTextDocumentPrivate *p = d->pieceTable;
716 QTextFormatCollection *c = p->formatCollection();
717 p->beginEditBlock();
718
719 QList<int> extendedSpans;
720 for (int i = 0; i < d->nRows; ++i) {
721 int cell;
722 if (i == d->nRows - 1 && pos == d->nCols) {
723 cell = d->fragment_end;
724 } else {
725 int logicalGridIndexBeforePosition = pos > 0 || i > 0
726 ? d->findCellIndex(d->grid[i*d->nCols + pos - 1])
727 : -1;
728
729 // Search for the logical insertion point by skipping past cells which are not the first
730 // cell in a rowspan. This means any cell for which the logical grid index is
731 // less than the logical cell index of the cell before the insertion.
732 int logicalGridIndex;
733 int gridArrayOffset = i*d->nCols + pos;
734 do {
735 cell = d->grid[gridArrayOffset];
736 logicalGridIndex = d->findCellIndex(cell);
737 gridArrayOffset++;
738 } while (logicalGridIndex < logicalGridIndexBeforePosition
739 && gridArrayOffset < d->nRows*d->nCols);
740
741 if (logicalGridIndex < logicalGridIndexBeforePosition
742 && gridArrayOffset == d->nRows*d->nCols)
743 cell = d->fragment_end;
744 }
745
746 if (pos > 0 && pos < d->nCols && cell == d->grid[i*d->nCols + pos - 1]) {
747 // cell spans the insertion place, extend it
748 if (!extendedSpans.contains(cell)) {
749 QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), cell);
750 QTextCharFormat fmt = c->charFormat(it->format);
751 fmt.setTableCellColumnSpan(fmt.tableCellColumnSpan() + num);
752 p->setCharFormat(it.position(), 1, fmt);
753 d->dirty = true;
754 extendedSpans << cell;
755 }
756 } else {
757 /* If the next cell is spanned from the row above, we need to find the right position
758 to insert to */
759 if (i > 0 && pos < d->nCols && cell == d->grid[(i-1) * d->nCols + pos]) {
760 int gridIndex = i*d->nCols + pos;
761 const int gridEnd = d->nRows * d->nCols - 1;
762 while (gridIndex < gridEnd && cell == d->grid[gridIndex]) {
763 ++gridIndex;
764 }
765 if (gridIndex == gridEnd)
766 cell = d->fragment_end;
767 else
768 cell = d->grid[gridIndex];
769 }
770 QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), cell);
771 QTextCharFormat fmt = c->charFormat(it->format);
772 fmt.setTableCellRowSpan(1);
773 fmt.setTableCellColumnSpan(1);
774 Q_ASSERT(fmt.objectIndex() == objectIndex());
775 int position = it.position();
776 int cfmt = p->formatCollection()->indexForFormat(fmt);
777 int bfmt = p->formatCollection()->indexForFormat(QTextBlockFormat());
778 for (int i = 0; i < num; ++i)
779 p->insertBlock(QTextBeginningOfFrame, position, bfmt, cfmt, QTextUndoCommand::MoveCursor);
780 }
781 }
782
783 QTextTableFormat tfmt = format();
784 tfmt.setColumns(tfmt.columns()+num);
785 QList<QTextLength> columnWidths = tfmt.columnWidthConstraints();
786 if (! columnWidths.isEmpty()) {
787 for (int i = num; i > 0; --i)
788 columnWidths.insert(pos, columnWidths.at(qMax(0, pos - 1)));
789 }
790 tfmt.setColumnWidthConstraints (columnWidths);
791 QTextObject::setFormat(tfmt);
792
793// qDebug() << "-------- end insertCols" << pos << num;
794 p->endEditBlock();
795}
796
797/*!
798 \since 4.5
799 Appends \a count rows at the bottom of the table.
800
801 \sa insertColumns(), insertRows(), resize(), removeRows(), removeColumns(), appendColumns()
802*/
803void QTextTable::appendRows(int count)
804{
805 insertRows(rows(), count);
806}
807
808/*!
809 \since 4.5
810 Appends \a count columns at the right side of the table.
811
812 \sa insertColumns(), insertRows(), resize(), removeRows(), removeColumns(), appendRows()
813*/
814void QTextTable::appendColumns(int count)
815{
816 insertColumns(columns(), count);
817}
818
819/*!
820 \fn void QTextTable::removeRows(int index, int rows)
821
822 Removes a number of \a rows starting with the row at the specified \a index.
823
824 \sa insertRows(), insertColumns(), resize(), removeColumns(), appendRows(), appendColumns()
825*/
826void QTextTable::removeRows(int pos, int num)
827{
828 Q_D(QTextTable);
829// qDebug() << "-------- removeRows" << pos << num;
830
831 if (num <= 0 || pos < 0)
832 return;
833 if (d->dirty)
834 d->update();
835 if (pos >= d->nRows)
836 return;
837 if (pos+num > d->nRows)
838 num = d->nRows - pos;
839
840 QTextDocumentPrivate *p = d->pieceTable;
841 QTextFormatCollection *collection = p->formatCollection();
842 p->beginEditBlock();
843
844 // delete whole table?
845 if (pos == 0 && num == d->nRows) {
846 const int pos = p->fragmentMap().position(d->fragment_start);
847 p->remove(pos, p->fragmentMap().position(d->fragment_end) - pos + 1);
848 p->endEditBlock();
849 return;
850 }
851
852 p->aboutToRemoveCell(cellAt(pos, 0).firstPosition(), cellAt(pos + num - 1, d->nCols - 1).lastPosition());
853
854 QList<int> touchedCells;
855 for (int r = pos; r < pos + num; ++r) {
856 for (int c = 0; c < d->nCols; ++c) {
857 int cell = d->grid[r*d->nCols + c];
858 if (touchedCells.contains(cell))
859 continue;
860 touchedCells << cell;
861 QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), cell);
862 QTextCharFormat fmt = collection->charFormat(it->format);
863 int span = fmt.tableCellRowSpan();
864 if (span > 1) {
865 fmt.setTableCellRowSpan(span - 1);
866 p->setCharFormat(it.position(), 1, fmt);
867 } else {
868 // remove cell
869 int index = d->cells.indexOf(cell) + 1;
870 int f_end = index < d->cells.size() ? d->cells.at(index) : d->fragment_end;
871 p->remove(it.position(), p->fragmentMap().position(f_end) - it.position());
872 }
873 }
874 }
875
876 p->endEditBlock();
877// qDebug() << "-------- end removeRows" << pos << num;
878}
879
880/*!
881 \fn void QTextTable::removeColumns(int index, int columns)
882
883 Removes a number of \a columns starting with the column at the specified
884 \a index.
885
886 \sa insertRows(), insertColumns(), removeRows(), resize(), appendRows(), appendColumns()
887*/
888void QTextTable::removeColumns(int pos, int num)
889{
890 Q_D(QTextTable);
891// qDebug() << "-------- removeCols" << pos << num;
892
893 if (num <= 0 || pos < 0)
894 return;
895 if (d->dirty)
896 d->update();
897 if (pos >= d->nCols)
898 return;
899 if (pos + num > d->nCols)
900 pos = d->nCols - num;
901
902 QTextDocumentPrivate *p = d->pieceTable;
903 QTextFormatCollection *collection = p->formatCollection();
904 p->beginEditBlock();
905
906 // delete whole table?
907 if (pos == 0 && num == d->nCols) {
908 const int pos = p->fragmentMap().position(d->fragment_start);
909 p->remove(pos, p->fragmentMap().position(d->fragment_end) - pos + 1);
910 p->endEditBlock();
911 return;
912 }
913
914 p->aboutToRemoveCell(cellAt(0, pos).firstPosition(), cellAt(d->nRows - 1, pos + num - 1).lastPosition());
915
916 QList<int> touchedCells;
917 for (int r = 0; r < d->nRows; ++r) {
918 for (int c = pos; c < pos + num; ++c) {
919 int cell = d->grid[r*d->nCols + c];
920 QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), cell);
921 QTextCharFormat fmt = collection->charFormat(it->format);
922 int span = fmt.tableCellColumnSpan();
923 if (touchedCells.contains(cell) && span <= 1)
924 continue;
925 touchedCells << cell;
926
927 if (span > 1) {
928 fmt.setTableCellColumnSpan(span - 1);
929 p->setCharFormat(it.position(), 1, fmt);
930 } else {
931 // remove cell
932 int index = d->cells.indexOf(cell) + 1;
933 int f_end = index < d->cells.size() ? d->cells.at(index) : d->fragment_end;
934 p->remove(it.position(), p->fragmentMap().position(f_end) - it.position());
935 }
936 }
937 }
938
939 QTextTableFormat tfmt = format();
940 tfmt.setColumns(tfmt.columns()-num);
941 QList<QTextLength> columnWidths = tfmt.columnWidthConstraints();
942 if (columnWidths.size() > pos) {
943 columnWidths.remove(pos, num);
944 tfmt.setColumnWidthConstraints (columnWidths);
945 }
946 QTextObject::setFormat(tfmt);
947
948 p->endEditBlock();
949// qDebug() << "-------- end removeCols" << pos << num;
950}
951
952/*!
953 \since 4.1
954
955 Merges the cell at the specified \a row and \a column with the adjacent cells
956 into one cell. The new cell will span \a numRows rows and \a numCols columns.
957 This method does nothing if \a numRows or \a numCols is less than the current
958 number of rows or columns spanned by the cell.
959
960 \sa splitCell()
961*/
962void QTextTable::mergeCells(int row, int column, int numRows, int numCols)
963{
964 Q_D(QTextTable);
965
966 if (d->dirty)
967 d->update();
968
969 QTextDocumentPrivate *p = d->pieceTable;
970 QTextFormatCollection *fc = p->formatCollection();
971
972 const QTextTableCell cell = cellAt(row, column);
973 if (!cell.isValid() || row != cell.row() || column != cell.column())
974 return;
975
976 QTextCharFormat fmt = cell.format();
977 const int rowSpan = fmt.tableCellRowSpan();
978 const int colSpan = fmt.tableCellColumnSpan();
979
980 numRows = qMin(numRows, rows() - cell.row());
981 numCols = qMin(numCols, columns() - cell.column());
982
983 // nothing to merge?
984 if (numRows < rowSpan || numCols < colSpan)
985 return;
986
987 // check the edges of the merge rect to make sure no cell spans the edge
988 for (int r = row; r < row + numRows; ++r) {
989 if (cellAt(r, column) == cellAt(r, column - 1))
990 return;
991 if (cellAt(r, column + numCols) == cellAt(r, column + numCols - 1))
992 return;
993 }
994
995 for (int c = column; c < column + numCols; ++c) {
996 if (cellAt(row, c) == cellAt(row - 1, c))
997 return;
998 if (cellAt(row + numRows, c) == cellAt(row + numRows - 1, c))
999 return;
1000 }
1001
1002 p->beginEditBlock();
1003
1004 const int origCellPosition = cell.firstPosition() - 1;
1005
1006 const int cellFragment = d->grid[row * d->nCols + column];
1007
1008 // find the position at which to insert the contents of the merged cells
1009 QFragmentFindHelper helper(origCellPosition, p->fragmentMap());
1010 const auto begin = d->cells.cbegin();
1011 const auto it = std::lower_bound(begin, d->cells.cend(), helper);
1012 Q_ASSERT(it != d->cells.cend());
1013 Q_ASSERT(!(helper < *it));
1014 Q_ASSERT(*it == cellFragment);
1015 const int insertCellIndex = it - begin;
1016 int insertFragment = d->cells.value(insertCellIndex + 1, d->fragment_end);
1017 uint insertPos = p->fragmentMap().position(insertFragment);
1018
1019 d->blockFragmentUpdates = true;
1020
1021 bool rowHasText = cell.firstCursorPosition().block().length();
1022 bool needsParagraph = rowHasText && colSpan == numCols;
1023
1024 // find all cells that will be erased by the merge
1025 for (int r = row; r < row + numRows; ++r) {
1026 int firstColumn = r < row + rowSpan ? column + colSpan : column;
1027
1028 // don't recompute the cell index for the first row
1029 int firstCellIndex = r == row ? insertCellIndex + 1 : -1;
1030 int cellIndex = firstCellIndex;
1031
1032 for (int c = firstColumn; c < column + numCols; ++c) {
1033 const int fragment = d->grid[r * d->nCols + c];
1034
1035 // already handled?
1036 if (fragment == cellFragment)
1037 continue;
1038
1039 QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), fragment);
1040 uint pos = it.position();
1041
1042 if (firstCellIndex == -1) {
1043 QFragmentFindHelper helper(pos, p->fragmentMap());
1044 const auto begin = d->cells.cbegin();
1045 const auto it = std::lower_bound(begin, d->cells.cend(), helper);
1046 Q_ASSERT(it != d->cells.cend());
1047 Q_ASSERT(!(helper < *it));
1048 Q_ASSERT(*it == fragment);
1049 firstCellIndex = cellIndex = it - begin;
1050 }
1051
1052 ++cellIndex;
1053
1054 QTextCharFormat fmt = fc->charFormat(it->format);
1055
1056 const int cellRowSpan = fmt.tableCellRowSpan();
1057 const int cellColSpan = fmt.tableCellColumnSpan();
1058
1059 // update the grid for this cell
1060 for (int i = r; i < r + cellRowSpan; ++i)
1061 for (int j = c; j < c + cellColSpan; ++j)
1062 d->grid[i * d->nCols + j] = cellFragment;
1063
1064 // erase the cell marker
1065 p->remove(pos, 1);
1066
1067 const int nextFragment = d->cells.value(cellIndex, d->fragment_end);
1068 const uint nextPos = p->fragmentMap().position(nextFragment);
1069
1070 Q_ASSERT(nextPos >= pos);
1071
1072 // merge the contents of the cell (if not empty)
1073 if (nextPos > pos) {
1074 if (needsParagraph) {
1075 needsParagraph = false;
1076 QTextCursorPrivate::fromPosition(p, insertPos++).insertBlock();
1077 p->move(pos + 1, insertPos, nextPos - pos);
1078 } else if (rowHasText) {
1079 QTextCursorPrivate::fromPosition(p, insertPos++).insertText(" "_L1);
1080 p->move(pos + 1, insertPos, nextPos - pos);
1081 } else {
1082 p->move(pos, insertPos, nextPos - pos);
1083 }
1084
1085 insertPos += nextPos - pos;
1086 rowHasText = true;
1087 }
1088 }
1089
1090 if (rowHasText) {
1091 needsParagraph = true;
1092 rowHasText = false;
1093 }
1094
1095 // erase cells from last row
1096 if (firstCellIndex >= 0) {
1097 d->cellIndices.remove(firstCellIndex, cellIndex - firstCellIndex);
1098 d->cells.erase(d->cells.begin() + firstCellIndex, d->cells.begin() + cellIndex);
1099 }
1100 }
1101
1102 d->fragment_start = d->cells.constFirst();
1103
1104 fmt.setTableCellRowSpan(numRows);
1105 fmt.setTableCellColumnSpan(numCols);
1106 p->setCharFormat(origCellPosition, 1, fmt);
1107
1108 d->blockFragmentUpdates = false;
1109 d->dirty = false;
1110
1111 p->endEditBlock();
1112}
1113
1114/*!
1115 \overload
1116 \since 4.1
1117
1118 Merges the cells selected by the provided \a cursor.
1119
1120 \sa splitCell()
1121*/
1122void QTextTable::mergeCells(const QTextCursor &cursor)
1123{
1124 if (!cursor.hasComplexSelection())
1125 return;
1126
1127 int firstRow, numRows, firstColumn, numColumns;
1128 cursor.selectedTableCells(&firstRow, &numRows, &firstColumn, &numColumns);
1129 mergeCells(firstRow, firstColumn, numRows, numColumns);
1130}
1131
1132/*!
1133 \since 4.1
1134
1135 Splits the specified cell at \a row and \a column into an array of multiple
1136 cells with dimensions specified by \a numRows and \a numCols.
1137
1138 \note It is only possible to split cells that span multiple rows or columns, such as rows
1139 that have been merged using mergeCells().
1140
1141 \sa mergeCells()
1142*/
1143void QTextTable::splitCell(int row, int column, int numRows, int numCols)
1144{
1145 Q_D(QTextTable);
1146
1147 if (d->dirty)
1148 d->update();
1149
1150 QTextDocumentPrivate *p = d->pieceTable;
1151 QTextFormatCollection *c = p->formatCollection();
1152
1153 const QTextTableCell cell = cellAt(row, column);
1154 if (!cell.isValid())
1155 return;
1156 row = cell.row();
1157 column = cell.column();
1158
1159 QTextCharFormat fmt = cell.format();
1160 const int rowSpan = fmt.tableCellRowSpan();
1161 const int colSpan = fmt.tableCellColumnSpan();
1162
1163 // nothing to split?
1164 if (numRows > rowSpan || numCols > colSpan)
1165 return;
1166
1167 p->beginEditBlock();
1168
1169 const int origCellPosition = cell.firstPosition() - 1;
1170
1171 QVarLengthArray<int> rowPositions(rowSpan);
1172
1173 rowPositions[0] = cell.lastPosition();
1174
1175 for (int r = row + 1; r < row + rowSpan; ++r) {
1176 // find the cell before which to insert the new cell markers
1177 int gridIndex = r * d->nCols + column;
1178 const auto begin = d->cellIndices.cbegin();
1179 const auto it = std::upper_bound(begin, d->cellIndices.cend(), gridIndex);
1180 int fragment = d->cells.value(it - begin, d->fragment_end);
1181 rowPositions[r - row] = p->fragmentMap().position(fragment);
1182 }
1183
1184 fmt.setTableCellColumnSpan(1);
1185 fmt.setTableCellRowSpan(1);
1186 const int fmtIndex = c->indexForFormat(fmt);
1187 const int blockIndex = p->blockMap().find(cell.lastPosition())->format;
1188
1189 int insertAdjustement = 0;
1190 for (int i = 0; i < numRows; ++i) {
1191 for (int c = 0; c < colSpan - numCols; ++c)
1192 p->insertBlock(QTextBeginningOfFrame, rowPositions[i] + insertAdjustement + c, blockIndex, fmtIndex);
1193 insertAdjustement += colSpan - numCols;
1194 }
1195
1196 for (int i = numRows; i < rowSpan; ++i) {
1197 for (int c = 0; c < colSpan; ++c)
1198 p->insertBlock(QTextBeginningOfFrame, rowPositions[i] + insertAdjustement + c, blockIndex, fmtIndex);
1199 insertAdjustement += colSpan;
1200 }
1201
1202 fmt.setTableCellRowSpan(numRows);
1203 fmt.setTableCellColumnSpan(numCols);
1204 p->setCharFormat(origCellPosition, 1, fmt);
1205
1206 p->endEditBlock();
1207}
1208
1209/*!
1210 Returns the number of rows in the table.
1211
1212 \sa columns()
1213*/
1214int QTextTable::rows() const
1215{
1216 Q_D(const QTextTable);
1217 if (d->dirty)
1218 d->update();
1219
1220 return d->nRows;
1221}
1222
1223/*!
1224 Returns the number of columns in the table.
1225
1226 \sa rows()
1227*/
1228int QTextTable::columns() const
1229{
1230 Q_D(const QTextTable);
1231 if (d->dirty)
1232 d->update();
1233
1234 return d->nCols;
1235}
1236
1237/*!
1238 \fn QTextCursor QTextTable::rowStart(const QTextCursor &cursor) const
1239
1240 Returns a cursor pointing to the start of the row that contains the
1241 given \a cursor.
1242
1243 \sa rowEnd()
1244*/
1245QTextCursor QTextTable::rowStart(const QTextCursor &c) const
1246{
1247 Q_D(const QTextTable);
1248 QTextTableCell cell = cellAt(c);
1249 if (!cell.isValid())
1250 return QTextCursor();
1251
1252 int row = cell.row();
1253 QTextDocumentPrivate *p = d->pieceTable;
1254 QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), d->grid[row*d->nCols]);
1255 return QTextCursorPrivate::fromPosition(p, it.position());
1256}
1257
1258/*!
1259 \fn QTextCursor QTextTable::rowEnd(const QTextCursor &cursor) const
1260
1261 Returns a cursor pointing to the end of the row that contains the given
1262 \a cursor.
1263
1264 \sa rowStart()
1265*/
1266QTextCursor QTextTable::rowEnd(const QTextCursor &c) const
1267{
1268 Q_D(const QTextTable);
1269 QTextTableCell cell = cellAt(c);
1270 if (!cell.isValid())
1271 return QTextCursor();
1272
1273 int row = cell.row() + 1;
1274 int fragment = row < d->nRows ? d->grid[row*d->nCols] : d->fragment_end;
1275 QTextDocumentPrivate *p = d->pieceTable;
1276 QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), fragment);
1277 return QTextCursorPrivate::fromPosition(p, it.position() - 1);
1278}
1279
1280/*!
1281 \fn void QTextTable::setFormat(const QTextTableFormat &format)
1282
1283 Sets the table's \a format.
1284
1285 \sa format()
1286*/
1287void QTextTable::setFormat(const QTextTableFormat &format)
1288{
1289 QTextTableFormat fmt = format;
1290 // don't try to change the number of table columns from here
1291 fmt.setColumns(columns());
1292 QTextObject::setFormat(fmt);
1293}
1294
1295/*!
1296 \fn QTextTableFormat QTextTable::format() const
1297
1298 Returns the table's format.
1299
1300 \sa setFormat()
1301*/
1302
1303QT_END_NAMESPACE
1304
1305#include "moc_qtexttable.cpp"
Combined button and popup list for selecting options.
#define QTextBeginningOfFrame
#define QTextEndOfFrame
static bool operator<(int fragment, const QFragmentFindHelper &helper)
static bool operator<(const QFragmentFindHelper &helper, int fragment)
const QTextDocumentPrivate::FragmentMap & fragmentMap
QFragmentFindHelper(int _pos, const QTextDocumentPrivate::FragmentMap &map)