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

Last change on this file since 793 was 651, checked in by Dmitry A. Kuminov, 15 years ago

trunk: Merged in qt 4.6.2 sources.

File size: 38.9 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2010 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 for (int i = 0; i < d->nRows; ++i) {
758 int cell;
759 if (i == d->nRows - 1 && pos == d->nCols)
760 cell = d->fragment_end;
761 else
762 cell = d->grid[i*d->nCols + pos];
763 QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), cell);
764 QTextCharFormat fmt = c->charFormat(it->format);
765 if (pos > 0 && pos < d->nCols && cell == d->grid[i*d->nCols + pos - 1]) {
766 // cell spans the insertion place, extend it
767 fmt.setTableCellColumnSpan(fmt.tableCellColumnSpan() + num);
768 p->setCharFormat(it.position(), 1, fmt);
769 } else {
770 fmt.setTableCellRowSpan(1);
771 fmt.setTableCellColumnSpan(1);
772 Q_ASSERT(fmt.objectIndex() == objectIndex());
773 int position = it.position();
774 int cfmt = p->formatCollection()->indexForFormat(fmt);
775 int bfmt = p->formatCollection()->indexForFormat(QTextBlockFormat());
776 for (int i = 0; i < num; ++i)
777 p->insertBlock(QTextBeginningOfFrame, position, bfmt, cfmt, QTextUndoCommand::MoveCursor);
778 }
779 }
780
781 QTextTableFormat tfmt = format();
782 tfmt.setColumns(tfmt.columns()+num);
783 QVector<QTextLength> columnWidths = tfmt.columnWidthConstraints();
784 if (! columnWidths.isEmpty()) {
785 for (int i = num; i > 0; --i)
786 columnWidths.insert(pos, columnWidths[qMax(0, pos-1)]);
787 }
788 tfmt.setColumnWidthConstraints (columnWidths);
789 QTextObject::setFormat(tfmt);
790
791// qDebug() << "-------- end insertCols" << pos << num;
792 p->endEditBlock();
793}
794
795/*!
796 \since 4.5
797 Appends \a count rows at the bottom of the table.
798
799 \sa insertColumns() insertRows() resize() removeRows() removeColumns() appendColumns()
800*/
801void QTextTable::appendRows(int count)
802{
803 insertRows(rows(), count);
804}
805
806/*!
807 \since 4.5
808 Appends \a count columns at the right side of the table.
809
810 \sa insertColumns() insertRows() resize() removeRows() removeColumns() appendRows()
811*/
812void QTextTable::appendColumns(int count)
813{
814 insertColumns(columns(), count);
815}
816
817/*!
818 \fn void QTextTable::removeRows(int index, int rows)
819
820 Removes a number of \a rows starting with the row at the specified \a index.
821
822 \sa insertRows(), insertColumns(), resize(), removeColumns() appendRows() appendColumns()
823*/
824void QTextTable::removeRows(int pos, int num)
825{
826 Q_D(QTextTable);
827// qDebug() << "-------- removeRows" << pos << num;
828
829 if (num <= 0 || pos < 0)
830 return;
831 if (d->dirty)
832 d->update();
833 if (pos >= d->nRows)
834 return;
835 if (pos+num > d->nRows)
836 num = d->nRows - pos;
837
838 QTextDocumentPrivate *p = d->pieceTable;
839 QTextFormatCollection *collection = p->formatCollection();
840 p->beginEditBlock();
841
842 // delete whole table?
843 if (pos == 0 && num == d->nRows) {
844 const int pos = p->fragmentMap().position(d->fragment_start);
845 p->remove(pos, p->fragmentMap().position(d->fragment_end) - pos + 1);
846 p->endEditBlock();
847 return;
848 }
849
850 p->aboutToRemoveCell(cellAt(pos, 0).firstPosition(), cellAt(pos + num - 1, d->nCols - 1).lastPosition());
851
852 QList<int> touchedCells;
853 for (int r = pos; r < pos + num; ++r) {
854 for (int c = 0; c < d->nCols; ++c) {
855 int cell = d->grid[r*d->nCols + c];
856 if (touchedCells.contains(cell))
857 continue;
858 touchedCells << cell;
859 QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), cell);
860 QTextCharFormat fmt = collection->charFormat(it->format);
861 int span = fmt.tableCellRowSpan();
862 if (span > 1) {
863 fmt.setTableCellRowSpan(span - 1);
864 p->setCharFormat(it.position(), 1, fmt);
865 } else {
866 // remove cell
867 int index = d->cells.indexOf(cell) + 1;
868 int f_end = index < d->cells.size() ? d->cells.at(index) : d->fragment_end;
869 p->remove(it.position(), p->fragmentMap().position(f_end) - it.position());
870 }
871 }
872 }
873
874 p->endEditBlock();
875// qDebug() << "-------- end removeRows" << pos << num;
876}
877
878/*!
879 \fn void QTextTable::removeColumns(int index, int columns)
880
881 Removes a number of \a columns starting with the column at the specified
882 \a index.
883
884 \sa insertRows() insertColumns() removeRows() resize() appendRows() appendColumns()
885*/
886void QTextTable::removeColumns(int pos, int num)
887{
888 Q_D(QTextTable);
889// qDebug() << "-------- removeCols" << pos << num;
890
891 if (num <= 0 || pos < 0)
892 return;
893 if (d->dirty)
894 d->update();
895 if (pos >= d->nCols)
896 return;
897 if (pos + num > d->nCols)
898 pos = d->nCols - num;
899
900 QTextDocumentPrivate *p = d->pieceTable;
901 QTextFormatCollection *collection = p->formatCollection();
902 p->beginEditBlock();
903
904 // delete whole table?
905 if (pos == 0 && num == d->nCols) {
906 const int pos = p->fragmentMap().position(d->fragment_start);
907 p->remove(pos, p->fragmentMap().position(d->fragment_end) - pos + 1);
908 p->endEditBlock();
909 return;
910 }
911
912 p->aboutToRemoveCell(cellAt(0, pos).firstPosition(), cellAt(d->nRows - 1, pos + num - 1).lastPosition());
913
914 QList<int> touchedCells;
915 for (int r = 0; r < d->nRows; ++r) {
916 for (int c = pos; c < pos + num; ++c) {
917 int cell = d->grid[r*d->nCols + c];
918 if (touchedCells.contains(cell))
919 continue;
920 touchedCells << cell;
921 QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), cell);
922 QTextCharFormat fmt = collection->charFormat(it->format);
923 int span = fmt.tableCellColumnSpan();
924 if (span > 1) {
925 fmt.setTableCellColumnSpan(span - 1);
926 p->setCharFormat(it.position(), 1, fmt);
927 } else {
928 // remove cell
929 int index = d->cells.indexOf(cell) + 1;
930 int f_end = index < d->cells.size() ? d->cells.at(index) : d->fragment_end;
931 p->remove(it.position(), p->fragmentMap().position(f_end) - it.position());
932 }
933 }
934 }
935
936 QTextTableFormat tfmt = format();
937 tfmt.setColumns(tfmt.columns()-num);
938 QVector<QTextLength> columnWidths = tfmt.columnWidthConstraints();
939 if (columnWidths.count() > pos) {
940 columnWidths.remove(pos, num);
941 tfmt.setColumnWidthConstraints (columnWidths);
942 }
943 QTextObject::setFormat(tfmt);
944
945 p->endEditBlock();
946// qDebug() << "-------- end removeCols" << pos << num;
947}
948
949/*!
950 \since 4.1
951
952 Merges the cell at the specified \a row and \a column with the adjacent cells
953 into one cell. The new cell will span \a numRows rows and \a numCols columns.
954 If \a numRows or \a numCols is less than the current number of rows or columns
955 the cell spans then this method does nothing.
956
957 \sa splitCell()
958*/
959void QTextTable::mergeCells(int row, int column, int numRows, int numCols)
960{
961 Q_D(QTextTable);
962
963 if (d->dirty)
964 d->update();
965
966 QTextDocumentPrivate *p = d->pieceTable;
967 QTextFormatCollection *fc = p->formatCollection();
968
969 const QTextTableCell cell = cellAt(row, column);
970 if (!cell.isValid() || row != cell.row() || column != cell.column())
971 return;
972
973 QTextCharFormat fmt = cell.format();
974 const int rowSpan = fmt.tableCellRowSpan();
975 const int colSpan = fmt.tableCellColumnSpan();
976
977 numRows = qMin(numRows, rows() - cell.row());
978 numCols = qMin(numCols, columns() - cell.column());
979
980 // nothing to merge?
981 if (numRows < rowSpan || numCols < colSpan)
982 return;
983
984 // check the edges of the merge rect to make sure no cell spans the edge
985 for (int r = row; r < row + numRows; ++r) {
986 if (cellAt(r, column) == cellAt(r, column - 1))
987 return;
988 if (cellAt(r, column + numCols) == cellAt(r, column + numCols - 1))
989 return;
990 }
991
992 for (int c = column; c < column + numCols; ++c) {
993 if (cellAt(row, c) == cellAt(row - 1, c))
994 return;
995 if (cellAt(row + numRows, c) == cellAt(row + numRows - 1, c))
996 return;
997 }
998
999 p->beginEditBlock();
1000
1001 const int origCellPosition = cell.firstPosition() - 1;
1002
1003 const int cellFragment = d->grid[row * d->nCols + column];
1004
1005 // find the position at which to insert the contents of the merged cells
1006 QFragmentFindHelper helper(origCellPosition, p->fragmentMap());
1007 QList<int>::Iterator it = qBinaryFind(d->cells.begin(), d->cells.end(), helper);
1008 Q_ASSERT(it != d->cells.end());
1009 Q_ASSERT(*it == cellFragment);
1010 const int insertCellIndex = it - d->cells.begin();
1011 int insertFragment = d->cells.value(insertCellIndex + 1, d->fragment_end);
1012 uint insertPos = p->fragmentMap().position(insertFragment);
1013
1014 d->blockFragmentUpdates = true;
1015
1016 bool rowHasText = cell.firstCursorPosition().block().length();
1017 bool needsParagraph = rowHasText && colSpan == numCols;
1018
1019 // find all cells that will be erased by the merge
1020 for (int r = row; r < row + numRows; ++r) {
1021 int firstColumn = r < row + rowSpan ? column + colSpan : column;
1022
1023 // don't recompute the cell index for the first row
1024 int firstCellIndex = r == row ? insertCellIndex + 1 : -1;
1025 int cellIndex = firstCellIndex;
1026
1027 for (int c = firstColumn; c < column + numCols; ++c) {
1028 const int fragment = d->grid[r * d->nCols + c];
1029
1030 // already handled?
1031 if (fragment == cellFragment)
1032 continue;
1033
1034 QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), fragment);
1035 uint pos = it.position();
1036
1037 if (firstCellIndex == -1) {
1038 QFragmentFindHelper helper(pos, p->fragmentMap());
1039 QList<int>::Iterator it = qBinaryFind(d->cells.begin(), d->cells.end(), helper);
1040 Q_ASSERT(it != d->cells.end());
1041 Q_ASSERT(*it == fragment);
1042 firstCellIndex = cellIndex = it - d->cells.begin();
1043 }
1044
1045 ++cellIndex;
1046
1047 QTextCharFormat fmt = fc->charFormat(it->format);
1048
1049 const int cellRowSpan = fmt.tableCellRowSpan();
1050 const int cellColSpan = fmt.tableCellColumnSpan();
1051
1052 // update the grid for this cell
1053 for (int i = r; i < r + cellRowSpan; ++i)
1054 for (int j = c; j < c + cellColSpan; ++j)
1055 d->grid[i * d->nCols + j] = cellFragment;
1056
1057 // erase the cell marker
1058 p->remove(pos, 1);
1059
1060 const int nextFragment = d->cells.value(cellIndex, d->fragment_end);
1061 const uint nextPos = p->fragmentMap().position(nextFragment);
1062
1063 Q_ASSERT(nextPos >= pos);
1064
1065 // merge the contents of the cell (if not empty)
1066 if (nextPos > pos) {
1067 if (needsParagraph) {
1068 needsParagraph = false;
1069 QTextCursor(p, insertPos++).insertBlock();
1070 p->move(pos + 1, insertPos, nextPos - pos);
1071 } else if (rowHasText) {
1072 QTextCursor(p, insertPos++).insertText(QLatin1String(" "));
1073 p->move(pos + 1, insertPos, nextPos - pos);
1074 } else {
1075 p->move(pos, insertPos, nextPos - pos);
1076 }
1077
1078 insertPos += nextPos - pos;
1079 rowHasText = true;
1080 }
1081 }
1082
1083 if (rowHasText) {
1084 needsParagraph = true;
1085 rowHasText = false;
1086 }
1087
1088 // erase cells from last row
1089 if (firstCellIndex >= 0) {
1090 d->cellIndices.remove(firstCellIndex, cellIndex - firstCellIndex);
1091 d->cells.erase(d->cells.begin() + firstCellIndex, d->cells.begin() + cellIndex);
1092 }
1093 }
1094
1095 d->fragment_start = d->cells.first();
1096
1097 fmt.setTableCellRowSpan(numRows);
1098 fmt.setTableCellColumnSpan(numCols);
1099 p->setCharFormat(origCellPosition, 1, fmt);
1100
1101 d->blockFragmentUpdates = false;
1102 d->dirty = false;
1103
1104 p->endEditBlock();
1105}
1106
1107/*!
1108 \overload
1109 \since 4.1
1110
1111 Merges the cells selected by the provided \a cursor.
1112
1113 \sa splitCell()
1114*/
1115void QTextTable::mergeCells(const QTextCursor &cursor)
1116{
1117 if (!cursor.hasComplexSelection())
1118 return;
1119
1120 int firstRow, numRows, firstColumn, numColumns;
1121 cursor.selectedTableCells(&firstRow, &numRows, &firstColumn, &numColumns);
1122 mergeCells(firstRow, firstColumn, numRows, numColumns);
1123}
1124
1125/*!
1126 \since 4.1
1127
1128 Splits the specified cell at \a row and \a column into an array of multiple
1129 cells with dimensions specified by \a numRows and \a numCols.
1130
1131 \note It is only possible to split cells that span multiple rows or columns, such as rows
1132 that have been merged using mergeCells().
1133
1134 \sa mergeCells()
1135*/
1136void QTextTable::splitCell(int row, int column, int numRows, int numCols)
1137{
1138 Q_D(QTextTable);
1139
1140 if (d->dirty)
1141 d->update();
1142
1143 QTextDocumentPrivate *p = d->pieceTable;
1144 QTextFormatCollection *c = p->formatCollection();
1145
1146 const QTextTableCell cell = cellAt(row, column);
1147 if (!cell.isValid())
1148 return;
1149 row = cell.row();
1150 column = cell.column();
1151
1152 QTextCharFormat fmt = cell.format();
1153 const int rowSpan = fmt.tableCellRowSpan();
1154 const int colSpan = fmt.tableCellColumnSpan();
1155
1156 // nothing to split?
1157 if (numRows > rowSpan || numCols > colSpan)
1158 return;
1159
1160 p->beginEditBlock();
1161
1162 const int origCellPosition = cell.firstPosition() - 1;
1163
1164 QVarLengthArray<int> rowPositions(rowSpan);
1165
1166 rowPositions[0] = cell.lastPosition();
1167
1168 for (int r = row + 1; r < row + rowSpan; ++r) {
1169 // find the cell before which to insert the new cell markers
1170 int gridIndex = r * d->nCols + column;
1171 QVector<int>::iterator it = qUpperBound(d->cellIndices.begin(), d->cellIndices.end(), gridIndex);
1172 int cellIndex = it - d->cellIndices.begin();
1173 int fragment = d->cells.value(cellIndex, d->fragment_end);
1174 rowPositions[r - row] = p->fragmentMap().position(fragment);
1175 }
1176
1177 fmt.setTableCellColumnSpan(1);
1178 fmt.setTableCellRowSpan(1);
1179 const int fmtIndex = c->indexForFormat(fmt);
1180 const int blockIndex = p->blockMap().find(cell.lastPosition())->format;
1181
1182 int insertAdjustement = 0;
1183 for (int i = 0; i < numRows; ++i) {
1184 for (int c = 0; c < colSpan - numCols; ++c)
1185 p->insertBlock(QTextBeginningOfFrame, rowPositions[i] + insertAdjustement + c, blockIndex, fmtIndex);
1186 insertAdjustement += colSpan - numCols;
1187 }
1188
1189 for (int i = numRows; i < rowSpan; ++i) {
1190 for (int c = 0; c < colSpan; ++c)
1191 p->insertBlock(QTextBeginningOfFrame, rowPositions[i] + insertAdjustement + c, blockIndex, fmtIndex);
1192 insertAdjustement += colSpan;
1193 }
1194
1195 fmt.setTableCellRowSpan(numRows);
1196 fmt.setTableCellColumnSpan(numCols);
1197 p->setCharFormat(origCellPosition, 1, fmt);
1198
1199 p->endEditBlock();
1200}
1201
1202/*!
1203 Returns the number of rows in the table.
1204
1205 \sa columns()
1206*/
1207int QTextTable::rows() const
1208{
1209 Q_D(const QTextTable);
1210 if (d->dirty)
1211 d->update();
1212
1213 return d->nRows;
1214}
1215
1216/*!
1217 Returns the number of columns in the table.
1218
1219 \sa rows()
1220*/
1221int QTextTable::columns() const
1222{
1223 Q_D(const QTextTable);
1224 if (d->dirty)
1225 d->update();
1226
1227 return d->nCols;
1228}
1229
1230#if 0
1231void QTextTable::mergeCells(const QTextCursor &selection)
1232{
1233}
1234#endif
1235
1236/*!
1237 \fn QTextCursor QTextTable::rowStart(const QTextCursor &cursor) const
1238
1239 Returns a cursor pointing to the start of the row that contains the
1240 given \a cursor.
1241
1242 \sa rowEnd()
1243*/
1244QTextCursor QTextTable::rowStart(const QTextCursor &c) const
1245{
1246 Q_D(const QTextTable);
1247 QTextTableCell cell = cellAt(c);
1248 if (!cell.isValid())
1249 return QTextCursor();
1250
1251 int row = cell.row();
1252 QTextDocumentPrivate *p = d->pieceTable;
1253 QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), d->grid[row*d->nCols]);
1254 return QTextCursor(p, it.position());
1255}
1256
1257/*!
1258 \fn QTextCursor QTextTable::rowEnd(const QTextCursor &cursor) const
1259
1260 Returns a cursor pointing to the end of the row that contains the given
1261 \a cursor.
1262
1263 \sa rowStart()
1264*/
1265QTextCursor QTextTable::rowEnd(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() + 1;
1273 int fragment = row < d->nRows ? d->grid[row*d->nCols] : d->fragment_end;
1274 QTextDocumentPrivate *p = d->pieceTable;
1275 QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), fragment);
1276 return QTextCursor(p, it.position() - 1);
1277}
1278
1279/*!
1280 \fn void QTextTable::setFormat(const QTextTableFormat &format)
1281
1282 Sets the table's \a format.
1283
1284 \sa format()
1285*/
1286void QTextTable::setFormat(const QTextTableFormat &format)
1287{
1288 QTextTableFormat fmt = format;
1289 // don't try to change the number of table columns from here
1290 fmt.setColumns(columns());
1291 QTextObject::setFormat(fmt);
1292}
1293
1294/*!
1295 \fn QTextTableFormat QTextTable::format() const
1296
1297 Returns the table's format.
1298
1299 \sa setFormat()
1300*/
1301
1302QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.