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 \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(propertyId: QTextFormat::ObjectIndex);
91 fmt.setObjectType(QTextFormat::TableCellObject);
92 QTextDocumentPrivate *p = const_cast<QTextDocumentPrivate *>(QTextDocumentPrivate::get(object: table));
93 QTextDocumentPrivate::FragmentIterator frag(&p->fragmentMap(), fragment);
94
95 QTextFormatCollection *c = p->formatCollection();
96 QTextCharFormat oldFormat = c->charFormat(index: frag->format);
97 fmt.setTableCellRowSpan(oldFormat.tableCellRowSpan());
98 fmt.setTableCellColumnSpan(oldFormat.tableCellColumnSpan());
99
100 p->setCharFormat(pos: frag.position(), length: 1, newFormat: fmt, mode: QTextDocumentPrivate::SetFormatAndPreserveObjectIndices);
101}
102
103/*!
104 Returns the cell's character format.
105*/
106QTextCharFormat QTextTableCell::format() const
107{
108 const QTextDocumentPrivate *p = QTextDocumentPrivate::get(object: table);
109 const QTextFormatCollection *c = p->formatCollection();
110
111 QTextCharFormat fmt = c->charFormat(index: 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(object: 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(i: 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(i: 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(d: table->d_func()->pieceTable, pos: 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(d: table->d_func()->pieceTable, pos: 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(object: table);
220 return p->fragmentMap().position(node: 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(object: 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(i: index + 1, defaultValue: td->fragment_end);
236 else
237 f = td->fragment_end;
238 return p->fragmentMap().position(node: 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(object: table);
250 int b = p->blockMap().findNode(k: firstPosition());
251 int e = p->blockMap().findNode(k: 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(object: table);
263 int b = p->blockMap().findNode(k: firstPosition());
264 int e = p->blockMap().findNode(k: 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 *>(object: pieceTable->createObject(newFormat: 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(f: charFmt);
306 int cellIdx = pieceTable->formatCollection()->indexForFormat(f: QTextBlockFormat());
307
308 QTextTablePrivate *d = table->d_func();
309 d->blockFragmentUpdates = true;
310
311 d->fragment_start = pieceTable->insertBlock(QTextBeginningOfFrame, pos, blockFormat: cellIdx, charFormat: charIdx);
312 d->cells.append(t: d->fragment_start);
313 ++pos;
314
315 for (int i = 1; i < rows*cols; ++i) {
316 d->cells.append(t: pieceTable->insertBlock(QTextBeginningOfFrame, pos, blockFormat: cellIdx, charFormat: charIdx));
317// qDebug(" addCell at %d", pos);
318 ++pos;
319 }
320
321 d->fragment_end = pieceTable->insertBlock(QTextEndOfFrame, pos, blockFormat: cellIdx, charFormat: 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
333struct QFragmentFindHelper
334{
335 inline QFragmentFindHelper(int _pos, const QTextDocumentPrivate::FragmentMap &map)
336 : pos(_pos), fragmentMap(map) {}
337 uint pos;
338 const QTextDocumentPrivate::FragmentMap &fragmentMap;
339};
340
341static inline bool operator<(int fragment, const QFragmentFindHelper &helper)
342{
343 return helper.fragmentMap.position(node: fragment) < helper.pos;
344}
345
346static inline bool operator<(const QFragmentFindHelper &helper, int fragment)
347{
348 return helper.pos < helper.fragmentMap.position(node: fragment);
349}
350
351int QTextTablePrivate::findCellIndex(int fragment) const
352{
353 QFragmentFindHelper helper(pieceTable->fragmentMap().position(node: 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(node: fragment);
369 QFragmentFindHelper helper(pos, pieceTable->fragmentMap());
370 auto it = std::lower_bound(cells.begin(), cells.end(), helper);
371 cells.insert(before: it, t: fragment);
372 if (!fragment_start || pos < pieceTable->fragmentMap().position(node: 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(t: int(fragment));
387 if (fragment_start == fragment && cells.size()) {
388 fragment_start = cells.at(i: 0);
389 }
390 if (fragment_start != fragment)
391 return;
392 }
393 QTextFramePrivate::fragmentRemoved(type, fragment);
394}
395
396/*!
397 /fn void QTextTablePrivate::update() const
398
399 This function is usually called when the table is "dirty".
400 It seems to update all kind of table information.
401
402*/
403void QTextTablePrivate::update() const
404{
405 Q_Q(const QTextTable);
406 nCols = q->format().columns();
407 nRows = (cells.size() + nCols-1)/nCols;
408// qDebug(">>>> QTextTablePrivate::update, nRows=%d, nCols=%d", nRows, nCols);
409
410 grid.assign(n: nRows * nCols, val: 0);
411
412 QTextDocumentPrivate *p = pieceTable;
413 QTextFormatCollection *c = p->formatCollection();
414
415 cellIndices.resize(size: cells.size());
416
417 int cell = 0;
418 for (int i = 0; i < cells.size(); ++i) {
419 int fragment = cells.at(i);
420 QTextCharFormat fmt = c->charFormat(index: QTextDocumentPrivate::FragmentIterator(&p->fragmentMap(), fragment)->format);
421 int rowspan = fmt.tableCellRowSpan();
422 int colspan = fmt.tableCellColumnSpan();
423
424 // skip taken cells
425 while (cell < nRows*nCols && grid[cell])
426 ++cell;
427
428 int r = cell/nCols;
429 int c = cell%nCols;
430 cellIndices[i] = cell;
431
432 if (r + rowspan > nRows) {
433 grid.resize(new_size: (r + rowspan) * nCols, x: 0);
434 nRows = r + rowspan;
435 }
436
437 Q_ASSERT(c + colspan <= nCols);
438 for (int ii = 0; ii < rowspan; ++ii) {
439 for (int jj = 0; jj < colspan; ++jj) {
440 Q_ASSERT(grid[(r+ii)*nCols + c+jj] == 0);
441 grid[(r+ii)*nCols + c+jj] = fragment;
442// qDebug(" setting cell %d span=%d/%d at %d/%d", fragment, rowspan, colspan, r+ii, c+jj);
443 }
444 }
445 }
446// qDebug("<<<< end: nRows=%d, nCols=%d", nRows, nCols);
447
448 dirty = false;
449}
450
451
452
453
454
455/*!
456 \class QTextTable
457 \reentrant
458
459 \brief The QTextTable class represents a table in a QTextDocument.
460 \inmodule QtGui
461
462 \ingroup richtext-processing
463
464 A table is a group of cells ordered into rows and columns. Each table
465 contains at least one row and one column. Each cell contains a block, and
466 is surrounded by a frame.
467
468 Tables are usually created and inserted into a document with the
469 QTextCursor::insertTable() function.
470 For example, we can insert a table with three rows and two columns at the
471 current cursor position in an editor using the following lines of code:
472
473 \snippet textdocument-tables/mainwindow.cpp 1
474 \codeline
475 \snippet textdocument-tables/mainwindow.cpp 3
476
477 The table format is either defined when the table is created or changed
478 later with setFormat().
479
480 The table currently being edited by the cursor is found with
481 QTextCursor::currentTable(). This allows its format or dimensions to be
482 changed after it has been inserted into a document.
483
484 A table's size can be changed with resize(), or by using
485 insertRows(), insertColumns(), removeRows(), or removeColumns().
486 Use cellAt() to retrieve table cells.
487
488 The starting and ending positions of table rows can be found by moving
489 a cursor within a table, and using the rowStart() and rowEnd() functions
490 to obtain cursors at the start and end of each row.
491
492 Rows and columns within a QTextTable can be merged and split using
493 the mergeCells() and splitCell() functions. However, only cells that span multiple
494 rows or columns can be split. (Merging or splitting does not increase or decrease
495 the number of rows and columns.)
496
497 Note that if you have merged multiple columns and rows into one cell, you will not
498 be able to split the merged cell into new cells spanning over more than one row
499 or column. To be able to split cells spanning over several rows and columns you
500 need to do this over several iterations.
501
502 \table 80%
503 \row
504 \li \inlineimage texttable-split.png Original Table
505 \li Suppose we have a 2x3 table of names and addresses. To merge both
506 columns in the first row we invoke mergeCells() with \a row = 0,
507 \a column = 0, \a numRows = 1 and \a numColumns = 2.
508 \snippet textdocument-texttable/main.cpp 0
509
510 \row
511 \li \inlineimage texttable-merge.png
512 \li This gives us the following table. To split the first row of the table
513 back into two cells, we invoke the splitCell() function with \a numRows
514 and \a numCols = 1.
515 \snippet textdocument-texttable/main.cpp 1
516
517 \row
518 \li \inlineimage texttable-split.png Split Table
519 \li This results in the original table.
520 \endtable
521
522 \sa QTextTableFormat
523*/
524
525/*! \internal
526 */
527QTextTable::QTextTable(QTextDocument *doc)
528 : QTextFrame(*new QTextTablePrivate(doc), doc)
529{
530}
531
532/*! \internal
533
534Destroys the table.
535 */
536QTextTable::~QTextTable()
537{
538}
539
540
541/*!
542 \fn QTextTableCell QTextTable::cellAt(int row, int column) const
543
544 Returns the table cell at the given \a row and \a column in the table.
545
546 \sa columns(), rows()
547*/
548QTextTableCell QTextTable::cellAt(int row, int col) const
549{
550 Q_D(const QTextTable);
551 if (d->dirty)
552 d->update();
553
554 if (row < 0 || row >= d->nRows || col < 0 || col >= d->nCols)
555 return QTextTableCell();
556
557 return QTextTableCell(this, d->grid[row*d->nCols + col]);
558}
559
560/*!
561 \overload
562
563 Returns the table cell that contains the character at the given \a position
564 in the document.
565*/
566QTextTableCell QTextTable::cellAt(int position) const
567{
568 Q_D(const QTextTable);
569 if (d->dirty)
570 d->update();
571
572 uint pos = (uint)position;
573 const QTextDocumentPrivate::FragmentMap &map = d->pieceTable->fragmentMap();
574 if (position < 0 || map.position(node: d->fragment_start) >= pos || map.position(node: d->fragment_end) < pos)
575 return QTextTableCell();
576
577 QFragmentFindHelper helper(position, map);
578 auto it = std::lower_bound(d->cells.begin(), d->cells.end(), helper);
579 if (it != d->cells.begin())
580 --it;
581
582 return QTextTableCell(this, *it);
583}
584
585/*!
586 \fn QTextTableCell QTextTable::cellAt(const QTextCursor &cursor) const
587
588 \overload
589
590 Returns the table cell containing the given \a cursor.
591*/
592QTextTableCell QTextTable::cellAt(const QTextCursor &c) const
593{
594 return cellAt(position: c.position());
595}
596
597/*!
598 \fn void QTextTable::resize(int rows, int columns)
599
600 Resizes the table to contain the required number of \a rows and \a columns.
601
602 \sa insertRows(), insertColumns(), removeRows(), removeColumns()
603*/
604void QTextTable::resize(int rows, int cols)
605{
606 Q_D(QTextTable);
607 if (d->dirty)
608 d->update();
609
610 int nRows = this->rows();
611 int nCols = this->columns();
612
613 if (rows == nRows && cols == nCols)
614 return;
615
616 d->pieceTable->beginEditBlock();
617
618 if (nCols < cols)
619 insertColumns(pos: nCols, num: cols - nCols);
620 else if (nCols > cols)
621 removeColumns(pos: cols, num: nCols - cols);
622
623 if (nRows < rows)
624 insertRows(pos: nRows, num: rows-nRows);
625 else if (nRows > rows)
626 removeRows(pos: rows, num: nRows-rows);
627
628 d->pieceTable->endEditBlock();
629}
630
631/*!
632 \fn void QTextTable::insertRows(int index, int rows)
633
634 Inserts a number of \a rows before the row with the specified \a index.
635
636 \sa resize(), insertColumns(), removeRows(), removeColumns(), appendRows(), appendColumns()
637*/
638void QTextTable::insertRows(int pos, int num)
639{
640 Q_D(QTextTable);
641 if (num <= 0)
642 return;
643
644 if (d->dirty)
645 d->update();
646
647 if (pos > d->nRows || pos < 0)
648 pos = d->nRows;
649
650// qDebug() << "-------- insertRows" << pos << num;
651 QTextDocumentPrivate *p = d->pieceTable;
652 QTextFormatCollection *c = p->formatCollection();
653 p->beginEditBlock();
654
655 int extended = 0;
656 int insert_before = 0;
657 if (pos > 0 && pos < d->nRows) {
658 int lastCell = -1;
659 for (int i = 0; i < d->nCols; ++i) {
660 int cell = d->grid[pos*d->nCols + i];
661 if (cell == d->grid[(pos-1)*d->nCols+i]) {
662 // cell spans the insertion place, extend it
663 if (cell != lastCell) {
664 QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), cell);
665 QTextCharFormat fmt = c->charFormat(index: it->format);
666 fmt.setTableCellRowSpan(fmt.tableCellRowSpan() + num);
667 p->setCharFormat(pos: it.position(), length: 1, newFormat: fmt);
668 }
669 extended++;
670 } else if (!insert_before) {
671 insert_before = cell;
672 }
673 lastCell = cell;
674 }
675 } else {
676 insert_before = (pos == 0 ? d->grid[0] : d->fragment_end);
677 }
678 if (extended < d->nCols) {
679 Q_ASSERT(insert_before);
680 QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), insert_before);
681 QTextCharFormat fmt = c->charFormat(index: it->format);
682 fmt.setTableCellRowSpan(1);
683 fmt.setTableCellColumnSpan(1);
684 Q_ASSERT(fmt.objectIndex() == objectIndex());
685 int pos = it.position();
686 int cfmt = p->formatCollection()->indexForFormat(f: fmt);
687 int bfmt = p->formatCollection()->indexForFormat(f: QTextBlockFormat());
688// qDebug("inserting %d cells, nCols=%d extended=%d", num*(d->nCols-extended), d->nCols, extended);
689 for (int i = 0; i < num*(d->nCols-extended); ++i)
690 p->insertBlock(QTextBeginningOfFrame, pos, blockFormat: bfmt, charFormat: cfmt, op: QTextUndoCommand::MoveCursor);
691 }
692
693// qDebug() << "-------- end insertRows" << pos << num;
694 p->endEditBlock();
695}
696
697/*!
698 \fn void QTextTable::insertColumns(int index, int columns)
699
700 Inserts a number of \a columns before the column with the specified \a index.
701
702 \sa insertRows(), resize(), removeRows(), removeColumns(), appendRows(), appendColumns()
703*/
704void QTextTable::insertColumns(int pos, int num)
705{
706 Q_D(QTextTable);
707 if (num <= 0)
708 return;
709
710 if (d->dirty)
711 d->update();
712
713 if (pos > d->nCols || pos < 0)
714 pos = d->nCols;
715
716// qDebug() << "-------- insertCols" << pos << num;
717 QTextDocumentPrivate *p = d->pieceTable;
718 QTextFormatCollection *c = p->formatCollection();
719 p->beginEditBlock();
720
721 QList<int> extendedSpans;
722 for (int i = 0; i < d->nRows; ++i) {
723 int cell;
724 if (i == d->nRows - 1 && pos == d->nCols) {
725 cell = d->fragment_end;
726 } else {
727 int logicalGridIndexBeforePosition = pos > 0
728 ? d->findCellIndex(fragment: d->grid[i*d->nCols + pos - 1])
729 : -1;
730
731 // Search for the logical insertion point by skipping past cells which are not the first
732 // cell in a rowspan. This means any cell for which the logical grid index is
733 // less than the logical cell index of the cell before the insertion.
734 int logicalGridIndex;
735 int gridArrayOffset = i*d->nCols + pos;
736 do {
737 cell = d->grid[gridArrayOffset];
738 logicalGridIndex = d->findCellIndex(fragment: cell);
739 gridArrayOffset++;
740 } while (logicalGridIndex < logicalGridIndexBeforePosition
741 && gridArrayOffset < d->nRows*d->nCols);
742
743 if (logicalGridIndex < logicalGridIndexBeforePosition
744 && gridArrayOffset == d->nRows*d->nCols)
745 cell = d->fragment_end;
746 }
747
748 if (pos > 0 && pos < d->nCols && cell == d->grid[i*d->nCols + pos - 1]) {
749 // cell spans the insertion place, extend it
750 if (!extendedSpans.contains(t: cell)) {
751 QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), cell);
752 QTextCharFormat fmt = c->charFormat(index: it->format);
753 fmt.setTableCellColumnSpan(fmt.tableCellColumnSpan() + num);
754 p->setCharFormat(pos: it.position(), length: 1, newFormat: fmt);
755 d->dirty = true;
756 extendedSpans << cell;
757 }
758 } else {
759 /* If the next cell is spanned from the row above, we need to find the right position
760 to insert to */
761 if (i > 0 && pos < d->nCols && cell == d->grid[(i-1) * d->nCols + pos]) {
762 int gridIndex = i*d->nCols + pos;
763 const int gridEnd = d->nRows * d->nCols - 1;
764 while (gridIndex < gridEnd && cell == d->grid[gridIndex]) {
765 ++gridIndex;
766 }
767 if (gridIndex == gridEnd)
768 cell = d->fragment_end;
769 else
770 cell = d->grid[gridIndex];
771 }
772 QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), cell);
773 QTextCharFormat fmt = c->charFormat(index: it->format);
774 fmt.setTableCellRowSpan(1);
775 fmt.setTableCellColumnSpan(1);
776 Q_ASSERT(fmt.objectIndex() == objectIndex());
777 int position = it.position();
778 int cfmt = p->formatCollection()->indexForFormat(f: fmt);
779 int bfmt = p->formatCollection()->indexForFormat(f: QTextBlockFormat());
780 for (int i = 0; i < num; ++i)
781 p->insertBlock(QTextBeginningOfFrame, pos: position, blockFormat: bfmt, charFormat: cfmt, op: QTextUndoCommand::MoveCursor);
782 }
783 }
784
785 QTextTableFormat tfmt = format();
786 tfmt.setColumns(tfmt.columns()+num);
787 QList<QTextLength> columnWidths = tfmt.columnWidthConstraints();
788 if (! columnWidths.isEmpty()) {
789 for (int i = num; i > 0; --i)
790 columnWidths.insert(i: pos, t: columnWidths.at(i: qMax(a: 0, b: pos - 1)));
791 }
792 tfmt.setColumnWidthConstraints (columnWidths);
793 QTextObject::setFormat(tfmt);
794
795// qDebug() << "-------- end insertCols" << pos << num;
796 p->endEditBlock();
797}
798
799/*!
800 \since 4.5
801 Appends \a count rows at the bottom of the table.
802
803 \sa insertColumns(), insertRows(), resize(), removeRows(), removeColumns(), appendColumns()
804*/
805void QTextTable::appendRows(int count)
806{
807 insertRows(pos: rows(), num: count);
808}
809
810/*!
811 \since 4.5
812 Appends \a count columns at the right side of the table.
813
814 \sa insertColumns(), insertRows(), resize(), removeRows(), removeColumns(), appendRows()
815*/
816void QTextTable::appendColumns(int count)
817{
818 insertColumns(pos: columns(), num: count);
819}
820
821/*!
822 \fn void QTextTable::removeRows(int index, int rows)
823
824 Removes a number of \a rows starting with the row at the specified \a index.
825
826 \sa insertRows(), insertColumns(), resize(), removeColumns(), appendRows(), appendColumns()
827*/
828void QTextTable::removeRows(int pos, int num)
829{
830 Q_D(QTextTable);
831// qDebug() << "-------- removeRows" << pos << num;
832
833 if (num <= 0 || pos < 0)
834 return;
835 if (d->dirty)
836 d->update();
837 if (pos >= d->nRows)
838 return;
839 if (pos+num > d->nRows)
840 num = d->nRows - pos;
841
842 QTextDocumentPrivate *p = d->pieceTable;
843 QTextFormatCollection *collection = p->formatCollection();
844 p->beginEditBlock();
845
846 // delete whole table?
847 if (pos == 0 && num == d->nRows) {
848 const int pos = p->fragmentMap().position(node: d->fragment_start);
849 p->remove(pos, length: p->fragmentMap().position(node: d->fragment_end) - pos + 1);
850 p->endEditBlock();
851 return;
852 }
853
854 p->aboutToRemoveCell(cursorFrom: cellAt(row: pos, col: 0).firstPosition(), cursorEnd: cellAt(row: pos + num - 1, col: d->nCols - 1).lastPosition());
855
856 QList<int> touchedCells;
857 for (int r = pos; r < pos + num; ++r) {
858 for (int c = 0; c < d->nCols; ++c) {
859 int cell = d->grid[r*d->nCols + c];
860 if (touchedCells.contains(t: cell))
861 continue;
862 touchedCells << cell;
863 QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), cell);
864 QTextCharFormat fmt = collection->charFormat(index: it->format);
865 int span = fmt.tableCellRowSpan();
866 if (span > 1) {
867 fmt.setTableCellRowSpan(span - 1);
868 p->setCharFormat(pos: it.position(), length: 1, newFormat: fmt);
869 } else {
870 // remove cell
871 int index = d->cells.indexOf(t: cell) + 1;
872 int f_end = index < d->cells.size() ? d->cells.at(i: index) : d->fragment_end;
873 p->remove(pos: it.position(), length: p->fragmentMap().position(node: f_end) - it.position());
874 }
875 }
876 }
877
878 p->endEditBlock();
879// qDebug() << "-------- end removeRows" << pos << num;
880}
881
882/*!
883 \fn void QTextTable::removeColumns(int index, int columns)
884
885 Removes a number of \a columns starting with the column at the specified
886 \a index.
887
888 \sa insertRows(), insertColumns(), removeRows(), resize(), appendRows(), appendColumns()
889*/
890void QTextTable::removeColumns(int pos, int num)
891{
892 Q_D(QTextTable);
893// qDebug() << "-------- removeCols" << pos << num;
894
895 if (num <= 0 || pos < 0)
896 return;
897 if (d->dirty)
898 d->update();
899 if (pos >= d->nCols)
900 return;
901 if (pos + num > d->nCols)
902 pos = d->nCols - num;
903
904 QTextDocumentPrivate *p = d->pieceTable;
905 QTextFormatCollection *collection = p->formatCollection();
906 p->beginEditBlock();
907
908 // delete whole table?
909 if (pos == 0 && num == d->nCols) {
910 const int pos = p->fragmentMap().position(node: d->fragment_start);
911 p->remove(pos, length: p->fragmentMap().position(node: d->fragment_end) - pos + 1);
912 p->endEditBlock();
913 return;
914 }
915
916 p->aboutToRemoveCell(cursorFrom: cellAt(row: 0, col: pos).firstPosition(), cursorEnd: cellAt(row: d->nRows - 1, col: pos + num - 1).lastPosition());
917
918 QList<int> touchedCells;
919 for (int r = 0; r < d->nRows; ++r) {
920 for (int c = pos; c < pos + num; ++c) {
921 int cell = d->grid[r*d->nCols + c];
922 QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), cell);
923 QTextCharFormat fmt = collection->charFormat(index: it->format);
924 int span = fmt.tableCellColumnSpan();
925 if (touchedCells.contains(t: cell) && span <= 1)
926 continue;
927 touchedCells << cell;
928
929 if (span > 1) {
930 fmt.setTableCellColumnSpan(span - 1);
931 p->setCharFormat(pos: it.position(), length: 1, newFormat: fmt);
932 } else {
933 // remove cell
934 int index = d->cells.indexOf(t: cell) + 1;
935 int f_end = index < d->cells.size() ? d->cells.at(i: index) : d->fragment_end;
936 p->remove(pos: it.position(), length: p->fragmentMap().position(node: f_end) - it.position());
937 }
938 }
939 }
940
941 QTextTableFormat tfmt = format();
942 tfmt.setColumns(tfmt.columns()-num);
943 QList<QTextLength> columnWidths = tfmt.columnWidthConstraints();
944 if (columnWidths.size() > pos) {
945 columnWidths.remove(i: pos, n: num);
946 tfmt.setColumnWidthConstraints (columnWidths);
947 }
948 QTextObject::setFormat(tfmt);
949
950 p->endEditBlock();
951// qDebug() << "-------- end removeCols" << pos << num;
952}
953
954/*!
955 \since 4.1
956
957 Merges the cell at the specified \a row and \a column with the adjacent cells
958 into one cell. The new cell will span \a numRows rows and \a numCols columns.
959 This method does nothing if \a numRows or \a numCols is less than the current
960 number of rows or columns spanned by the cell.
961
962 \sa splitCell()
963*/
964void QTextTable::mergeCells(int row, int column, int numRows, int numCols)
965{
966 Q_D(QTextTable);
967
968 if (d->dirty)
969 d->update();
970
971 QTextDocumentPrivate *p = d->pieceTable;
972 QTextFormatCollection *fc = p->formatCollection();
973
974 const QTextTableCell cell = cellAt(row, col: column);
975 if (!cell.isValid() || row != cell.row() || column != cell.column())
976 return;
977
978 QTextCharFormat fmt = cell.format();
979 const int rowSpan = fmt.tableCellRowSpan();
980 const int colSpan = fmt.tableCellColumnSpan();
981
982 numRows = qMin(a: numRows, b: rows() - cell.row());
983 numCols = qMin(a: numCols, b: columns() - cell.column());
984
985 // nothing to merge?
986 if (numRows < rowSpan || numCols < colSpan)
987 return;
988
989 // check the edges of the merge rect to make sure no cell spans the edge
990 for (int r = row; r < row + numRows; ++r) {
991 if (cellAt(row: r, col: column) == cellAt(row: r, col: column - 1))
992 return;
993 if (cellAt(row: r, col: column + numCols) == cellAt(row: r, col: column + numCols - 1))
994 return;
995 }
996
997 for (int c = column; c < column + numCols; ++c) {
998 if (cellAt(row, col: c) == cellAt(row: row - 1, col: c))
999 return;
1000 if (cellAt(row: row + numRows, col: c) == cellAt(row: row + numRows - 1, col: c))
1001 return;
1002 }
1003
1004 p->beginEditBlock();
1005
1006 const int origCellPosition = cell.firstPosition() - 1;
1007
1008 const int cellFragment = d->grid[row * d->nCols + column];
1009
1010 // find the position at which to insert the contents of the merged cells
1011 QFragmentFindHelper helper(origCellPosition, p->fragmentMap());
1012 const auto begin = d->cells.cbegin();
1013 const auto it = std::lower_bound(begin, d->cells.cend(), helper);
1014 Q_ASSERT(it != d->cells.cend());
1015 Q_ASSERT(!(helper < *it));
1016 Q_ASSERT(*it == cellFragment);
1017 const int insertCellIndex = it - begin;
1018 int insertFragment = d->cells.value(i: insertCellIndex + 1, defaultValue: d->fragment_end);
1019 uint insertPos = p->fragmentMap().position(node: insertFragment);
1020
1021 d->blockFragmentUpdates = true;
1022
1023 bool rowHasText = cell.firstCursorPosition().block().length();
1024 bool needsParagraph = rowHasText && colSpan == numCols;
1025
1026 // find all cells that will be erased by the merge
1027 for (int r = row; r < row + numRows; ++r) {
1028 int firstColumn = r < row + rowSpan ? column + colSpan : column;
1029
1030 // don't recompute the cell index for the first row
1031 int firstCellIndex = r == row ? insertCellIndex + 1 : -1;
1032 int cellIndex = firstCellIndex;
1033
1034 for (int c = firstColumn; c < column + numCols; ++c) {
1035 const int fragment = d->grid[r * d->nCols + c];
1036
1037 // already handled?
1038 if (fragment == cellFragment)
1039 continue;
1040
1041 QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), fragment);
1042 uint pos = it.position();
1043
1044 if (firstCellIndex == -1) {
1045 QFragmentFindHelper helper(pos, p->fragmentMap());
1046 const auto begin = d->cells.cbegin();
1047 const auto it = std::lower_bound(begin, d->cells.cend(), helper);
1048 Q_ASSERT(it != d->cells.cend());
1049 Q_ASSERT(!(helper < *it));
1050 Q_ASSERT(*it == fragment);
1051 firstCellIndex = cellIndex = it - begin;
1052 }
1053
1054 ++cellIndex;
1055
1056 QTextCharFormat fmt = fc->charFormat(index: it->format);
1057
1058 const int cellRowSpan = fmt.tableCellRowSpan();
1059 const int cellColSpan = fmt.tableCellColumnSpan();
1060
1061 // update the grid for this cell
1062 for (int i = r; i < r + cellRowSpan; ++i)
1063 for (int j = c; j < c + cellColSpan; ++j)
1064 d->grid[i * d->nCols + j] = cellFragment;
1065
1066 // erase the cell marker
1067 p->remove(pos, length: 1);
1068
1069 const int nextFragment = d->cells.value(i: cellIndex, defaultValue: d->fragment_end);
1070 const uint nextPos = p->fragmentMap().position(node: nextFragment);
1071
1072 Q_ASSERT(nextPos >= pos);
1073
1074 // merge the contents of the cell (if not empty)
1075 if (nextPos > pos) {
1076 if (needsParagraph) {
1077 needsParagraph = false;
1078 QTextCursorPrivate::fromPosition(d: p, pos: insertPos++).insertBlock();
1079 p->move(from: pos + 1, to: insertPos, length: nextPos - pos);
1080 } else if (rowHasText) {
1081 QTextCursorPrivate::fromPosition(d: p, pos: insertPos++).insertText(text: " "_L1);
1082 p->move(from: pos + 1, to: insertPos, length: nextPos - pos);
1083 } else {
1084 p->move(from: pos, to: insertPos, length: nextPos - pos);
1085 }
1086
1087 insertPos += nextPos - pos;
1088 rowHasText = true;
1089 }
1090 }
1091
1092 if (rowHasText) {
1093 needsParagraph = true;
1094 rowHasText = false;
1095 }
1096
1097 // erase cells from last row
1098 if (firstCellIndex >= 0) {
1099 d->cellIndices.remove(i: firstCellIndex, n: cellIndex - firstCellIndex);
1100 d->cells.erase(begin: d->cells.begin() + firstCellIndex, end: d->cells.begin() + cellIndex);
1101 }
1102 }
1103
1104 d->fragment_start = d->cells.constFirst();
1105
1106 fmt.setTableCellRowSpan(numRows);
1107 fmt.setTableCellColumnSpan(numCols);
1108 p->setCharFormat(pos: origCellPosition, length: 1, newFormat: fmt);
1109
1110 d->blockFragmentUpdates = false;
1111 d->dirty = false;
1112
1113 p->endEditBlock();
1114}
1115
1116/*!
1117 \overload
1118 \since 4.1
1119
1120 Merges the cells selected by the provided \a cursor.
1121
1122 \sa splitCell()
1123*/
1124void QTextTable::mergeCells(const QTextCursor &cursor)
1125{
1126 if (!cursor.hasComplexSelection())
1127 return;
1128
1129 int firstRow, numRows, firstColumn, numColumns;
1130 cursor.selectedTableCells(firstRow: &firstRow, numRows: &numRows, firstColumn: &firstColumn, numColumns: &numColumns);
1131 mergeCells(row: firstRow, column: firstColumn, numRows, numCols: numColumns);
1132}
1133
1134/*!
1135 \since 4.1
1136
1137 Splits the specified cell at \a row and \a column into an array of multiple
1138 cells with dimensions specified by \a numRows and \a numCols.
1139
1140 \note It is only possible to split cells that span multiple rows or columns, such as rows
1141 that have been merged using mergeCells().
1142
1143 \sa mergeCells()
1144*/
1145void QTextTable::splitCell(int row, int column, int numRows, int numCols)
1146{
1147 Q_D(QTextTable);
1148
1149 if (d->dirty)
1150 d->update();
1151
1152 QTextDocumentPrivate *p = d->pieceTable;
1153 QTextFormatCollection *c = p->formatCollection();
1154
1155 const QTextTableCell cell = cellAt(row, col: column);
1156 if (!cell.isValid())
1157 return;
1158 row = cell.row();
1159 column = cell.column();
1160
1161 QTextCharFormat fmt = cell.format();
1162 const int rowSpan = fmt.tableCellRowSpan();
1163 const int colSpan = fmt.tableCellColumnSpan();
1164
1165 // nothing to split?
1166 if (numRows > rowSpan || numCols > colSpan)
1167 return;
1168
1169 p->beginEditBlock();
1170
1171 const int origCellPosition = cell.firstPosition() - 1;
1172
1173 QVarLengthArray<int> rowPositions(rowSpan);
1174
1175 rowPositions[0] = cell.lastPosition();
1176
1177 for (int r = row + 1; r < row + rowSpan; ++r) {
1178 // find the cell before which to insert the new cell markers
1179 int gridIndex = r * d->nCols + column;
1180 const auto begin = d->cellIndices.cbegin();
1181 const auto it = std::upper_bound(first: begin, last: d->cellIndices.cend(), val: gridIndex);
1182 int fragment = d->cells.value(i: it - begin, defaultValue: d->fragment_end);
1183 rowPositions[r - row] = p->fragmentMap().position(node: fragment);
1184 }
1185
1186 fmt.setTableCellColumnSpan(1);
1187 fmt.setTableCellRowSpan(1);
1188 const int fmtIndex = c->indexForFormat(f: fmt);
1189 const int blockIndex = p->blockMap().find(k: cell.lastPosition())->format;
1190
1191 int insertAdjustement = 0;
1192 for (int i = 0; i < numRows; ++i) {
1193 for (int c = 0; c < colSpan - numCols; ++c)
1194 p->insertBlock(QTextBeginningOfFrame, pos: rowPositions[i] + insertAdjustement + c, blockFormat: blockIndex, charFormat: fmtIndex);
1195 insertAdjustement += colSpan - numCols;
1196 }
1197
1198 for (int i = numRows; i < rowSpan; ++i) {
1199 for (int c = 0; c < colSpan; ++c)
1200 p->insertBlock(QTextBeginningOfFrame, pos: rowPositions[i] + insertAdjustement + c, blockFormat: blockIndex, charFormat: fmtIndex);
1201 insertAdjustement += colSpan;
1202 }
1203
1204 fmt.setTableCellRowSpan(numRows);
1205 fmt.setTableCellColumnSpan(numCols);
1206 p->setCharFormat(pos: origCellPosition, length: 1, newFormat: fmt);
1207
1208 p->endEditBlock();
1209}
1210
1211/*!
1212 Returns the number of rows in the table.
1213
1214 \sa columns()
1215*/
1216int QTextTable::rows() const
1217{
1218 Q_D(const QTextTable);
1219 if (d->dirty)
1220 d->update();
1221
1222 return d->nRows;
1223}
1224
1225/*!
1226 Returns the number of columns in the table.
1227
1228 \sa rows()
1229*/
1230int QTextTable::columns() const
1231{
1232 Q_D(const QTextTable);
1233 if (d->dirty)
1234 d->update();
1235
1236 return d->nCols;
1237}
1238
1239/*!
1240 \fn QTextCursor QTextTable::rowStart(const QTextCursor &cursor) const
1241
1242 Returns a cursor pointing to the start of the row that contains the
1243 given \a cursor.
1244
1245 \sa rowEnd()
1246*/
1247QTextCursor QTextTable::rowStart(const QTextCursor &c) const
1248{
1249 Q_D(const QTextTable);
1250 QTextTableCell cell = cellAt(c);
1251 if (!cell.isValid())
1252 return QTextCursor();
1253
1254 int row = cell.row();
1255 QTextDocumentPrivate *p = d->pieceTable;
1256 QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), d->grid[row*d->nCols]);
1257 return QTextCursorPrivate::fromPosition(d: p, pos: it.position());
1258}
1259
1260/*!
1261 \fn QTextCursor QTextTable::rowEnd(const QTextCursor &cursor) const
1262
1263 Returns a cursor pointing to the end of the row that contains the given
1264 \a cursor.
1265
1266 \sa rowStart()
1267*/
1268QTextCursor QTextTable::rowEnd(const QTextCursor &c) const
1269{
1270 Q_D(const QTextTable);
1271 QTextTableCell cell = cellAt(c);
1272 if (!cell.isValid())
1273 return QTextCursor();
1274
1275 int row = cell.row() + 1;
1276 int fragment = row < d->nRows ? d->grid[row*d->nCols] : d->fragment_end;
1277 QTextDocumentPrivate *p = d->pieceTable;
1278 QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), fragment);
1279 return QTextCursorPrivate::fromPosition(d: p, pos: it.position() - 1);
1280}
1281
1282/*!
1283 \fn void QTextTable::setFormat(const QTextTableFormat &format)
1284
1285 Sets the table's \a format.
1286
1287 \sa format()
1288*/
1289void QTextTable::setFormat(const QTextTableFormat &format)
1290{
1291 QTextTableFormat fmt = format;
1292 // don't try to change the number of table columns from here
1293 fmt.setColumns(columns());
1294 QTextObject::setFormat(fmt);
1295}
1296
1297/*!
1298 \fn QTextTableFormat QTextTable::format() const
1299
1300 Returns the table's format.
1301
1302 \sa setFormat()
1303*/
1304
1305QT_END_NAMESPACE
1306
1307#include "moc_qtexttable.cpp"
1308

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