source: trunk/src/gui/text/qtexttable.cpp@ 881

Last change on this file since 881 was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

File size: 39.8 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation (qt-info@nokia.com)
6**
7** This file is part of the QtGui module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at qt-info@nokia.com.
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qtexttable.h"
43#include "qtextcursor.h"
44#include "qtextformat.h"
45#include <qdebug.h>
46#include "qtexttable_p.h"
47#include "qvarlengtharray.h"
48#include "private/qfunctions_p.h"
49
50#include <stdlib.h>
51
52QT_BEGIN_NAMESPACE
53
54/*!
55 \class QTextTableCell
56 \reentrant
57
58 \brief The QTextTableCell class represents the properties of a
59 cell in a QTextTable.
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 = table->docHandle();
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 QTextDocumentPrivate *p = table->docHandle();
141 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 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(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 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 QTextCursor(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 QTextCursor(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 QTextDocumentPrivate *p = table->docHandle();
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 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(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 QTextDocumentPrivate *p = table->docHandle();
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 QTextDocumentPrivate *p = table->docHandle();
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 true if this cell object and the \a other cell object
305 describe the same cell; otherwise returns false.
306*/
307
308/*!
309 \fn QTextCursor QTextTableCell::operator!=(const QTextTableCell &other) const
310
311 Returns true if this cell object and the \a other cell object
312 describe different cells; otherwise returns 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
380Q_STATIC_GLOBAL_INLINE_OPERATOR bool operator<(int fragment, const QFragmentFindHelper &helper)
381{
382 return helper.fragmentMap.position(fragment) < helper.pos;
383}
384
385Q_STATIC_GLOBAL_INLINE_OPERATOR 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 QList<int>::ConstIterator it = qBinaryFind(cells.begin(), cells.end(), helper);
395 if (it == cells.end())
396 return -1;
397 return it - cells.begin();
398}
399
400void QTextTablePrivate::fragmentAdded(const QChar &type, uint fragment)
401{
402 dirty = true;
403 if (blockFragmentUpdates)
404 return;
405 if (type == QTextBeginningOfFrame) {
406 Q_ASSERT(cells.indexOf(fragment) == -1);
407 const uint pos = pieceTable->fragmentMap().position(fragment);
408 QFragmentFindHelper helper(pos, pieceTable->fragmentMap());
409 QList<int>::Iterator it = qLowerBound(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(const QChar &type, uint fragment)
419{
420 dirty = true;
421 if (blockFragmentUpdates)
422 return;
423 if (type == QTextBeginningOfFrame) {
424 Q_ASSERT(cells.indexOf(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
502 \ingroup richtext-processing
503
504 A table is a group of cells ordered into rows and columns. Each table
505 contains at least one row and one column. Each cell contains a block, and
506 is surrounded by a frame.
507
508 Tables are usually created and inserted into a document with the
509 QTextCursor::insertTable() function.
510 For example, we can insert a table with three rows and two columns at the
511 current cursor position in an editor using the following lines of code:
512
513 \snippet doc/src/snippets/textdocument-tables/mainwindow.cpp 1
514 \codeline
515 \snippet doc/src/snippets/textdocument-tables/mainwindow.cpp 3
516
517 The table format is either defined when the table is created or changed
518 later with setFormat().
519
520 The table currently being edited by the cursor is found with
521 QTextCursor::currentTable(). This allows its format or dimensions to be
522 changed after it has been inserted into a document.
523
524 A table's size can be changed with resize(), or by using
525 insertRows(), insertColumns(), removeRows(), or removeColumns().
526 Use cellAt() to retrieve table cells.
527
528 The starting and ending positions of table rows can be found by moving
529 a cursor within a table, and using the rowStart() and rowEnd() functions
530 to obtain cursors at the start and end of each row.
531
532 Rows and columns within a QTextTable can be merged and split using
533 the mergeCells() and splitCell() functions. However, only cells that span multiple
534 rows or columns can be split. (Merging or splitting does not increase or decrease
535 the number of rows and columns.)
536
537 Note that if you have merged multiple columns and rows into one cell, you will not
538 be able to split the merged cell into new cells spanning over more than one row
539 or column. To be able to split cells spanning over several rows and columns you
540 need to do this over several iterations.
541
542 \table 80%
543 \row
544 \o \inlineimage texttable-split.png Original Table
545 \o Suppose we have a 2x3 table of names and addresses. To merge both
546 columns in the first row we invoke mergeCells() with \a row = 0,
547 \a column = 0, \a numRows = 1 and \a numColumns = 2.
548 \snippet doc/src/snippets/textdocument-texttable/main.cpp 0
549
550 \row
551 \o \inlineimage texttable-merge.png
552 \o This gives us the following table. To split the first row of the table
553 back into two cells, we invoke the splitCell() function with \a numRows
554 and \a numCols = 1.
555 \snippet doc/src/snippets/textdocument-texttable/main.cpp 1
556
557 \row
558 \o \inlineimage texttable-split.png Split Table
559 \o This results in the original table.
560 \endtable
561
562 \sa QTextTableFormat
563*/
564
565/*! \internal
566 */
567QTextTable::QTextTable(QTextDocument *doc)
568 : QTextFrame(*new QTextTablePrivate(doc), doc)
569{
570}
571
572/*! \internal
573
574Destroys the table.
575 */
576QTextTable::~QTextTable()
577{
578}
579
580
581/*!
582 \fn QTextTableCell QTextTable::cellAt(int row, int column) const
583
584 Returns the table cell at the given \a row and \a column in the table.
585
586 \sa columns() rows()
587*/
588QTextTableCell QTextTable::cellAt(int row, int col) const
589{
590 Q_D(const QTextTable);
591 if (d->dirty)
592 d->update();
593
594 if (row < 0 || row >= d->nRows || col < 0 || col >= d->nCols)
595 return QTextTableCell();
596
597 return QTextTableCell(this, d->grid[row*d->nCols + col]);
598}
599
600/*!
601 \overload
602
603 Returns the table cell that contains the character at the given \a position
604 in the document.
605*/
606QTextTableCell QTextTable::cellAt(int position) const
607{
608 Q_D(const QTextTable);
609 if (d->dirty)
610 d->update();
611
612 uint pos = (uint)position;
613 const QTextDocumentPrivate::FragmentMap &map = d->pieceTable->fragmentMap();
614 if (position < 0 || map.position(d->fragment_start) >= pos || map.position(d->fragment_end) < pos)
615 return QTextTableCell();
616
617 QFragmentFindHelper helper(position, map);
618 QList<int>::ConstIterator it = qLowerBound(d->cells.begin(), d->cells.end(), helper);
619 if (it != d->cells.begin())
620 --it;
621
622 return QTextTableCell(this, *it);
623}
624
625/*!
626 \fn QTextTableCell QTextTable::cellAt(const QTextCursor &cursor) const
627
628 \overload
629
630 Returns the table cell containing the given \a cursor.
631*/
632QTextTableCell QTextTable::cellAt(const QTextCursor &c) const
633{
634 return cellAt(c.position());
635}
636
637/*!
638 \fn void QTextTable::resize(int rows, int columns)
639
640 Resizes the table to contain the required number of \a rows and \a columns.
641
642 \sa insertRows() insertColumns() removeRows() removeColumns()
643*/
644void QTextTable::resize(int rows, int cols)
645{
646 Q_D(QTextTable);
647 if (d->dirty)
648 d->update();
649
650 int nRows = this->rows();
651 int nCols = this->columns();
652
653 if (rows == nRows && cols == nCols)
654 return;
655
656 d->pieceTable->beginEditBlock();
657
658 if (nCols < cols)
659 insertColumns(nCols, cols - nCols);
660 else if (nCols > cols)
661 removeColumns(cols, nCols - cols);
662
663 if (nRows < rows)
664 insertRows(nRows, rows-nRows);
665 else if (nRows > rows)
666 removeRows(rows, nRows-rows);
667
668 d->pieceTable->endEditBlock();
669}
670
671/*!
672 \fn void QTextTable::insertRows(int index, int rows)
673
674 Inserts a number of \a rows before the row with the specified \a index.
675
676 \sa resize() insertColumns() removeRows() removeColumns() appendRows() appendColumns()
677*/
678void QTextTable::insertRows(int pos, int num)
679{
680 Q_D(QTextTable);
681 if (num <= 0)
682 return;
683
684 if (d->dirty)
685 d->update();
686
687 if (pos > d->nRows || pos < 0)
688 pos = d->nRows;
689
690// qDebug() << "-------- insertRows" << pos << num;
691 QTextDocumentPrivate *p = d->pieceTable;
692 QTextFormatCollection *c = p->formatCollection();
693 p->beginEditBlock();
694
695 int extended = 0;
696 int insert_before = 0;
697 if (pos > 0 && pos < d->nRows) {
698 for (int i = 0; i < d->nCols; ++i) {
699 int cell = d->grid[pos*d->nCols + i];
700 if (cell == d->grid[(pos-1)*d->nCols+i]) {
701 // cell spans the insertion place, extend it
702 QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), cell);
703 QTextCharFormat fmt = c->charFormat(it->format);
704 fmt.setTableCellRowSpan(fmt.tableCellRowSpan() + num);
705 p->setCharFormat(it.position(), 1, fmt);
706 extended++;
707 } else if (!insert_before) {
708 insert_before = cell;
709 }
710 }
711 } else {
712 insert_before = (pos == 0 ? d->grid[0] : d->fragment_end);
713 }
714 if (extended < d->nCols) {
715 Q_ASSERT(insert_before);
716 QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), insert_before);
717 QTextCharFormat fmt = c->charFormat(it->format);
718 fmt.setTableCellRowSpan(1);
719 fmt.setTableCellColumnSpan(1);
720 Q_ASSERT(fmt.objectIndex() == objectIndex());
721 int pos = it.position();
722 int cfmt = p->formatCollection()->indexForFormat(fmt);
723 int bfmt = p->formatCollection()->indexForFormat(QTextBlockFormat());
724// qDebug("inserting %d cells, nCols=%d extended=%d", num*(d->nCols-extended), d->nCols, extended);
725 for (int i = 0; i < num*(d->nCols-extended); ++i)
726 p->insertBlock(QTextBeginningOfFrame, pos, bfmt, cfmt, QTextUndoCommand::MoveCursor);
727 }
728
729// qDebug() << "-------- end insertRows" << pos << num;
730 p->endEditBlock();
731}
732
733/*!
734 \fn void QTextTable::insertColumns(int index, int columns)
735
736 Inserts a number of \a columns before the column with the specified \a index.
737
738 \sa insertRows() resize() removeRows() removeColumns() appendRows() appendColumns()
739*/
740void QTextTable::insertColumns(int pos, int num)
741{
742 Q_D(QTextTable);
743 if (num <= 0)
744 return;
745
746 if (d->dirty)
747 d->update();
748
749 if (pos > d->nCols || pos < 0)
750 pos = d->nCols;
751
752// qDebug() << "-------- insertCols" << pos << num;
753 QTextDocumentPrivate *p = d->pieceTable;
754 QTextFormatCollection *c = p->formatCollection();
755 p->beginEditBlock();
756
757 QList<int> extendedSpans;
758 for (int i = 0; i < d->nRows; ++i) {
759 int cell;
760 if (i == d->nRows - 1 && pos == d->nCols)
761 cell = d->fragment_end;
762 else
763 cell = d->grid[i*d->nCols + pos];
764 if (pos > 0 && pos < d->nCols && cell == d->grid[i*d->nCols + pos - 1]) {
765 // cell spans the insertion place, extend it
766 if (!extendedSpans.contains(cell)) {
767 QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), cell);
768 QTextCharFormat fmt = c->charFormat(it->format);
769 fmt.setTableCellColumnSpan(fmt.tableCellColumnSpan() + num);
770 p->setCharFormat(it.position(), 1, fmt);
771 d->dirty = true;
772 extendedSpans << cell;
773 }
774 } else {
775 /* If the next cell is spanned from the row above, we need to find the right position
776 to insert to */
777 if (i > 0 && pos < d->nCols && cell == d->grid[(i-1) * d->nCols + pos]) {
778 int gridIndex = i*d->nCols + pos;
779 const int gridEnd = d->nRows * d->nCols - 1;
780 while (gridIndex < gridEnd && cell == d->grid[gridIndex]) {
781 ++gridIndex;
782 }
783 if (gridIndex == gridEnd)
784 cell = d->fragment_end;
785 else
786 cell = d->grid[gridIndex];
787 }
788 QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), cell);
789 QTextCharFormat fmt = c->charFormat(it->format);
790 fmt.setTableCellRowSpan(1);
791 fmt.setTableCellColumnSpan(1);
792 Q_ASSERT(fmt.objectIndex() == objectIndex());
793 int position = it.position();
794 int cfmt = p->formatCollection()->indexForFormat(fmt);
795 int bfmt = p->formatCollection()->indexForFormat(QTextBlockFormat());
796 for (int i = 0; i < num; ++i)
797 p->insertBlock(QTextBeginningOfFrame, position, bfmt, cfmt, QTextUndoCommand::MoveCursor);
798 }
799 }
800
801 QTextTableFormat tfmt = format();
802 tfmt.setColumns(tfmt.columns()+num);
803 QVector<QTextLength> columnWidths = tfmt.columnWidthConstraints();
804 if (! columnWidths.isEmpty()) {
805 for (int i = num; i > 0; --i)
806 columnWidths.insert(pos, columnWidths[qMax(0, pos-1)]);
807 }
808 tfmt.setColumnWidthConstraints (columnWidths);
809 QTextObject::setFormat(tfmt);
810
811// qDebug() << "-------- end insertCols" << pos << num;
812 p->endEditBlock();
813}
814
815/*!
816 \since 4.5
817 Appends \a count rows at the bottom of the table.
818
819 \sa insertColumns() insertRows() resize() removeRows() removeColumns() appendColumns()
820*/
821void QTextTable::appendRows(int count)
822{
823 insertRows(rows(), count);
824}
825
826/*!
827 \since 4.5
828 Appends \a count columns at the right side of the table.
829
830 \sa insertColumns() insertRows() resize() removeRows() removeColumns() appendRows()
831*/
832void QTextTable::appendColumns(int count)
833{
834 insertColumns(columns(), count);
835}
836
837/*!
838 \fn void QTextTable::removeRows(int index, int rows)
839
840 Removes a number of \a rows starting with the row at the specified \a index.
841
842 \sa insertRows(), insertColumns(), resize(), removeColumns() appendRows() appendColumns()
843*/
844void QTextTable::removeRows(int pos, int num)
845{
846 Q_D(QTextTable);
847// qDebug() << "-------- removeRows" << pos << num;
848
849 if (num <= 0 || pos < 0)
850 return;
851 if (d->dirty)
852 d->update();
853 if (pos >= d->nRows)
854 return;
855 if (pos+num > d->nRows)
856 num = d->nRows - pos;
857
858 QTextDocumentPrivate *p = d->pieceTable;
859 QTextFormatCollection *collection = p->formatCollection();
860 p->beginEditBlock();
861
862 // delete whole table?
863 if (pos == 0 && num == d->nRows) {
864 const int pos = p->fragmentMap().position(d->fragment_start);
865 p->remove(pos, p->fragmentMap().position(d->fragment_end) - pos + 1);
866 p->endEditBlock();
867 return;
868 }
869
870 p->aboutToRemoveCell(cellAt(pos, 0).firstPosition(), cellAt(pos + num - 1, d->nCols - 1).lastPosition());
871
872 QList<int> touchedCells;
873 for (int r = pos; r < pos + num; ++r) {
874 for (int c = 0; c < d->nCols; ++c) {
875 int cell = d->grid[r*d->nCols + c];
876 if (touchedCells.contains(cell))
877 continue;
878 touchedCells << cell;
879 QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), cell);
880 QTextCharFormat fmt = collection->charFormat(it->format);
881 int span = fmt.tableCellRowSpan();
882 if (span > 1) {
883 fmt.setTableCellRowSpan(span - 1);
884 p->setCharFormat(it.position(), 1, fmt);
885 } else {
886 // remove cell
887 int index = d->cells.indexOf(cell) + 1;
888 int f_end = index < d->cells.size() ? d->cells.at(index) : d->fragment_end;
889 p->remove(it.position(), p->fragmentMap().position(f_end) - it.position());
890 }
891 }
892 }
893
894 p->endEditBlock();
895// qDebug() << "-------- end removeRows" << pos << num;
896}
897
898/*!
899 \fn void QTextTable::removeColumns(int index, int columns)
900
901 Removes a number of \a columns starting with the column at the specified
902 \a index.
903
904 \sa insertRows() insertColumns() removeRows() resize() appendRows() appendColumns()
905*/
906void QTextTable::removeColumns(int pos, int num)
907{
908 Q_D(QTextTable);
909// qDebug() << "-------- removeCols" << pos << num;
910
911 if (num <= 0 || pos < 0)
912 return;
913 if (d->dirty)
914 d->update();
915 if (pos >= d->nCols)
916 return;
917 if (pos + num > d->nCols)
918 pos = d->nCols - num;
919
920 QTextDocumentPrivate *p = d->pieceTable;
921 QTextFormatCollection *collection = p->formatCollection();
922 p->beginEditBlock();
923
924 // delete whole table?
925 if (pos == 0 && num == d->nCols) {
926 const int pos = p->fragmentMap().position(d->fragment_start);
927 p->remove(pos, p->fragmentMap().position(d->fragment_end) - pos + 1);
928 p->endEditBlock();
929 return;
930 }
931
932 p->aboutToRemoveCell(cellAt(0, pos).firstPosition(), cellAt(d->nRows - 1, pos + num - 1).lastPosition());
933
934 QList<int> touchedCells;
935 for (int r = 0; r < d->nRows; ++r) {
936 for (int c = pos; c < pos + num; ++c) {
937 int cell = d->grid[r*d->nCols + c];
938 QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), cell);
939 QTextCharFormat fmt = collection->charFormat(it->format);
940 int span = fmt.tableCellColumnSpan();
941 if (touchedCells.contains(cell) && span <= 1)
942 continue;
943 touchedCells << cell;
944
945 if (span > 1) {
946 fmt.setTableCellColumnSpan(span - 1);
947 p->setCharFormat(it.position(), 1, fmt);
948 } else {
949 // remove cell
950 int index = d->cells.indexOf(cell) + 1;
951 int f_end = index < d->cells.size() ? d->cells.at(index) : d->fragment_end;
952 p->remove(it.position(), p->fragmentMap().position(f_end) - it.position());
953 }
954 }
955 }
956
957 QTextTableFormat tfmt = format();
958 tfmt.setColumns(tfmt.columns()-num);
959 QVector<QTextLength> columnWidths = tfmt.columnWidthConstraints();
960 if (columnWidths.count() > pos) {
961 columnWidths.remove(pos, num);
962 tfmt.setColumnWidthConstraints (columnWidths);
963 }
964 QTextObject::setFormat(tfmt);
965
966 p->endEditBlock();
967// qDebug() << "-------- end removeCols" << pos << num;
968}
969
970/*!
971 \since 4.1
972
973 Merges the cell at the specified \a row and \a column with the adjacent cells
974 into one cell. The new cell will span \a numRows rows and \a numCols columns.
975 If \a numRows or \a numCols is less than the current number of rows or columns
976 the cell spans then this method does nothing.
977
978 \sa splitCell()
979*/
980void QTextTable::mergeCells(int row, int column, int numRows, int numCols)
981{
982 Q_D(QTextTable);
983
984 if (d->dirty)
985 d->update();
986
987 QTextDocumentPrivate *p = d->pieceTable;
988 QTextFormatCollection *fc = p->formatCollection();
989
990 const QTextTableCell cell = cellAt(row, column);
991 if (!cell.isValid() || row != cell.row() || column != cell.column())
992 return;
993
994 QTextCharFormat fmt = cell.format();
995 const int rowSpan = fmt.tableCellRowSpan();
996 const int colSpan = fmt.tableCellColumnSpan();
997
998 numRows = qMin(numRows, rows() - cell.row());
999 numCols = qMin(numCols, columns() - cell.column());
1000
1001 // nothing to merge?
1002 if (numRows < rowSpan || numCols < colSpan)
1003 return;
1004
1005 // check the edges of the merge rect to make sure no cell spans the edge
1006 for (int r = row; r < row + numRows; ++r) {
1007 if (cellAt(r, column) == cellAt(r, column - 1))
1008 return;
1009 if (cellAt(r, column + numCols) == cellAt(r, column + numCols - 1))
1010 return;
1011 }
1012
1013 for (int c = column; c < column + numCols; ++c) {
1014 if (cellAt(row, c) == cellAt(row - 1, c))
1015 return;
1016 if (cellAt(row + numRows, c) == cellAt(row + numRows - 1, c))
1017 return;
1018 }
1019
1020 p->beginEditBlock();
1021
1022 const int origCellPosition = cell.firstPosition() - 1;
1023
1024 const int cellFragment = d->grid[row * d->nCols + column];
1025
1026 // find the position at which to insert the contents of the merged cells
1027 QFragmentFindHelper helper(origCellPosition, p->fragmentMap());
1028 QList<int>::Iterator it = qBinaryFind(d->cells.begin(), d->cells.end(), helper);
1029 Q_ASSERT(it != d->cells.end());
1030 Q_ASSERT(*it == cellFragment);
1031 const int insertCellIndex = it - d->cells.begin();
1032 int insertFragment = d->cells.value(insertCellIndex + 1, d->fragment_end);
1033 uint insertPos = p->fragmentMap().position(insertFragment);
1034
1035 d->blockFragmentUpdates = true;
1036
1037 bool rowHasText = cell.firstCursorPosition().block().length();
1038 bool needsParagraph = rowHasText && colSpan == numCols;
1039
1040 // find all cells that will be erased by the merge
1041 for (int r = row; r < row + numRows; ++r) {
1042 int firstColumn = r < row + rowSpan ? column + colSpan : column;
1043
1044 // don't recompute the cell index for the first row
1045 int firstCellIndex = r == row ? insertCellIndex + 1 : -1;
1046 int cellIndex = firstCellIndex;
1047
1048 for (int c = firstColumn; c < column + numCols; ++c) {
1049 const int fragment = d->grid[r * d->nCols + c];
1050
1051 // already handled?
1052 if (fragment == cellFragment)
1053 continue;
1054
1055 QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), fragment);
1056 uint pos = it.position();
1057
1058 if (firstCellIndex == -1) {
1059 QFragmentFindHelper helper(pos, p->fragmentMap());
1060 QList<int>::Iterator it = qBinaryFind(d->cells.begin(), d->cells.end(), helper);
1061 Q_ASSERT(it != d->cells.end());
1062 Q_ASSERT(*it == fragment);
1063 firstCellIndex = cellIndex = it - d->cells.begin();
1064 }
1065
1066 ++cellIndex;
1067
1068 QTextCharFormat fmt = fc->charFormat(it->format);
1069
1070 const int cellRowSpan = fmt.tableCellRowSpan();
1071 const int cellColSpan = fmt.tableCellColumnSpan();
1072
1073 // update the grid for this cell
1074 for (int i = r; i < r + cellRowSpan; ++i)
1075 for (int j = c; j < c + cellColSpan; ++j)
1076 d->grid[i * d->nCols + j] = cellFragment;
1077
1078 // erase the cell marker
1079 p->remove(pos, 1);
1080
1081 const int nextFragment = d->cells.value(cellIndex, d->fragment_end);
1082 const uint nextPos = p->fragmentMap().position(nextFragment);
1083
1084 Q_ASSERT(nextPos >= pos);
1085
1086 // merge the contents of the cell (if not empty)
1087 if (nextPos > pos) {
1088 if (needsParagraph) {
1089 needsParagraph = false;
1090 QTextCursor(p, insertPos++).insertBlock();
1091 p->move(pos + 1, insertPos, nextPos - pos);
1092 } else if (rowHasText) {
1093 QTextCursor(p, insertPos++).insertText(QLatin1String(" "));
1094 p->move(pos + 1, insertPos, nextPos - pos);
1095 } else {
1096 p->move(pos, insertPos, nextPos - pos);
1097 }
1098
1099 insertPos += nextPos - pos;
1100 rowHasText = true;
1101 }
1102 }
1103
1104 if (rowHasText) {
1105 needsParagraph = true;
1106 rowHasText = false;
1107 }
1108
1109 // erase cells from last row
1110 if (firstCellIndex >= 0) {
1111 d->cellIndices.remove(firstCellIndex, cellIndex - firstCellIndex);
1112 d->cells.erase(d->cells.begin() + firstCellIndex, d->cells.begin() + cellIndex);
1113 }
1114 }
1115
1116 d->fragment_start = d->cells.first();
1117
1118 fmt.setTableCellRowSpan(numRows);
1119 fmt.setTableCellColumnSpan(numCols);
1120 p->setCharFormat(origCellPosition, 1, fmt);
1121
1122 d->blockFragmentUpdates = false;
1123 d->dirty = false;
1124
1125 p->endEditBlock();
1126}
1127
1128/*!
1129 \overload
1130 \since 4.1
1131
1132 Merges the cells selected by the provided \a cursor.
1133
1134 \sa splitCell()
1135*/
1136void QTextTable::mergeCells(const QTextCursor &cursor)
1137{
1138 if (!cursor.hasComplexSelection())
1139 return;
1140
1141 int firstRow, numRows, firstColumn, numColumns;
1142 cursor.selectedTableCells(&firstRow, &numRows, &firstColumn, &numColumns);
1143 mergeCells(firstRow, firstColumn, numRows, numColumns);
1144}
1145
1146/*!
1147 \since 4.1
1148
1149 Splits the specified cell at \a row and \a column into an array of multiple
1150 cells with dimensions specified by \a numRows and \a numCols.
1151
1152 \note It is only possible to split cells that span multiple rows or columns, such as rows
1153 that have been merged using mergeCells().
1154
1155 \sa mergeCells()
1156*/
1157void QTextTable::splitCell(int row, int column, int numRows, int numCols)
1158{
1159 Q_D(QTextTable);
1160
1161 if (d->dirty)
1162 d->update();
1163
1164 QTextDocumentPrivate *p = d->pieceTable;
1165 QTextFormatCollection *c = p->formatCollection();
1166
1167 const QTextTableCell cell = cellAt(row, column);
1168 if (!cell.isValid())
1169 return;
1170 row = cell.row();
1171 column = cell.column();
1172
1173 QTextCharFormat fmt = cell.format();
1174 const int rowSpan = fmt.tableCellRowSpan();
1175 const int colSpan = fmt.tableCellColumnSpan();
1176
1177 // nothing to split?
1178 if (numRows > rowSpan || numCols > colSpan)
1179 return;
1180
1181 p->beginEditBlock();
1182
1183 const int origCellPosition = cell.firstPosition() - 1;
1184
1185 QVarLengthArray<int> rowPositions(rowSpan);
1186
1187 rowPositions[0] = cell.lastPosition();
1188
1189 for (int r = row + 1; r < row + rowSpan; ++r) {
1190 // find the cell before which to insert the new cell markers
1191 int gridIndex = r * d->nCols + column;
1192 QVector<int>::iterator it = qUpperBound(d->cellIndices.begin(), d->cellIndices.end(), gridIndex);
1193 int cellIndex = it - d->cellIndices.begin();
1194 int fragment = d->cells.value(cellIndex, d->fragment_end);
1195 rowPositions[r - row] = p->fragmentMap().position(fragment);
1196 }
1197
1198 fmt.setTableCellColumnSpan(1);
1199 fmt.setTableCellRowSpan(1);
1200 const int fmtIndex = c->indexForFormat(fmt);
1201 const int blockIndex = p->blockMap().find(cell.lastPosition())->format;
1202
1203 int insertAdjustement = 0;
1204 for (int i = 0; i < numRows; ++i) {
1205 for (int c = 0; c < colSpan - numCols; ++c)
1206 p->insertBlock(QTextBeginningOfFrame, rowPositions[i] + insertAdjustement + c, blockIndex, fmtIndex);
1207 insertAdjustement += colSpan - numCols;
1208 }
1209
1210 for (int i = numRows; i < rowSpan; ++i) {
1211 for (int c = 0; c < colSpan; ++c)
1212 p->insertBlock(QTextBeginningOfFrame, rowPositions[i] + insertAdjustement + c, blockIndex, fmtIndex);
1213 insertAdjustement += colSpan;
1214 }
1215
1216 fmt.setTableCellRowSpan(numRows);
1217 fmt.setTableCellColumnSpan(numCols);
1218 p->setCharFormat(origCellPosition, 1, fmt);
1219
1220 p->endEditBlock();
1221}
1222
1223/*!
1224 Returns the number of rows in the table.
1225
1226 \sa columns()
1227*/
1228int QTextTable::rows() const
1229{
1230 Q_D(const QTextTable);
1231 if (d->dirty)
1232 d->update();
1233
1234 return d->nRows;
1235}
1236
1237/*!
1238 Returns the number of columns in the table.
1239
1240 \sa rows()
1241*/
1242int QTextTable::columns() const
1243{
1244 Q_D(const QTextTable);
1245 if (d->dirty)
1246 d->update();
1247
1248 return d->nCols;
1249}
1250
1251#if 0
1252void QTextTable::mergeCells(const QTextCursor &selection)
1253{
1254}
1255#endif
1256
1257/*!
1258 \fn QTextCursor QTextTable::rowStart(const QTextCursor &cursor) const
1259
1260 Returns a cursor pointing to the start of the row that contains the
1261 given \a cursor.
1262
1263 \sa rowEnd()
1264*/
1265QTextCursor QTextTable::rowStart(const QTextCursor &c) const
1266{
1267 Q_D(const QTextTable);
1268 QTextTableCell cell = cellAt(c);
1269 if (!cell.isValid())
1270 return QTextCursor();
1271
1272 int row = cell.row();
1273 QTextDocumentPrivate *p = d->pieceTable;
1274 QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), d->grid[row*d->nCols]);
1275 return QTextCursor(p, it.position());
1276}
1277
1278/*!
1279 \fn QTextCursor QTextTable::rowEnd(const QTextCursor &cursor) const
1280
1281 Returns a cursor pointing to the end of the row that contains the given
1282 \a cursor.
1283
1284 \sa rowStart()
1285*/
1286QTextCursor QTextTable::rowEnd(const QTextCursor &c) const
1287{
1288 Q_D(const QTextTable);
1289 QTextTableCell cell = cellAt(c);
1290 if (!cell.isValid())
1291 return QTextCursor();
1292
1293 int row = cell.row() + 1;
1294 int fragment = row < d->nRows ? d->grid[row*d->nCols] : d->fragment_end;
1295 QTextDocumentPrivate *p = d->pieceTable;
1296 QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), fragment);
1297 return QTextCursor(p, it.position() - 1);
1298}
1299
1300/*!
1301 \fn void QTextTable::setFormat(const QTextTableFormat &format)
1302
1303 Sets the table's \a format.
1304
1305 \sa format()
1306*/
1307void QTextTable::setFormat(const QTextTableFormat &format)
1308{
1309 QTextTableFormat fmt = format;
1310 // don't try to change the number of table columns from here
1311 fmt.setColumns(columns());
1312 QTextObject::setFormat(fmt);
1313}
1314
1315/*!
1316 \fn QTextTableFormat QTextTable::format() const
1317
1318 Returns the table's format.
1319
1320 \sa setFormat()
1321*/
1322
1323QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.