1 /*
2  * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB.  All rights reserved.
3  *
4  * This file is part of the KD Chart library.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of
9  * the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
18  */
19 
20 #include "KChartLayoutItems.h"
21 
22 #include "KTextDocument.h"
23 #include "KChartAbstractArea.h"
24 #include "KChartAbstractDiagram.h"
25 #include "KChartBackgroundAttributes.h"
26 #include "KChartFrameAttributes.h"
27 #include "KChartPaintContext.h"
28 #include "KChartPainterSaver_p.h"
29 #include "KChartPrintingParameters.h"
30 #include "KChartMath_p.h"
31 
32 #include <QTextCursor>
33 #include <QTextBlockFormat>
34 #include <QTextDocumentFragment>
35 #include <QAbstractTextDocumentLayout>
36 #include <QLayout>
37 #include <QPainter>
38 #include <QDebug>
39 #include <QCoreApplication>
40 #include <QApplication>
41 #include <QStringList>
42 #include <QStyle>
43 
44 
45 //#define DEBUG_ITEMS_PAINT
46 
setParentWidget(QWidget * widget)47 void KChart::AbstractLayoutItem::setParentWidget( QWidget* widget )
48 {
49     mParent = widget;
50 }
51 
paintAll(QPainter & painter)52 void KChart::AbstractLayoutItem::paintAll( QPainter& painter )
53 {
54     paint( &painter );
55 }
56 
paintCtx(PaintContext * context)57 void KChart::AbstractLayoutItem::paintCtx( PaintContext* context )
58 {
59     if ( context )
60         paint( context->painter() );
61 }
62 
sizeHintChanged() const63 void KChart::AbstractLayoutItem::sizeHintChanged() const
64 {
65     // This is exactly like what QWidget::updateGeometry does.
66 //  qDebug("KChart::AbstractLayoutItem::sizeHintChanged() called");
67     if ( mParent ) {
68         if ( mParent->layout() )
69             mParent->layout()->invalidate();
70         else
71             QApplication::postEvent( mParent, new QEvent( QEvent::LayoutRequest ) );
72     }
73 }
74 
TextBubbleLayoutItem(const QString & text,const KChart::TextAttributes & attributes,const QObject * area,KChartEnums::MeasureOrientation orientation,Qt::Alignment alignment)75 KChart::TextBubbleLayoutItem::TextBubbleLayoutItem( const QString& text,
76                                          const KChart::TextAttributes& attributes,
77                                          const QObject* area,
78                                          KChartEnums::MeasureOrientation orientation,
79                                          Qt::Alignment alignment )
80     : AbstractLayoutItem( alignment ),
81       m_text( new TextLayoutItem( text, attributes, area, orientation, alignment ) )
82 {
83 }
84 
TextBubbleLayoutItem()85 KChart::TextBubbleLayoutItem::TextBubbleLayoutItem()
86     : AbstractLayoutItem( Qt::AlignLeft ),
87       m_text( new TextLayoutItem() )
88 {
89 }
90 
~TextBubbleLayoutItem()91 KChart::TextBubbleLayoutItem::~TextBubbleLayoutItem()
92 {
93     delete m_text;
94 }
95 
setAutoReferenceArea(const QObject * area)96 void KChart::TextBubbleLayoutItem::setAutoReferenceArea( const QObject* area )
97 {
98     m_text->setAutoReferenceArea( area );
99 }
100 
autoReferenceArea() const101 const QObject* KChart::TextBubbleLayoutItem::autoReferenceArea() const
102 {
103     return m_text->autoReferenceArea();
104 }
105 
setText(const QString & text)106 void KChart::TextBubbleLayoutItem::setText( const QString& text )
107 {
108     m_text->setText( text );
109 }
110 
text() const111 QString KChart::TextBubbleLayoutItem::text() const
112 {
113     return m_text->text();
114 }
115 
setTextAttributes(const TextAttributes & a)116 void KChart::TextBubbleLayoutItem::setTextAttributes( const TextAttributes& a )
117 {
118     m_text->setTextAttributes( a );
119 }
120 
textAttributes() const121 KChart::TextAttributes KChart::TextBubbleLayoutItem::textAttributes() const
122 {
123     return m_text->textAttributes();
124 }
125 
isEmpty() const126 bool KChart::TextBubbleLayoutItem::isEmpty() const
127 {
128     return m_text->isEmpty();
129 }
130 
expandingDirections() const131 Qt::Orientations KChart::TextBubbleLayoutItem::expandingDirections() const
132 {
133     return m_text->expandingDirections();
134 }
135 
maximumSize() const136 QSize KChart::TextBubbleLayoutItem::maximumSize() const
137 {
138     const int border = borderWidth();
139     return m_text->maximumSize() + QSize( 2 * border, 2 * border );
140 }
141 
minimumSize() const142 QSize KChart::TextBubbleLayoutItem::minimumSize() const
143 {
144     const int border = borderWidth();
145     return m_text->minimumSize() + QSize( 2 * border, 2 * border );
146 }
147 
sizeHint() const148 QSize KChart::TextBubbleLayoutItem::sizeHint() const
149 {
150     const int border = borderWidth();
151     return m_text->sizeHint() + QSize( 2 * border, 2 * border );
152 }
153 
setGeometry(const QRect & r)154 void KChart::TextBubbleLayoutItem::setGeometry( const QRect& r )
155 {
156     const int border = borderWidth();
157     m_text->setGeometry( r.adjusted( border, border, -border, -border ) );
158 }
159 
geometry() const160 QRect KChart::TextBubbleLayoutItem::geometry() const
161 {
162     const int border = borderWidth();
163     return m_text->geometry().adjusted( -border, -border, border, border );
164 }
165 
paint(QPainter * painter)166 void KChart::TextBubbleLayoutItem::paint( QPainter* painter )
167 {
168     const QPen oldPen = painter->pen();
169     const QBrush oldBrush = painter->brush();
170     painter->setPen( Qt::black );
171     painter->setBrush( QColor( 255, 255, 220 ) );
172     painter->drawRoundRect( geometry(), 10 );
173     painter->setPen( oldPen );
174     painter->setBrush( oldBrush );
175     m_text->paint( painter );
176 }
177 
borderWidth() const178 int KChart::TextBubbleLayoutItem::borderWidth() const
179 {
180     return 1;
181 }
182 
TextLayoutItem(const QString & text,const KChart::TextAttributes & attributes,const QObject * area,KChartEnums::MeasureOrientation orientation,Qt::Alignment alignment)183 KChart::TextLayoutItem::TextLayoutItem( const QString& text,
184                                          const KChart::TextAttributes& attributes,
185                                          const QObject* area,
186                                          KChartEnums::MeasureOrientation orientation,
187                                          Qt::Alignment alignment )
188     : AbstractLayoutItem( alignment )
189     , mText( text )
190     , mTextAlignment( alignment )
191     , mAttributes( attributes )
192     , mAutoReferenceArea( area )
193     , mAutoReferenceOrientation( orientation )
194     , cachedSizeHint() // default this to invalid to force just-in-time calculation before first use of sizeHint()
195     , cachedFontSize( 0.0 )
196     , cachedFont( mAttributes.font() )
197 {
198 }
199 
TextLayoutItem()200 KChart::TextLayoutItem::TextLayoutItem()
201     : AbstractLayoutItem( Qt::AlignLeft )
202     , mText()
203     , mTextAlignment( Qt::AlignLeft )
204     , mAttributes()
205     , mAutoReferenceArea( nullptr )
206     , mAutoReferenceOrientation( KChartEnums::MeasureOrientationHorizontal )
207     , cachedSizeHint() // default this to invalid to force just-in-time calculation before first use of sizeHint()
208     , cachedFontSize( 0.0 )
209     , cachedFont( mAttributes.font() )
210 {
211 
212 }
213 
setAutoReferenceArea(const QObject * area)214 void KChart::TextLayoutItem::setAutoReferenceArea( const QObject* area )
215 {
216     mAutoReferenceArea = area;
217     cachedSizeHint = QSize();
218     sizeHint();
219 }
220 
autoReferenceArea() const221 const QObject* KChart::TextLayoutItem::autoReferenceArea() const
222 {
223     return mAutoReferenceArea;
224 }
225 
setText(const QString & text)226 void KChart::TextLayoutItem::setText(const QString & text)
227 {
228     mText = text;
229     cachedSizeHint = QSize();
230     sizeHint();
231     if ( mParent )
232         mParent->update();
233 }
234 
text() const235 QString KChart::TextLayoutItem::text() const
236 {
237     return mText;
238 }
239 
setTextAlignment(Qt::Alignment alignment)240 void KChart::TextLayoutItem::setTextAlignment( Qt::Alignment alignment)
241 {
242     if ( mTextAlignment == alignment )
243         return;
244     mTextAlignment = alignment;
245     if ( mParent )
246         mParent->update();
247 }
248 
textAlignment() const249 Qt::Alignment KChart::TextLayoutItem::textAlignment() const
250 {
251     return mTextAlignment;
252 }
253 
setTextAttributes(const TextAttributes & a)254 void KChart::TextLayoutItem::setTextAttributes( const TextAttributes &a )
255 {
256     mAttributes = a;
257     cachedFont = a.font();
258     cachedSizeHint = QSize(); // invalidate size hint
259     sizeHint();
260     if ( mParent )
261         mParent->update();
262 }
263 
textAttributes() const264 KChart::TextAttributes KChart::TextLayoutItem::textAttributes() const
265 {
266     return mAttributes;
267 }
268 
269 
expandingDirections() const270 Qt::Orientations KChart::TextLayoutItem::expandingDirections() const
271 {
272     return Qt::Orientations(); // Grow neither vertically nor horizontally
273 }
274 
geometry() const275 QRect KChart::TextLayoutItem::geometry() const
276 {
277     return mRect;
278 }
279 
isEmpty() const280 bool KChart::TextLayoutItem::isEmpty() const
281 {
282     return false; // never empty, otherwise the layout item would not exist
283 }
284 
maximumSize() const285 QSize KChart::TextLayoutItem::maximumSize() const
286 {
287     return sizeHint(); // PENDING(kalle) Review, quite inflexible
288 }
289 
minimumSize() const290 QSize KChart::TextLayoutItem::minimumSize() const
291 {
292     return sizeHint(); // PENDING(kalle) Review, quite inflexible
293 }
294 
setGeometry(const QRect & r)295 void KChart::TextLayoutItem::setGeometry( const QRect& r )
296 {
297     mRect = r;
298 }
299 
300 // returns the bounding box of rect rotated around its center
rotatedRect(const QRectF & rect,qreal rotation)301 QRectF rotatedRect( const QRectF& rect, qreal rotation )
302 {
303     QTransform t;
304     QPointF center = rect.center();
305     t.translate( center.x(), center.y() );
306     t.rotate( rotation );
307     t.translate( -center.x(), -center.y() );
308     return t.mapRect( rect );
309 }
310 
fitFontSizeToGeometry() const311 qreal KChart::TextLayoutItem::fitFontSizeToGeometry() const
312 {
313     QFont f = realFont();
314     const qreal origResult = f.pointSizeF();
315     qreal result = origResult;
316     const qreal minSize = mAttributes.minimalFontSize().value();
317     const QSize mySize = geometry().size();
318     if ( mySize.isNull() ) {
319         return result;
320     }
321 
322     QFontMetrics fm( f );
323     while ( true ) {
324         const QSizeF textSize = rotatedRect( fm.boundingRect( mText ), mAttributes.rotation() ).normalized().size();
325 
326         if ( textSize.height() <= mySize.height() && textSize.width() <= mySize.width() ) {
327             return result;
328         }
329 
330         result -= 0.5;
331         if ( minSize > 0 && result < minSize ) {
332             return result + 0.5;
333         } else if ( result <= 0.0 ) {
334             return origResult;
335         }
336         f.setPointSizeF( result );
337         fm = QFontMetrics( f );
338     }
339 }
340 
realFontSize() const341 qreal KChart::TextLayoutItem::realFontSize() const
342 {
343     return mAttributes.calculatedFontSize( mAutoReferenceArea, mAutoReferenceOrientation );
344 }
345 
maybeUpdateRealFont() const346 bool KChart::TextLayoutItem::maybeUpdateRealFont() const
347 {
348     const qreal fntSiz = realFontSize();
349     const bool doUpdate = !cachedSizeHint.isValid() || cachedFontSize != fntSiz;
350 
351     if ( doUpdate && fntSiz > 0.0 ) {
352         cachedFontSize = fntSiz;
353         cachedFont.setPointSizeF( fntSiz );
354     }
355     return doUpdate; // "didUpdate" by now
356 }
357 
realFont() const358 QFont KChart::TextLayoutItem::realFont() const
359 {
360     maybeUpdateRealFont();
361     return cachedFont;
362 }
363 
boundingPolygon() const364 QPolygon KChart::TextLayoutItem::boundingPolygon() const
365 {
366     // should probably call sizeHint() here, but that one is expensive (see TODO there)
367     return mCachedBoundingPolygon;
368 }
369 
intersects(const TextLayoutItem & other,const QPointF & myPos,const QPointF & otherPos) const370 bool KChart::TextLayoutItem::intersects( const TextLayoutItem& other, const QPointF& myPos, const QPointF& otherPos ) const
371 {
372     return intersects( other, myPos.toPoint(), otherPos.toPoint() );
373 }
374 
intersects(const TextLayoutItem & other,const QPoint & myPos,const QPoint & otherPos) const375 bool KChart::TextLayoutItem::intersects( const TextLayoutItem& other, const QPoint& myPos, const QPoint& otherPos ) const
376 {
377     QRegion myRegion( boundingPolygon().translated( myPos - otherPos ) );
378     QRegion otherRegion( other.boundingPolygon() );
379 
380     return myRegion.intersects( otherRegion );
381 }
382 
sizeHint() const383 QSize KChart::TextLayoutItem::sizeHint() const
384 {
385     // ### we only really need to recalculate the size hint when mAttributes.rotation has *changed*
386     if ( maybeUpdateRealFont() || mAttributes.rotation() || !cachedSizeHint.isValid() ) {
387         const QSize newSizeHint( calcSizeHint( cachedFont ) );
388         Q_ASSERT( newSizeHint.isValid() );
389         if ( newSizeHint != cachedSizeHint ) {
390             cachedSizeHint = newSizeHint;
391             sizeHintChanged();
392         }
393     }
394     return cachedSizeHint;
395 }
396 
sizeHintUnrotated() const397 QSize KChart::TextLayoutItem::sizeHintUnrotated() const
398 {
399     maybeUpdateRealFont(); // make sure the cached font is up to date
400     return unrotatedSizeHint( cachedFont );
401 }
402 
403 
404 // PENDING(kalle) Support auto shrink
405 
406 
unrotatedTextSize(QFont fnt) const407 QSize KChart::TextLayoutItem::unrotatedTextSize( QFont fnt ) const
408 {
409     if ( fnt == QFont() ) {
410         fnt = realFont(); // this is the cached font in most cases
411     }
412 
413     const QFontMetricsF fm( fnt, GlobalMeasureScaling::paintDevice() );
414     QRect veryLarge( 0, 0, 100000, 100000 );
415     // this overload of boundingRect() interprets \n as line breaks, not as regular characters.
416     return fm.boundingRect( veryLarge, Qt::AlignLeft | Qt::AlignTop, mText ).size().toSize();
417 }
418 
marginWidth() const419 int KChart::TextLayoutItem::marginWidth() const
420 {
421     return marginWidth( unrotatedTextSize() );
422 }
423 
marginWidth(const QSize & textSize) const424 int KChart::TextLayoutItem::marginWidth( const QSize& textSize ) const
425 {
426     return qMin ( QApplication::style()->pixelMetric( QStyle::PM_ButtonMargin, nullptr, nullptr ),
427                   // decrease frame size if the text is small
428                   textSize.height() * 2 / 3 );
429 }
430 
unrotatedSizeHint(const QFont & fnt) const431 QSize KChart::TextLayoutItem::unrotatedSizeHint( const QFont& fnt ) const
432 {
433     QSize ret = unrotatedTextSize( fnt );
434     const int margin = marginWidth( ret );
435     ret += QSize( margin, margin );
436     return ret;
437 }
438 
calcSizeHint(const QFont & font) const439 QSize KChart::TextLayoutItem::calcSizeHint( const QFont& font ) const
440 {
441     const QSize size = unrotatedSizeHint( font );
442     QPoint topLeft( -size.width() * 0.5, -size.height() * 0.5 );
443     if ( !mAttributes.rotation() ) {
444         mCachedBoundingPolygon.resize( 4 );
445         // using the same winding order as returned by QPolygon QTransform::mapToPolygon(const QRect&),
446         // which is: 0-1: top edge, 1-2: right edge, 2-3: bottom edge, 3-0: left edge (of input rect)
447         mCachedBoundingPolygon[ 0 ] = topLeft;
448         mCachedBoundingPolygon[ 1 ] = topLeft + QPoint( size.width(), 0 ); // top right
449         mCachedBoundingPolygon[ 2 ] = topLeft + QPoint( size.width(), size.height() ); // bottom right
450         mCachedBoundingPolygon[ 3 ] = topLeft + QPoint( 0, size.height() ); // bottom left
451         return size;
452     }
453 
454     const QRect rect( topLeft, size );
455     QTransform t;
456     t.rotate( mAttributes.rotation() );
457     mCachedBoundingPolygon = t.mapToPolygon( rect );
458 
459     return mCachedBoundingPolygon.boundingRect().size();
460 }
461 
paint(QPainter * painter)462 void KChart::TextLayoutItem::paint( QPainter* painter )
463 {
464     if ( !mRect.isValid() ) {
465         return;
466     }
467     const PainterSaver painterSaver( painter );
468     QFont f = realFont();
469     if ( mAttributes.autoShrink() ) {
470         f.setPointSizeF( fitFontSizeToGeometry() );
471     }
472     painter->setFont( f );
473 
474     QSize innerSize = unrotatedTextSize();
475     QRectF rect = QRectF( QPointF( 0, 0 ), innerSize );
476     rect.translate( -rect.center() );
477     painter->translate( mRect.center() );
478     painter->rotate( mAttributes.rotation() );
479 #ifdef DEBUG_ITEMS_PAINT
480     painter->setPen( Qt::red );
481     painter->drawRect( rect );
482 #endif
483 
484     painter->setPen( PrintingParameters::scalePen( mAttributes.pen() ) );
485     QTextDocument* document = mAttributes.textDocument();
486     if ( document ) {
487         document->setPageSize( rect.size() );
488         document->setHtml( mText );
489         QAbstractTextDocumentLayout::PaintContext paintcontext;
490         // ### this doesn't work for rotated painting because clip does not translate the painting
491         // TODO translate the painting either using a QTransform or one of QPainter's transform stages
492         paintcontext.clip = rect;
493         document->documentLayout()->draw( painter, paintcontext );
494     } else {
495         painter->drawText( rect, mTextAlignment, mText );
496     }
497 }
498 
HorizontalLineLayoutItem()499 KChart::HorizontalLineLayoutItem::HorizontalLineLayoutItem()
500     : AbstractLayoutItem( Qt::AlignCenter )
501 {
502 }
503 
expandingDirections() const504 Qt::Orientations KChart::HorizontalLineLayoutItem::expandingDirections() const
505 {
506     return Qt::Horizontal;
507 }
508 
geometry() const509 QRect KChart::HorizontalLineLayoutItem::geometry() const
510 {
511     return mRect;
512 }
513 
isEmpty() const514 bool KChart::HorizontalLineLayoutItem::isEmpty() const
515 {
516     return false; // never empty, otherwise the layout item would not exist
517 }
518 
maximumSize() const519 QSize KChart::HorizontalLineLayoutItem::maximumSize() const
520 {
521     return QSize( QWIDGETSIZE_MAX, QWIDGETSIZE_MAX );
522 }
523 
minimumSize() const524 QSize KChart::HorizontalLineLayoutItem::minimumSize() const
525 {
526     return QSize( 0, 0 );
527 }
528 
setGeometry(const QRect & r)529 void KChart::HorizontalLineLayoutItem::setGeometry( const QRect& r )
530 {
531     mRect = r;
532 }
533 
sizeHint() const534 QSize KChart::HorizontalLineLayoutItem::sizeHint() const
535 {
536     return QSize( -1, 3 ); // see qframe.cpp
537 }
538 
539 
paint(QPainter * painter)540 void KChart::HorizontalLineLayoutItem::paint( QPainter* painter )
541 {
542     if ( !mRect.isValid() )
543         return;
544 
545     painter->drawLine( QPointF( mRect.left(), mRect.center().y() ),
546                        QPointF( mRect.right(), mRect.center().y() ) );
547 }
548 
549 
VerticalLineLayoutItem()550 KChart::VerticalLineLayoutItem::VerticalLineLayoutItem()
551     : AbstractLayoutItem( Qt::AlignCenter )
552 {
553 }
554 
expandingDirections() const555 Qt::Orientations KChart::VerticalLineLayoutItem::expandingDirections() const
556 {
557     return Qt::Vertical;
558 }
559 
geometry() const560 QRect KChart::VerticalLineLayoutItem::geometry() const
561 {
562     return mRect;
563 }
564 
isEmpty() const565 bool KChart::VerticalLineLayoutItem::isEmpty() const
566 {
567     return false; // never empty, otherwise the layout item would not exist
568 }
569 
maximumSize() const570 QSize KChart::VerticalLineLayoutItem::maximumSize() const
571 {
572     return QSize( QWIDGETSIZE_MAX, QWIDGETSIZE_MAX );
573 }
574 
minimumSize() const575 QSize KChart::VerticalLineLayoutItem::minimumSize() const
576 {
577     return QSize( 0, 0 );
578 }
579 
setGeometry(const QRect & r)580 void KChart::VerticalLineLayoutItem::setGeometry( const QRect& r )
581 {
582     mRect = r;
583 }
584 
sizeHint() const585 QSize KChart::VerticalLineLayoutItem::sizeHint() const
586 {
587     return QSize( 3, -1 ); // see qframe.cpp
588 }
589 
590 
paint(QPainter * painter)591 void KChart::VerticalLineLayoutItem::paint( QPainter* painter )
592 {
593     if ( !mRect.isValid() )
594         return;
595 
596     painter->drawLine( QPointF( mRect.center().x(), mRect.top() ),
597                        QPointF( mRect.center().x(), mRect.bottom() ) );
598 }
599 
600 
601 
MarkerLayoutItem(KChart::AbstractDiagram * diagram,const MarkerAttributes & marker,const QBrush & brush,const QPen & pen,Qt::Alignment alignment)602 KChart::MarkerLayoutItem::MarkerLayoutItem( KChart::AbstractDiagram* diagram,
603                                              const MarkerAttributes& marker,
604                                              const QBrush& brush, const QPen& pen,
605                                              Qt::Alignment alignment )
606     : AbstractLayoutItem( alignment )
607     , mDiagram( diagram )
608     , mMarker( marker )
609     , mBrush( brush )
610     , mPen( pen )
611 {
612 }
613 
expandingDirections() const614 Qt::Orientations KChart::MarkerLayoutItem::expandingDirections() const
615 {
616     return Qt::Orientations(); // Grow neither vertically nor horizontally
617 }
618 
geometry() const619 QRect KChart::MarkerLayoutItem::geometry() const
620 {
621     return mRect;
622 }
623 
isEmpty() const624 bool KChart::MarkerLayoutItem::isEmpty() const
625 {
626     return false; // never empty, otherwise the layout item would not exist
627 }
628 
maximumSize() const629 QSize KChart::MarkerLayoutItem::maximumSize() const
630 {
631     return sizeHint(); // PENDING(kalle) Review, quite inflexible
632 }
633 
minimumSize() const634 QSize KChart::MarkerLayoutItem::minimumSize() const
635 {
636     return sizeHint(); // PENDING(kalle) Review, quite inflexible
637 }
638 
setGeometry(const QRect & r)639 void KChart::MarkerLayoutItem::setGeometry( const QRect& r )
640 {
641     mRect = r;
642 }
643 
sizeHint() const644 QSize KChart::MarkerLayoutItem::sizeHint() const
645 {
646     //qDebug() << "KChart::MarkerLayoutItem::sizeHint() returns:"<<mMarker.markerSize().toSize();
647     return mMarker.markerSize().toSize();
648 }
649 
paint(QPainter * painter)650 void KChart::MarkerLayoutItem::paint( QPainter* painter )
651 {
652     paintIntoRect( painter, mRect, mDiagram, mMarker, mBrush, mPen );
653 }
654 
paintIntoRect(QPainter * painter,const QRect & rect,AbstractDiagram * diagram,const MarkerAttributes & marker,const QBrush & brush,const QPen & pen)655 void KChart::MarkerLayoutItem::paintIntoRect(
656         QPainter* painter,
657         const QRect& rect,
658         AbstractDiagram* diagram,
659         const MarkerAttributes& marker,
660         const QBrush& brush,
661         const QPen& pen )
662 {
663     if ( !rect.isValid() )
664         return;
665 
666     // The layout management may assign a larger rect than what we
667     // wanted. We need to adjust the position.
668     const QSize siz = marker.markerSize().toSize();
669     QPointF pos = rect.topLeft();
670     pos += QPointF( static_cast<qreal>(( rect.width()  - siz.width()) / 2.0 ),
671                     static_cast<qreal>(( rect.height() - siz.height()) / 2.0 ) );
672 
673 #ifdef DEBUG_ITEMS_PAINT
674     QPointF oldPos = pos;
675 #endif
676 
677 // And finally, drawMarker() assumes the position to be the center
678     // of the marker, adjust again.
679     pos += QPointF( static_cast<qreal>( siz.width() ) / 2.0,
680                     static_cast<qreal>( siz.height() )/ 2.0 );
681 
682     diagram->paintMarker( painter, marker, brush, pen, pos.toPoint(), siz );
683 
684 #ifdef DEBUG_ITEMS_PAINT
685     const QPen oldPen( painter->pen() );
686     painter->setPen( Qt::red );
687     painter->drawRect( QRect( oldPos.toPoint(), siz ) );
688     painter->setPen( oldPen );
689 #endif
690 }
691 
692 
LineLayoutItem(KChart::AbstractDiagram * diagram,int length,const QPen & pen,Qt::Alignment legendLineSymbolAlignment,Qt::Alignment alignment)693 KChart::LineLayoutItem::LineLayoutItem( KChart::AbstractDiagram* diagram,
694                                          int length,
695                                          const QPen& pen,
696                                          Qt::Alignment legendLineSymbolAlignment,
697                                          Qt::Alignment alignment )
698     : AbstractLayoutItem( alignment )
699     , mDiagram( diagram )
700     , mLength( length )
701     , mPen( pen )
702     , mLegendLineSymbolAlignment(legendLineSymbolAlignment)
703 {
704     // enforce a minimum pen width
705     if ( pen.width() < 2 )
706         mPen.setWidth( 2 );
707 }
708 
expandingDirections() const709 Qt::Orientations KChart::LineLayoutItem::expandingDirections() const
710 {
711     return Qt::Orientations(); // Grow neither vertically nor horizontally
712 }
713 
geometry() const714 QRect KChart::LineLayoutItem::geometry() const
715 {
716     return mRect;
717 }
718 
isEmpty() const719 bool KChart::LineLayoutItem::isEmpty() const
720 {
721     return false; // never empty, otherwise the layout item would not exist
722 }
723 
maximumSize() const724 QSize KChart::LineLayoutItem::maximumSize() const
725 {
726     return sizeHint(); // PENDING(kalle) Review, quite inflexible
727 }
728 
minimumSize() const729 QSize KChart::LineLayoutItem::minimumSize() const
730 {
731     return sizeHint(); // PENDING(kalle) Review, quite inflexible
732 }
733 
setGeometry(const QRect & r)734 void KChart::LineLayoutItem::setGeometry( const QRect& r )
735 {
736     mRect = r;
737 }
738 
sizeHint() const739 QSize KChart::LineLayoutItem::sizeHint() const
740 {
741     return QSize( mLength, mPen.width() + 2 );
742 }
743 
744 
setLegendLineSymbolAlignment(Qt::Alignment legendLineSymbolAlignment)745 void KChart::LineLayoutItem::setLegendLineSymbolAlignment(Qt::Alignment legendLineSymbolAlignment)
746 {
747     if (mLegendLineSymbolAlignment == legendLineSymbolAlignment)
748         return;
749 
750     mLegendLineSymbolAlignment = legendLineSymbolAlignment;
751 }
752 
legendLineSymbolAlignment() const753 Qt::Alignment KChart::LineLayoutItem::legendLineSymbolAlignment() const
754 {
755     return mLegendLineSymbolAlignment;
756 }
757 
paint(QPainter * painter)758 void KChart::LineLayoutItem::paint( QPainter* painter )
759 {
760     paintIntoRect( painter, mRect, mPen, mLegendLineSymbolAlignment );
761 }
762 
paintIntoRect(QPainter * painter,const QRect & rect,const QPen & pen,Qt::Alignment lineAlignment)763 void KChart::LineLayoutItem::paintIntoRect(
764         QPainter* painter,
765         const QRect& rect,
766         const QPen& pen,
767         Qt::Alignment lineAlignment)
768 {
769     if ( ! rect.isValid() )
770         return;
771 
772     const QPen oldPen = painter->pen();
773     painter->setPen( PrintingParameters::scalePen( pen ) );
774     qreal y = 0;
775     if (lineAlignment == Qt::AlignTop)
776         y = rect.top();
777     else if (lineAlignment == Qt::AlignBottom)
778         y = rect.bottom();
779     else
780         y = rect.center().y();
781 
782     painter->drawLine( QPointF( rect.left(), y ),
783                        QPointF( rect.right(), y ) );
784     painter->setPen( oldPen );
785 }
786 
787 
LineWithMarkerLayoutItem(KChart::AbstractDiagram * diagram,int lineLength,const QPen & linePen,int markerOffs,const MarkerAttributes & marker,const QBrush & markerBrush,const QPen & markerPen,Qt::Alignment alignment)788 KChart::LineWithMarkerLayoutItem::LineWithMarkerLayoutItem(
789         KChart::AbstractDiagram* diagram,
790         int lineLength,
791         const QPen& linePen,
792         int markerOffs,
793         const MarkerAttributes& marker,
794         const QBrush& markerBrush,
795         const QPen& markerPen,
796         Qt::Alignment alignment )
797     : AbstractLayoutItem( alignment )
798     , mDiagram( diagram )
799     , mLineLength( lineLength )
800     , mLinePen( linePen )
801     , mMarkerOffs( markerOffs )
802     , mMarker( marker )
803     , mMarkerBrush( markerBrush )
804     , mMarkerPen( markerPen )
805 {
806 }
807 
expandingDirections() const808 Qt::Orientations KChart::LineWithMarkerLayoutItem::expandingDirections() const
809 {
810     return Qt::Orientations(); // Grow neither vertically nor horizontally
811 }
812 
geometry() const813 QRect KChart::LineWithMarkerLayoutItem::geometry() const
814 {
815     return mRect;
816 }
817 
isEmpty() const818 bool KChart::LineWithMarkerLayoutItem::isEmpty() const
819 {
820     return false; // never empty, otherwise the layout item would not exist
821 }
822 
maximumSize() const823 QSize KChart::LineWithMarkerLayoutItem::maximumSize() const
824 {
825     return sizeHint(); // PENDING(kalle) Review, quite inflexible
826 }
827 
minimumSize() const828 QSize KChart::LineWithMarkerLayoutItem::minimumSize() const
829 {
830     return sizeHint(); // PENDING(kalle) Review, quite inflexible
831 }
832 
setGeometry(const QRect & r)833 void KChart::LineWithMarkerLayoutItem::setGeometry( const QRect& r )
834 {
835     mRect = r;
836 }
837 
sizeHint() const838 QSize KChart::LineWithMarkerLayoutItem::sizeHint() const
839 {
840     const QSize lineSize( mLineLength, mLinePen.width() + 2 );
841     return lineSize.expandedTo( mMarker.markerSize().toSize() );
842 }
843 
paint(QPainter * painter)844 void KChart::LineWithMarkerLayoutItem::paint( QPainter* painter )
845 {
846     // paint the line over the full width, into the vertical middle of the rect
847     LineLayoutItem::paintIntoRect( painter, mRect, mLinePen, Qt::AlignCenter );
848 
849     // paint the marker with the given offset from the left side of the line
850     const QRect r(
851             QPoint( mRect.x()+mMarkerOffs, mRect.y() ),
852             QSize( mMarker.markerSize().toSize().width(), mRect.height() ) );
853     MarkerLayoutItem::paintIntoRect(
854             painter, r, mDiagram, mMarker, mMarkerBrush, mMarkerPen );
855 }
856 
AutoSpacerLayoutItem(bool layoutIsAtTopPosition,QHBoxLayout * rightLeftLayout,bool layoutIsAtLeftPosition,QVBoxLayout * topBottomLayout)857 KChart::AutoSpacerLayoutItem::AutoSpacerLayoutItem(
858         bool layoutIsAtTopPosition, QHBoxLayout *rightLeftLayout,
859         bool layoutIsAtLeftPosition, QVBoxLayout *topBottomLayout )
860     : AbstractLayoutItem( Qt::AlignCenter )
861     , mLayoutIsAtTopPosition( layoutIsAtTopPosition )
862     , mRightLeftLayout( rightLeftLayout )
863     , mLayoutIsAtLeftPosition( layoutIsAtLeftPosition )
864     , mTopBottomLayout( topBottomLayout )
865 {
866 }
867 
expandingDirections() const868 Qt::Orientations KChart::AutoSpacerLayoutItem::expandingDirections() const
869 {
870     return Qt::Orientations(); // Grow neither vertically nor horizontally
871 }
872 
geometry() const873 QRect KChart::AutoSpacerLayoutItem::geometry() const
874 {
875     return mRect;
876 }
877 
isEmpty() const878 bool KChart::AutoSpacerLayoutItem::isEmpty() const
879 {
880     return true; // never empty, otherwise the layout item would not exist
881 }
882 
maximumSize() const883 QSize KChart::AutoSpacerLayoutItem::maximumSize() const
884 {
885     return sizeHint();
886 }
887 
minimumSize() const888 QSize KChart::AutoSpacerLayoutItem::minimumSize() const
889 {
890     return sizeHint();
891 }
892 
setGeometry(const QRect & r)893 void KChart::AutoSpacerLayoutItem::setGeometry( const QRect& r )
894 {
895     mRect = r;
896 }
897 
898 
updateCommonBrush(QBrush & commonBrush,bool & bStart,const KChart::AbstractArea & area)899 static void updateCommonBrush( QBrush& commonBrush, bool& bStart, const KChart::AbstractArea& area )
900 {
901     const KChart::BackgroundAttributes ba( area.backgroundAttributes() );
902     const bool hasSimpleBrush = (
903             ! area.frameAttributes().isVisible() &&
904             ba.isVisible() &&
905             ba.pixmapMode() == KChart::BackgroundAttributes::BackgroundPixmapModeNone &&
906             ba.brush().gradient() == nullptr );
907     if ( bStart ) {
908         bStart = false;
909         commonBrush = hasSimpleBrush ? ba.brush() : QBrush();
910     } else {
911         if ( ! hasSimpleBrush || ba.brush() != commonBrush )
912         {
913             commonBrush = QBrush();
914         }
915     }
916 }
917 
sizeHint() const918 QSize KChart::AutoSpacerLayoutItem::sizeHint() const
919 {
920     QBrush commonBrush;
921     bool bStart=true;
922     // calculate the maximal overlap of the top/bottom axes:
923     int topBottomOverlap = 0;
924     if ( mTopBottomLayout ) {
925         for (int i = 0; i < mTopBottomLayout->count(); ++i) {
926             AbstractArea* area = dynamic_cast<AbstractArea*>(mTopBottomLayout->itemAt(i));
927             if ( area ) {
928                 //qDebug() << "AutoSpacerLayoutItem testing" << area;
929                 topBottomOverlap = qMax( topBottomOverlap,
930                                          mLayoutIsAtLeftPosition ? area->rightOverlap()
931                                                                  : area->leftOverlap() );
932                 updateCommonBrush( commonBrush, bStart, *area );
933             }
934         }
935     }
936     // calculate the maximal overlap of the left/right axes:
937     int leftRightOverlap = 0;
938     if ( mRightLeftLayout ) {
939         for (int i = 0; i < mRightLeftLayout->count(); ++i) {
940             AbstractArea* area = dynamic_cast<AbstractArea*>(mRightLeftLayout->itemAt(i));
941             if ( area ) {
942                 //qDebug() << "AutoSpacerLayoutItem testing" << area;
943                 leftRightOverlap = qMax( leftRightOverlap,
944                                          mLayoutIsAtTopPosition ? area->bottomOverlap()
945                                                                 : area->topOverlap() );
946                 updateCommonBrush( commonBrush, bStart, *area );
947             }
948         }
949     }
950     if ( topBottomOverlap > 0 && leftRightOverlap > 0 )
951         mCommonBrush = commonBrush;
952     else
953         mCommonBrush = QBrush();
954     mCachedSize = QSize( topBottomOverlap, leftRightOverlap );
955     //qDebug() << mCachedSize;
956     return mCachedSize;
957 }
958 
959 
paint(QPainter * painter)960 void KChart::AutoSpacerLayoutItem::paint( QPainter* painter )
961 {
962     if ( mParentLayout && mRect.isValid() && mCachedSize.isValid() &&
963         mCommonBrush.style() != Qt::NoBrush )
964     {
965         QPoint p1( mRect.topLeft() );
966         QPoint p2( mRect.bottomRight() );
967         if ( mLayoutIsAtLeftPosition )
968             p1.rx() += mCachedSize.width() - mParentLayout->spacing();
969         else
970             p2.rx() -= mCachedSize.width() - mParentLayout->spacing();
971         if ( mLayoutIsAtTopPosition ) {
972             p1.ry() += mCachedSize.height() - mParentLayout->spacing() - 1;
973             p2.ry() -= 1;
974         } else
975             p2.ry() -= mCachedSize.height() - mParentLayout->spacing() - 1;
976         //qDebug() << mLayoutIsAtTopPosition << mLayoutIsAtLeftPosition;
977         //qDebug() << mRect;
978         //qDebug() << mParentLayout->margin();
979         //qDebug() << QRect( p1, p2 );
980         const QPoint oldBrushOrigin( painter->brushOrigin() );
981         const QBrush oldBrush( painter->brush() );
982         const QPen   oldPen( painter->pen() );
983         const QPointF newTopLeft( painter->deviceMatrix().map( p1 ) );
984         painter->setBrushOrigin( newTopLeft );
985         painter->setBrush( mCommonBrush );
986         painter->setPen( Qt::NoPen );
987         painter->drawRect( QRect( p1, p2 ) );
988         painter->setBrushOrigin( oldBrushOrigin );
989         painter->setBrush( oldBrush );
990         painter->setPen( oldPen );
991     }
992     // debug code:
993 #if 0
994     //qDebug() << "KChart::AutoSpacerLayoutItem::paint()";
995     if ( !mRect.isValid() )
996         return;
997 
998     painter->drawRect( mRect );
999     painter->drawLine( QPointF( mRect.topLeft(), mRect.bottomRight() ) );
1000     painter->drawLine( QPointF( mRect.topRight(), mRect.bottomLeft() ) );
1001 #endif
1002 }
1003