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 }