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

Last change on this file since 352 was 2, checked in by Dmitry A. Kuminov, 16 years ago

Initially imported qt-all-opensource-src-4.5.1 from Trolltech.

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