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