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

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