File indexing completed on 2024-05-12 16:36:07
0001 /* This file is part of the KDE project 0002 Copyright 2010 Marijn Kruisselbrink <mkruisselbrink@kde.org> 0003 Copyright 2006-2007 Stefan Nikolaus <stefan.nikolaus@kdemail.net> 0004 Copyright 2005 Raphael Langerhorst <raphael.langerhorst@kdemail.net> 0005 Copyright 2004-2005 Tomas Mecir <mecirt@gmail.com> 0006 Copyright 2004-2006 Inge Wallin <inge@lysator.liu.se> 0007 Copyright 1999-2002,2004,2005 Laurent Montel <montel@kde.org> 0008 Copyright 2002-2005 Ariya Hidayat <ariya@kde.org> 0009 Copyright 2001-2003 Philipp Mueller <philipp.mueller@gmx.de> 0010 Copyright 2002-2003 Norbert Andres <nandres@web.de> 0011 Copyright 2003 Reinhart Geiser <geiseri@kde.org> 0012 Copyright 2003-2005 Meni Livne <livne@kde.org> 0013 Copyright 2003 Peter Simonsson <psn@linux.se> 0014 Copyright 1999-2002 David Faure <faure@kde.org> 0015 Copyright 2000-2002 Werner Trobin <trobin@kde.org> 0016 Copyright 1999,2002 Harri Porten <porten@kde.org> 0017 Copyright 2002 John Dailey <dailey@vt.edu> 0018 Copyright 1998-2000 Torben Weis <weis@kde.org> 0019 Copyright 2000 Bernd Wuebben <wuebben@kde.org> 0020 Copyright 2000 Simon Hausmann <hausmann@kde.org 0021 Copyright 1999 Stephan Kulow <coolo@kde.org> 0022 Copyright 1999 Michael Reiher <michael.reiher@gmx.de> 0023 Copyright 1999 Boris Wedl <boris.wedl@kfunigraz.ac.at> 0024 Copyright 1998-1999 Reginald Stadlbauer <reggie@kde.org> 0025 0026 This library is free software; you can redistribute it and/or 0027 modify it under the terms of the GNU Library General Public 0028 License as published by the Free Software Foundation; only 0029 version 2 of the License. 0030 0031 This library is distributed in the hope that it will be useful, 0032 but WITHOUT ANY WARRANTY; without even the implied warranty of 0033 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0034 Library General Public License for more details. 0035 0036 You should have received a copy of the GNU Library General Public License 0037 along with this library; see the file COPYING.LIB. If not, write to 0038 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0039 Boston, MA 02110-1301, USA. 0040 */ 0041 0042 // Local 0043 #include "CellView.h" 0044 0045 // Qt 0046 #include <QApplication> 0047 #include <QColor> 0048 #include <QPainter> 0049 #include <QRectF> 0050 #include <QStyleOptionComboBox> 0051 #include <QTextLayout> 0052 #include <QTextCursor> 0053 #include <QAbstractTextDocumentLayout> 0054 #ifdef CALLIGRA_SHEETS_MT 0055 #include <QMutex> 0056 #include <QMutexLocker> 0057 #include <QThread> 0058 #endif 0059 0060 // KF5 0061 #include <kcolorutils.h> 0062 #include <klocale.h> 0063 0064 // Calligra 0065 #include <KoPostscriptPaintDevice.h> 0066 #include <KoZoomHandler.h> 0067 0068 // Sheets 0069 #include "ApplicationSettings.h" 0070 #include "CalculationSettings.h" 0071 #include "CellStorage.h" 0072 #include "Condition.h" 0073 #include "Map.h" 0074 #include "PrintSettings.h" 0075 #include "RowColumnFormat.h" 0076 #include "RowFormatStorage.h" 0077 #include "Selection.h" 0078 #include "Sheet.h" 0079 #include "SheetPrint.h" 0080 #include "SheetView.h" 0081 #include "StyleManager.h" 0082 #include "Value.h" 0083 #include "ValueFormatter.h" 0084 0085 using namespace Calligra::Sheets; 0086 0087 const int s_borderSpace = 1; 0088 0089 class Q_DECL_HIDDEN CellView::Private : public QSharedData 0090 { 0091 public: 0092 Private(Style* defaultStyle, qreal defaultWidth, qreal defaultHeight) 0093 : style(*defaultStyle) 0094 , width(defaultWidth) 0095 , height(defaultHeight) 0096 , rtlOffset(0.0) 0097 , textX(0.0) 0098 , textY(0.0) 0099 , textWidth(0.0) 0100 , textHeight(0.0) 0101 , textLinesCount(0) 0102 , shrinkToFitFontSize(0.0) 0103 , hidden(false) 0104 , merged(false) 0105 , fittingHeight(true) 0106 , fittingWidth(true) 0107 , filterButton(false) 0108 , obscuredCellsX(0) 0109 , obscuredCellsY(0) 0110 , richText(0) 0111 #ifdef CALLIGRA_SHEETS_MT 0112 , mutex(new QMutex()) 0113 #endif 0114 {} 0115 ~Private() { 0116 } 0117 0118 Style style; 0119 qreal width; 0120 qreal height; 0121 0122 // difference in position between topleft corner of cell 0123 // and where painting should be started in the case of 0124 // merged and/or obscured cells in an rtl document 0125 qreal rtlOffset; 0126 0127 // Position and dimension of displayed text. 0128 // Doc coordinate system; points; no zoom 0129 qreal textX; 0130 qreal textY; 0131 qreal textWidth; 0132 qreal textHeight; 0133 0134 int textLinesCount; 0135 qreal shrinkToFitFontSize; 0136 0137 bool hidden : 1; 0138 bool merged : 1; 0139 bool fittingHeight : 1; 0140 bool fittingWidth : 1; 0141 bool filterButton : 1; 0142 // NOTE Stefan: A cell is either obscured by an other one or obscures others itself. 0143 // But never both at the same time, so we can share the memory for this. 0144 int obscuredCellsX : 16; // KS_colMax 0145 int obscuredCellsY : 24; // KS_rowMax 0146 0147 // This is the text we want to display. Not necessarily the same 0148 // as the user input, e.g. Cell::userInput()="1" and displayText="1.00". 0149 QString displayText; 0150 QSharedPointer<QTextDocument> richText; 0151 #ifdef CALLIGRA_SHEETS_MT 0152 QSharedPointer<QMutex> mutex; 0153 #endif 0154 public: 0155 void calculateCellBorders(const Cell&, SheetView* sheetView); 0156 void checkForFilterButton(const Cell&); 0157 void calculateTextSize(const QFont& font, const QFontMetricsF& fontMetrics); 0158 void calculateHorizontalTextSize(const QFont& font, const QFontMetricsF& fontMetrics); 0159 void calculateVerticalTextSize(const QFont& font, const QFontMetricsF& fontMetrics); 0160 void calculateAngledTextSize(const QFont& font, const QFontMetricsF& fontMetrics); 0161 void calculateRichTextSize(const QFont& font, const QFontMetricsF& fontMetrics); 0162 void truncateText(const QFont& font, const QFontMetricsF& fontMetrics); 0163 void truncateHorizontalText(const QFont& font, const QFontMetricsF& fontMetrics); 0164 void truncateVerticalText(const QFont& font, const QFontMetricsF& fontMetrics); 0165 void truncateAngledText(const QFont& font, const QFontMetricsF& fontMetrics); 0166 QFont calculateFont() const; 0167 QTextOption textOptions() const; 0168 }; 0169 0170 QFont CellView::Private::calculateFont() const 0171 { 0172 QFont f = style.font(); 0173 if (shrinkToFitFontSize > 0.0) 0174 f.setPointSizeF(shrinkToFitFontSize); 0175 return f; 0176 } 0177 0178 CellView::CellView(SheetView* sheetView) 0179 : d(new Private(sheetView->sheet()->map()->styleManager()->defaultStyle(), 0180 sheetView->sheet()->map()->defaultColumnFormat()->width(), 0181 sheetView->sheet()->map()->defaultRowFormat()->height())) 0182 { 0183 } 0184 0185 CellView::CellView(SheetView* sheetView, int col, int row) 0186 : d(sheetView->defaultCellView().d) 0187 { 0188 detach(); 0189 Q_ASSERT(1 <= col && col <= KS_colMax); 0190 Q_ASSERT(1 <= row && row <= KS_rowMax); 0191 0192 const Sheet* sheet = sheetView->sheet(); 0193 Cell cell(sheet, col, row); 0194 0195 // create the effective style 0196 if (cell.isPartOfMerged()) { 0197 d->merged = true; 0198 Cell masterCell = cell.masterCell(); 0199 d->style = sheetView->cellView(masterCell.column(), masterCell.row()).style(); 0200 } else { 0201 // lookup the 'normal' style 0202 Style style = cell.style(); 0203 if (!style.isDefault()) 0204 d->style = style; 0205 0206 // use conditional formatting attributes 0207 Conditions conditions = cell.conditions(); 0208 const Style conditionalStyle = conditions.testConditions(cell); 0209 if (!conditionalStyle.isEmpty()) { 0210 d->style.merge(conditionalStyle); 0211 } 0212 } 0213 0214 if (cell.width() != sheetView->sheet()->map()->defaultColumnFormat()->width()) 0215 d->width = cell.width(); 0216 if (cell.height() != sheetView->sheet()->map()->defaultRowFormat()->height()) 0217 d->height = cell.height(); 0218 0219 if (cell.sheet()->layoutDirection() == Qt::RightToLeft && cell.doesMergeCells()) { 0220 for (int i = 1; i <= cell.mergedXCells(); i++) { 0221 d->rtlOffset += cell.sheet()->columnFormat(cell.column() + i)->width(); 0222 } 0223 } 0224 0225 if (sheet->columnFormat(col)->isHiddenOrFiltered() || 0226 sheet->rowFormats()->isHiddenOrFiltered(row) || 0227 (sheet->columnFormat(col)->width() <= sheetView->viewConverter()->viewToDocumentY(2)) || 0228 (sheet->rowFormats()->rowHeight(row) <= sheetView->viewConverter()->viewToDocumentY(2))) { 0229 d->hidden = true; 0230 d->height = 0.0; 0231 d->width = 0.0; 0232 return; // nothing more to do 0233 } 0234 0235 d->checkForFilterButton(cell); 0236 0237 // do not touch the other Private members, just return here. 0238 if (cell.isDefault()) return; 0239 0240 Value value; 0241 // Display a formula if warranted. If not, simply display the value. 0242 if (cell.isFormula() && cell.sheet()->getShowFormula() && 0243 !(cell.sheet()->isProtected() && d->style.hideFormula())) { 0244 d->displayText = cell.userInput(); 0245 value.setFormat(Value::fmt_String); 0246 } else if (!cell.isEmpty()) { 0247 // Format the value appropriately and set the display text. 0248 // The format of the resulting value is used below to determine the alignment. 0249 d->displayText = cell.displayText(d->style, &value); 0250 0251 QSharedPointer<QTextDocument> doc = cell.richText(); 0252 if (!doc.isNull()) 0253 d->richText = QSharedPointer<QTextDocument>(doc->clone()); 0254 } 0255 0256 // Hide zero. 0257 if (sheet->getHideZero() && cell.value().isNumber() && cell.value().asFloat() == 0.0) 0258 d->displayText.clear(); 0259 0260 // If text is empty, there's nothing more to do. 0261 if (d->displayText.isEmpty()) 0262 return; 0263 0264 // horizontal align 0265 if (d->style.halign() == Style::HAlignUndefined) { 0266 // errors are always centered 0267 if (cell.value().type() == Value::Error) 0268 d->style.setHAlign(Style::Center); 0269 // if the format is text, align it according to the text direction 0270 else if (d->style.formatType() == Format::Text || value.format() == Value::fmt_String) 0271 d->style.setHAlign(d->displayText.isRightToLeft() ? Style::Right : Style::Left); 0272 // if the value is a boolean, center-align 0273 else if (cell.value().type() == Value::Boolean) 0274 d->style.setHAlign(Style::Center); 0275 // if the style does not define a specific format, align it according to the sheet layout 0276 else 0277 d->style.setHAlign(cell.sheet()->layoutDirection() == Qt::RightToLeft ? Style::Left : Style::Right); 0278 } 0279 // force left alignment, if there's a formula and it should be shown 0280 if (cell.isFormula() && sheet->getShowFormula() && !(sheet->isProtected() && d->style.hideFormula())) 0281 d->style.setHAlign(Style::Left); 0282 0283 0284 // figure out what border each side of the cell has 0285 d->calculateCellBorders(cell, sheetView); 0286 0287 makeLayout(sheetView, cell); 0288 } 0289 0290 CellView::CellView(const CellView& other) 0291 : d(other.d) 0292 { 0293 } 0294 0295 CellView& CellView::operator=(const CellView& other) 0296 { 0297 d = other.d; 0298 return *this; 0299 } 0300 0301 CellView::~CellView() 0302 { 0303 } 0304 0305 void CellView::detach() 0306 { 0307 d.detach(); 0308 if (!d->richText.isNull()) { 0309 #ifdef CALLIGRA_SHEETS_MT 0310 QMutexLocker(d->mutex.data()); 0311 #endif 0312 d->richText = QSharedPointer<QTextDocument>(d->richText->clone()); 0313 } 0314 #ifdef CALLIGRA_SHEETS_MT 0315 d->mutex = QSharedPointer<QMutex>(new QMutex()); 0316 #endif 0317 } 0318 0319 Style CellView::style() const 0320 { 0321 return d->style; 0322 } 0323 0324 qreal CellView::textWidth() const 0325 { 0326 return d->textWidth; 0327 } 0328 0329 qreal CellView::textHeight() const 0330 { 0331 return d->textHeight; 0332 } 0333 0334 QRectF CellView::textRect() const 0335 { 0336 return QRectF(d->textX, d->textY, d->textWidth, d->textWidth); 0337 } 0338 0339 QString CellView::testAnchor(SheetView* sheetView, const Cell& cell, qreal x, qreal y) const 0340 { 0341 if (sheetView->isObscured(cell.cellPosition())) { 0342 QPoint obscuringCell = sheetView->obscuringCell(cell.cellPosition()); 0343 Sheet* sheet = cell.sheet(); 0344 Cell otherCell = Cell(sheet, obscuringCell.x(), obscuringCell.y()); 0345 const CellView& otherView = sheetView->cellView(otherCell.column(), otherCell.row()); 0346 if (cell.column() != otherCell.column()) x += sheet->columnPosition(cell.column()) - sheet->columnPosition(otherCell.column()); 0347 if (cell.row() != otherCell.row()) y += sheet->rowPosition(cell.row()) - sheet->rowPosition(otherCell.row()); 0348 return otherView.testAnchor(sheetView, otherCell, x, y); 0349 } 0350 if (cell.link().isEmpty()) 0351 return QString(); 0352 0353 if (x > d->textX) if (x < d->textX + d->textWidth) 0354 if (y > d->textY - d->textHeight) if (y < d->textY) 0355 return cell.link(); 0356 0357 return QString(); 0358 } 0359 0360 bool CellView::hitTestFilterButton(const Cell& cell, const QRect& cellRect, const QPoint& position) const 0361 { 0362 if (!d->filterButton) 0363 return false; 0364 0365 QStyleOptionComboBox options; 0366 options.direction = cell.sheet()->layoutDirection(); 0367 options.editable = true; 0368 // options.fontMetrics = painter.fontMetrics(); 0369 options.frame = false; 0370 options.rect = cellRect; 0371 // options.subControls = QStyle::SC_ComboBoxEditField | QStyle::SC_ComboBoxArrow; 0372 0373 return QApplication::style()->hitTestComplexControl(QStyle::CC_ComboBox, &options, position) == QStyle::SC_ComboBoxArrow; 0374 } 0375 0376 // ================================================================ 0377 // Painting 0378 0379 0380 // Paint the cell. This is the main function that calls a lot of 0381 // helper functions. 0382 // 0383 // `paintRect' is the rectangle that we should paint on in document coordinates. 0384 // If the cell does not overlap this, we can return immediately. 0385 // `coordinate' is the origin (the upper left) of the cell in document 0386 // coordinates. 0387 // 0388 void CellView::paintCellContents(const QRectF& /*paintRect*/, QPainter& painter, const QRegion &clipRegion, 0389 const QPointF& coord, 0390 const Cell& cell, SheetView* sheetView) const 0391 { 0392 if (d->hidden) 0393 return; 0394 if (d->merged) 0395 return; 0396 if (sheetView->isObscured(cell.cellPosition())) 0397 return; 0398 0399 // somehow sometimes the obscured cell info in SheetView and the one stored in the 0400 // actual cell views gets out of sync... for now this hack will do to fix that 0401 if (d->obscuredCellsX || d->obscuredCellsY) { 0402 sheetView->obscureCells(cell.cellPosition(), d->obscuredCellsX, d->obscuredCellsY); 0403 } 0404 0405 // ---------------- Start the actual painting. ---------------- 0406 const QPointF coordinate(coord.x() - d->rtlOffset, coord.y()); 0407 0408 // If the rect of this cell doesn't intersect the rect that should 0409 // be painted, we can skip the rest and return. (Note that we need 0410 // to calculate `left' first before we can do this.) 0411 const QRectF cellRect(coordinate, QSizeF(d->width, d->height)); 0412 // Does the cell intersect the clipped painting region? 0413 if (!clipRegion.intersects(cellRect.toRect())) 0414 return; 0415 0416 // 0. Paint possible filter button 0417 if (d->filterButton && !dynamic_cast<QPrinter*>(painter.device())) 0418 paintFilterButton(painter, coordinate, cell, sheetView); 0419 0420 // 1. Paint possible comment indicator. 0421 if (!dynamic_cast<QPrinter*>(painter.device()) 0422 || cell.sheet()->printSettings()->printCommentIndicator()) 0423 paintCommentIndicator(painter, coordinate, cell); 0424 0425 // 2. Paint possible formula indicator. 0426 if (!dynamic_cast<QPrinter*>(painter.device()) 0427 || cell.sheet()->printSettings()->printFormulaIndicator()) { 0428 paintFormulaIndicator(painter, coordinate, cell); 0429 paintMatrixElementIndicator(painter, coordinate, cell); 0430 } 0431 0432 // 3. Paint possible indicator for clipped text. 0433 paintMoreTextIndicator(painter, coordinate); 0434 0435 // 5. Paint the text in the cell unless: 0436 // a) it is empty 0437 // b) something indicates that the text should not be painted 0438 // c) the sheet is protected and the cell is hidden. 0439 if (!d->displayText.isEmpty() 0440 && (!dynamic_cast<QPrinter*>(painter.device()) || style().printText()) 0441 && !(cell.sheet()->isProtected() 0442 && style().hideAll())) { 0443 paintText(painter, coordinate, cell); 0444 } 0445 } 0446 0447 void CellView::Private::calculateCellBorders(const Cell& cell, SheetView* sheetView) 0448 { 0449 const int col = cell.column(); 0450 const int row = cell.row(); 0451 const Sheet* sheet = sheetView->sheet(); 0452 0453 if (col != 1) { 0454 Style otherStyle = Cell(sheet, col - 1, row).style(); 0455 if (style.leftPenValue() < otherStyle.rightPenValue()) 0456 style.setLeftBorderPen(otherStyle.rightBorderPen()); 0457 } 0458 0459 if (col != KS_colMax) { 0460 Style otherStyle = Cell(sheet, col + 1, row).style(); 0461 if (style.rightPenValue() < otherStyle.leftPenValue()) 0462 style.setRightBorderPen(otherStyle.leftBorderPen()); 0463 } 0464 0465 if (row != 1) { 0466 Style otherStyle = Cell(sheet, col, row - 1).style(); 0467 if (style.topPenValue() < otherStyle.bottomPenValue()) 0468 style.setTopBorderPen(otherStyle.bottomBorderPen()); 0469 } 0470 0471 if (row != KS_rowMax) { 0472 Style otherStyle = Cell(sheet, col, row + 1).style(); 0473 if (style.bottomPenValue() < otherStyle.topPenValue()) 0474 style.setBottomBorderPen(otherStyle.topBorderPen()); 0475 } 0476 } 0477 0478 void CellView::paintCellBorders(const QRectF& paintRegion, QPainter& painter, const QRegion &clipRegion, 0479 const QPointF& coord, 0480 const QRect& cellRegion, 0481 const Cell& cell, SheetView* sheetView) const 0482 { 0483 const QPointF coordinate(coord.x() - d->rtlOffset, coord.y()); 0484 // If the rect of this cell doesn't intersect the rect that should 0485 // be painted, we can skip the rest and return. (Note that we need 0486 // to calculate `left' first before we can do this.) 0487 const QRectF cellRect(coordinate.x(), coordinate.y(), d->width, d->height); 0488 // Does the cell intersect the clipped painting region? 0489 if (!clipRegion.intersects(cellRect.toRect())) 0490 return; 0491 0492 const int col = cell.column(); 0493 const int row = cell.row(); 0494 0495 CellView::Borders paintBorder = CellView::NoBorder; 0496 0497 // borders 0498 // NOTE Stefan: the borders of the adjacent cells are taken for the case, 0499 // that the cell is located on the edge of the cell range, 0500 // that is painted. 0501 // NOTE Sebsauer: this won't work for merged cells 0502 if (col == 1) 0503 paintBorder |= LeftBorder; 0504 else if (!(d->style.leftPenValue() < sheetView->cellView(col - 1, row).style().rightPenValue())) 0505 // if ( d->style.leftPenValue() >= sheetView->cellView( col - 1, row ).style().rightPenValue() ) 0506 paintBorder |= LeftBorder; 0507 0508 if (col == KS_colMax) 0509 paintBorder |= CellView::RightBorder; 0510 else if (!(d->style.rightPenValue() < sheetView->cellView(col + 1, row).style().leftPenValue())) 0511 // if (d->style.rightPenValue() > sheetView->cellView(col + 1, row).style().leftPenValue()) 0512 paintBorder |= CellView::RightBorder; 0513 0514 if (row == 1) 0515 paintBorder |= TopBorder; 0516 else if (!(d->style.topPenValue() < sheetView->cellView(col, row - 1).style().bottomPenValue())) 0517 // if ( d->style.topPenValue() >= sheetView->cellView( col, row - 1 ).style().bottomPenValue() ) 0518 paintBorder |= TopBorder; 0519 0520 if (row == KS_rowMax) 0521 paintBorder |= BottomBorder; 0522 else if (!(d->style.bottomPenValue() < sheetView->cellView(col, row + 1).style().topPenValue())) 0523 // if (d->style.bottomPenValue() >= sheetView->cellView(col, row + 1).style().topPenValue()) 0524 paintBorder |= BottomBorder; 0525 0526 // Paint border if outermost cell or if the pen is more "worth" 0527 // than the border pen of the cell on the other side of the 0528 // border or if the cell on the other side is not painted. In 0529 // the latter case get the pen that is of more "worth" 0530 if (col == cellRegion.right()) 0531 paintBorder |= CellView::RightBorder; 0532 if (row == cellRegion.bottom()) 0533 paintBorder |= CellView::BottomBorder; 0534 if (col == cellRegion.left()) 0535 paintBorder |= CellView::LeftBorder; 0536 if (row == cellRegion.top()) 0537 paintBorder |= CellView::TopBorder; 0538 0539 // ---------------- Start the actual painting. ---------------- 0540 0541 // 2. Paint the borders of the cell if no other cell is forcing this 0542 // one, i.e. this cell is not part of a merged cell. 0543 // 0544 0545 // If we print pages, then we disable clipping, otherwise borders are 0546 // cut in the middle at the page borders. 0547 if (dynamic_cast<QPrinter*>(painter.device())) 0548 painter.setClipping(false); 0549 0550 // Paint the borders if this cell is not part of another merged cell. 0551 if (!d->merged) { 0552 paintCustomBorders(painter, paintRegion, coordinate, paintBorder, sheetView->sheet()->layoutDirection() == Qt::RightToLeft); 0553 } 0554 0555 // Turn clipping back on. 0556 if (dynamic_cast<QPrinter*>(painter.device())) 0557 painter.setClipping(true); 0558 0559 // 3. Paint diagonal lines and page borders. 0560 paintCellDiagonalLines(painter, coordinate); 0561 paintPageBorders(painter, coordinate, paintBorder, cell); 0562 } 0563 0564 // 0565 // Paint the background of this cell. 0566 // 0567 void CellView::paintCellBackground(QPainter& painter, const QRegion &clipRegion, const QPointF& coordinate) const 0568 { 0569 if (d->merged) 0570 return; 0571 0572 const QRectF cellRect = QRectF(coordinate, QSizeF(d->width, d->height)).translated(-d->rtlOffset, 0); 0573 // Does the cell intersect the clipped painting region? 0574 if (!clipRegion.intersects(cellRect.toRect())) 0575 return; 0576 0577 QBrush bgbrush = d->style.backgroundBrush(); 0578 0579 if (d->style.backgroundColor().isValid() && 0580 d->style.backgroundColor() != QApplication::palette().base().color()) { 0581 // optimization to not draw the background-color if the background-brush would overwrite it. 0582 if (bgbrush.style() != Qt::SolidPattern || bgbrush.color().alphaF() < 1.) { 0583 // disable antialiasing 0584 painter.setRenderHint(QPainter::Antialiasing, false); 0585 // Simply fill the cell with its background color, 0586 painter.fillRect(cellRect, d->style.backgroundColor()); 0587 // restore antialiasing 0588 painter.setRenderHint(QPainter::Antialiasing, true); 0589 } 0590 } 0591 0592 if (bgbrush.style() != Qt::NoBrush) { 0593 // Draw the background pattern. 0594 painter.fillRect(cellRect, bgbrush); 0595 } 0596 } 0597 0598 0599 // Paint the standard light grey borders that are always visible. 0600 // 0601 void CellView::paintDefaultBorders(QPainter& painter, const QRegion &clipRegion, const QRectF& paintRect, 0602 const QPointF &coord, 0603 Borders paintBorder, const QRect& cellRegion, 0604 const Cell& cell, SheetView* sheetView) const 0605 { 0606 const QPointF coordinate(coord.x() - d->rtlOffset, coord.y()); 0607 // Should the default borders be shown? 0608 if (!cell.sheet()->getShowGrid()) 0609 return; 0610 // Does the cell intersect the clipped painting region? 0611 if (!clipRegion.intersects(QRectF(coordinate, QSizeF(d->width, d->height)).toRect())) 0612 return; 0613 // Don't draw the border if a background fill-color was define. 0614 if (d->style.backgroundColor().isValid()) 0615 return; 0616 // disable antialiasing 0617 painter.setRenderHint(QPainter::Antialiasing, false); 0618 0619 /* 0620 *** Notes about optimization *** 0621 0622 This function was painting the top, left, right & bottom lines in almost 0623 all cells previously, contrary to what the comment below says should happen. 0624 There doesn't appear to be a UI option to enable or disable showing of the 0625 grid when printing at the moment, so I have disabled drawing of right and 0626 bottom borders for all cells. 0627 0628 I also couldn't work out under what conditions the variables dt / db would 0629 come out as anything other than 0 in the code for painting the various borders. 0630 The cell.effTopBorderPen / cell.effBottomBorderPen calls were taking 0631 up a lot of time according some profiling I did. If that code really is 0632 necessary, we need to find a more efficient way of getting the widths than 0633 grabbing the whole QPen object and asking it. 0634 0635 --Robert Knight (robertknight@gmail.com) 0636 */ 0637 const bool paintingToExternalDevice = dynamic_cast<QPrinter*>(painter.device()); 0638 0639 const int col = cell.column(); 0640 const int row = cell.row(); 0641 0642 paintBorder = CellView::NoBorder; 0643 0644 // borders 0645 // Paint border if outermost cell or if the pen is more "worth" 0646 // than the border pen of the cell on the other side of the 0647 // border or if the cell on the other side is not painted. In 0648 // the latter case get the pen that is of more "worth" 0649 0650 // Each cell is responsible for drawing it's top and left portions 0651 // of the "default" grid. --Or not drawing it if it shouldn't be 0652 // there. It's also responsible to paint the right and bottom, if 0653 // it is the last cell on a print out. 0654 0655 // NOTE Stefan: the borders of the adjacent cells are taken for the case, 0656 // that the cell is located on the edge of the cell range, 0657 // that is painted. 0658 if (col == 1) 0659 paintBorder |= LeftBorder; 0660 else if (!(d->style.leftPenValue() < sheetView->cellView(col - 1, row).style().rightPenValue())) 0661 // if ( d->style.leftPenValue() >= sheetView->cellView( col - 1, row ).style().rightPenValue() ) 0662 paintBorder |= LeftBorder; 0663 if (col == KS_colMax) 0664 paintBorder |= CellView::RightBorder; 0665 else if (!(d->style.rightPenValue() < sheetView->cellView(col + cell.mergedXCells(), row).style().leftPenValue())) { 0666 if (d->style.rightPenValue() > sheetView->cellView(col + cell.mergedXCells(), row).style().leftPenValue()) 0667 paintBorder |= CellView::RightBorder; 0668 } 0669 if (row == 1) 0670 paintBorder |= TopBorder; 0671 else if (!(d->style.topPenValue() < sheetView->cellView(col, row - 1).style().bottomPenValue())) 0672 // if ( d->style.topPenValue() >= sheetView->cellView( col, row - 1 ).style().bottomPenValue() ) 0673 paintBorder |= TopBorder; 0674 if (row == KS_rowMax) 0675 paintBorder |= BottomBorder; 0676 else if (!(d->style.bottomPenValue() < sheetView->cellView(col, row + cell.mergedYCells()).style().topPenValue())) { 0677 if (d->style.bottomPenValue() >= sheetView->cellView(col, row + cell.mergedYCells()).style().topPenValue()) 0678 paintBorder |= BottomBorder; 0679 } 0680 0681 // Check merging... 0682 if (d->merged) { 0683 // by default: none ... 0684 paintBorder = NoBorder; 0685 // left and top, only if it's the left or top of the merged cell 0686 if (cell.column() == cell.masterCell().column()) 0687 paintBorder |= LeftBorder; 0688 else if (cell.row() == cell.masterCell().row()) 0689 paintBorder |= TopBorder; 0690 // right and bottom only, if it's the outermost border of the cell region being painted 0691 // checked later below... 0692 } 0693 0694 // Check obscuring... 0695 if (sheetView->isObscured(cell.cellPosition())) { 0696 // by default: none ... 0697 paintBorder = NoBorder; 0698 // left and top, only if it's the left or top of the obscuring cell 0699 const QPoint obscuringCell = sheetView->obscuringCell(cell.cellPosition()); 0700 if (cell.column() == obscuringCell.x()) 0701 paintBorder |= LeftBorder; 0702 else if (cell.row() == obscuringCell.y()) 0703 paintBorder |= TopBorder; 0704 // right and bottom only, if it's the outermost border of the cell region being painted 0705 // checked later below... 0706 } 0707 0708 // Force painting, if it's the outermost border of the cell region being painted... 0709 if (col == cellRegion.right()) 0710 paintBorder |= CellView::RightBorder; 0711 if (row == cellRegion.bottom()) 0712 paintBorder |= CellView::BottomBorder; 0713 if (col == cellRegion.left()) 0714 paintBorder |= CellView::LeftBorder; 0715 if (row == cellRegion.top()) 0716 paintBorder |= CellView::TopBorder; 0717 0718 // Check, if a custom border exists and the default border is not necessary... 0719 if (d->style.leftBorderPen().style() != Qt::NoPen) 0720 paintBorder &= ~LeftBorder; 0721 if (d->style.topBorderPen().style() != Qt::NoPen) 0722 paintBorder &= ~TopBorder; 0723 if (d->style.rightBorderPen().style() != Qt::NoPen) 0724 paintBorder &= ~RightBorder; 0725 if (d->style.bottomBorderPen().style() != Qt::NoPen) 0726 paintBorder &= ~BottomBorder; 0727 0728 // Check if the neighbor-cells do have a background fill-color in which case the border is not drawn. 0729 if(col > 1 && sheetView->cellView(col - 1, row).style().backgroundColor().isValid()) 0730 paintBorder &= ~LeftBorder; 0731 if(col < KS_colMax && sheetView->cellView(col + 1, row).style().backgroundColor().isValid()) 0732 paintBorder &= ~RightBorder; 0733 if(row > 1 && sheetView->cellView(col, row - 1).style().backgroundColor().isValid()) 0734 paintBorder &= ~TopBorder; 0735 if(row < KS_rowMax && sheetView->cellView(col, row + 1).style().backgroundColor().isValid()) 0736 paintBorder &= ~BottomBorder; 0737 0738 // Check if we're in right-to-left mode, and if so swap left and right border bits 0739 if (cell.sheet()->layoutDirection() == Qt::RightToLeft) { 0740 Borders lrBorder = paintBorder & (LeftBorder | RightBorder); 0741 paintBorder &= ~(LeftBorder | RightBorder); 0742 if (lrBorder & LeftBorder) { 0743 paintBorder |= RightBorder; 0744 } 0745 if (lrBorder & RightBorder) { 0746 paintBorder |= LeftBorder; 0747 } 0748 } 0749 0750 // Set the single-pixel width pen for drawing the borders with. 0751 // NOTE Stefan: Use a cosmetic pen (width = 0), because we want the grid always one pixel wide 0752 painter.setPen(QPen(cell.sheet()->map()->settings()->gridColor(), 0, Qt::SolidLine)); 0753 0754 QLineF line; 0755 0756 // The left border. 0757 if (paintBorder & LeftBorder) { 0758 int dt = 0; 0759 int db = 0; 0760 0761 #if 0 // CALLIGRA_SHEETS_WIP_STYLE_BORDER 0762 if (cellRef.x() > 1) { 0763 Cell *cell_west = Cell(cell.sheet(), cellRef.x() - 1, 0764 cellRef.y()); 0765 QPen t = cell_west->effTopBorderPen(cellRef.x() - 1, cellRef.y()); 0766 QPen b = cell_west->effBottomBorderPen(cellRef.x() - 1, cellRef.y()); 0767 0768 if (t.style() != Qt::NoPen) 0769 dt = (t.width() + 1) / 2; 0770 if (b.style() != Qt::NoPen) 0771 db = (t.width() / 2); 0772 } 0773 #endif 0774 0775 // If we are on paper printout, we limit the length of the lines. 0776 // On paper, we always have full cells, on screen not. 0777 if (paintingToExternalDevice) { 0778 line = QLineF(qMax(paintRect.left(), coordinate.x()), 0779 qMax(paintRect.top(), coordinate.y() + dt), 0780 qMin(paintRect.right(), coordinate.x()), 0781 qMin(paintRect.bottom(), coordinate.y() + d->height - db)); 0782 } else { 0783 line = QLineF(coordinate.x(), 0784 coordinate.y() + dt, 0785 coordinate.x(), 0786 coordinate.y() + d->height - db); 0787 } 0788 painter.drawLine(line); 0789 } 0790 0791 0792 // The top border. 0793 if (paintBorder & TopBorder) { 0794 int dl = 0; 0795 int dr = 0; 0796 0797 #if 0 // CALLIGRA_SHEETS_WIP_STYLE_BORDER 0798 if (cellRef.y() > 1) { 0799 Cell *cell_north = Cell(cell.sheet(), cellRef.x(), 0800 cellRef.y() - 1); 0801 0802 QPen l = cell_north->effLeftBorderPen(cellRef.x(), cellRef.y() - 1); 0803 QPen r = cell_north->effRightBorderPen(cellRef.x(), cellRef.y() - 1); 0804 0805 if (l.style() != Qt::NoPen) 0806 dl = (l.width() - 1) / 2 + 1; 0807 if (r.style() != Qt::NoPen) 0808 dr = r.width() / 2; 0809 } 0810 #endif 0811 0812 // If we are on paper printout, we limit the length of the lines. 0813 // On paper, we always have full cells, on screen not. 0814 if (paintingToExternalDevice) { 0815 line = QLineF(qMax(paintRect.left(), coordinate.x() + dl), 0816 qMax(paintRect.top(), coordinate.y()), 0817 qMin(paintRect.right(), coordinate.x() + d->width - dr), 0818 qMin(paintRect.bottom(), coordinate.y())); 0819 } else { 0820 line = QLineF(coordinate.x() + dl, 0821 coordinate.y(), 0822 coordinate.x() + d->width - dr, 0823 coordinate.y()); 0824 } 0825 painter.drawLine(line); 0826 } 0827 0828 0829 // The right border. 0830 if (paintBorder & RightBorder) { 0831 int dt = 0; 0832 int db = 0; 0833 0834 #if 0 // CALLIGRA_SHEETS_WIP_STYLE_BORDER 0835 if (cellRef.x() < KS_colMax) { 0836 Cell *cell_east = Cell(cell.sheet(), cellRef.x() + 1, 0837 cellRef.y()); 0838 0839 QPen t = cell_east->effTopBorderPen(cellRef.x() + 1, cellRef.y()); 0840 QPen b = cell_east->effBottomBorderPen(cellRef.x() + 1, cellRef.y()); 0841 0842 if (t.style() != Qt::NoPen) 0843 dt = (t.width() + 1) / 2; 0844 if (b.style() != Qt::NoPen) 0845 db = (t.width() / 2); 0846 } 0847 #endif 0848 0849 //painter.setPen( QPen( cell.sheet()->map()->settings()->gridColor(), 1, Qt::SolidLine ) ); 0850 0851 // If we are on paper printout, we limit the length of the lines. 0852 // On paper, we always have full cells, on screen not. 0853 if (dynamic_cast<QPrinter*>(painter.device())) { 0854 line = QLineF(qMax(paintRect.left(), coordinate.x() + d->width), 0855 qMax(paintRect.top(), coordinate.y() + dt), 0856 qMin(paintRect.right(), coordinate.x() + d->width), 0857 qMin(paintRect.bottom(), coordinate.y() + d->height - db)); 0858 } else { 0859 line = QLineF(coordinate.x() + d->width, 0860 coordinate.y() + dt, 0861 coordinate.x() + d->width, 0862 coordinate.y() + d->height - db); 0863 } 0864 painter.drawLine(line); 0865 } 0866 0867 // The bottom border. 0868 if (paintBorder & BottomBorder) { 0869 int dl = 0; 0870 int dr = 0; 0871 #if 0 // CALLIGRA_SHEETS_WIP_STYLE_BORDER 0872 if (cellRef.y() < KS_rowMax) { 0873 Cell *cell_south = Cell(cell.sheet(), cellRef.x(), 0874 cellRef.y() + 1); 0875 0876 QPen l = cell_south->effLeftBorderPen(cellRef.x(), cellRef.y() + 1); 0877 QPen r = cell_south->effRightBorderPen(cellRef.x(), cellRef.y() + 1); 0878 0879 if (l.style() != Qt::NoPen) 0880 dl = (l.width() - 1) / 2 + 1; 0881 if (r.style() != Qt::NoPen) 0882 dr = r.width() / 2; 0883 } 0884 #endif 0885 0886 // If we are on paper printout, we limit the length of the lines. 0887 // On paper, we always have full cells, on screen not. 0888 if (dynamic_cast<QPrinter*>(painter.device())) { 0889 line = QLineF(qMax(paintRect.left(), coordinate.x() + dl), 0890 qMax(paintRect.top(), coordinate.y() + d->height), 0891 qMin(paintRect.right(), coordinate.x() + d->width - dr), 0892 qMin(paintRect.bottom(), coordinate.y() + d->height)); 0893 } else { 0894 line = QLineF(coordinate.x() + dl, 0895 coordinate.y() + d->height, 0896 coordinate.x() + d->width - dr, 0897 coordinate.y() + d->height); 0898 } 0899 painter.drawLine(line); 0900 } 0901 0902 // restore antialiasing 0903 painter.setRenderHint(QPainter::Antialiasing, true); 0904 } 0905 0906 0907 // Paint a comment indicator if the cell has a comment. 0908 // 0909 void CellView::paintCommentIndicator(QPainter& painter, 0910 const QPointF& coordinate, 0911 const Cell& cell) const 0912 { 0913 // Point the little corner if there is a comment attached 0914 // to this cell. 0915 if ((!cell.comment().isEmpty()) 0916 && d->width > 10.0 0917 && d->height > 10.0 0918 && (cell.sheet()->printSettings()->printCommentIndicator() 0919 || (!dynamic_cast<QPrinter*>(painter.device()) && cell.sheet()->getShowCommentIndicator()))) { 0920 QColor penColor = Qt::red; 0921 0922 // If background has high red part, switch to blue. 0923 if (qRed(d->style.backgroundColor().rgb()) > 127 && 0924 qGreen(d->style.backgroundColor().rgb()) < 80 && 0925 qBlue(d->style.backgroundColor().rgb()) < 80) { 0926 penColor = Qt::blue; 0927 } 0928 0929 // Get the triangle. 0930 QPolygonF polygon(3); 0931 polygon.clear(); 0932 if (cell.sheet()->layoutDirection() == Qt::RightToLeft) { 0933 polygon << QPointF(coordinate.x() + 6.0, coordinate.y()); 0934 polygon << QPointF(coordinate.x(), coordinate.y()); 0935 polygon << QPointF(coordinate.x(), coordinate.y() + 6.0); 0936 } else { 0937 polygon << QPointF(coordinate.x() + cell.width() - 5.0, coordinate.y()); 0938 polygon << QPointF(coordinate.x() + cell.width(), coordinate.y()); 0939 polygon << QPointF(coordinate.x() + cell.width(), coordinate.y() + 5.0); 0940 } 0941 0942 // And draw it. 0943 painter.setBrush(QBrush(penColor)); 0944 painter.setPen(Qt::NoPen); 0945 painter.drawPolygon(polygon); 0946 } 0947 } 0948 0949 0950 // Paint a small rectangle if this cell holds a formula. 0951 // 0952 void CellView::paintFormulaIndicator(QPainter& painter, 0953 const QPointF& coordinate, 0954 const Cell& cell) const 0955 { 0956 if (cell.isFormula() && 0957 cell.sheet()->getShowFormulaIndicator() && 0958 d->width > 10.0 && 0959 d->height > 10.0) { 0960 QColor penColor = Qt::blue; 0961 // If background has high blue part, switch to red. 0962 if (qRed(d->style.backgroundColor().rgb()) < 80 && 0963 qGreen(d->style.backgroundColor().rgb()) < 80 && 0964 qBlue(d->style.backgroundColor().rgb()) > 127) { 0965 penColor = Qt::red; 0966 } 0967 0968 // Get the triangle... 0969 QPolygonF polygon(3); 0970 polygon.clear(); 0971 if (cell.sheet()->layoutDirection() == Qt::RightToLeft) { 0972 polygon << QPointF(coordinate.x() + d->width - 6.0, coordinate.y() + d->height); 0973 polygon << QPointF(coordinate.x() + d->width, coordinate.y() + d->height); 0974 polygon << QPointF(coordinate.x() + d->width, coordinate.y() + d->height - 6.0); 0975 } else { 0976 polygon << QPointF(coordinate.x(), coordinate.y() + d->height - 6.0); 0977 polygon << QPointF(coordinate.x(), coordinate.y() + d->height); 0978 polygon << QPointF(coordinate.x() + 6.0, coordinate.y() + d->height); 0979 } 0980 0981 // ...and draw it. 0982 painter.setBrush(QBrush(penColor)); 0983 painter.setPen(Qt::NoPen); 0984 painter.drawPolygon(polygon); 0985 } 0986 } 0987 0988 0989 // Paint a small rectangle if this cell is an element of a matrix. 0990 // 0991 void CellView::paintMatrixElementIndicator(QPainter& painter, 0992 const QPointF& coordinate, 0993 const Cell& cell) const 0994 { 0995 if (cell.isLocked() && 0996 cell.sheet()->getShowFormulaIndicator() && 0997 d->width > 10.0 && 0998 d->height > 10.0) { 0999 QColor penColor = Qt::blue; 1000 // If background has high blue part, switch to red. 1001 if (qRed(d->style.backgroundColor().rgb()) < 80 && 1002 qGreen(d->style.backgroundColor().rgb()) < 80 && 1003 qBlue(d->style.backgroundColor().rgb()) > 127) { 1004 penColor = Qt::red; 1005 } 1006 1007 // Get the triangle... 1008 QPolygonF polygon(3); 1009 polygon.clear(); 1010 if (cell.sheet()->layoutDirection() == Qt::RightToLeft) { 1011 polygon << QPointF(coordinate.x() + d->width - 6.0, coordinate.y()); 1012 polygon << QPointF(coordinate.x() + d->width, coordinate.y()); 1013 polygon << QPointF(coordinate.x() + d->width, coordinate.y() + 6.0); 1014 } else { 1015 polygon << QPointF(coordinate.x(), coordinate.y() + 6.0); 1016 polygon << QPointF(coordinate.x(), coordinate.y()); 1017 polygon << QPointF(coordinate.x() + 6.0, coordinate.y()); 1018 } 1019 1020 // ...and draw it. 1021 painter.setBrush(QBrush(penColor)); 1022 painter.setPen(Qt::NoPen); 1023 painter.drawPolygon(polygon); 1024 } 1025 } 1026 1027 1028 // Paint an indicator that the text in the cell is cut. 1029 // 1030 void CellView::paintMoreTextIndicator(QPainter& painter, const QPointF& coordinate) const 1031 { 1032 if (d->style.shrinkToFit()) 1033 return; 1034 // Show a red triangle when it's not possible to write all text in cell. 1035 // Don't print the red triangle if we're printing. 1036 if (!d->fittingWidth && 1037 !dynamic_cast<QPrinter*>(painter.device()) && 1038 d->height > 4.0 && 1039 d->width > 4.0) { 1040 QColor penColor = Qt::red; 1041 // If background has high red part, switch to blue. 1042 if (qRed(d->style.backgroundColor().rgb()) > 127 1043 && qGreen(d->style.backgroundColor().rgb()) < 80 1044 && qBlue(d->style.backgroundColor().rgb()) < 80) { 1045 penColor = Qt::blue; 1046 } 1047 1048 // Get the triangle... 1049 QPolygonF polygon(3); 1050 polygon.clear(); 1051 if (d->displayText.isRightToLeft()) { 1052 polygon << QPointF(coordinate.x() + 4.0, coordinate.y() + d->height / 2.0 - 4.0); 1053 polygon << QPointF(coordinate.x(), coordinate.y() + d->height / 2.0); 1054 polygon << QPointF(coordinate.x() + 4.0, coordinate.y() + d->height / 2.0 + 4.0); 1055 } else { 1056 polygon << QPointF(coordinate.x() + d->width - 4.0, coordinate.y() + d->height / 2.0 - 4.0); 1057 polygon << QPointF(coordinate.x() + d->width, coordinate.y() + d->height / 2.0); 1058 polygon << QPointF(coordinate.x() + d->width - 4.0, coordinate.y() + d->height / 2.0 + 4.0); 1059 } 1060 1061 // ...and paint it. 1062 painter.setBrush(QBrush(penColor)); 1063 painter.setPen(Qt::NoPen); 1064 painter.drawPolygon(polygon); 1065 } 1066 } 1067 1068 static int fixAngle(int angle) { 1069 angle = ((angle % 360) + 360) % 360; 1070 // now angle is between 0 and 359, but 181-359 should be -179 - -1 1071 if (angle > 180) angle = angle - 360; 1072 return angle; 1073 } 1074 1075 // Paint the real contents of a cell - the text. 1076 // 1077 void CellView::paintText(QPainter& painter, 1078 const QPointF& coordinate, 1079 const Cell& cell) const 1080 { 1081 QColor textColorPrint = d->style.fontColor(); 1082 // Resolve the text color if invalid (=default). 1083 if (!textColorPrint.isValid()) { 1084 if (dynamic_cast<QPrinter*>(painter.device())) 1085 textColorPrint = Qt::black; 1086 else 1087 textColorPrint = QApplication::palette().text().color(); 1088 1089 QColor bgColor = d->style.backgroundColor(); 1090 if (bgColor.isValid()) { 1091 qreal contrast = KColorUtils::contrastRatio(bgColor, textColorPrint); 1092 if (contrast < 3) 1093 textColorPrint = QColor(255 - textColorPrint.red(), 255 - textColorPrint.green(), 255 - textColorPrint.blue()); 1094 } 1095 } 1096 1097 QPen tmpPen(textColorPrint, 0); 1098 QFont font = d->calculateFont(); 1099 1100 // Check for red font color for negative values. 1101 if (cell.value().isNumber() 1102 && !(cell.sheet()->getShowFormula() 1103 && !(cell.sheet()->isProtected() 1104 && style().hideFormula()))) { 1105 if (style().floatColor() == Style::NegRed && cell.value().asFloat() < 0.0) 1106 tmpPen.setColor(Qt::red); 1107 } 1108 1109 // Check for blue color, for hyperlink. 1110 if (!cell.link().isEmpty()) { 1111 tmpPen.setColor(QApplication::palette().link().color()); 1112 font.setUnderline(true); 1113 } 1114 painter.setPen(tmpPen); 1115 1116 qreal indent = 0.0; 1117 qreal offsetCellTooShort = 0.0; 1118 const Style::HAlign hAlign = d->style.halign(); 1119 const Style::VAlign vAlign = d->style.valign(); 1120 1121 // Apply indent if text is align to left not when text is at right or middle. 1122 if (hAlign == Style::Left && !cell.isEmpty()) { 1123 indent = d->style.indentation(); 1124 } 1125 1126 // Made an offset, otherwise ### is under red triangle. 1127 if (hAlign == Style::Right && !cell.isEmpty() && !d->fittingWidth) 1128 offsetCellTooShort = 4; 1129 1130 KoPostscriptPaintDevice device; 1131 const QFontMetricsF fontMetrics(font, &device); 1132 qreal fontOffset = 0.0; 1133 1134 if (style().valign() == Style::Bottom || style().valign() == Style::VAlignUndefined) { 1135 // The descent can be bigger then the underlinePos which seems to be the case at least 1136 // with thai characters. So, to be sure we are not losing the bottom characters we are 1137 // using either the underlinePos() (plus 1 for the underline itself) or the descent() 1138 // whatever is bigger to be sure we do not but of anything, neither parts of the font 1139 // nor the an optional displayed underline. 1140 // According to the docs this is still not perfect cause some unusual character in 1141 // an exotic language can still be bigger but we ignore that here. 1142 fontOffset = qMax(fontMetrics.underlinePos() + 1, fontMetrics.descent()); 1143 } 1144 1145 const int tmpAngle = fixAngle(d->style.angle()); 1146 const bool tmpVerticalText = d->style.verticalText(); 1147 // force multiple rows on explicitly set line breaks 1148 const bool tmpMultiRow = d->style.wrapText() || d->displayText.contains('\n'); 1149 const bool tmpVDistributed = vAlign == Style::VJustified || vAlign == Style::VDistributed; 1150 const bool tmpRichText = !d->richText.isNull(); 1151 1152 1153 // set a clipping region for non-rotated text 1154 painter.save(); 1155 if (tmpAngle == 0) { 1156 painter.setClipRect(QRectF(coordinate.x(), coordinate.y(), d->width, d->height), Qt::IntersectClip); 1157 } 1158 1159 1160 // Actually paint the text. 1161 // There are 5 possible cases: 1162 // - One line of plain text , horizontal 1163 // - Angled text 1164 // - Multiple rows of plain text , horizontal 1165 // - Vertical text 1166 // - Rich text 1167 if (!tmpMultiRow && !tmpVerticalText && !tmpAngle && !tmpRichText) { 1168 // Case 1: The simple case, one line, no angle. 1169 1170 const QPointF position(indent + coordinate.x() - offsetCellTooShort, 1171 coordinate.y() + d->textY - fontOffset); 1172 drawText(painter, position, d->displayText.split('\n'), cell); 1173 } else if (tmpAngle != 0) { 1174 // Case 2: an angle. 1175 1176 painter.rotate(tmpAngle); 1177 qreal x; 1178 1179 if (tmpAngle > 0) 1180 x = indent + d->textX + coordinate.x(); 1181 else 1182 x = indent + d->textX + coordinate.x() 1183 - (fontMetrics.descent() + fontMetrics.ascent()) * ::sin(tmpAngle * M_PI / 180); 1184 qreal y; 1185 if (tmpAngle > 0) 1186 y = coordinate.y() + d->textY; 1187 else 1188 y = coordinate.y() + d->textY + d->textHeight; 1189 if (tmpAngle < -90 || tmpAngle > 90) { 1190 x += d->textWidth; 1191 } 1192 const QPointF position(x * ::cos(tmpAngle * M_PI / 180) + y * ::sin(tmpAngle * M_PI / 180), 1193 -x * ::sin(tmpAngle * M_PI / 180) + y * ::cos(tmpAngle * M_PI / 180)); 1194 drawText(painter, position, d->displayText.split('\n'), cell); 1195 painter.rotate(-tmpAngle); 1196 } else if (tmpMultiRow && !tmpVerticalText && !tmpRichText) { 1197 // Case 3: Multiple rows, but horizontal. 1198 const QPointF position(indent + coordinate.x(), coordinate.y() + d->textY); 1199 const qreal space = d->height - d->textHeight; 1200 const qreal lineSpacing = tmpVDistributed && space > 0 ? space / (d->textLinesCount - 1) : 0; 1201 drawText(painter, position, d->displayText.split('\n'), cell, lineSpacing); 1202 } else if (tmpVerticalText && !d->displayText.isEmpty()) { 1203 // Case 4: Vertical text. 1204 QStringList textLines = d->displayText.split('\n'); 1205 qreal dx = 0.0; 1206 1207 qreal space = d->width - d->textWidth; 1208 if (space > 0) { 1209 switch (hAlign) { 1210 case Style::Center: 1211 case Style::HAlignUndefined: 1212 dx += space / 2; 1213 break; 1214 case Style::Right: 1215 dx += space; 1216 break; 1217 default: 1218 break; 1219 } 1220 } 1221 for (int i = 0; i < textLines.count(); ++i) { 1222 QStringList textColumn; 1223 for (int j = 0; j < textLines[i].count(); ++j) 1224 textColumn << QString(textLines[i][j]); 1225 1226 const QPointF position(indent + coordinate.x() + dx, coordinate.y() + d->textY); 1227 drawText(painter, position, textColumn, cell); 1228 dx += fontMetrics.maxWidth(); 1229 } 1230 } else if (tmpRichText) { 1231 // Case 5: Rich text. 1232 #ifdef CALLIGRA_SHEETS_MT 1233 QMutexLocker(d->mutex.data()); 1234 #endif 1235 QTextDocument* doc = d->richText->clone(); 1236 doc->setDefaultTextOption(d->textOptions()); 1237 doc->setUseDesignMetrics(true); 1238 const QPointF position(coordinate.x() + indent, 1239 coordinate.y() + d->textY - d->textHeight); 1240 painter.translate(position); 1241 1242 QAbstractTextDocumentLayout::PaintContext ctx; 1243 ctx.palette.setColor(QPalette::Text, textColorPrint); 1244 doc->documentLayout()->draw(&painter, ctx); 1245 delete doc; 1246 // painter.drawRect(QRectF(QPointF(0, 0), QSizeF(d->textWidth, d->textHeight))); 1247 } 1248 1249 painter.restore(); 1250 } 1251 1252 1253 // Paint page borders on the page. Only do this on the screen. 1254 // 1255 void CellView::paintPageBorders(QPainter& painter, const QPointF& coordinate, 1256 Borders paintBorder, const Cell& cell) const 1257 { 1258 // Not screen? Return immediately. 1259 if (dynamic_cast<QPrinter*>(painter.device())) 1260 return; 1261 1262 if (! cell.sheet()->isShowPageOutline()) 1263 return; 1264 1265 SheetPrint* const print = cell.sheet()->print(); 1266 const PrintSettings *const settings = cell.sheet()->printSettings(); 1267 const QRect printRange = settings->printRegion().lastRange(); 1268 1269 // Draw page borders 1270 QLineF line; 1271 1272 if (cell.column() >= printRange.left() 1273 && cell.column() <= printRange.right() + 1 1274 && cell.row() >= printRange.top() 1275 && cell.row() <= printRange.bottom() + 1) { 1276 if (print->isColumnOnNewPage(cell.column()) 1277 && cell.row() <= printRange.bottom()) { 1278 painter.setPen(QPen(cell.sheet()->map()->settings()->pageOutlineColor(), 0)); 1279 1280 if (cell.sheet()->layoutDirection() == Qt::RightToLeft) 1281 line = QLineF(coordinate.x() + d->width, coordinate.y(), 1282 coordinate.x() + d->width, coordinate.y() + d->height); 1283 else 1284 line = QLineF(coordinate.x(), coordinate.y(), 1285 coordinate.x(), coordinate.y() + d->height); 1286 painter.drawLine(line); 1287 } 1288 1289 if (print->isRowOnNewPage(cell.row()) && 1290 (cell.column() <= printRange.right())) { 1291 painter.setPen(QPen(cell.sheet()->map()->settings()->pageOutlineColor(), 0)); 1292 line = QLineF(coordinate.x(), coordinate.y(), 1293 coordinate.x() + d->width, coordinate.y()); 1294 painter.drawLine(line); 1295 } 1296 1297 if (paintBorder & RightBorder) { 1298 if (print->isColumnOnNewPage(cell.column() + 1) 1299 && cell.row() <= printRange.bottom()) { 1300 painter.setPen(QPen(cell.sheet()->map()->settings()->pageOutlineColor(), 0)); 1301 1302 if (cell.sheet()->layoutDirection() == Qt::RightToLeft) 1303 line = QLineF(coordinate.x(), coordinate.y(), 1304 coordinate.x(), coordinate.y() + d->height); 1305 else 1306 line = QLineF(coordinate.x() + d->width, coordinate.y(), 1307 coordinate.x() + d->width, coordinate.y() + d->height); 1308 painter.drawLine(line); 1309 } 1310 } 1311 1312 if (paintBorder & BottomBorder) { 1313 if (print->isRowOnNewPage(cell.row() + 1) 1314 && cell.column() <= printRange.right()) { 1315 painter.setPen(QPen(cell.sheet()->map()->settings()->pageOutlineColor(), 0)); 1316 line = QLineF(coordinate.x(), coordinate.y() + d->height, 1317 coordinate.x() + d->width, coordinate.y() + d->height); 1318 painter.drawLine(line); 1319 } 1320 } 1321 } 1322 } 1323 1324 1325 // Paint the cell borders. 1326 // 1327 void CellView::paintCustomBorders(QPainter& painter, const QRectF& paintRect, 1328 const QPointF& coordinate, Borders paintBorder, bool rtl) const 1329 { 1330 //Sanity check: If we are not painting any of the borders then the function 1331 //really shouldn't be called at all. 1332 if (paintBorder == NoBorder) 1333 return; 1334 1335 // Must create copies of these since otherwise the zoomIt() 1336 // operation will be performed on them repeatedly. 1337 QPen leftPen(d->style.leftBorderPen()); 1338 QPen rightPen(d->style.rightBorderPen()); 1339 QPen topPen(d->style.topBorderPen()); 1340 QPen bottomPen(d->style.bottomBorderPen()); 1341 1342 // if in right-to-left mode, swap left&right pens and bits 1343 if (rtl) { 1344 qSwap(leftPen, rightPen); 1345 Borders lrBorder = paintBorder & (LeftBorder | RightBorder); 1346 paintBorder &= ~(LeftBorder | RightBorder); 1347 if (lrBorder & LeftBorder) { 1348 paintBorder |= RightBorder; 1349 } 1350 if (lrBorder & RightBorder) { 1351 paintBorder |= LeftBorder; 1352 } 1353 } 1354 1355 // Determine the pens that should be used for drawing 1356 // the borders. 1357 // NOTE Stefan: This prevents cosmetic pens (width==0). 1358 int left_penWidth = qMax(1, (leftPen.width())); 1359 int right_penWidth = qMax(1, (rightPen.width())); 1360 int top_penWidth = qMax(1, (topPen.width())); 1361 int bottom_penWidth = qMax(1, (bottomPen.width())); 1362 1363 leftPen.setWidth(left_penWidth); 1364 rightPen.setWidth(right_penWidth); 1365 topPen.setWidth(top_penWidth); 1366 bottomPen.setWidth(bottom_penWidth); 1367 1368 QLineF line; 1369 1370 if ((paintBorder & LeftBorder) && leftPen.style() != Qt::NoPen) { 1371 painter.setPen(leftPen); 1372 1373 //debugSheetsRender <<" painting left border of cell" << name(); 1374 1375 // If we are on paper printout, we limit the length of the lines. 1376 // On paper, we always have full cells, on screen not. 1377 if (dynamic_cast<QPrinter*>(painter.device())) { 1378 if (coordinate.x() >= paintRect.left() + left_penWidth / 2) 1379 line = QLineF(coordinate.x() , 1380 qMax(paintRect.top(), coordinate.y()), 1381 coordinate.x(), 1382 qMin(paintRect.bottom(), coordinate.y() + d->height)); 1383 } else { 1384 line = QLineF(coordinate.x(), coordinate.y(), coordinate.x(), coordinate.y() + d->height); 1385 } 1386 painter.drawLine(line); 1387 } 1388 1389 if ((paintBorder & RightBorder) && rightPen.style() != Qt::NoPen) { 1390 painter.setPen(rightPen); 1391 1392 //debugSheetsRender <<" painting right border of cell" << name(); 1393 1394 // If we are on paper printout, we limit the length of the lines. 1395 // On paper, we always have full cells, on screen not. 1396 if (dynamic_cast<QPrinter*>(painter.device())) { 1397 // Only print the right border if it is visible. 1398 if (coordinate.x() + d->width <= paintRect.right() + right_penWidth / 2) 1399 line = QLineF(coordinate.x() + d->width, 1400 qMax(paintRect.top(), coordinate.y()), 1401 coordinate.x() + d->width, 1402 qMin(paintRect.bottom(), coordinate.y() + d->height)); 1403 } else { 1404 line = QLineF(coordinate.x() + d->width, coordinate.y(), coordinate.x() + d->width, coordinate.y() + d->height); 1405 } 1406 painter.drawLine(line); 1407 } 1408 1409 if ((paintBorder & TopBorder) && topPen.style() != Qt::NoPen) { 1410 painter.setPen(topPen); 1411 1412 //debugSheetsRender <<" painting top border of cell" << name() 1413 // << " [" << coordinate.x() << "," << coordinate.x() + d->width 1414 // << ": " << coordinate.x() + d->width - coordinate.x() << "]" << endl; 1415 1416 // If we are on paper printout, we limit the length of the lines. 1417 // On paper, we always have full cells, on screen not. 1418 if (dynamic_cast<QPrinter*>(painter.device())) { 1419 if (coordinate.y() >= paintRect.top() + top_penWidth / 2) 1420 line = QLineF(qMax(paintRect.left(), coordinate.x()), 1421 coordinate.y(), 1422 qMin(paintRect.right(), coordinate.x() + d->width), 1423 coordinate.y()); 1424 } else { 1425 line = QLineF(coordinate.x(), coordinate.y(), coordinate.x() + d->width, coordinate.y()); 1426 } 1427 painter.drawLine(line); 1428 } 1429 1430 if ((paintBorder & BottomBorder) && bottomPen.style() != Qt::NoPen) { 1431 painter.setPen(bottomPen); 1432 1433 //debugSheetsRender <<" painting bottom border of cell" << name() 1434 // << " [" << coordinate.x() << "," << coordinate.x() + d->width 1435 // << ": " << coordinate.x() + d->width - coordinate.x() << "]" << endl; 1436 1437 // If we are on paper printout, we limit the length of the lines. 1438 // On paper, we always have full cells, on screen not. 1439 if (dynamic_cast<QPrinter*>(painter.device())) { 1440 if (coordinate.y() + d->height <= paintRect.bottom() + bottom_penWidth / 2) 1441 line = QLineF(qMax(paintRect.left(), coordinate.x()), 1442 coordinate.y() + d->height, 1443 qMin(paintRect.right(), coordinate.x() + d->width), 1444 coordinate.y() + d->height); 1445 } else { 1446 line = QLineF(coordinate.x(), coordinate.y() + d->height, coordinate.x() + d->width, coordinate.y() + d->height); 1447 } 1448 painter.drawLine(line); 1449 } 1450 } 1451 1452 1453 // Paint diagonal lines through the cell. 1454 // 1455 void CellView::paintCellDiagonalLines(QPainter& painter, const QPointF& coordinate) const 1456 { 1457 if (d->merged) 1458 return; 1459 1460 QPen fallDiagonalPen(d->style.fallDiagonalPen()); 1461 QPen goUpDiagonalPen(d->style.goUpDiagonalPen()); 1462 1463 if (fallDiagonalPen.style() != Qt::NoPen) { 1464 painter.setPen(fallDiagonalPen); 1465 painter.drawLine(QLineF(coordinate.x(), coordinate.y(), coordinate.x() + d->width, coordinate.y() + d->height)); 1466 } 1467 1468 if (goUpDiagonalPen.style() != Qt::NoPen) { 1469 painter.setPen(goUpDiagonalPen); 1470 painter.drawLine(QLineF(coordinate.x(), coordinate.y() + d->height, coordinate.x() + d->width, coordinate.y())); 1471 } 1472 } 1473 1474 void CellView::paintFilterButton(QPainter& painter, const QPointF& coordinate, 1475 const Cell& cell, SheetView* sheetView) const 1476 { 1477 Q_UNUSED(cell); 1478 QStyleOptionComboBox options; 1479 options.direction = cell.sheet()->layoutDirection(); 1480 options.editable = true; 1481 options.fontMetrics = painter.fontMetrics(); 1482 options.frame = false; 1483 options.rect = sheetView->viewConverter()->documentToView(QRectF(coordinate, QSizeF(d->width, d->height))).toRect(); 1484 options.subControls =/* QStyle::SC_ComboBoxEditField | */QStyle::SC_ComboBoxArrow; 1485 1486 painter.save(); 1487 painter.scale(sheetView->viewConverter()->viewToDocumentX(1.0), 1488 sheetView->viewConverter()->viewToDocumentY(1.0)); 1489 QApplication::style()->drawComplexControl(QStyle::CC_ComboBox, &options, &painter); 1490 painter.restore(); 1491 } 1492 1493 1494 // Cut d->displayText, so that it only holds the part that can be displayed. 1495 // 1496 // Used in paintText(). 1497 // 1498 QString CellView::textDisplaying(const QFontMetricsF& fm, const Cell& cell) 1499 { 1500 Style::HAlign hAlign = style().halign(); 1501 if (!d->fittingWidth) 1502 hAlign = Style::Left; // force left alignment, if text does not fit 1503 1504 const bool isNumeric = cell.value().isNumber(); 1505 1506 if (style().wrapText() || d->richText) { 1507 // For wrapping text and richtext always draw all text 1508 return d->displayText; 1509 } else if (style().angle() != 0) { 1510 // Rotated text, return all text 1511 return d->displayText; 1512 } else if (!style().verticalText()) { 1513 // Non-vertical text: the ordinary case. 1514 1515 // Not enough space but align to left 1516 qreal len = 0.0; 1517 1518 // If it fits in the width, chopping won't do anything 1519 if (d->fittingWidth) { 1520 return d->displayText; 1521 } 1522 1523 len = d->width; 1524 #if 0 1525 for (int i = cell.column(); i <= cell.column() + d->obscuredCellsX; i++) { 1526 ColumnFormat *cl2 = cell.sheet()->columnFormat(i); 1527 len += cl2->width() - 1.0; //-1.0 because the pixel in between 2 cells is shared between both cells 1528 } 1529 #endif 1530 1531 QString tmp; 1532 qreal tmpIndent = 0.0; 1533 if (!cell.isEmpty()) 1534 tmpIndent = style().indentation(); 1535 1536 KLocale* locale = cell.sheet()->map()->calculationSettings()->locale(); 1537 1538 // Estimate worst case length to reduce the number of iterations. 1539 int start = qRound((len - 4.0 - 1.0 - tmpIndent) / fm.width('.')); 1540 start = qMin(d->displayText.length(), start); 1541 int idxOfDecimal = d->displayText.indexOf(locale->decimalSymbol()); 1542 if (idxOfDecimal < 0) idxOfDecimal = d->displayText.length(); 1543 1544 // Start out with the whole text, cut one character at a time, and 1545 // when the text finally fits, return it. 1546 for (int i = start; i >= 0; i--) { 1547 //Note that numbers are always treated as left-aligned since if we have to cut digits off, they should 1548 //always be the least significant ones at the end of the string 1549 if (hAlign == Style::Left || hAlign == Style::HAlignUndefined || isNumeric) 1550 tmp = d->displayText.left(i); 1551 else if (hAlign == Style::Right) 1552 tmp = d->displayText.right(i); 1553 else 1554 tmp = d->displayText.mid((d->displayText.length() - i) / 2, i); 1555 1556 if (isNumeric) { 1557 //For numeric values, we can cut off digits after the decimal point to make it fit, 1558 //but not the integer part of the number. 1559 //If this number still contains a fraction part then we don't need to do anything, if we have run 1560 //out of space to fit even the integer part of the number then display ######### 1561 //TODO Perhaps try to display integer part in standard form if there is not enough room for it? 1562 1563 if (i < idxOfDecimal) { 1564 //first try removing thousands separators before replacing text with ###### 1565 QString tmp2 = d->displayText; 1566 tmp2.remove(locale->thousandsSeparator()); 1567 int sepCount = d->displayText.length() - tmp2.length(); 1568 if (i < idxOfDecimal - sepCount) { 1569 tmp = QString().fill('#', i); 1570 } else { 1571 tmp = tmp2.left(i); 1572 } 1573 } 1574 } 1575 1576 // 4 equal length of red triangle +1 point. 1577 if (fm.width(tmp) + tmpIndent < len - 4.0 - 1.0) { 1578 if (style().angle() != 0) { 1579 QString tmp2; 1580 const qreal rowHeight = cell.sheet()->rowFormats()->rowHeight(cell.row()); 1581 if (d->textHeight > rowHeight) { 1582 for (int j = d->displayText.length(); j != 0; j--) { 1583 tmp2 = d->displayText.left(j); 1584 if (fm.width(tmp2) < rowHeight - 1.0) { 1585 return d->displayText.left(qMin(tmp.length(), tmp2.length())); 1586 } 1587 } 1588 } else 1589 return tmp; 1590 1591 } else 1592 return tmp; 1593 } 1594 } 1595 return QString(""); 1596 } else if (style().verticalText()) { 1597 // Vertical text. 1598 1599 const qreal rowHeight = cell.sheet()->rowFormats()->rowHeight(cell.row()); 1600 qreal tmpIndent = 0.0; 1601 1602 // Not enough space but align to left. 1603 qreal len = 0.0; 1604 1605 len = d->width; 1606 #if 0 1607 for (int i = cell.column(); i <= cell.column() + d->obscuredCellsX; i++) { 1608 ColumnFormat *cl2 = cell.sheet()->columnFormat(i); 1609 1610 // -1.0 because the pixel in between 2 cells is shared between both cells 1611 len += cl2->width() - 1.0; 1612 } 1613 #endif 1614 1615 if (!cell.isEmpty()) 1616 tmpIndent = style().indentation(); 1617 1618 if ((d->textWidth + tmpIndent > len) || d->textWidth == 0.0) 1619 return QString(""); 1620 1621 for (int i = d->displayText.length(); i != 0; i--) { 1622 if (fm.ascent() + fm.descent() * i < rowHeight - 1.0) 1623 return d->displayText.left(i); 1624 } 1625 1626 return QString(""); 1627 } 1628 1629 QString tmp; 1630 for (int i = d->displayText.length(); i != 0; i--) { 1631 tmp = d->displayText.left(i); 1632 1633 // 4 equals length of red triangle +1 pixel 1634 if (fm.width(tmp) < d->width - 4.0 - 1.0) 1635 return tmp; 1636 } 1637 1638 return QString(); 1639 } 1640 1641 1642 // End of Painting 1643 // ================================================================ 1644 1645 // ================================================================ 1646 // Layout 1647 1648 1649 // Recalculate the entire layout. This includes the following members: 1650 // 1651 // d->textX, d->textY 1652 // d->textWidth, d->textHeight 1653 // d->obscuredCellsX, d->obscuredCellsY 1654 // d->width, d->height 1655 // 1656 // and, of course, 1657 // 1658 // d->displayText 1659 // 1660 void CellView::makeLayout(SheetView* sheetView, const Cell& cell) 1661 { 1662 // Up to here, we have just cared about the contents, not the 1663 // painting of it. Now it is time to see if the contents fits into 1664 // the cell and, if not, maybe rearrange the outtext a bit. 1665 1666 // First, create a device independent font and its metrics. 1667 KoPostscriptPaintDevice device; 1668 QFont font(d->style.font(), &device); 1669 QFontMetricsF fontMetrics(font, &device); 1670 1671 // Then calculate text dimensions, i.e. d->textWidth and d->textHeight, 1672 // and check whether the text fits into the cell dimension by the way. 1673 1674 d->calculateTextSize(font, fontMetrics); 1675 d->shrinkToFitFontSize = 0.0; 1676 1677 //if shrink-to-fit is enabled, try to find a font size so that the string fits into the cell 1678 if (d->style.shrinkToFit()) { 1679 int lower = 1; 1680 int upper = font.pointSize() * 2; 1681 int siz = 0; 1682 while (lower != upper) { 1683 siz = static_cast<int>( std::ceil( lower + ( upper - lower ) / 2. ) ); 1684 font.setPointSizeF(siz / 2.); 1685 fontMetrics = QFontMetricsF(font, &device); 1686 d->calculateTextSize(font, fontMetrics); 1687 if (d->fittingWidth) 1688 lower = siz; 1689 else 1690 upper = siz - 1; 1691 } 1692 d->shrinkToFitFontSize = upper / 2.0; 1693 font.setPointSizeF(upper / 2.0); 1694 fontMetrics = QFontMetricsF(font, &device); 1695 d->calculateTextSize(font, fontMetrics); // update fittingWidth et al 1696 d->fittingWidth = true; 1697 } 1698 1699 // Obscure horizontal cells, if necessary. 1700 if (!d->fittingWidth) { 1701 obscureHorizontalCells(sheetView, cell); 1702 // Recalculate the text dimensions and check whether the text fits. 1703 d->calculateTextSize(font, fontMetrics); 1704 } 1705 1706 // Obscure vertical cells, if necessary. 1707 if (!d->fittingHeight) { 1708 obscureVerticalCells(sheetView, cell); 1709 // Recalculate the text dimensions and check whether the text fits. 1710 d->calculateTextSize(font, fontMetrics); 1711 } 1712 1713 // text still does not fit into cell dimension? 1714 if (!d->fittingWidth || !d->fittingHeight) { 1715 // Truncate the output text. 1716 d->displayText = textDisplaying(fontMetrics, cell); 1717 // d->truncateText(font, fontMetrics); 1718 // // Recalculate the text dimensions and check whether the text fits. 1719 // d->calculateTextSize(font, fontMetrics); 1720 } 1721 // Recalculate the text offset. 1722 textOffset(fontMetrics, cell); 1723 } 1724 1725 1726 void CellView::calculateCellDimension(const Cell& cell) 1727 { 1728 Q_UNUSED(cell); 1729 #if 0 1730 qreal width = cell.sheet()->columnFormat(cell.column())->width(); 1731 qreal height = cell.sheet()->rowFormat(cell.row())->height(); 1732 1733 // Calculate extraWidth and extraHeight if we have a merged cell. 1734 if (cell.testFlag(Cell::Flag_Merged)) { 1735 // FIXME: Introduce qreal extraWidth/Height here and use them 1736 // instead (see FIXME about this in paintCell()). 1737 1738 for (int x = cell.column() + 1; x <= cell.column() + d->obscuredCellsX; x++) 1739 width += cell.sheet()->columnFormat(x)->width(); 1740 1741 for (int y = cell.row() + 1; y <= cell.row() + d->obscuredCellsY; y++) 1742 height += cell.sheet()->rowFormat(y)->height(); 1743 } 1744 1745 // Cache the newly calculated extraWidth and extraHeight if we have 1746 // already allocated a struct for it. Otherwise it will be zero, so 1747 // don't bother. 1748 if (cell.d->hasExtra()) { 1749 cell.d->extra()->extraWidth = width; 1750 cell.d->extra()->extraHeight = height; 1751 } 1752 #endif 1753 } 1754 1755 1756 // Recalculate d->textX and d->textY. 1757 // 1758 // Used in makeLayout(). 1759 // 1760 void CellView::textOffset(const QFontMetricsF& fontMetrics, const Cell& cell) 1761 { 1762 Q_UNUSED(cell) 1763 const qreal ascent = fontMetrics.ascent(); 1764 const Style::HAlign hAlign = d->style.halign(); 1765 const Style::VAlign vAlign = d->style.valign(); 1766 const int tmpAngle = fixAngle(d->style.angle()); 1767 const bool tmpVerticalText = d->style.verticalText(); 1768 const bool tmpMultiRow = d->style.wrapText() || d->displayText.contains('\n'); 1769 const bool tmpRichText = !d->richText.isNull(); 1770 1771 qreal w = d->width; 1772 qreal h = d->height; 1773 1774 // doc coordinate system; no zoom applied 1775 const qreal effTop = s_borderSpace + 0.5 * d->style.topBorderPen().width(); 1776 const qreal effBottom = h - s_borderSpace - 0.5 * d->style.bottomBorderPen().width(); 1777 1778 // Calculate d->textY based on the vertical alignment and a few 1779 // other inputs. 1780 switch (vAlign) { 1781 case Style::VJustified: 1782 case Style::Top: { 1783 if (tmpAngle == 0 && tmpRichText) { 1784 d->textY = effTop + d->textHeight; 1785 } else if (tmpAngle == 0) { 1786 d->textY = effTop + ascent; 1787 } else if (tmpAngle < 0) { 1788 d->textY = effTop; 1789 } else { 1790 d->textY = effTop + ascent * ::cos(tmpAngle * M_PI / 180); 1791 } 1792 break; 1793 } 1794 case Style::VAlignUndefined: // fall through 1795 case Style::Bottom: { 1796 if (!tmpVerticalText && !tmpMultiRow && !tmpAngle && !tmpRichText) { 1797 d->textY = effBottom; 1798 } else if (tmpAngle != 0) { 1799 if (tmpAngle < 0) { 1800 d->textY = effBottom - d->textHeight; 1801 } else { 1802 d->textY = effBottom - d->textHeight + ascent * ::cos(tmpAngle * M_PI / 180); 1803 } 1804 if (tmpAngle < -90 || tmpAngle > 90) { 1805 d->textY += ascent * ::cos(tmpAngle * M_PI / 180); 1806 } 1807 } else if (tmpRichText) { 1808 d->textY = effBottom; 1809 } else if (tmpMultiRow && !tmpVerticalText) { 1810 d->textY = effBottom - d->textHeight + ascent; 1811 } else { // vertical text 1812 // Is enough place available? 1813 if (effBottom - effTop - d->textHeight > 0) { 1814 d->textY = effBottom - d->textHeight + ascent; 1815 } else { 1816 d->textY = effTop + ascent; 1817 } 1818 } 1819 break; 1820 } 1821 case Style::VDistributed: 1822 if (!tmpVerticalText && !tmpAngle && d->textLinesCount > 1) { 1823 d->textY = effTop + ascent; 1824 break; 1825 } 1826 // fall through 1827 case Style::Middle: { 1828 if (!tmpVerticalText && !tmpMultiRow && !tmpAngle && !tmpRichText) { 1829 d->textY = (h - d->textHeight) / 2 + ascent; 1830 } else if (tmpAngle != 0) { 1831 // Is enough place available? 1832 if (effBottom - effTop - d->textHeight > 0) { 1833 if (tmpAngle < 0) { 1834 d->textY = (h - d->textHeight) / 2; 1835 } else { 1836 d->textY = (h - d->textHeight) / 2 + ascent * ::cos(tmpAngle * M_PI / 180); 1837 } 1838 } else { 1839 if (tmpAngle < 0) { 1840 d->textY = effTop; 1841 } else { 1842 d->textY = effTop + ascent * ::cos(tmpAngle * M_PI / 180); 1843 } 1844 } 1845 } else if (tmpRichText && !tmpVerticalText) { 1846 d->textY = (h - d->textHeight) / 2 + d->textHeight; 1847 } else if (tmpMultiRow && !tmpVerticalText) { 1848 // Is enough place available? 1849 if (effBottom - effTop - d->textHeight > 0) { 1850 d->textY = (h - d->textHeight) / 2 + ascent; 1851 } else { 1852 d->textY = effTop + ascent; 1853 } 1854 } else { 1855 // Is enough place available? 1856 if (effBottom - effTop - d->textHeight > 0) { 1857 d->textY = (h - d->textHeight) / 2 + ascent; 1858 } else 1859 d->textY = effTop + ascent; 1860 } 1861 break; 1862 } 1863 } 1864 1865 // Calculate d->textX based on alignment and textwidth. 1866 switch (hAlign) { 1867 case Style::Left: 1868 d->textX = 0.5 * d->style.leftBorderPen().width() + s_borderSpace; 1869 break; 1870 case Style::Right: 1871 d->textX = w - s_borderSpace - d->textWidth 1872 - 0.5 * d->style.rightBorderPen().width(); 1873 break; 1874 case Style::Center: 1875 d->textX = 0.5 * (w - s_borderSpace - d->textWidth - 1876 0.5 * d->style.rightBorderPen().width()); 1877 break; 1878 default: 1879 break; 1880 } 1881 } 1882 1883 void CellView::obscureHorizontalCells(SheetView* sheetView, const Cell& masterCell) 1884 { 1885 if (d->hidden) 1886 return; 1887 1888 qreal extraWidth = 0.0; 1889 const Style::HAlign align = d->style.halign(); 1890 1891 // Get indentation. This is only used for left aligned text. 1892 qreal indent = 0.0; 1893 if (align == Style::Left && !masterCell.isEmpty()) 1894 indent = style().indentation(); 1895 1896 // Set d->fittingWidth to false, if the text is vertical or angled, and too 1897 // high for the cell. 1898 if (style().verticalText() || style().angle() != 0) 1899 if (d->textHeight >= d->height) 1900 d->fittingWidth = false; 1901 1902 // Do we have to occupy additional cells to the right? This is only 1903 // done for cells that have no merged cells in the Y direction. 1904 // 1905 // FIXME: Check if all cells along the merged edge to the right are 1906 // empty and use the extra space? No, probably not. 1907 // 1908 if (d->textWidth + indent > (d->width - 2 * s_borderSpace 1909 - style().leftBorderPen().width() - style().rightBorderPen().width()) && 1910 masterCell.mergedYCells() == 0) { 1911 const int effectiveCol = masterCell.column() + masterCell.mergedXCells(); 1912 int col = effectiveCol; 1913 1914 // Find free cells to the right of this one. 1915 enum { Undefined, EnoughSpace, NotEnoughSpace } status = Undefined; 1916 while (status == Undefined) { 1917 Cell nextCell = Cell(masterCell.sheet(), col + 1, masterCell.row()).masterCell(); 1918 1919 if (nextCell.isEmpty()) { 1920 extraWidth += nextCell.width(); 1921 col += 1 + nextCell.mergedXCells(); 1922 1923 // Enough space? 1924 if (d->textWidth + indent <= (d->width + extraWidth - 2 * s_borderSpace 1925 - style().leftBorderPen().width() - style().rightBorderPen().width())) 1926 status = EnoughSpace; 1927 } else 1928 // Not enough space, but the next cell is not empty 1929 status = NotEnoughSpace; 1930 } 1931 1932 // Try to use additional space from the neighboring cells that 1933 // were calculated in the last step. 1934 // 1935 // Currently this is only done for left aligned cells. We have to 1936 // check to make sure we haven't already force-merged enough cells 1937 // 1938 // FIXME: Why not right/center aligned text? 1939 // 1940 // FIXME: Shouldn't we check to see if end == -1 here before 1941 // setting d->fittingWidth to false? 1942 // 1943 if (style().halign() == Style::Left || (style().halign() == Style::HAlignUndefined 1944 && !masterCell.value().isNumber())) { 1945 if (col > effectiveCol) { 1946 d->obscuredCellsX = col - effectiveCol; 1947 d->width += extraWidth; 1948 if (sheetView->sheet()->layoutDirection() == Qt::RightToLeft) { 1949 d->rtlOffset += extraWidth; 1950 } 1951 1952 const QRect obscuredRange(effectiveCol + 1, masterCell.row(), d->obscuredCellsX, 1); 1953 sheetView->obscureCells(masterCell.cellPosition(), d->obscuredCellsX, d->obscuredCellsY); 1954 1955 // Not enough space 1956 if (status == NotEnoughSpace) 1957 d->fittingWidth = false; 1958 } else 1959 d->fittingWidth = false; 1960 } else 1961 d->fittingWidth = false; 1962 } 1963 } 1964 1965 void CellView::obscureVerticalCells(SheetView* sheetView, const Cell& masterCell) 1966 { 1967 if (d->hidden) 1968 return; 1969 1970 qreal extraHeight = 0.0; 1971 1972 // Do we have to occupy additional cells at the bottom ? 1973 // 1974 // FIXME: Setting to make the current cell grow. 1975 // 1976 if (d->displayText.contains('\n') && 1977 d->textHeight > (d->height - 2 * s_borderSpace 1978 - style().topBorderPen().width() - style().bottomBorderPen().width())) { 1979 const int effectiveRow = masterCell.row() + masterCell.mergedYCells(); 1980 int row = effectiveRow; 1981 1982 // Find free cells bottom to this one 1983 enum { Undefined, EnoughSpace, NotEnoughSpace } status = Undefined; 1984 while (status == Undefined) { 1985 Cell nextCell = Cell(masterCell.sheet(), masterCell.column(), row + 1).masterCell(); 1986 1987 bool isEmpty = true; 1988 1989 for (int col = 0; col < masterCell.mergedXCells() + d->obscuredCellsX+1; col++) { 1990 Cell cellNext = Cell(masterCell.sheet(), masterCell.column() + col, row + 1).masterCell(); 1991 if (!cellNext.isEmpty()) { 1992 isEmpty = false; 1993 break; 1994 } 1995 } 1996 if (isEmpty) { 1997 extraHeight += nextCell.height(); 1998 row += 1 + nextCell.mergedYCells(); 1999 2000 // Enough space ? 2001 if (d->textHeight <= (d->height + extraHeight - 2 * s_borderSpace 2002 - style().topBorderPen().width() - style().bottomBorderPen().width())) 2003 status = EnoughSpace; 2004 } else 2005 // Not enough space, but the next cell is not empty. 2006 status = NotEnoughSpace; 2007 } 2008 2009 // Check to make sure we haven't already force-merged enough cells. 2010 if (row > effectiveRow) { 2011 d->obscuredCellsY = row - effectiveRow; 2012 d->height += extraHeight; 2013 2014 const QRect obscuredRange(masterCell.column(), effectiveRow + 1, 1, d->obscuredCellsY); 2015 sheetView->obscureCells(masterCell.cellPosition(), d->obscuredCellsX, d->obscuredCellsY); 2016 2017 // Not enough space 2018 if (status == NotEnoughSpace) 2019 d->fittingHeight = false; 2020 } else 2021 d->fittingHeight = false; 2022 } 2023 } 2024 2025 void CellView::drawText(QPainter& painter, const QPointF& location, const QStringList& textLines, 2026 const Cell& cell, qreal lineSpacing) const 2027 { 2028 Q_UNUSED(cell) 2029 2030 KoPostscriptPaintDevice device; 2031 const QFont font(d->calculateFont(), &device); 2032 const QFontMetricsF fontMetrics(font, &device); 2033 2034 const qreal leading = fontMetrics.leading(); 2035 2036 const QTextOption options = d->textOptions(); 2037 2038 const bool tmpVerticalText = d->style.verticalText(); 2039 const bool tmpAngled = fixAngle(d->style.angle()) != 0; 2040 const qreal tmpIndent = cell.isEmpty() || d->style.halign() != Style::Left ? 0.0 : style().indentation(); 2041 const qreal lineWidth = tmpAngled ? 1e9 : tmpVerticalText ? fontMetrics.maxWidth() : 2042 (d->width - 2 * s_borderSpace 2043 - 0.5 * d->style.leftBorderPen().width() 2044 - 0.5 * d->style.rightBorderPen().width()) 2045 - tmpIndent; 2046 2047 qreal offset = 1.0 - fontMetrics.ascent(); 2048 for (int i = 0; i < textLines.count(); ++i) { 2049 QTextLayout textLayout(textLines[i], font); 2050 textLayout.setCacheEnabled(true); 2051 textLayout.setTextOption(options); 2052 textLayout.beginLayout(); 2053 qreal height = 0.0; 2054 forever { 2055 // we don't need to break if the text no longer fits in the cell; 2056 // a clipping region is set on the painter to make sure we won't draw outside the cell 2057 QTextLine line = textLayout.createLine(); 2058 if (!line.isValid()) 2059 break; 2060 line.setLineWidth(lineWidth); 2061 height += leading; 2062 line.setPosition(QPointF((s_borderSpace + 0.5 * d->style.leftBorderPen().widthF()), 2063 height)); 2064 2065 height += line.height() + lineSpacing; 2066 } 2067 textLayout.endLayout(); 2068 2069 textLayout.draw(&painter, QPointF(location.x(), (location.y() + offset))); 2070 offset += height; 2071 } 2072 } 2073 2074 qreal CellView::cellHeight() const 2075 { 2076 return d->height; 2077 } 2078 2079 qreal CellView::cellWidth() const 2080 { 2081 return d->width; 2082 } 2083 2084 bool CellView::dimensionFits() const 2085 { 2086 return d->fittingHeight && d->fittingWidth; 2087 } 2088 2089 void CellView::Private::checkForFilterButton(const Cell& cell) 2090 { 2091 const Database database = cell.database(); 2092 if (database.isEmpty() || !database.displayFilterButtons()) { 2093 filterButton = false; 2094 return; 2095 } 2096 if (database.orientation() == Qt::Horizontal) 2097 filterButton = database.range().firstRange().left() == cell.column(); 2098 else // Qt::Vertical 2099 filterButton = database.range().firstRange().top() == cell.row(); 2100 } 2101 2102 void CellView::Private::calculateTextSize(const QFont& font, const QFontMetricsF& fontMetrics) 2103 { 2104 if (style.angle() != 0) 2105 calculateAngledTextSize(font, fontMetrics); 2106 else if (style.verticalText()) 2107 calculateVerticalTextSize(font, fontMetrics); 2108 else if (richText) 2109 calculateRichTextSize(font, fontMetrics); 2110 else 2111 calculateHorizontalTextSize(font, fontMetrics); 2112 } 2113 2114 void CellView::Private::calculateHorizontalTextSize(const QFont& font, const QFontMetricsF& fontMetrics) 2115 { 2116 const QStringList textLines = displayText.split('\n'); 2117 const qreal leading = fontMetrics.leading(); 2118 const QTextOption options = textOptions(); 2119 2120 const qreal tmpIndent = style.halign() != Style::Left ? 0.0 : style.indentation(); 2121 const qreal lineWidth = (width - 2 * s_borderSpace 2122 - 0.5 * style.leftBorderPen().width() 2123 - 0.5 * style.rightBorderPen().width()) 2124 - tmpIndent; 2125 2126 textHeight = 0.0; 2127 textWidth = 0.0; 2128 textLinesCount = 0; 2129 fittingHeight = true; 2130 fittingWidth = true; 2131 for (int i = 0; i < textLines.count(); ++i) { 2132 textWidth = qMax(textWidth, fontMetrics.width(textLines[i])); 2133 QTextLayout textLayout(textLines[i], font); 2134 textLayout.setTextOption(options); 2135 textLayout.beginLayout(); 2136 forever { 2137 QTextLine line = textLayout.createLine(); 2138 if (!line.isValid()) 2139 break; // forever 2140 line.setLineWidth(lineWidth); 2141 textHeight += leading + line.height(); 2142 if ((textHeight - fontMetrics.descent()) > (height - 2 * s_borderSpace 2143 - 0.5 * style.topBorderPen().width() 2144 - 0.5 * style.bottomBorderPen().width())) { 2145 fittingHeight = false; 2146 break; // forever 2147 } 2148 } 2149 textLinesCount += textLayout.lineCount(); 2150 textLayout.endLayout(); 2151 } 2152 // The width fits, if the text is wrapped or all lines are smaller than the cell width. 2153 fittingWidth = style.wrapText() || 2154 textWidth <= lineWidth; 2155 } 2156 2157 void CellView::Private::calculateVerticalTextSize(const QFont& font, const QFontMetricsF& fontMetrics) 2158 { 2159 Q_UNUSED(font) 2160 int rows = 0; 2161 const QStringList textLines = displayText.split('\n'); 2162 for (int i = 0; i < textLines.count(); ++i) 2163 rows = qMax(rows, textLines[i].count()); 2164 textHeight = (fontMetrics.ascent() + fontMetrics.descent()) * rows; 2165 textWidth = (displayText.count('\n') + 1) * fontMetrics.maxWidth(); 2166 fittingHeight = textHeight <= this->width; 2167 fittingWidth = textWidth <= this->height; 2168 } 2169 2170 void CellView::Private::calculateAngledTextSize(const QFont& font, const QFontMetricsF& fontMetrics) 2171 { 2172 Q_UNUSED(font) 2173 const qreal angle = fixAngle(style.angle()); 2174 QStringList lines = displayText.split('\n'); 2175 const qreal height = fontMetrics.ascent() + fontMetrics.descent() * lines.count(); 2176 qreal width = 0; 2177 foreach (const QString& line, lines) { 2178 width = qMax(width, fontMetrics.width(line)); 2179 } 2180 textHeight = qAbs(height * ::cos(angle * M_PI / 180)) + qAbs(width * ::sin(angle * M_PI / 180)); 2181 textWidth = qAbs(height * ::sin(angle * M_PI / 180)) + qAbs(width * ::cos(angle * M_PI / 180)); 2182 fittingHeight = textHeight <= this->width; 2183 fittingWidth = textWidth <= this->height; 2184 } 2185 2186 void CellView::Private::calculateRichTextSize(const QFont& font, const QFontMetricsF& fontMetrics) 2187 { 2188 Q_UNUSED(fontMetrics); 2189 #ifdef CALLIGRA_SHEETS_MT 2190 QMutexLocker(mutex.data()); 2191 #endif 2192 richText->setDefaultFont(font); 2193 richText->setDocumentMargin(0); 2194 2195 const qreal lineWidth = width - 2 * s_borderSpace 2196 - 0.5 * style.leftBorderPen().widthF() 2197 - 0.5 * style.rightBorderPen().widthF(); 2198 2199 if (style.wrapText()) 2200 richText->setTextWidth(lineWidth); 2201 else 2202 richText->setTextWidth(-1); 2203 const QSizeF textSize = richText->size(); 2204 textHeight = textSize.height(); 2205 textWidth = textSize.width(); 2206 textLinesCount = richText->lineCount(); 2207 // TODO: linescount is not correct, and distributed vertical alignment doesn't 2208 // work anyway for richtext at the moment 2209 fittingHeight = textHeight <= (height - 2 * s_borderSpace 2210 - 0.5 * style.topBorderPen().widthF() 2211 - 0.5 * style.bottomBorderPen().widthF()); 2212 fittingWidth = textWidth <= lineWidth; 2213 } 2214 2215 void CellView::Private::truncateText(const QFont& font, const QFontMetricsF& fontMetrics) 2216 { 2217 if (style.angle() != 0) 2218 truncateAngledText(font, fontMetrics); 2219 else if (style.verticalText()) 2220 truncateVerticalText(font, fontMetrics); 2221 else 2222 truncateHorizontalText(font, fontMetrics); 2223 } 2224 2225 void CellView::Private::truncateHorizontalText(const QFont& font, const QFontMetricsF& fontMetrics) 2226 { 2227 if (!style.wrapText()) { 2228 const QStringList textLines = displayText.split('\n'); 2229 displayText.clear(); 2230 qreal height = font.pointSizeF(); 2231 for (int i = 0; i < textLines.count(); ++i) { 2232 if (height > this->height) 2233 break; 2234 int count = 0; 2235 while (count < textLines[i].count() && fontMetrics.width(textLines[i].left(count)) <= this->width) 2236 ++count; 2237 displayText += textLines[i].left(count); 2238 height += fontMetrics.height(); 2239 if (height <= this->height) 2240 displayText += '\n'; 2241 } 2242 } 2243 // else it is handled by QTextLayout 2244 } 2245 2246 void CellView::Private::truncateVerticalText(const QFont& font, const QFontMetricsF& fontMetrics) 2247 { 2248 Q_UNUSED(font); 2249 Q_UNUSED(fontMetrics); 2250 } 2251 2252 void CellView::Private::truncateAngledText(const QFont& font, const QFontMetricsF& fontMetrics) 2253 { 2254 Q_UNUSED(font); 2255 Q_UNUSED(fontMetrics); 2256 } 2257 2258 QTextOption CellView::Private::textOptions() const 2259 { 2260 QTextOption options; 2261 switch (style.halign()) { 2262 default: 2263 case Style::Left: 2264 options.setAlignment(Qt::AlignLeft); 2265 break; 2266 case Style::Right: 2267 options.setAlignment(Qt::AlignRight); 2268 break; 2269 case Style::Center: 2270 options.setAlignment(Qt::AlignHCenter); 2271 break; 2272 case Style::Justified: 2273 options.setAlignment(Qt::AlignJustify); 2274 break; 2275 } 2276 // The text consists of a single character, if it's vertical. Always center it. 2277 if (style.verticalText()) 2278 options.setAlignment(Qt::AlignHCenter); 2279 options.setWrapMode(style.wrapText() ? QTextOption::WrapAtWordBoundaryOrAnywhere : QTextOption::NoWrap); 2280 options.setUseDesignMetrics(true); 2281 return options; 2282 }