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>
8#include "qtextcursor_p.h"
9#include "qtexttable_p.h"
10#include "qvarlengtharray.h"
11
12#include <algorithm>
13#include <stdlib.h>
14
15QT_BEGIN_NAMESPACE
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 QTextTableCell cell = table->cellAt(2, 3);
76 QTextCharFormat format = cell.format();
77 format.setBackground(Qt::blue);
78 cell.setFormat(format);
79
80 Note that the cell's row or column span cannot be changed through this function. You have
81 to use QTextTable::mergeCells and QTextTable::splitCell instead.
82
83 \sa format()
84*/
85void QTextTableCell::setFormat(const QTextCharFormat &format)
86{
87 QTextCharFormat fmt = format;
88 fmt.clearProperty(propertyId: QTextFormat::ObjectIndex);
89 fmt.setObjectType(QTextFormat::TableCellObject);
90 QTextDocumentPrivate *p = const_cast<QTextDocumentPrivate *>(QTextDocumentPrivate::get(object: table));
91 QTextDocumentPrivate::FragmentIterator frag(&p->fragmentMap(), fragment);
92
93 QTextFormatCollection *c = p->formatCollection();
94 QTextCharFormat oldFormat = c->charFormat(index: frag->format);
95 fmt.setTableCellRowSpan(oldFormat.tableCellRowSpan());
96 fmt.setTableCellColumnSpan(oldFormat.tableCellColumnSpan());
97
98 p->setCharFormat(pos: frag.position(), length: 1, newFormat: fmt, mode: QTextDocumentPrivate::SetFormatAndPreserveObjectIndices);
99}
100
101/*!
102 Returns the cell's character format.
103*/
104QTextCharFormat QTextTableCell::format() const
105{
106 const QTextDocumentPrivate *p = QTextDocumentPrivate::get(object: table);
107 const QTextFormatCollection *c = p->formatCollection();
108
109 QTextCharFormat fmt = c->charFormat(index: tableCellFormatIndex());
110 fmt.setObjectType(QTextFormat::TableCellObject);
111 return fmt;
112}
113
114/*!
115 \since 4.5
116
117 Returns the index of the tableCell's format in the document's internal list of formats.
118
119 \sa QTextDocument::allFormats()
120*/
121int QTextTableCell::tableCellFormatIndex() const
122{
123 const QTextDocumentPrivate *p = QTextDocumentPrivate::get(object: table);
124 return QTextDocumentPrivate::FragmentIterator(&p->fragmentMap(), fragment)->format;
125}
126
127/*!
128 Returns the number of the row in the table that contains this cell.
129
130 \sa column()
131*/
132int QTextTableCell::row() const
133{
134 const QTextTablePrivate *tp = table->d_func();
135 if (tp->dirty)
136 tp->update();
137
138 int idx = tp->findCellIndex(fragment);
139 if (idx == -1)
140 return idx;
141 return tp->cellIndices.at(i: idx) / tp->nCols;
142}
143
144/*!
145 Returns the number of the column in the table that contains this cell.
146
147 \sa row()
148*/
149int QTextTableCell::column() const
150{
151 const QTextTablePrivate *tp = table->d_func();
152 if (tp->dirty)
153 tp->update();
154
155 int idx = tp->findCellIndex(fragment);
156 if (idx == -1)
157 return idx;
158 return tp->cellIndices.at(i: idx) % tp->nCols;
159}
160
161/*!
162 Returns the number of rows this cell spans. The default is 1.
163
164 \sa columnSpan()
165*/
166int QTextTableCell::rowSpan() const
167{
168 return format().tableCellRowSpan();
169}
170
171/*!
172 Returns the number of columns this cell spans. The default is 1.
173
174 \sa rowSpan()
175*/
176int QTextTableCell::columnSpan() const
177{
178 return format().tableCellColumnSpan();
179}
180
181/*!
182 \fn bool QTextTableCell::isValid() const
183
184 Returns \c true if this is a valid table cell; otherwise returns
185 false.
186*/
187
188
189/*!
190 Returns the first valid cursor position in this cell.
191
192 \sa lastCursorPosition()
193*/
194QTextCursor QTextTableCell::firstCursorPosition() const
195{
196 return QTextCursorPrivate::fromPosition(d: table->d_func()->pieceTable, pos: firstPosition());
197}
198
199/*!
200 Returns the last valid cursor position in this cell.
201
202 \sa firstCursorPosition()
203*/
204QTextCursor QTextTableCell::lastCursorPosition() const
205{
206 return QTextCursorPrivate::fromPosition(d: table->d_func()->pieceTable, pos: lastPosition());
207}
208
209
210/*!
211 \internal
212
213 Returns the first valid position in the document occupied by this cell.
214*/
215int QTextTableCell::firstPosition() const
216{
217 const QTextDocumentPrivate *p = QTextDocumentPrivate::get(object: table);
218 return p->fragmentMap().position(node: fragment) + 1;
219}
220
221/*!
222 \internal
223
224 Returns the last valid position in the document occupied by this cell.
225*/
226int QTextTableCell::lastPosition() const
227{
228 const QTextDocumentPrivate *p = QTextDocumentPrivate::get(object: table);
229 const QTextTablePrivate *td = table->d_func();
230 int index = table->d_func()->findCellIndex(fragment);
231 int f;
232 if (index != -1)
233 f = td->cells.value(i: index + 1, defaultValue: td->fragment_end);
234 else
235 f = td->fragment_end;
236 return p->fragmentMap().position(node: f);
237}
238
239
240/*!
241 Returns a frame iterator pointing to the beginning of the table's cell.
242
243 \sa end()
244*/
245QTextFrame::iterator QTextTableCell::begin() const
246{
247 const QTextDocumentPrivate *p = QTextDocumentPrivate::get(object: table);
248 int b = p->blockMap().findNode(k: firstPosition());
249 int e = p->blockMap().findNode(k: lastPosition()+1);
250 return QTextFrame::iterator(const_cast<QTextTable *>(table), b, b, e);
251}
252
253/*!
254 Returns a frame iterator pointing to the end of the table's cell.
255
256 \sa begin()
257*/
258QTextFrame::iterator QTextTableCell::end() const
259{
260 const QTextDocumentPrivate *p = QTextDocumentPrivate::get(object: table);
261 int b = p->blockMap().findNode(k: firstPosition());
262 int e = p->blockMap().findNode(k: lastPosition()+1);
263 return QTextFrame::iterator(const_cast<QTextTable *>(table), e, b, e);
264}
265
266
267/*!
268 \fn QTextCursor QTextTableCell::operator==(const QTextTableCell &other) const
269
270 Returns \c true if this cell object and the \a other cell object
271 describe the same cell; otherwise returns \c false.
272*/
273
274/*!
275 \fn QTextCursor QTextTableCell::operator!=(const QTextTableCell &other) const
276
277 Returns \c true if this cell object and the \a other cell object
278 describe different cells; otherwise returns \c false.
279*/
280
281/*!
282 \fn QTextTableCell::~QTextTableCell()
283
284 Destroys the table cell.
285*/
286
287QTextTable *QTextTablePrivate::createTable(QTextDocumentPrivate *pieceTable, int pos, int rows, int cols, const QTextTableFormat &tableFormat)
288{
289 QTextTableFormat fmt = tableFormat;
290 fmt.setColumns(cols);
291 QTextTable *table = qobject_cast<QTextTable *>(object: pieceTable->createObject(newFormat: fmt));
292 Q_ASSERT(table);
293
294 pieceTable->beginEditBlock();
295
296// qDebug("---> createTable: rows=%d, cols=%d at %d", rows, cols, pos);
297 // add block after table
298 QTextCharFormat charFmt;
299 charFmt.setObjectIndex(table->objectIndex());
300 charFmt.setObjectType(QTextFormat::TableCellObject);
301
302
303 int charIdx = pieceTable->formatCollection()->indexForFormat(f: charFmt);
304 int cellIdx = pieceTable->formatCollection()->indexForFormat(f: QTextBlockFormat());
305
306 QTextTablePrivate *d = table->d_func();
307 d->blockFragmentUpdates = true;
308
309 d->fragment_start = pieceTable->insertBlock(QTextBeginningOfFrame, pos, blockFormat: cellIdx, charFormat: charIdx);
310 d->cells.append(t: d->fragment_start);
311 ++pos;
312
313 for (int i = 1; i < rows*cols; ++i) {
314 d->cells.append(t: pieceTable->insertBlock(QTextBeginningOfFrame, pos, blockFormat: cellIdx, charFormat: charIdx));
315// qDebug(" addCell at %d", pos);
316 ++pos;
317 }
318
319 d->fragment_end = pieceTable->insertBlock(QTextEndOfFrame, pos, blockFormat: cellIdx, charFormat: charIdx);
320// qDebug(" addEOR at %d", pos);
321 ++pos;
322
323 d->blockFragmentUpdates = false;
324 d->dirty = true;
325
326 pieceTable->endEditBlock();
327
328 return table;
329}
330
331struct QFragmentFindHelper
332{
333 inline QFragmentFindHelper(int _pos, const QTextDocumentPrivate::FragmentMap &map)
334 : pos(_pos), fragmentMap(map) {}
335 uint pos;
336 const QTextDocumentPrivate::FragmentMap &fragmentMap;
337};
338
339static inline bool operator<(int fragment, const QFragmentFindHelper &helper)
340{
341 return helper.fragmentMap.position(node: fragment) < helper.pos;
342}
343
344static inline bool operator<(const QFragmentFindHelper &helper, int fragment)
345{
346 return helper.pos < helper.fragmentMap.position(node: fragment);
347}
348
349int QTextTablePrivate::findCellIndex(int fragment) const
350{
351 QFragmentFindHelper helper(pieceTable->fragmentMap().position(node: fragment),
352 pieceTable->fragmentMap());
353 const auto it = std::lower_bound(cells.constBegin(), cells.constEnd(), helper);
354 if ((it == cells.constEnd()) || (helper < *it))
355 return -1;
356 return it - cells.constBegin();
357}
358
359void QTextTablePrivate::fragmentAdded(QChar type, uint fragment)
360{
361 dirty = true;
362 if (blockFragmentUpdates)
363 return;
364 if (type == QTextBeginningOfFrame) {
365 Q_ASSERT(cells.indexOf(int(fragment)) == -1);
366 const uint pos = pieceTable->fragmentMap().position(node: fragment);
367 QFragmentFindHelper helper(pos, pieceTable->fragmentMap());
368 auto it = std::lower_bound(cells.begin(), cells.end(), helper);
369 cells.insert(before: it, t: fragment);
370 if (!fragment_start || pos < pieceTable->fragmentMap().position(node: fragment_start))
371 fragment_start = fragment;
372 return;
373 }
374 QTextFramePrivate::fragmentAdded(type, fragment);
375}
376
377void QTextTablePrivate::fragmentRemoved(QChar type, uint fragment)
378{
379 dirty = true;
380 if (blockFragmentUpdates)
381 return;
382 if (type == QTextBeginningOfFrame) {
383 Q_ASSERT(cells.indexOf(int(fragment)) != -1);
384 cells.removeAll(t: int(fragment));
385 if (fragment_start == fragment && cells.size()) {
386 fragment_start = cells.at(i: 0);
387 }
388 if (fragment_start != fragment)
389 return;
390 }
391 QTextFramePrivate::fragmentRemoved(type, fragment);
392}
393
394/*!
395 /fn void QTextTablePrivate::update() const
396
397 This function is usually called when the table is "dirty".
398 It seems to update all kind of table information.
399
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(n: nRows * nCols, val: 0);
409
410 QTextDocumentPrivate *p = pieceTable;
411 QTextFormatCollection *c = p->formatCollection();
412
413 cellIndices.resize(size: 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(index: 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(new_size: (r + rowspan) * nCols, x: 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(node: d->fragment_start) >= pos || map.position(node: 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(position: 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(pos: nCols, num: cols - nCols);
618 else if (nCols > cols)
619 removeColumns(pos: cols, num: nCols - cols);
620
621 if (nRows < rows)
622 insertRows(pos: nRows, num: rows-nRows);
623 else if (nRows > rows)
624 removeRows(pos: rows, num: 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(index: it->format);
664 fmt.setTableCellRowSpan(fmt.tableCellRowSpan() + num);
665 p->setCharFormat(pos: it.position(), length: 1, newFormat: 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(index: 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(f: fmt);
685 int bfmt = p->formatCollection()->indexForFormat(f: 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, blockFormat: bfmt, charFormat: cfmt, op: 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
726 ? d->findCellIndex(fragment: 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(fragment: 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(t: cell)) {
749 QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), cell);
750 QTextCharFormat fmt = c->charFormat(index: it->format);
751 fmt.setTableCellColumnSpan(fmt.tableCellColumnSpan() + num);
752 p->setCharFormat(pos: it.position(), length: 1, newFormat: 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(index: 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(f: fmt);
777 int bfmt = p->formatCollection()->indexForFormat(f: QTextBlockFormat());
778 for (int i = 0; i < num; ++i)
779 p->insertBlock(QTextBeginningOfFrame, pos: position, blockFormat: bfmt, charFormat: cfmt, op: 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(i: pos, t: columnWidths.at(i: qMax(a: 0, b: 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(pos: rows(), num: 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(pos: columns(), num: 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(node: d->fragment_start);
847 p->remove(pos, length: p->fragmentMap().position(node: d->fragment_end) - pos + 1);
848 p->endEditBlock();
849 return;
850 }
851
852 p->aboutToRemoveCell(cursorFrom: cellAt(row: pos, col: 0).firstPosition(), cursorEnd: cellAt(row: pos + num - 1, col: 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(t: cell))
859 continue;
860 touchedCells << cell;
861 QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), cell);
862 QTextCharFormat fmt = collection->charFormat(index: it->format);
863 int span = fmt.tableCellRowSpan();
864 if (span > 1) {
865 fmt.setTableCellRowSpan(span - 1);
866 p->setCharFormat(pos: it.position(), length: 1, newFormat: fmt);
867 } else {
868 // remove cell
869 int index = d->cells.indexOf(t: cell) + 1;
870 int f_end = index < d->cells.size() ? d->cells.at(i: index) : d->fragment_end;
871 p->remove(pos: it.position(), length: p->fragmentMap().position(node: 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(node: d->fragment_start);
909 p->remove(pos, length: p->fragmentMap().position(node: d->fragment_end) - pos + 1);
910 p->endEditBlock();
911 return;
912 }
913
914 p->aboutToRemoveCell(cursorFrom: cellAt(row: 0, col: pos).firstPosition(), cursorEnd: cellAt(row: d->nRows - 1, col: 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(index: it->format);
922 int span = fmt.tableCellColumnSpan();
923 if (touchedCells.contains(t: cell) && span <= 1)
924 continue;
925 touchedCells << cell;
926
927 if (span > 1) {
928 fmt.setTableCellColumnSpan(span - 1);
929 p->setCharFormat(pos: it.position(), length: 1, newFormat: fmt);
930 } else {
931 // remove cell
932 int index = d->cells.indexOf(t: cell) + 1;
933 int f_end = index < d->cells.size() ? d->cells.at(i: index) : d->fragment_end;
934 p->remove(pos: it.position(), length: p->fragmentMap().position(node: 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(i: pos, n: 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, col: 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(a: numRows, b: rows() - cell.row());
981 numCols = qMin(a: numCols, b: 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(row: r, col: column) == cellAt(row: r, col: column - 1))
990 return;
991 if (cellAt(row: r, col: column + numCols) == cellAt(row: r, col: column + numCols - 1))
992 return;
993 }
994
995 for (int c = column; c < column + numCols; ++c) {
996 if (cellAt(row, col: c) == cellAt(row: row - 1, col: c))
997 return;
998 if (cellAt(row: row + numRows, col: c) == cellAt(row: row + numRows - 1, col: 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(i: insertCellIndex + 1, defaultValue: d->fragment_end);
1017 uint insertPos = p->fragmentMap().position(node: 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(index: 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, length: 1);
1066
1067 const int nextFragment = d->cells.value(i: cellIndex, defaultValue: d->fragment_end);
1068 const uint nextPos = p->fragmentMap().position(node: 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(d: p, pos: insertPos++).insertBlock();
1077 p->move(from: pos + 1, to: insertPos, length: nextPos - pos);
1078 } else if (rowHasText) {
1079 QTextCursorPrivate::fromPosition(d: p, pos: insertPos++).insertText(text: " "_L1);
1080 p->move(from: pos + 1, to: insertPos, length: nextPos - pos);
1081 } else {
1082 p->move(from: pos, to: insertPos, length: 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(i: firstCellIndex, n: cellIndex - firstCellIndex);
1098 d->cells.erase(begin: d->cells.begin() + firstCellIndex, end: 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(pos: origCellPosition, length: 1, newFormat: 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: &firstRow, numRows: &numRows, firstColumn: &firstColumn, numColumns: &numColumns);
1129 mergeCells(row: firstRow, column: firstColumn, numRows, numCols: 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, col: 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(first: begin, last: d->cellIndices.cend(), val: gridIndex);
1180 int fragment = d->cells.value(i: it - begin, defaultValue: d->fragment_end);
1181 rowPositions[r - row] = p->fragmentMap().position(node: fragment);
1182 }
1183
1184 fmt.setTableCellColumnSpan(1);
1185 fmt.setTableCellRowSpan(1);
1186 const int fmtIndex = c->indexForFormat(f: fmt);
1187 const int blockIndex = p->blockMap().find(k: 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, pos: rowPositions[i] + insertAdjustement + c, blockFormat: blockIndex, charFormat: 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, pos: rowPositions[i] + insertAdjustement + c, blockFormat: blockIndex, charFormat: fmtIndex);
1199 insertAdjustement += colSpan;
1200 }
1201
1202 fmt.setTableCellRowSpan(numRows);
1203 fmt.setTableCellColumnSpan(numCols);
1204 p->setCharFormat(pos: origCellPosition, length: 1, newFormat: 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(d: p, pos: 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(d: p, pos: 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"
1306

source code of qtbase/src/gui/text/qtexttable.cpp