1 /*
2 Copyright (C) 2011 Elvis Stansvik <elvstone@gmail.com>
3
4 For general Scribus (>=1.3.2) copyright and licensing information please refer
5 to the COPYING file provided with the program. Following this notice may exist
6 a copyright and/or license notice that predates the release of Scribus 1.3.2
7 for which a new license (GPL+exception) is in place.
8 */
9 #include <algorithm>
10
11 #include <QDebug>
12 #include <QList>
13 #include <QMutableListIterator>
14 #include <QMutableSetIterator>
15 #include <QPointF>
16 #include <QRectF>
17 #include <QSet>
18 #include <QString>
19 #include <QTransform>
20 #include <QtAlgorithms>
21
22 #include "appmodes.h"
23 #include "cellarea.h"
24 #include "collapsedtablepainter.h"
25 #include "pageitem.h"
26 #include "pageitem_textframe.h"
27 #include "scpainter.h"
28 #include "scribusdoc.h"
29 #include "styles/tablestyle.h"
30 #include "tablehandle.h"
31 #include "tableutils.h"
32
33 #include "pageitem_table.h"
34
35 #ifdef WANT_DEBUG
36 #define ASSERT_VALID() assertValid(); qt_noop()
37 #else
38 #define ASSERT_VALID() qt_noop()
39 #endif
40
41 // The minimum row height.
42 const double PageItem_Table::MinimumRowHeight = 3.0;
43
44 // The minimum column width.
45 const double PageItem_Table::MinimumColumnWidth = 3.0;
46
PageItem_Table(ScribusDoc * pa,double x,double y,double w,double h,double w2,const QString & fill,const QString & outline,int numRows,int numColumns)47 PageItem_Table::PageItem_Table(ScribusDoc *pa, double x, double y, double w, double h, double w2, const QString& fill, const QString& outline, int numRows, int numColumns) :
48 PageItem(pa, PageItem::Table, x, y, w, h, w2, fill, outline),
49 m_rows(0), m_columns(0), m_tablePainter(new CollapsedTablePainter(this))
50 {
51 initialize(numRows, numColumns);
52
53 doc()->dontResize = true;
54 adjustTableToFrame();
55 adjustFrameToTable();
56 doc()->dontResize = false;
57
58 ASSERT_VALID();
59 }
60
~PageItem_Table()61 PageItem_Table::~PageItem_Table()
62 {
63 delete m_tablePainter;
64 }
65
adjustTable()66 void PageItem_Table::adjustTable()
67 {
68 doc()->dontResize = true;
69 adjustTableToFrame();
70 adjustFrameToTable();
71 doc()->dontResize = false;
72 }
73
currentTextProps(ParagraphStyle & parStyle) const74 void PageItem_Table::currentTextProps(ParagraphStyle& parStyle) const
75 {
76 if (m_Doc && (m_Doc->appMode == modeEditTable))
77 m_activeCell.textFrame()->currentTextProps(parStyle);
78 else
79 parStyle = this->itemText.defaultStyle();
80 }
81
getChildren() const82 QList<PageItem*> PageItem_Table::getChildren() const
83 {
84 QList<PageItem*> ret;
85
86 int numRows = this->rows();
87 int numColums = this->columns();
88 for (int row = 0; row < numRows; ++row)
89 {
90 for (int col = 0; col < numColums; ++col)
91 {
92 TableCell cell = cellAt(row, col);
93 if (cell.row() == row && cell.column() == col)
94 {
95 PageItem* textFrame = cell.textFrame();
96 ret.append(textFrame);
97 }
98 }
99 }
100
101 return ret;
102 }
103
getNamedResources(ResourceCollection & lists) const104 void PageItem_Table::getNamedResources(ResourceCollection& lists) const
105 {
106 TableBorder lborder = leftBorder();
107 for (const TableBorderLine& line : lborder.borderLines())
108 {
109 if (line.color() == CommonStrings::None)
110 continue;
111 lists.collectColor(line.color());
112 }
113
114 TableBorder rborder = rightBorder();
115 for (const TableBorderLine& line : rborder.borderLines())
116 {
117 if (line.color() == CommonStrings::None)
118 continue;
119 lists.collectColor(line.color());
120 }
121
122 TableBorder bborder = bottomBorder();
123 for (const TableBorderLine& line : bborder.borderLines())
124 {
125 if (line.color() == CommonStrings::None)
126 continue;
127 lists.collectColor(line.color());
128 }
129
130 TableBorder tborder = topBorder();
131 for (const TableBorderLine& line : tborder.borderLines())
132 {
133 if (line.color() == CommonStrings::None)
134 continue;
135 lists.collectColor(line.color());
136 }
137
138 QString tableStyleName = this->styleName();
139 if (!tableStyleName.isEmpty())
140 lists.collectTableStyle(tableStyleName);
141
142 for (int row = 0; row < rows(); ++row)
143 {
144 int colSpan = 0;
145 for (int col = 0; col < columns(); col += colSpan)
146 {
147 TableCell cell = cellAt(row, col);
148 PageItem_TextFrame* textFrame = cell.textFrame();
149 textFrame->getNamedResources(lists);
150
151 QString cellStyle = cell.styleName();
152 if (!cellStyle.isEmpty())
153 lists.collectCellStyle(cellStyle);
154
155 QString cellFill = cell.fillColor();
156 if (cellFill != CommonStrings::None)
157 lists.collectColor(cellFill);
158
159 lborder = cell.leftBorder();
160 for (const TableBorderLine& line : lborder.borderLines())
161 {
162 if (line.color() == CommonStrings::None)
163 continue;
164 lists.collectColor(line.color());
165 }
166
167 rborder = cell.rightBorder();
168 for (const TableBorderLine& line : rborder.borderLines())
169 {
170 if (line.color() == CommonStrings::None)
171 continue;
172 lists.collectColor(line.color());
173 }
174
175 bborder = cell.bottomBorder();
176 for (const TableBorderLine& line : bborder.borderLines())
177 {
178 if (line.color() == CommonStrings::None)
179 continue;
180 lists.collectColor(line.color());
181 }
182
183 tborder = cell.topBorder();
184 for (const TableBorderLine& line : tborder.borderLines())
185 {
186 if (line.color() == CommonStrings::None)
187 continue;
188 lists.collectColor(line.color());
189 }
190
191 colSpan = cell.columnSpan();
192 }
193 }
194
195 PageItem::getNamedResources(lists);
196 }
197
replaceNamedResources(ResourceCollection & newNames)198 void PageItem_Table::replaceNamedResources(ResourceCollection& newNames)
199 {
200 QMap<QString, QString>::ConstIterator it;
201
202 PageItem::replaceNamedResources(newNames);
203
204 bool lborderChanged = false;
205 TableBorder lborder = leftBorder();
206 auto lborderLines = lborder.borderLines();
207 for (int i = 0; i < lborderLines.count(); ++i)
208 {
209 TableBorderLine line = lborderLines.at(i);
210 if (line.color() == CommonStrings::None)
211 continue;
212 it = newNames.colors().find(line.color());
213 if (it != newNames.colors().end())
214 {
215 line.setColor(*it);
216 lborder.replaceBorderLine(i, line);
217 lborderChanged = true;
218 }
219 }
220
221 if (lborderChanged)
222 setLeftBorder(lborder);
223
224 bool rborderChanged = false;
225 TableBorder rborder = rightBorder();
226 auto rborderLines = rborder.borderLines();
227 for (int i = 0; i < rborderLines.count(); ++i)
228 {
229 TableBorderLine line = rborderLines.at(i);
230 if (line.color() == CommonStrings::None)
231 continue;
232 it = newNames.colors().find(line.color());
233 if (it != newNames.colors().end())
234 {
235 line.setColor(*it);
236 rborder.replaceBorderLine(i, line);
237 rborderChanged = true;
238 }
239 }
240
241 if (rborderChanged)
242 setRightBorder(rborder);
243
244 bool bborderChanged = false;
245 TableBorder bborder = bottomBorder();
246 auto bborderLines = bborder.borderLines();
247 for (int i = 0; i < bborderLines.count(); ++i)
248 {
249 TableBorderLine line = bborderLines.at(i);
250 if (line.color() == CommonStrings::None)
251 continue;
252 it = newNames.colors().find(line.color());
253 if (it != newNames.colors().end())
254 {
255 line.setColor(*it);
256 bborder.replaceBorderLine(i, line);
257 bborderChanged = true;
258 }
259 }
260
261 if (bborderChanged)
262 setBottomBorder(bborder);
263
264 bool tborderChanged = false;
265 TableBorder tborder = topBorder();
266 auto tborderLines = tborder.borderLines();
267 for (int i = 0; i < tborderLines.count(); ++i)
268 {
269 TableBorderLine line = tborderLines.at(i);
270 if (line.color() == CommonStrings::None)
271 continue;
272 it = newNames.colors().find(line.color());
273 if (it != newNames.colors().end())
274 {
275 line.setColor(*it);
276 tborder.replaceBorderLine(i, line);
277 tborderChanged = true;
278 }
279 }
280
281 if (tborderChanged)
282 setTopBorder(tborder);
283
284 it = newNames.tableStyles().find(this->styleName());
285 if (it != newNames.tableStyles().end())
286 this->setStyle(*it);
287
288 for (int row = 0; row < rows(); ++row)
289 {
290 int colSpan = 0;
291 for (int col = 0; col < columns(); col += colSpan)
292 {
293 TableCell cell = cellAt(row, col);
294 PageItem_TextFrame* textFrame = cell.textFrame();
295 textFrame->replaceNamedResources(newNames);
296
297 it = newNames.cellStyles().find(cell.styleName());
298 if (it != newNames.cellStyles().end())
299 cell.setStyle(*it);
300
301 it = newNames.colors().find(cell.fillColor());
302 if (it != newNames.colors().end())
303 cell.setFillColor(*it);
304
305 lborderChanged = false;
306 lborder = cell.leftBorder();
307 lborderLines = lborder.borderLines();
308 for (int i = 0; i < lborderLines.count(); ++i)
309 {
310 TableBorderLine line = lborderLines.at(i);
311 if (line.color() == CommonStrings::None)
312 continue;
313 it = newNames.colors().find(line.color());
314 if (it != newNames.colors().end())
315 {
316 line.setColor(*it);
317 lborder.replaceBorderLine(i, line);
318 lborderChanged = true;
319 }
320 }
321
322 if (lborderChanged)
323 setLeftBorder(lborder);
324
325 rborderChanged = false;
326 rborder = cell.rightBorder();
327 rborderLines = rborder.borderLines();
328 for (int i = 0; i < rborderLines.count(); ++i)
329 {
330 TableBorderLine line = rborderLines.at(i);
331 if (line.color() == CommonStrings::None)
332 continue;
333 it = newNames.colors().find(line.color());
334 if (it != newNames.colors().end())
335 {
336 line.setColor(*it);
337 rborder.replaceBorderLine(i, line);
338 rborderChanged = true;
339 }
340 }
341
342 if (rborderChanged)
343 setRightBorder(rborder);
344
345 bborderChanged = false;
346 bborder = cell.bottomBorder();
347 bborderLines = bborder.borderLines();
348 for (int i = 0; i < bborderLines.count(); ++i)
349 {
350 TableBorderLine line = bborderLines.at(i);
351 if (line.color() == CommonStrings::None)
352 continue;
353 it = newNames.colors().find(line.color());
354 if (it != newNames.colors().end())
355 {
356 line.setColor(*it);
357 bborder.replaceBorderLine(i, line);
358 bborderChanged = true;
359 }
360 }
361
362 if (bborderChanged)
363 setBottomBorder(bborder);
364
365 tborderChanged = false;
366 tborder = cell.topBorder();
367 tborderLines = tborder.borderLines();
368 for (int i = 0; i < tborderLines.count(); ++i)
369 {
370 TableBorderLine line = tborderLines.at(i);
371 if (line.color() == CommonStrings::None)
372 continue;
373 it = newNames.colors().find(line.color());
374 if (it != newNames.colors().end())
375 {
376 line.setColor(*it);
377 tborder.replaceBorderLine(i, line);
378 tborderChanged = true;
379 }
380 }
381
382 if (tborderChanged)
383 setTopBorder(tborder);
384
385 colSpan = cell.columnSpan();
386 }
387 }
388 }
389
layout()390 void PageItem_Table::layout()
391 {
392 int rowCount = rows();
393 int columnCount = columns();
394
395 for (int row = 0; row < rowCount; ++row)
396 {
397 for (int col = 0; col < columnCount; col ++)
398 {
399 TableCell cell = cellAt(row, col);
400 if (cell.row() == row && cell.column() == col)
401 {
402 PageItem* textFrame = cell.textFrame();
403 textFrame->layout();
404 }
405 }
406 }
407 }
408
setLayer(int newLayerID)409 void PageItem_Table::setLayer(int newLayerID)
410 {
411 m_layerID = newLayerID;
412
413 int rowCount = rows();
414 int columnCount = columns();
415
416 for (int row = 0; row < rowCount; ++row)
417 {
418 for (int col = 0; col < columnCount; col++)
419 {
420 TableCell cell = cellAt(row, col);
421 if (cell.row() == row && cell.column() == col)
422 {
423 PageItem* textFrame = cell.textFrame();
424 textFrame->m_layerID = newLayerID;
425 }
426 }
427 }
428 }
429
setMasterPage(int page,const QString & mpName)430 void PageItem_Table::setMasterPage(int page, const QString& mpName)
431 {
432 PageItem::setMasterPage(page, mpName);
433
434 int rowCount = rows();
435 int columnCount = columns();
436
437 for (int row = 0; row < rowCount; ++row)
438 {
439 for (int col = 0; col < columnCount; col++)
440 {
441 TableCell cell = cellAt(row, col);
442 if (cell.row() == row && cell.column() == col)
443 {
444 PageItem* textFrame = cell.textFrame();
445 textFrame->OwnPage = page;
446 textFrame->OnMasterPage = mpName;
447 }
448 }
449 }
450 }
451
setMasterPageName(const QString & mpName)452 void PageItem_Table::setMasterPageName(const QString& mpName)
453 {
454 PageItem::setMasterPageName(mpName);
455
456 int rowCount = rows();
457 int columnCount = columns();
458
459 for (int row = 0; row < rowCount; ++row)
460 {
461 for (int col = 0; col < columnCount; col++)
462 {
463 TableCell cell = cellAt(row, col);
464 if (cell.row() == row && cell.column() == col)
465 {
466 PageItem* textFrame = cell.textFrame();
467 textFrame->OnMasterPage = mpName;
468 }
469 }
470 }
471 }
472
setOwnerPage(int page)473 void PageItem_Table::setOwnerPage(int page)
474 {
475 PageItem::setOwnerPage(page);
476
477 int rowCount = rows();
478 int columnCount = columns();
479
480 for (int row = 0; row < rowCount; ++row)
481 {
482 for (int col = 0; col < columnCount; col++)
483 {
484 TableCell cell = cellAt(row, col);
485 if (cell.row() == row && cell.column() == col)
486 {
487 PageItem* textFrame = cell.textFrame();
488 textFrame->OwnPage = page;
489 }
490 }
491 }
492 }
493
resize(double width,double height)494 void PageItem_Table::resize(double width, double height)
495 {
496 ASSERT_VALID();
497
498 /*
499 * Distribute width proportionally to columns, but don't let any column width below
500 * MinimumColumnWidth.
501 */
502 double requestedWidthFactor = width / tableWidth();
503 double oldMinWidth = *std::min_element(m_columnWidths.begin(), m_columnWidths.end());
504 double newMinWidth = qMax(oldMinWidth * requestedWidthFactor, MinimumColumnWidth);
505 double actualWidthFactor = newMinWidth / oldMinWidth;
506 for (int col = 0; col < columns(); ++col)
507 {
508 m_columnWidths[col] *= actualWidthFactor;
509 m_columnPositions[col] *= actualWidthFactor;
510 }
511
512 /*
513 * Distribute height proportionally to rows, but don't let any row height below
514 * MinimumRowHeight.
515 */
516 double requestedHeightFactor = height / tableHeight();
517 double oldMinHeight = *std::min_element(m_rowHeights.begin(), m_rowHeights.end());
518 double newMinHeight = qMax(oldMinHeight * requestedHeightFactor, MinimumRowHeight);
519 double actualHeightFactor = newMinHeight / oldMinHeight;
520 for (int row = 0; row < rows(); ++row)
521 {
522 m_rowHeights[row] *= actualHeightFactor;
523 m_rowPositions[row] *= actualHeightFactor;
524 }
525
526 // Update cells. TODO: Not for entire table.
527 updateCells();
528
529 emit changed();
530
531 ASSERT_VALID();
532 }
533
insertRows(int index,int numRows)534 void PageItem_Table::insertRows(int index, int numRows)
535 {
536 ASSERT_VALID();
537
538 if (index < 0 || index > rows() || numRows < 1)
539 return;
540
541 double rowHeight = m_rowHeights.at(qMax(index - 1, 0));
542 double rowPosition = index == 0 ? 0.0 : m_rowPositions.at(index - 1) + rowHeight;
543
544 for (int row = index; row < index + numRows; ++row)
545 {
546 // Insert row height and position.
547 m_rowHeights.insert(row, rowHeight);
548 m_rowPositions.insert(row, rowPosition);
549 rowPosition += rowHeight;
550
551 // Insert a row of cells.
552 QList<TableCell> cellRow;
553 cellRow.reserve(columns());
554 for (int col = 0; col < columns(); ++col)
555 cellRow.append(TableCell(row, col, this));
556 m_cellRows.insert(row, cellRow);
557 }
558
559 // Adjust following rows.
560 double insertedHeight = rowHeight * numRows;
561 for (int nextRow = index + numRows; nextRow < rows() + numRows; ++nextRow)
562 {
563 // Adjust position of following row.
564 m_rowPositions[nextRow] += insertedHeight;
565
566 // "Move" cells in following row down.
567 foreach (TableCell cell, m_cellRows[nextRow])
568 cell.moveDown(numRows);
569 }
570
571 // Update row spans.
572 updateSpans(index, numRows, RowsInserted);
573
574 // Increase number of rows.
575 m_rows += numRows;
576
577 // Update cells. TODO: Not for entire table.
578 updateCells();
579
580 emit changed();
581
582 ASSERT_VALID();
583 }
584
removeRows(int index,int numRows)585 void PageItem_Table::removeRows(int index, int numRows)
586 {
587 ASSERT_VALID();
588
589 if (!validRow(index) || numRows < 1 || numRows >= rows() || index + numRows > rows())
590 return;
591
592 // Remove row heights, row positions and rows of cells.
593 double removedHeight = 0.0;
594 for (int i = 0; i < numRows; ++i)
595 {
596 // Remove row height and position.
597 removedHeight += m_rowHeights.takeAt(index);
598 m_rowPositions.removeAt(index);
599
600 // Invalidate removed cells.
601 foreach (TableCell removedCell, m_cellRows[index])
602 removedCell.setValid(false);
603
604 // Remove row of cells.
605 m_cellRows.removeAt(index);
606 }
607
608 // Adjust following rows.
609 for (int nextRow = index; nextRow < rows() - numRows; ++nextRow)
610 {
611 // Adjust position of following row.
612 m_rowPositions[nextRow] -= removedHeight;
613
614 // "Move" cells in following row up.
615 foreach (TableCell cell, m_cellRows[nextRow])
616 cell.moveUp(numRows);
617 }
618
619 // Update row spans.
620 updateSpans(index, numRows, RowsRemoved);
621
622 // Decrease number of rows.
623 m_rows -= numRows;
624
625 // Update cells. TODO: Not for entire table.
626 updateCells();
627
628 // Remove any invalid cells from selection.
629 QMutableSetIterator<TableCell> cellIt(m_selection);
630 while (cellIt.hasNext())
631 if (!cellIt.next().isValid())
632 cellIt.remove();
633
634 // Move to cell below.
635 moveTo(cellAt(qMin(index + 1, rows() - 1), m_activeColumn));
636
637 emit changed();
638
639 ASSERT_VALID();
640 }
641
insertColumns(int index,int numColumns)642 void PageItem_Table::insertColumns(int index, int numColumns)
643 {
644 ASSERT_VALID();
645
646 if (index < 0 || index > columns() || numColumns < 1)
647 return;
648
649 double columnWidth = m_columnWidths.at(qMax(index - 1, 0));
650 double columnPosition = index == 0 ? 0.0 : m_columnPositions.at(index - 1) + columnWidth;
651
652 for (int col = index; col < index + numColumns; ++col)
653 {
654 // Insert column width and position.
655 m_columnWidths.insert(col, columnWidth);
656 m_columnPositions.insert(col, columnPosition);
657 columnPosition += columnWidth;
658
659 // Insert a column of cells.
660 for (int row = 0; row < rows(); ++row)
661 m_cellRows[row].insert(col, TableCell(row, col, this));
662 }
663
664 // Adjust following columns.
665 double insertedWidth = columnWidth * numColumns;
666 for (int nextColumn = index + numColumns; nextColumn < columns() + numColumns; ++nextColumn)
667 {
668 // Adjust position of following column.
669 m_columnPositions[nextColumn] += insertedWidth;
670
671 // "Move" cells in following column right.
672 foreach (QList<TableCell> cellRow, m_cellRows)
673 cellRow[nextColumn].moveRight(numColumns);
674 }
675
676 // Update column spans.
677 updateSpans(index, numColumns, ColumnsInserted);
678
679 // Increase number of columns.
680 m_columns += numColumns;
681
682 // Update cells. TODO: Not for entire table.
683 updateCells();
684
685 emit changed();
686
687 ASSERT_VALID();
688 }
689
removeColumns(int index,int numColumns)690 void PageItem_Table::removeColumns(int index, int numColumns)
691 {
692 ASSERT_VALID();
693
694 if (!validColumn(index) || numColumns < 1 || numColumns >= columns() || index + numColumns > columns())
695 return;
696
697 // Remove column widths, column positions and columns of cells.
698 double removedWidth = 0.0;
699 for (int i = 0; i < numColumns; ++i)
700 {
701 // Remove columns widths and positions.
702 removedWidth += m_columnWidths.takeAt(index);
703 m_columnPositions.removeAt(index);
704
705 // Remove and invalidate cells.
706 QMutableListIterator<QList<TableCell> > rowIt(m_cellRows);
707 while (rowIt.hasNext())
708 rowIt.next().takeAt(index).setValid(false);
709 }
710
711 // Adjust following columns.
712 for (int nextColumn = index; nextColumn < columns() - numColumns; ++nextColumn)
713 {
714 // Adjust position of following column.
715 m_columnPositions[nextColumn] -= removedWidth;
716
717 // "Move" cells in following column left.
718 foreach (QList<TableCell> cellRow, m_cellRows)
719 cellRow[nextColumn].moveLeft(numColumns);
720 }
721
722 // Update column spans.
723 updateSpans(index, numColumns, ColumnsRemoved);
724
725 // Decrease number of columns.
726 m_columns -= numColumns;
727
728 // Update cells. TODO: Not for entire table.
729 updateCells();
730
731 // Remove any invalid cells from selection.
732 QMutableSetIterator<TableCell> cellIt(m_selection);
733 while (cellIt.hasNext())
734 if (!cellIt.next().isValid())
735 cellIt.remove();
736
737 // Move to cell to the right.
738 moveTo(cellAt(m_activeRow, qMin(m_activeColumn + 1, columns() - 1)));
739
740 emit changed();
741
742 ASSERT_VALID();
743 }
744
rowHeight(int row) const745 double PageItem_Table::rowHeight(int row) const
746 {
747 if (!validRow(row))
748 return 0.0;
749
750 return m_rowHeights.at(row);
751 }
752
resizeRow(int row,double height,ResizeStrategy strategy)753 void PageItem_Table::resizeRow(int row, double height, ResizeStrategy strategy)
754 {
755 ASSERT_VALID();
756
757 if (!validRow(row))
758 return;
759
760 if (strategy == MoveFollowing)
761 resizeRowMoveFollowing(row, height);
762 else if (strategy == ResizeFollowing)
763 resizeRowResizeFollowing(row, height);
764 else
765 qWarning("Unknown resize strategy!");
766
767 // Update cells. TODO: Not for entire table.
768 updateCells();
769
770 emit changed();
771
772 ASSERT_VALID();
773 }
774
distributeRows(int startRow,int endRow)775 void PageItem_Table::distributeRows(int startRow, int endRow)
776 {
777 if (startRow < 0 || endRow > rows() - 1 || startRow > endRow)
778 return;
779
780 const int numRows = endRow - startRow + 1;
781 const double newHeight = (rowPosition(endRow) + rowHeight(endRow) - rowPosition(startRow)) / numRows;
782
783 for (int row = startRow; row <= endRow; ++row)
784 resizeRow(row, newHeight);
785 }
786
distributeColumns(int startColumn,int endColumn)787 void PageItem_Table::distributeColumns(int startColumn, int endColumn)
788 {
789 if (startColumn < 0 || endColumn > columns() - 1 || startColumn > endColumn)
790 return;
791
792 const int numColumns = endColumn - startColumn + 1;
793 const double newWidth = (columnPosition(endColumn) + columnWidth(endColumn) - columnPosition(startColumn)) / numColumns;
794
795 for (int column = startColumn; column <= endColumn; ++column)
796 resizeColumn(column, newWidth);
797 }
798
rowPosition(int row) const799 double PageItem_Table::rowPosition(int row) const
800 {
801 if (!validRow(row))
802 return 0.0;
803
804 return m_rowPositions.at(row);
805 }
806
columnWidth(int column) const807 double PageItem_Table::columnWidth(int column) const
808 {
809 if (!validColumn(column))
810 return 0.0;
811
812 return m_columnWidths.at(column);
813 }
814
resizeColumn(int column,double width,ResizeStrategy strategy)815 void PageItem_Table::resizeColumn(int column, double width, ResizeStrategy strategy)
816 {
817 ASSERT_VALID();
818
819 if (!validColumn(column))
820 return;
821
822 if (strategy == MoveFollowing)
823 resizeColumnMoveFollowing(column, width);
824 else if (strategy == ResizeFollowing)
825 resizeColumnResizeFollowing(column, width);
826 else
827 qWarning("Unknown resize strategy!");
828
829 // Update cells. TODO: Not for entire table.
830 updateCells();
831
832 emit changed();
833
834 ASSERT_VALID();
835 }
836
columnPosition(int column) const837 double PageItem_Table::columnPosition(int column) const
838 {
839 if (!validColumn(column))
840 return 0.0;
841
842 return m_columnPositions.at(column);
843 }
844
mergeCells(int row,int column,int numRows,int numCols)845 void PageItem_Table::mergeCells(int row, int column, int numRows, int numCols)
846 {
847 ASSERT_VALID();
848
849 if (!validCell(row, column) || !validCell(row + numRows - 1, column + numCols - 1))
850 return;
851
852 CellArea newArea(row, column, numCols, numRows);
853
854 // Unite intersecting areas.
855 QMutableListIterator<CellArea> areaIt(m_cellAreas);
856 while (areaIt.hasNext())
857 {
858 CellArea oldArea = areaIt.next();
859 if (newArea.intersects(oldArea))
860 {
861 // The two areas intersect, so unite them.
862 newArea = newArea.united(oldArea);
863
864 // Reset row/column span of old spanning cell, then remove old area.
865 TableCell oldSpanningCell = cellAt(oldArea.row(), oldArea.column());
866 oldSpanningCell.setRowSpan(1);
867 oldSpanningCell.setColumnSpan(1);
868 areaIt.remove();
869 }
870 }
871
872 // Set row/column span of new spanning cell, and add new area.
873 TableCell newSpanningCell = cellAt(newArea.row(), newArea.column());
874 newSpanningCell.setRowSpan(newArea.height());
875 newSpanningCell.setColumnSpan(newArea.width());
876 m_cellAreas.append(newArea);
877
878 // Update cells. TODO: Not for entire table.
879 updateCells();
880
881 // If merged area covers active position, move to the spanning cell.
882 if (newArea.contains(m_activeRow, m_activeColumn))
883 moveTo(newSpanningCell);
884
885 // Remove all cells covered by the merged area from the selection.
886 QMutableSetIterator<TableCell> cellIt(m_selection);
887 while (cellIt.hasNext())
888 {
889 TableCell cell = cellIt.next();
890 if (newArea.contains(cell.row(), cell.column()) &&
891 !(cell.row() == newArea.row() && cell.column() == newArea.column()))
892 cellIt.remove();
893 }
894
895 emit changed();
896
897 ASSERT_VALID();
898 }
899
splitCell(int row,int column,int numRows,int numCols)900 void PageItem_Table::splitCell(int row, int column, int numRows, int numCols)
901 {
902 // Not implemented.
903 emit changed();
904 }
905
selectedRows() const906 QSet<int> PageItem_Table::selectedRows() const
907 {
908 QSet<int> rows;
909 for (const TableCell& cell : selectedCells())
910 {
911 const int startRow = cell.row();
912 const int endRow = startRow + cell.rowSpan() - 1;
913 for (int row = startRow; row <= endRow; ++row)
914 rows.insert(row);
915 }
916 return rows;
917 }
918
selectedColumns() const919 QSet<int> PageItem_Table::selectedColumns() const
920 {
921 QSet<int> columns;
922 for (const TableCell& cell : selectedCells())
923 {
924 const int startColumn = cell.column();
925 const int endColumn = startColumn + cell.columnSpan() - 1;
926 for (int col = startColumn; col <= endColumn; ++col)
927 columns.insert(col);
928 }
929 return columns;
930 }
931
selectCell(int row,int column)932 void PageItem_Table::selectCell(int row, int column)
933 {
934 if (!validCell(row, column))
935 return;
936
937 m_selection.insert(cellAt(row, column));
938 emit selectionChanged();
939 }
940
selectCells(int startRow,int startColumn,int endRow,int endColumn)941 void PageItem_Table::selectCells(int startRow, int startColumn, int endRow, int endColumn)
942 {
943 if (!validCell(startRow, startColumn) || !validCell(endRow, endColumn))
944 return;
945
946 const TableCell startCell = cellAt(startRow, startColumn);
947 const TableCell endCell = cellAt(endRow, endColumn);
948
949 const int topRow = qMin(startCell.row(), endCell.row());
950 const int bottomRow = qMax(startCell.row() + startCell.rowSpan() - 1,
951 endCell.row() + endCell.rowSpan() - 1);
952
953 const int leftCol = qMin(startCell.column(), endCell.column());
954 const int rightCol = qMax(startCell.column() + startCell.columnSpan() - 1,
955 endCell.column() + endCell.columnSpan() - 1);
956
957 for (int row = topRow; row <= bottomRow; ++row)
958 for (int col = leftCol; col <= rightCol; ++col)
959 selectCell(row, col);
960 emit selectionChanged();
961 }
962
selectColumn(int column)963 void PageItem_Table::selectColumn(int column)
964 {
965 if (!validCell(0, column))
966 return;
967 selectCells(0, column, rows() - 1, column);
968 }
969
selectRow(int row)970 void PageItem_Table::selectRow(int row)
971 {
972 if (!validCell(row, 0))
973 return;
974 selectCells(row, 0, row, columns() - 1);
975 }
976
clearSelection()977 void PageItem_Table::clearSelection()
978 {
979 m_selection.clear();
980 emit selectionChanged();
981 }
982
cellAt(int row,int column) const983 TableCell PageItem_Table::cellAt(int row, int column) const
984 {
985 if (!validCell(row, column))
986 return TableCell();
987
988 TableCell cell = m_cellRows[row][column];
989
990 QList<CellArea>::const_iterator areaIt;
991 for (areaIt = m_cellAreas.begin(); areaIt != m_cellAreas.end(); ++areaIt)
992 {
993 CellArea area = (*areaIt);
994 if (area.contains(row, column))
995 {
996 // Cell was contained in merged area, so use spanning cell.
997 cell = m_cellRows[area.row()][area.column()];
998 break;
999 }
1000 }
1001
1002 return cell;
1003 }
1004
cellAt(const QPointF & point) const1005 TableCell PageItem_Table::cellAt(const QPointF& point) const
1006 {
1007 QPointF gridPoint = getTransform().inverted().map(point) - gridOffset();
1008
1009 if (!QRectF(0, 0, tableWidth(), tableHeight()).contains(gridPoint))
1010 return TableCell(); // Outside table grid.
1011
1012 return cellAt(
1013 std::upper_bound(m_rowPositions.begin(), m_rowPositions.end(), gridPoint.y()) - m_rowPositions.begin() - 1,
1014 std::upper_bound(m_columnPositions.begin(), m_columnPositions.end(), gridPoint.x()) - m_columnPositions.begin() - 1);
1015 }
1016
moveLeft()1017 void PageItem_Table::moveLeft()
1018 {
1019 if (m_activeCell.column() < 1)
1020 return;
1021
1022 // Move active position left and activate cell at new position.
1023 // m_activeColumn = m_activeCell.column() - 1;
1024 activateCell(cellAt(m_activeRow, m_activeCell.column() - 1));
1025 }
1026
moveRight()1027 void PageItem_Table::moveRight()
1028 {
1029 if (m_activeCell.column() + m_activeCell.columnSpan() >= columns())
1030 return;
1031
1032 // Move active position right and activate cell at new position.
1033 // m_activeColumn = m_activeCell.column() + m_activeCell.columnSpan();
1034 activateCell(cellAt(m_activeRow, m_activeCell.column() + m_activeCell.columnSpan()));
1035 }
1036
moveUp()1037 void PageItem_Table::moveUp()
1038 {
1039 if (m_activeCell.row() < 1)
1040 return;
1041
1042 // Move active position up and activate cell at new position.
1043 // m_activeRow = m_activeCell.row() - 1;
1044 activateCell(cellAt(m_activeCell.row() - 1, m_activeColumn));
1045 }
1046
moveDown()1047 void PageItem_Table::moveDown()
1048 {
1049 if (m_activeCell.row() + m_activeCell.rowSpan() >= rows())
1050 return;
1051 activateCell(cellAt(m_activeCell.row() + m_activeCell.rowSpan(), m_activeColumn));
1052
1053 // Deselect previous active cell and its text.
1054 // m_activeCell.textFrame()->setSelected(false);
1055 // m_activeCell.textFrame()->itemText.deselectAll();
1056
1057 // Move active logical position down.
1058 // m_activeRow = m_activeCell.row() + m_activeCell.rowSpan();
1059
1060 // Set the new active cell and select it.
1061 // m_activeCell = cellAt(m_activeRow, m_activeColumn);
1062 // m_activeCell.textFrame()->setSelected(true);
1063 // m_Doc->currentStyle = m_activeCell.textFrame()->currentStyle();
1064 }
1065
moveTo(const TableCell & cell)1066 void PageItem_Table::moveTo(const TableCell& cell)
1067 {
1068 // Activate the cell and move active position to its top left.
1069 activateCell(cell);
1070 }
1071
hitTest(const QPointF & point,double threshold) const1072 TableHandle PageItem_Table::hitTest(const QPointF& point, double threshold) const
1073 {
1074 const QPointF framePoint = getTransform().inverted().map(point);
1075 const QPointF gridPoint = framePoint - gridOffset();
1076 const QRectF gridRect = QRectF(0.0, 0.0, tableWidth(), tableHeight());
1077
1078 // Test if hit is outside frame.
1079 if (!QRectF(0.0, 0.0, width(), height()).contains(framePoint))
1080 return TableHandle(TableHandle::None);
1081
1082 // Test if hit is outside table.
1083 if (!gridRect.adjusted(-threshold, -threshold, threshold, threshold).contains(gridPoint))
1084 return TableHandle(TableHandle::None);
1085
1086 const double tableHeight = this->tableHeight();
1087 const double tableWidth = this->tableWidth();
1088 const double x = gridPoint.x();
1089 const double y = gridPoint.y();
1090
1091 // Test if hit is on left edge of table.
1092 if (x <= threshold)
1093 return TableHandle(TableHandle::RowSelect);
1094
1095 // Test if hit is on top edge of table.
1096 if (y <= threshold)
1097 return TableHandle(TableHandle::ColumnSelect);
1098
1099 // Test if hit is on bottom right corner of table.
1100 if (x >= tableWidth - threshold && y >= tableHeight - threshold)
1101 return TableHandle(TableHandle::TableResize);
1102
1103 // Test if hit is on right edge of table.
1104 if (y >= tableHeight - threshold && y <= tableHeight + threshold)
1105 return TableHandle(TableHandle::RowResize, rows() - 1);
1106
1107 // Test if hit is on bottom edge of table.
1108 if (x >= tableWidth - threshold && x <= tableWidth + threshold)
1109 return TableHandle(TableHandle::ColumnResize, columns() - 1);
1110
1111 const TableCell hitCell = cellAt(point);
1112 const QRectF hitRect = hitCell.boundingRect();
1113
1114 // Test if hit is on cell interior.
1115 if (hitRect.adjusted(threshold, threshold, -threshold, -threshold).contains(gridPoint))
1116 return TableHandle(TableHandle::CellSelect); // Hit interior of cell.
1117
1118 const double toLeft = x - hitRect.left();
1119 const double toRight = hitRect.right() - x;
1120 const double toTop = y - hitRect.top();
1121 const double toBottom = hitRect.bottom() - y;
1122 TableHandle handle(TableHandle::None);
1123
1124 // Test which side of the cell was hit.
1125 if (qMin(toLeft, toRight) < qMin(toTop, toBottom))
1126 {
1127 handle.setType(TableHandle::ColumnResize);
1128 handle.setIndex((toLeft < toRight ? hitCell.column() : hitCell.column() + hitCell.columnSpan()) - 1);
1129 }
1130 else
1131 {
1132 handle.setType(TableHandle::RowResize);
1133 handle.setIndex((toTop < toBottom ? hitCell.row() : hitCell.row() + hitCell.rowSpan()) - 1);
1134 }
1135 return handle;
1136 }
1137
adjustTableToFrame()1138 void PageItem_Table::adjustTableToFrame()
1139 {
1140 resize(width() - (maxLeftBorderWidth() + maxRightBorderWidth()) / 2,
1141 height() - (maxTopBorderWidth() + maxBottomBorderWidth()) / 2);
1142 }
1143
adjustFrameToTable()1144 void PageItem_Table::adjustFrameToTable()
1145 {
1146 if (!m_Doc)
1147 return;
1148
1149 m_Doc->sizeItem(effectiveWidth(), effectiveHeight(), this);
1150 }
1151
setFillColor(const QString & color)1152 void PageItem_Table::setFillColor(const QString& color)
1153 {
1154 m_style.setFillColor(color);
1155 emit changed();
1156 }
1157
unsetFillColor()1158 void PageItem_Table::unsetFillColor()
1159 {
1160 m_style.resetFillColor();
1161 emit changed();
1162 }
1163
fillColor() const1164 QString PageItem_Table::fillColor() const
1165 {
1166 return m_style.fillColor();
1167 }
1168
setFillShade(const double & shade)1169 void PageItem_Table::setFillShade(const double& shade)
1170 {
1171 m_style.setFillShade(shade);
1172 emit changed();
1173 }
1174
unsetFillShade()1175 void PageItem_Table::unsetFillShade()
1176 {
1177 m_style.resetFillShade();
1178 emit changed();
1179 }
1180
fillShade() const1181 double PageItem_Table::fillShade() const
1182 {
1183 return m_style.fillShade();
1184 }
1185
setLeftBorder(const TableBorder & border)1186 void PageItem_Table::setLeftBorder(const TableBorder& border)
1187 {
1188 m_style.setLeftBorder(border);
1189 updateCells(0, 0, rows() - 1, 0);
1190 emit changed();
1191 }
1192
unsetLeftBorder()1193 void PageItem_Table::unsetLeftBorder()
1194 {
1195 m_style.resetLeftBorder();
1196 updateCells(0, 0, rows() - 1, 0);
1197 emit changed();
1198 }
1199
leftBorder() const1200 TableBorder PageItem_Table::leftBorder() const
1201 {
1202 return m_style.leftBorder();
1203 }
1204
setRightBorder(const TableBorder & border)1205 void PageItem_Table::setRightBorder(const TableBorder& border)
1206 {
1207 m_style.setRightBorder(border);
1208 updateCells(0, columns() - 1, rows() - 1, columns() - 1);
1209 emit changed();
1210 }
1211
unsetRightBorder()1212 void PageItem_Table::unsetRightBorder()
1213 {
1214 m_style.resetRightBorder();
1215 updateCells(0, columns() - 1, rows() - 1, columns() - 1);
1216 emit changed();
1217 }
1218
rightBorder() const1219 TableBorder PageItem_Table::rightBorder() const
1220 {
1221 return m_style.rightBorder();
1222 }
1223
setTopBorder(const TableBorder & border)1224 void PageItem_Table::setTopBorder(const TableBorder& border)
1225 {
1226 m_style.setTopBorder(border);
1227 updateCells(0, 0, 0, columns() - 1);
1228 emit changed();
1229 }
1230
unsetTopBorder()1231 void PageItem_Table::unsetTopBorder()
1232 {
1233 m_style.resetTopBorder();
1234 updateCells(0, 0, 0, columns() - 1);
1235 emit changed();
1236 }
1237
topBorder() const1238 TableBorder PageItem_Table::topBorder() const
1239 {
1240 return m_style.topBorder();
1241 }
1242
setBottomBorder(const TableBorder & border)1243 void PageItem_Table::setBottomBorder(const TableBorder& border)
1244 {
1245 m_style.setBottomBorder(border);
1246 updateCells(rows() - 1, 0, rows() - 1, columns() - 1);
1247 emit changed();
1248 }
1249
unsetBottomBorder()1250 void PageItem_Table::unsetBottomBorder()
1251 {
1252 m_style.resetBottomBorder();
1253 updateCells(rows() - 1, 0, rows() - 1, columns() - 1);
1254 emit changed();
1255 }
1256
bottomBorder() const1257 TableBorder PageItem_Table::bottomBorder() const
1258 {
1259 return m_style.bottomBorder();
1260 }
1261
setStyle(const QString & style)1262 void PageItem_Table::setStyle(const QString& style)
1263 {
1264 doc()->dontResize = true;
1265 m_style.setParent(style);
1266 updateCells();
1267 doc()->dontResize = false;
1268 emit changed();
1269 }
1270
unsetStyle()1271 void PageItem_Table::unsetStyle()
1272 {
1273 doc()->dontResize = true;
1274 m_style.setParent("");
1275 updateCells();
1276 doc()->dontResize = false;
1277 emit changed();
1278 }
1279
unsetDirectFormatting()1280 void PageItem_Table::unsetDirectFormatting()
1281 {
1282 doc()->dontResize = true;
1283 QString parentStyle = m_style.parent();
1284 m_style.setParent("");
1285 m_style.erase();
1286 m_style.setParent(parentStyle);
1287 adjustTableToFrame();
1288 adjustFrameToTable();
1289 updateCells();
1290 doc()->dontResize = false;
1291 emit changed();
1292 }
1293
style() const1294 const TableStyle& PageItem_Table::style() const
1295 {
1296 return m_style;
1297 }
1298
styleName() const1299 QString PageItem_Table::styleName() const
1300 {
1301 return m_style.parent();
1302 }
1303
handleStyleChanged()1304 void PageItem_Table::handleStyleChanged()
1305 {
1306 doc()->dontResize = true;
1307 updateCells();
1308 doc()->dontResize = false;
1309 }
1310
applicableActions(QStringList & actionList)1311 void PageItem_Table::applicableActions(QStringList& actionList)
1312 {
1313 const bool tableEdit = m_Doc->appMode == modeEditTable;
1314 const int tableRows = rows();
1315 const int tableColumns = columns();
1316 const int selectedRows = tableEdit ? this->selectedRows().size() : 0;
1317 const int selectedColumns = tableEdit ? this->selectedColumns().size() : 0;
1318 const int selectedCells = tableEdit ? this->selectedCells().size() : 0;
1319
1320 if (!tableEdit || selectedCells<1)
1321 actionList << "tableInsertRows";
1322 if (!tableEdit || selectedCells<1)
1323 actionList << "tableInsertColumns";
1324 if (tableEdit && ((selectedRows < 1 && tableRows > 1) || (selectedRows > 0 && selectedRows < tableRows)))
1325 actionList << "tableDeleteRows";
1326 if ((selectedColumns < 1 && tableColumns > 1) || (selectedColumns > 0 && selectedColumns < tableColumns))
1327 actionList << "tableDeleteColumns";
1328 if (selectedCells > 1)
1329 actionList << "tableMergeCells";
1330 if (tableEdit)
1331 actionList << "tableSetRowHeights";
1332 if (tableEdit)
1333 actionList << "tableSetColumnWidths";
1334 if (!tableEdit || (tableEdit && selectedRows > 1))
1335 actionList << "tableDistributeRowsEvenly";
1336 if (!tableEdit || (tableEdit && selectedColumns > 1))
1337 actionList << "tableDistributeColumnsEvenly";
1338 actionList << "tableAdjustFrameToTable";
1339 actionList << "tableAdjustTableToFrame";
1340 }
1341
DrawObj_Item(ScPainter * p,QRectF)1342 void PageItem_Table::DrawObj_Item(ScPainter *p, QRectF /*e*/)
1343 {
1344 if (m_Doc->RePos)
1345 return;
1346
1347 p->save();
1348
1349 // Set the clip path.
1350 p->setupPolygon(&PoLine);
1351 p->setClipPath();
1352
1353 // Paint the table.
1354 m_tablePainter->paintTable(p);
1355
1356 p->restore();
1357
1358 // Paint the overflow marker.
1359 if (isOverflowing())
1360 drawOverflowMarker(p);
1361 }
1362
initialize(int numRows,int numColumns)1363 void PageItem_Table::initialize(int numRows, int numColumns)
1364 {
1365 Q_ASSERT(m_Doc);
1366 Q_ASSERT(numRows > 0);
1367 Q_ASSERT(numColumns > 0);
1368
1369 // Internal style is in document-wide style context.
1370 m_style.setContext(&m_Doc->tableStyles());
1371
1372 // Reserve space in lists.
1373 m_cellRows.reserve(numRows);
1374 m_rowHeights.reserve(numRows);
1375 m_rowPositions.reserve(numRows);
1376 m_columnWidths.reserve(numColumns);
1377 m_columnPositions.reserve(numColumns);
1378
1379 // Initialize rows of cells.
1380 QList<TableCell> initialRow;
1381 initialRow.append(TableCell(0, 0, this));
1382 m_cellRows.append(initialRow);
1383
1384 // Initialize row/column geometries.
1385 m_rowPositions.insert(0, 0.0);
1386 m_rowHeights.insert(0, 10.0);
1387 m_columnPositions.insert(0, 0.0);
1388 m_columnWidths.insert(0, 10.0);
1389
1390 // Initialize row/column counts.
1391 m_rows = 1;
1392 m_columns = 1;
1393
1394 // Insert any remaining rows and/or columns.
1395 insertRows(0, numRows - 1);
1396 insertColumns(0, numColumns - 1);
1397
1398 // Listen to changes in the document-wide cell/table style contexts.
1399 m_Doc->tableStyles().connect(this, SLOT(handleStyleChanged()));
1400 m_Doc->cellStyles().connect(this, SLOT(handleStyleChanged()));
1401
1402 m_activeCell = cellAt(0, 0);
1403 m_activeRow = 0;
1404 m_activeColumn = 0;
1405 }
1406
activateCell(const TableCell & cell)1407 void PageItem_Table::activateCell(const TableCell& cell)
1408 {
1409 ASSERT_VALID();
1410
1411 TableCell newActiveCell = validCell(cell.row(), cell.column()) ? cell : cellAt(0, 0);
1412
1413 // Deselect previous active cell and its text.
1414 PageItem_TextFrame* textFrame = m_activeCell.textFrame();
1415 textFrame->setSelected(false);
1416 textFrame->itemText.deselectAll();
1417 textFrame->HasSel = false;
1418
1419 // Set current style context befor assigning new active cell:
1420 // if old active cell ref count is 1, the old context might be deleted
1421 const ParagraphStyle& curStyle = newActiveCell.textFrame()->currentStyle();
1422 m_Doc->currentStyle.setContext(curStyle.context());
1423 m_Doc->currentStyle = newActiveCell.textFrame()->currentStyle();
1424
1425 // Set the new active cell and select it.
1426 m_activeCell = newActiveCell;
1427 m_activeCell.textFrame()->setSelected(true);
1428 m_activeRow = m_activeCell.row();
1429 m_activeColumn = m_activeCell.column();
1430 emit selectionChanged();
1431
1432 ASSERT_VALID();
1433 }
1434
maxLeftBorderWidth() const1435 double PageItem_Table::maxLeftBorderWidth() const
1436 {
1437 double maxWidth = 0.0;
1438 TableCell cell;
1439 for (int row = 0; row < rows(); row += cell.rowSpan())
1440 {
1441 cell = cellAt(row, 0);
1442 maxWidth = qMax(maxWidth, TableUtils::collapseBorders(cell.leftBorder(), leftBorder()).width());
1443 }
1444 return maxWidth;
1445 }
1446
maxRightBorderWidth() const1447 double PageItem_Table::maxRightBorderWidth() const
1448 {
1449 double maxWidth = 0.0;
1450 TableCell cell;
1451 for (int row = 0; row < rows(); row += cell.rowSpan())
1452 {
1453 cell = cellAt(row, columns() - 1);
1454 maxWidth = qMax(maxWidth, TableUtils::collapseBorders(leftBorder(), cell.rightBorder()).width());
1455 }
1456 return maxWidth;
1457 }
1458
maxTopBorderWidth() const1459 double PageItem_Table::maxTopBorderWidth() const
1460 {
1461 double maxWidth = 0.0;
1462 TableCell cell;
1463 for (int col = 0; col < columns(); col += cell.columnSpan())
1464 {
1465 cell = cellAt(0, col);
1466 maxWidth = qMax(maxWidth, TableUtils::collapseBorders(cell.topBorder(), topBorder()).width());
1467 }
1468 return maxWidth;
1469 }
1470
maxBottomBorderWidth() const1471 double PageItem_Table::maxBottomBorderWidth() const
1472 {
1473 double maxWidth = 0.0;
1474 TableCell cell;
1475 for (int col = 0; col < columns(); col += cell.columnSpan())
1476 {
1477 cell = cellAt(rows() - 1, col);
1478 maxWidth = qMax(maxWidth, TableUtils::collapseBorders(bottomBorder(), cell.bottomBorder()).width());
1479 }
1480 return maxWidth;
1481 }
1482
resizeRowMoveFollowing(int row,double height)1483 double PageItem_Table::resizeRowMoveFollowing(int row, double height)
1484 {
1485 // Set row height.
1486 double newHeight = m_rowHeights[row] = qMax(MinimumRowHeight, height);
1487
1488 // Move following rows.
1489 double rowPosition = m_rowPositions[row];
1490 for (int nextRow = row; nextRow < m_rowPositions.size(); ++nextRow)
1491 {
1492 m_rowPositions[nextRow] = rowPosition;
1493 rowPosition += m_rowHeights[nextRow];
1494 }
1495
1496 return newHeight;
1497 }
1498
resizeRowResizeFollowing(int row,double height)1499 double PageItem_Table::resizeRowResizeFollowing(int row, double height)
1500 {
1501 double oldHeight = m_rowHeights[row];
1502 double newHeight = oldHeight;
1503
1504 if (row < rows() - 1)
1505 {
1506 // Following row exists, so height is bounded at both ends.
1507 newHeight = m_rowHeights[row] = qBound(
1508 PageItem_Table::MinimumRowHeight, height,
1509 oldHeight + m_rowHeights[row + 1] - MinimumRowHeight);
1510
1511 // Resize/move following row.
1512 double heightChange = newHeight - oldHeight;
1513 m_rowPositions[row + 1] += heightChange;
1514 m_rowHeights[row + 1] -= heightChange;
1515 }
1516 else
1517 {
1518 // Last row, so height only bounded by MinimumRowHeight.
1519 newHeight = m_rowHeights[row] = qMax(MinimumRowHeight, height);
1520 }
1521
1522 return newHeight;
1523 }
1524
resizeColumnMoveFollowing(int column,double width)1525 double PageItem_Table::resizeColumnMoveFollowing(int column, double width)
1526 {
1527 // Set column width.
1528 double newWidth = m_columnWidths[column] = qMax(MinimumColumnWidth, width);
1529
1530 // Move following columns.
1531 double columnPosition = m_columnPositions[column];
1532 for (int nextColumn = column; nextColumn < m_columnPositions.size(); ++nextColumn)
1533 {
1534 m_columnPositions[nextColumn] = columnPosition;
1535 columnPosition += m_columnWidths[nextColumn];
1536 }
1537
1538 return newWidth;
1539 }
1540
resizeColumnResizeFollowing(int column,double width)1541 double PageItem_Table::resizeColumnResizeFollowing(int column, double width)
1542 {
1543 double oldWidth = m_columnWidths[column];
1544 double newWidth = oldWidth;
1545
1546 if (column < columns() - 1)
1547 {
1548 // Following column exists, so width is bounded at both ends.
1549 newWidth = m_columnWidths[column] = qBound(
1550 PageItem_Table::MinimumColumnWidth, width,
1551 oldWidth + m_columnWidths[column + 1] - MinimumColumnWidth);
1552
1553 // Resize/move following column.
1554 double widthChange = newWidth - oldWidth;
1555 m_columnPositions[column + 1] += widthChange;
1556 m_columnWidths[column + 1] -= widthChange;
1557 }
1558 else
1559 {
1560 // Last column, so width only bounded by MinimumColumnWidth.
1561 newWidth = m_columnWidths[column] = qMax(MinimumColumnWidth, width);
1562 }
1563
1564 return newWidth;
1565 }
1566
updateCells(int startRow,int startColumn,int endRow,int endColumn)1567 void PageItem_Table::updateCells(int startRow, int startColumn, int endRow, int endColumn)
1568 {
1569 if (startRow > endRow || startColumn > endColumn)
1570 return; // Invalid area.
1571
1572 if (!validCell(startRow, startColumn) || !validCell(endRow, endColumn))
1573 return; // Invalid area.
1574
1575 foreach (const QList<TableCell>& cellRow, m_cellRows)
1576 foreach (TableCell cell, cellRow)
1577 cell.updateContent();
1578 }
1579
updateSpans(int index,int number,ChangeType changeType)1580 void PageItem_Table::updateSpans(int index, int number, ChangeType changeType)
1581 {
1582 // Loop through areas of merged cells.
1583 QMutableListIterator<CellArea> areaIt(m_cellAreas);
1584 while (areaIt.hasNext())
1585 {
1586 CellArea oldArea = areaIt.next();
1587
1588 // Get a copy of the area adjusted to the change.
1589 CellArea newArea;
1590 switch (changeType)
1591 {
1592 case RowsInserted:
1593 newArea = oldArea.adjustedForRowInsertion(index, number);
1594 break;
1595 case RowsRemoved:
1596 newArea = oldArea.adjustedForRowRemoval(index, number);
1597 break;
1598 case ColumnsInserted:
1599 newArea = oldArea.adjustedForColumnInsertion(index, number);
1600 break;
1601 case ColumnsRemoved:
1602 newArea = oldArea.adjustedForColumnRemoval(index, number);
1603 break;
1604 default:
1605 break;
1606 }
1607
1608 // Check if the area was affected by the change.
1609 if (newArea != oldArea)
1610 {
1611 if (newArea.height() < 1 || newArea.width() < 1)
1612 {
1613 // Adjusted area was annihilated, so remove it.
1614 areaIt.remove();
1615 }
1616 else if (newArea.height() == 1 && newArea.width() == 1)
1617 {
1618 // Adjusted area is 1x1, so remove it.
1619 areaIt.remove();
1620
1621 // And reset row/column span of spanning cell to 1.
1622 TableCell oldSpanningCell = cellAt(oldArea.row(), oldArea.column());
1623 oldSpanningCell.setRowSpan(1);
1624 oldSpanningCell.setColumnSpan(1);
1625 }
1626 else
1627 {
1628 // Replace the area with the adjusted copy.
1629 areaIt.setValue(newArea);
1630
1631 // And set row/column spanning of spanning cell.
1632 TableCell newSpanningCell = cellAt(newArea.row(), newArea.column());
1633 newSpanningCell.setRowSpan(newArea.height());
1634 newSpanningCell.setColumnSpan(newArea.width());
1635 }
1636 }
1637 }
1638 }
1639
debug() const1640 void PageItem_Table::debug() const
1641 {
1642 qDebug() << "-------------------------------------------------";
1643 qDebug() << "Table Debug";
1644 qDebug() << "-------------------------------------------------";
1645 qDebug() << "m_rows: " << m_rows;
1646 qDebug() << "m_columns: " << m_columns;
1647 qDebug() << "m_columnPositions: " << m_columnPositions;
1648 qDebug() << "m_columnWidths: " << m_columnWidths;
1649 qDebug() << "m_rowPositions: " << m_rowPositions;
1650 qDebug() << "m_rowHeights: " << m_rowHeights;
1651 qDebug() << "m_cellAreas: " << m_cellAreas;
1652 qDebug() << "m_cellRows: ";
1653 for (const QList<TableCell>& cellRow : m_cellRows)
1654 for (const TableCell& cell : cellRow)
1655 qDebug() << cell.asString();
1656 qDebug() << "-------------------------------------------------";
1657 }
1658
assertValid() const1659 void PageItem_Table::assertValid() const
1660 {
1661 // Check list sizes.
1662 Q_ASSERT(rows() == m_rowPositions.size());
1663 Q_ASSERT(rows() == m_rowHeights.size());
1664 Q_ASSERT(columns() == m_columnPositions.size());
1665 Q_ASSERT(columns() == m_columnWidths.size());
1666 Q_ASSERT(rows() == m_cellRows.size());
1667 for (const QList<TableCell>& cellRow : m_cellRows)
1668 Q_ASSERT(columns() == cellRow.size());
1669
1670 for (int row = 0; row < rows(); ++row)
1671 {
1672 for (int col = 0; col < columns(); ++col)
1673 {
1674 TableCell cell = m_cellRows[row][col];
1675
1676 // Check that the cell reports correct row and column.
1677 Q_ASSERT(cell.row() == row);
1678 Q_ASSERT(cell.column() == col);
1679
1680 // Check that the row and column span is sane.
1681 Q_ASSERT(cell.rowSpan() >= 1 && cell.columnSpan() >= 1);
1682
1683 if (cell.rowSpan() > 1 || cell.columnSpan() > 1)
1684 {
1685 // Check that there's exactly one matching cell area.
1686 CellArea expectedArea(cell.row(), cell.column(), cell.columnSpan(), cell.rowSpan());
1687 Q_ASSERT(m_cellAreas.count(expectedArea) == 1);
1688 }
1689 }
1690 }
1691
1692 // Check that the active position is in this table.
1693 Q_ASSERT(validCell(m_activeRow, m_activeColumn));
1694
1695 // Check that the active cell is valid.
1696 Q_ASSERT(m_activeCell.isValid());
1697 Q_ASSERT(validCell(m_activeCell.row(), m_activeCell.column()));
1698
1699 // Check that selected cells are valid.
1700 for (const TableCell& cell : m_selection)
1701 {
1702 Q_ASSERT(cell.isValid());
1703 Q_ASSERT(validCell(cell.row(), cell.column()));
1704 }
1705
1706 for (const CellArea& cellArea : m_cellAreas)
1707 {
1708 // Check that the active cell is not covered.
1709 if (cellArea.contains(m_activeCell.row(), m_activeCell.column()))
1710 Q_ASSERT(m_activeCell.row() == cellArea.row() && m_activeCell.column() == cellArea.column());
1711
1712 // Check that the selected cells are not covered.
1713 for (const TableCell& cell : m_selection)
1714 if (cellArea.contains(cell.row(), cell.column()))
1715 Q_ASSERT(cell.row() == cellArea.row() && cell.column() == cellArea.column());
1716 }
1717 }
1718