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 "KChartAbstractCoordinatePlane.h"
21 #include "KChartAbstractCoordinatePlane_p.h"
22 
23 #include "KChartChart.h"
24 #include "KChartGridAttributes.h"
25 
26 #include <QGridLayout>
27 #include <QRubberBand>
28 #include <QMouseEvent>
29 #include <qmath.h>
30 
31 using namespace KChart;
32 
33 #define d d_func()
34 
Private()35 AbstractCoordinatePlane::Private::Private()
36     : AbstractArea::Private()
37     , parent( nullptr )
38     , grid( nullptr )
39     , referenceCoordinatePlane( nullptr )
40     , enableCornerSpacers( true )
41     , enableRubberBandZooming( false )
42     , rubberBand( nullptr )
43 {
44     // this bloc left empty intentionally
45 }
46 
47 
AbstractCoordinatePlane(KChart::Chart * parent)48 AbstractCoordinatePlane::AbstractCoordinatePlane ( KChart::Chart* parent )
49     : AbstractArea ( new Private() )
50 {
51     d->parent = parent;
52     d->init();
53 }
54 
~AbstractCoordinatePlane()55 AbstractCoordinatePlane::~AbstractCoordinatePlane()
56 {
57     Q_EMIT destroyedCoordinatePlane( this );
58 }
59 
init()60 void AbstractCoordinatePlane::init()
61 {
62     d->initialize();  // virtual method to init the correct grid: cartesian, polar, ...
63     connect( this, SIGNAL(internal_geometryChanged(QRect,QRect)),
64              this, SIGNAL(geometryChanged(QRect,QRect)),
65              Qt::QueuedConnection );
66 }
67 
addDiagram(AbstractDiagram * diagram)68 void AbstractCoordinatePlane::addDiagram ( AbstractDiagram* diagram )
69 {
70     // diagrams are invisible and paint through their paint() method
71     diagram->hide();
72 
73     d->diagrams.append( diagram );
74     diagram->setParent( d->parent );
75     diagram->setCoordinatePlane( this );
76     layoutDiagrams();
77     layoutPlanes(); // there might be new axes, etc
78     connect( diagram, SIGNAL(modelsChanged()), this, SLOT(layoutPlanes()) );
79     connect( diagram, SIGNAL(modelDataChanged()), this, SLOT(update()) );
80     connect( diagram, SIGNAL(modelDataChanged()), this, SLOT(relayout()) );
81     connect( this, SIGNAL(boundariesChanged()), diagram, SIGNAL(boundariesChanged()) );
82 
83     update();
84     Q_EMIT boundariesChanged();
85 }
86 
87 /*virtual*/
replaceDiagram(AbstractDiagram * diagram,AbstractDiagram * oldDiagram_)88 void AbstractCoordinatePlane::replaceDiagram ( AbstractDiagram* diagram, AbstractDiagram* oldDiagram_ )
89 {
90     if ( diagram && oldDiagram_ != diagram ) {
91         AbstractDiagram* oldDiagram = oldDiagram_;
92         if ( d->diagrams.count() ) {
93             if ( ! oldDiagram ) {
94                 oldDiagram = d->diagrams.first();
95                 if ( oldDiagram == diagram )
96                     return;
97             }
98             takeDiagram( oldDiagram );
99         }
100         delete oldDiagram;
101         addDiagram( diagram );
102         layoutDiagrams();
103         layoutPlanes(); // there might be new axes, etc
104         update();
105     }
106 }
107 
108 /*virtual*/
takeDiagram(AbstractDiagram * diagram)109 void AbstractCoordinatePlane::takeDiagram ( AbstractDiagram* diagram )
110 {
111     const int idx = d->diagrams.indexOf( diagram );
112     if ( idx != -1 ) {
113         d->diagrams.removeAt( idx );
114         diagram->setParent( nullptr );
115         diagram->setCoordinatePlane( nullptr );
116         disconnect( diagram, SIGNAL(modelsChanged()), this, SLOT(layoutPlanes()) );
117         disconnect( diagram, SIGNAL(modelDataChanged()), this, SLOT(update()) );
118         disconnect( diagram, SIGNAL(modelDataChanged()), this, SLOT(relayout()) );
119         layoutDiagrams();
120         update();
121     }
122 }
123 
124 
diagram()125 AbstractDiagram* AbstractCoordinatePlane::diagram()
126 {
127     if ( d->diagrams.isEmpty() )
128     {
129         return nullptr;
130     } else {
131         return d->diagrams.first();
132     }
133 }
134 
diagrams()135 AbstractDiagramList AbstractCoordinatePlane::diagrams()
136 {
137     return d->diagrams;
138 }
139 
diagrams() const140 ConstAbstractDiagramList AbstractCoordinatePlane::diagrams() const
141 {
142     ConstAbstractDiagramList list;
143 #ifndef QT_NO_STL
144     qCopy( d->diagrams.begin(), d->diagrams.end(), std::back_inserter( list ) );
145 #else
146     Q_FOREACH( AbstractDiagram * a, d->diagrams )
147         list.push_back( a );
148 #endif
149     return list;
150 }
151 
setGlobalGridAttributes(const GridAttributes & a)152 void KChart::AbstractCoordinatePlane::setGlobalGridAttributes( const GridAttributes& a )
153 {
154     d->gridAttributes = a;
155     update();
156 }
157 
globalGridAttributes() const158 GridAttributes KChart::AbstractCoordinatePlane::globalGridAttributes() const
159 {
160     return d->gridAttributes;
161 }
162 
gridDimensionsList()163 KChart::DataDimensionsList KChart::AbstractCoordinatePlane::gridDimensionsList()
164 {
165     return d->grid->updateData( this );
166 }
167 
setGridNeedsRecalculate()168 void KChart::AbstractCoordinatePlane::setGridNeedsRecalculate()
169 {
170     d->grid->setNeedRecalculate();
171 }
172 
setReferenceCoordinatePlane(AbstractCoordinatePlane * plane)173 void KChart::AbstractCoordinatePlane::setReferenceCoordinatePlane( AbstractCoordinatePlane * plane )
174 {
175     d->referenceCoordinatePlane = plane;
176 }
177 
referenceCoordinatePlane() const178 AbstractCoordinatePlane * KChart::AbstractCoordinatePlane::referenceCoordinatePlane( ) const
179 {
180     return d->referenceCoordinatePlane;
181 }
182 
setParent(KChart::Chart * parent)183 void KChart::AbstractCoordinatePlane::setParent( KChart::Chart* parent )
184 {
185     d->parent = parent;
186 }
187 
parent() const188 const KChart::Chart* KChart::AbstractCoordinatePlane::parent() const
189 {
190     return d->parent;
191 }
192 
parent()193 KChart::Chart* KChart::AbstractCoordinatePlane::parent()
194 {
195     return d->parent;
196 }
197 
198 /* pure virtual in QLayoutItem */
isEmpty() const199 bool KChart::AbstractCoordinatePlane::isEmpty() const
200 {
201     return false; // never empty!
202     // coordinate planes with no associated diagrams
203     // are showing a default grid of ()1..10, 1..10) stepWidth 1
204 }
205 /* pure virtual in QLayoutItem */
expandingDirections() const206 Qt::Orientations KChart::AbstractCoordinatePlane::expandingDirections() const
207 {
208     return Qt::Vertical | Qt::Horizontal;
209 }
210 /* pure virtual in QLayoutItem */
maximumSize() const211 QSize KChart::AbstractCoordinatePlane::maximumSize() const
212 {
213     // No maximum size set. Especially not parent()->size(), we are not layouting
214     // to the parent widget's size when using Chart::paint()!
215     return QSize(QLAYOUTSIZE_MAX, QLAYOUTSIZE_MAX);
216 }
217 /* pure virtual in QLayoutItem */
minimumSize() const218 QSize KChart::AbstractCoordinatePlane::minimumSize() const
219 {
220     return QSize(60, 60); // this default can be overwritten by derived classes
221 }
222 /* pure virtual in QLayoutItem */
sizeHint() const223 QSize KChart::AbstractCoordinatePlane::sizeHint() const
224 {
225     // we return our maxiumu (which is the full size of the Chart)
226     // even if we know the plane will be smaller
227     return maximumSize();
228 }
229 /* pure virtual in QLayoutItem */
setGeometry(const QRect & r)230 void KChart::AbstractCoordinatePlane::setGeometry( const QRect& r )
231 {
232     if ( d->geometry != r ) {
233         // inform the outside word by Signal geometryChanged()
234         // via a queued connection to internal_geometryChanged()
235         Q_EMIT internal_geometryChanged( d->geometry, r );
236 
237         d->geometry = r;
238         // Note: We do *not* call update() here
239         //       because it would invoke KChart::update() recursively.
240     }
241 }
242 /* pure virtual in QLayoutItem */
geometry() const243 QRect KChart::AbstractCoordinatePlane::geometry() const
244 {
245     return d->geometry;
246 }
247 
update()248 void KChart::AbstractCoordinatePlane::update()
249 {
250     //qDebug("KChart::AbstractCoordinatePlane::update() called");
251     Q_EMIT needUpdate();
252 }
253 
relayout()254 void KChart::AbstractCoordinatePlane::relayout()
255 {
256     //qDebug("KChart::AbstractCoordinatePlane::relayout() called");
257     Q_EMIT needRelayout();
258 }
259 
layoutPlanes()260 void KChart::AbstractCoordinatePlane::layoutPlanes()
261 {
262     //qDebug("KChart::AbstractCoordinatePlane::relayout() called");
263     Q_EMIT needLayoutPlanes();
264 }
265 
setRubberBandZoomingEnabled(bool enable)266 void KChart::AbstractCoordinatePlane::setRubberBandZoomingEnabled( bool enable )
267 {
268     d->enableRubberBandZooming = enable;
269 
270     if ( !enable && d->rubberBand != nullptr )
271     {
272         delete d->rubberBand;
273         d->rubberBand = nullptr;
274     }
275 }
276 
isRubberBandZoomingEnabled() const277 bool KChart::AbstractCoordinatePlane::isRubberBandZoomingEnabled() const
278 {
279     return d->enableRubberBandZooming;
280 }
281 
setCornerSpacersEnabled(bool enable)282 void KChart::AbstractCoordinatePlane::setCornerSpacersEnabled( bool enable )
283 {
284     if ( d->enableCornerSpacers == enable ) return;
285 
286     d->enableCornerSpacers = enable;
287     Q_EMIT needRelayout();
288 }
289 
isCornerSpacersEnabled() const290 bool KChart::AbstractCoordinatePlane::isCornerSpacersEnabled() const
291 {
292     return d->enableCornerSpacers;
293 }
294 
mousePressEvent(QMouseEvent * event)295 void KChart::AbstractCoordinatePlane::mousePressEvent( QMouseEvent* event )
296 {
297     if ( event->button() == Qt::LeftButton )
298     {
299         if ( d->enableRubberBandZooming && d->rubberBand == nullptr )
300             d->rubberBand = new QRubberBand( QRubberBand::Rectangle, qobject_cast< QWidget* >( parent() ) );
301 
302         if ( d->rubberBand != nullptr )
303         {
304             d->rubberBandOrigin = event->pos();
305             d->rubberBand->setGeometry( QRect( event->pos(), QSize() ) );
306             d->rubberBand->show();
307 
308             event->accept();
309         }
310     }
311     else if ( event->button() == Qt::RightButton )
312     {
313         if ( d->enableRubberBandZooming && !d->rubberBandZoomConfigHistory.isEmpty() )
314         {
315             // restore the last config from the stack
316             ZoomParameters config = d->rubberBandZoomConfigHistory.pop();
317             setZoomFactorX( config.xFactor );
318             setZoomFactorY( config.yFactor );
319             setZoomCenter( config.center() );
320 
321             QWidget* const p = qobject_cast< QWidget* >( parent() );
322             if ( p != nullptr )
323                 p->update();
324 
325             event->accept();
326         }
327     }
328 
329     Q_FOREACH( AbstractDiagram * a, d->diagrams )
330     {
331         a->mousePressEvent( event );
332     }
333 }
334 
mouseDoubleClickEvent(QMouseEvent * event)335 void KChart::AbstractCoordinatePlane::mouseDoubleClickEvent( QMouseEvent* event )
336 {
337     if ( event->button() == Qt::RightButton )
338     {
339         // othewise the second click gets lost
340         // which is pretty annoying when zooming out fast
341         mousePressEvent( event );
342     }
343     Q_FOREACH( AbstractDiagram * a, d->diagrams )
344     {
345         a->mouseDoubleClickEvent( event );
346     }
347 }
348 
mouseReleaseEvent(QMouseEvent * event)349 void KChart::AbstractCoordinatePlane::mouseReleaseEvent( QMouseEvent* event )
350 {
351     if ( d->rubberBand != nullptr )
352     {
353         // save the old config on the stack
354         d->rubberBandZoomConfigHistory.push( ZoomParameters( zoomFactorX(), zoomFactorY(), zoomCenter() ) );
355 
356         // this is the height/width of the rubber band in pixel space
357         const qreal rubberWidth = static_cast< qreal >( d->rubberBand->width() );
358         const qreal rubberHeight = static_cast< qreal >( d->rubberBand->height() );
359 
360         if ( rubberWidth > 0.0 && rubberHeight > 0.0 )
361         {
362             // this is the center of the rubber band in pixel space
363             const qreal centerX = qFloor( d->rubberBand->geometry().width() / 2.0 + d->rubberBand->geometry().x() );
364             const qreal centerY = qCeil( d->rubberBand->geometry().height() / 2.0 + d->rubberBand->geometry().y() );
365 
366             const qreal rubberCenterX = static_cast< qreal >( centerX - geometry().x() );
367             const qreal rubberCenterY = static_cast< qreal >( centerY - geometry().y() );
368 
369             // this is the height/width of the plane in pixel space
370             const qreal myWidth = static_cast< qreal >( geometry().width() );
371             const qreal myHeight = static_cast< qreal >( geometry().height() );
372 
373             // this describes the new center of zooming, relative to the plane pixel space
374             const qreal newCenterX = rubberCenterX / myWidth / zoomFactorX() + zoomCenter().x() - 0.5 / zoomFactorX();
375             const qreal newCenterY = rubberCenterY / myHeight / zoomFactorY() + zoomCenter().y() - 0.5 / zoomFactorY();
376 
377             // this will be the new zoom factor
378             const qreal newZoomFactorX = zoomFactorX() * myWidth / rubberWidth;
379             const qreal newZoomFactorY = zoomFactorY() * myHeight / rubberHeight;
380 
381             // and this the new center
382             const QPointF newZoomCenter( newCenterX, newCenterY );
383 
384             setZoomFactorX( newZoomFactorX );
385             setZoomFactorY( newZoomFactorY );
386             setZoomCenter( newZoomCenter );
387         }
388 
389         d->rubberBand->parentWidget()->update();
390         delete d->rubberBand;
391         d->rubberBand = nullptr;
392 
393         event->accept();
394     }
395 
396     Q_FOREACH( AbstractDiagram * a, d->diagrams )
397     {
398         a->mouseReleaseEvent( event );
399     }
400 }
401 
mouseMoveEvent(QMouseEvent * event)402 void KChart::AbstractCoordinatePlane::mouseMoveEvent( QMouseEvent* event )
403 {
404     if ( d->rubberBand != nullptr )
405     {
406         const QRect normalized = QRect( d->rubberBandOrigin, event->pos() ).normalized();
407         d->rubberBand->setGeometry( normalized &  geometry() );
408 
409         event->accept();
410     }
411 
412     Q_FOREACH( AbstractDiagram * a, d->diagrams )
413     {
414         a->mouseMoveEvent( event );
415     }
416 }
417 
418 #if defined(Q_COMPILER_MANGLES_RETURN_TYPE)
419 const
420 #endif
isVisiblePoint(const QPointF & point) const421 bool KChart::AbstractCoordinatePlane::isVisiblePoint( const QPointF& point ) const
422 {
423     return d->isVisiblePoint( this, point );
424 }
425 
sharedAxisMasterPlane(QPainter * p)426 AbstractCoordinatePlane* KChart::AbstractCoordinatePlane::sharedAxisMasterPlane( QPainter* p )
427 {
428     Q_UNUSED( p );
429     return this;
430 }
431 
432 #if !defined(QT_NO_DEBUG_STREAM)
433 #include "KChartEnums.h"
434 
operator <<(QDebug stream,const DataDimension & r)435 QDebug KChart::operator<<( QDebug stream, const DataDimension& r )
436 {
437     stream << "DataDimension("
438            << " start=" << r.start
439            << " end=" << r.end
440            << " sequence=" << KChartEnums::granularitySequenceToString( r.sequence )
441            << " isCalculated=" << r.isCalculated
442            << " calcMode=" << ( r.calcMode == AbstractCoordinatePlane::Logarithmic ? "Logarithmic" : "Linear" )
443            << " stepWidth=" << r.stepWidth
444            << " subStepWidth=" << r.subStepWidth
445            << " )";
446     return stream;
447 }
448 #endif
449 
450 #undef d
451