1
/****************************************************************************
2
**
3
** Copyright (C) 2012 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
** GNU Lesser General Public License Usage
11
** This file may be used under the terms of the GNU Lesser General Public
12
** License version 2.1 as published by the Free Software Foundation and
13
** appearing in the file LICENSE.LGPL included in the packaging of this
14
** file. Please review the following information to ensure the GNU Lesser
15
** General Public License version 2.1 requirements will be met:
16
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17
**
18
** In addition, as a special exception, Nokia gives you certain additional
19
** rights. These rights are described in the Nokia Qt LGPL Exception
20
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21
**
22
** GNU General Public License Usage
23
** Alternatively, this file may be used under the terms of the GNU General
24
** Public License version 3.0 as published by the Free Software Foundation
25
** and appearing in the file LICENSE.GPL included in the packaging of this
26
** file. Please review the following information to ensure the GNU General
27
** Public License version 3.0 requirements will be met:
28
** http://www.gnu.org/copyleft/gpl.html.
29
**
30
** Other Usage
31
** Alternatively, this file may be used in accordance with the terms and
32
** conditions contained in a signed written agreement between you and Nokia.
33
**
34
**
35
**
36
**
37
**
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
52
QT_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
*/
119
void 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
*/
138
QTextCharFormat 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
*/
155
int 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
*/
166
int 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
*/
183
int 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
*/
200
int 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
*/
210
int 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
*/
228
QTextCursor 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
*/
238
QTextCursor 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
*/
249
int 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
*/
260
int 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
*/
279
QTextFrame::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
*/
292
QTextFrame::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
321
QTextTablePrivate::~QTextTablePrivate()
322
{
323
    if (grid)
324
        free(grid);
325
}
326
327
328
QTextTable *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
372
struct 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
380
Q_STATIC_GLOBAL_INLINE_OPERATOR bool operator<(int fragment, const QFragmentFindHelper &helper)
381
{
382
    return helper.fragmentMap.position(fragment) < helper.pos;
383
}
384
385
Q_STATIC_GLOBAL_INLINE_OPERATOR bool operator<(const QFragmentFindHelper &helper, int fragment)
386
{
387
    return helper.pos < helper.fragmentMap.position(fragment);
388
}
389
390
int 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
400
void 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
418
void 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
*/
442
void 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
 */
567
QTextTable::QTextTable(QTextDocument *doc)
568
    : QTextFrame(*new QTextTablePrivate(doc), doc)
569
{
570
}
571
572
/*! \internal
573
574
Destroys the table.
575
 */
576
QTextTable::~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
*/
588
QTextTableCell 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
*/
606
QTextTableCell 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
*/
632
QTextTableCell 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
*/
644
void 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
*/
678
void 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
*/
740
void 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
*/
821
void 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
*/
832
void 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
*/
844
void 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
*/
906
void 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
*/
980
void 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
*/
1136
void 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
*/
1157
void 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
*/
1228
int 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
*/
1242
int 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
1252
void 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
*/
1265
QTextCursor 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
*/
1286
QTextCursor 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
*/
1307
void 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
1323
QT_END_NAMESPACE