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