File indexing completed on 2024-05-12 04:20:28
0001 /* 0002 * SPDX-FileCopyrightText: 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. 0003 * 0004 * This file is part of the KD Chart library. 0005 * 0006 * SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "KChartAbstractCoordinatePlane.h" 0010 #include "KChartAbstractCoordinatePlane_p.h" 0011 0012 #include "KChartChart.h" 0013 #include "KChartGridAttributes.h" 0014 0015 #include <QGridLayout> 0016 #include <QRubberBand> 0017 #include <QMouseEvent> 0018 #include <qmath.h> 0019 0020 using namespace KChart; 0021 0022 #define d d_func() 0023 0024 AbstractCoordinatePlane::Private::Private() 0025 : AbstractArea::Private() 0026 , parent( nullptr ) 0027 , grid( nullptr ) 0028 , referenceCoordinatePlane( nullptr ) 0029 , enableCornerSpacers( true ) 0030 , enableRubberBandZooming( false ) 0031 , rubberBand( nullptr ) 0032 { 0033 // this block left empty intentionally 0034 } 0035 0036 0037 AbstractCoordinatePlane::AbstractCoordinatePlane ( KChart::Chart* parent ) 0038 : AbstractArea ( new Private() ) 0039 { 0040 d->parent = parent; 0041 d->init(); 0042 } 0043 0044 AbstractCoordinatePlane::~AbstractCoordinatePlane() 0045 { 0046 Q_EMIT destroyedCoordinatePlane( this ); 0047 } 0048 0049 void AbstractCoordinatePlane::init() 0050 { 0051 d->initialize(); // virtual method to init the correct grid: cartesian, polar, ... 0052 connect( this, SIGNAL(internal_geometryChanged(QRect,QRect)), 0053 this, SIGNAL(geometryChanged(QRect,QRect)), 0054 Qt::QueuedConnection ); 0055 } 0056 0057 void AbstractCoordinatePlane::addDiagram ( AbstractDiagram* diagram ) 0058 { 0059 // diagrams are invisible and paint through their paint() method 0060 diagram->hide(); 0061 0062 d->diagrams.append( diagram ); 0063 diagram->setParent( d->parent ); 0064 diagram->setCoordinatePlane( this ); 0065 layoutDiagrams(); 0066 layoutPlanes(); // there might be new axes, etc 0067 connect( diagram, SIGNAL(modelsChanged()), this, SLOT(layoutPlanes()) ); 0068 connect( diagram, SIGNAL(modelDataChanged()), this, SLOT(update()) ); 0069 connect( diagram, SIGNAL(modelDataChanged()), this, SLOT(relayout()) ); 0070 connect( this, SIGNAL(boundariesChanged()), diagram, SIGNAL(boundariesChanged()) ); 0071 0072 update(); 0073 Q_EMIT boundariesChanged(); 0074 } 0075 0076 /*virtual*/ 0077 void AbstractCoordinatePlane::replaceDiagram ( AbstractDiagram* diagram, AbstractDiagram* oldDiagram_ ) 0078 { 0079 if ( diagram && oldDiagram_ != diagram ) { 0080 AbstractDiagram* oldDiagram = oldDiagram_; 0081 if ( d->diagrams.count() ) { 0082 if ( ! oldDiagram ) { 0083 oldDiagram = d->diagrams.first(); 0084 if ( oldDiagram == diagram ) 0085 return; 0086 } 0087 takeDiagram( oldDiagram ); 0088 } 0089 delete oldDiagram; 0090 addDiagram( diagram ); 0091 layoutDiagrams(); 0092 layoutPlanes(); // there might be new axes, etc 0093 update(); 0094 } 0095 } 0096 0097 /*virtual*/ 0098 void AbstractCoordinatePlane::takeDiagram ( AbstractDiagram* diagram ) 0099 { 0100 const int idx = d->diagrams.indexOf( diagram ); 0101 if ( idx != -1 ) { 0102 d->diagrams.removeAt( idx ); 0103 diagram->setParent( nullptr ); 0104 diagram->setCoordinatePlane( nullptr ); 0105 disconnect( diagram, SIGNAL(modelsChanged()), this, SLOT(layoutPlanes()) ); 0106 disconnect( diagram, SIGNAL(modelDataChanged()), this, SLOT(update()) ); 0107 disconnect( diagram, SIGNAL(modelDataChanged()), this, SLOT(relayout()) ); 0108 layoutDiagrams(); 0109 update(); 0110 } 0111 } 0112 0113 0114 AbstractDiagram* AbstractCoordinatePlane::diagram() 0115 { 0116 if ( d->diagrams.isEmpty() ) 0117 { 0118 return nullptr; 0119 } else { 0120 return d->diagrams.first(); 0121 } 0122 } 0123 0124 AbstractDiagramList AbstractCoordinatePlane::diagrams() 0125 { 0126 return d->diagrams; 0127 } 0128 0129 ConstAbstractDiagramList AbstractCoordinatePlane::diagrams() const 0130 { 0131 ConstAbstractDiagramList list; 0132 #ifndef QT_NO_STL 0133 qCopy( d->diagrams.begin(), d->diagrams.end(), std::back_inserter( list ) ); 0134 #else 0135 for ( AbstractDiagram * a : d->diagrams ) 0136 list.push_back( a ); 0137 #endif 0138 return list; 0139 } 0140 0141 void KChart::AbstractCoordinatePlane::setGlobalGridAttributes( const GridAttributes& a ) 0142 { 0143 d->gridAttributes = a; 0144 update(); 0145 } 0146 0147 GridAttributes KChart::AbstractCoordinatePlane::globalGridAttributes() const 0148 { 0149 return d->gridAttributes; 0150 } 0151 0152 KChart::DataDimensionsList KChart::AbstractCoordinatePlane::gridDimensionsList() 0153 { 0154 return d->grid->updateData( this ); 0155 } 0156 0157 void KChart::AbstractCoordinatePlane::setGridNeedsRecalculate() 0158 { 0159 d->grid->setNeedRecalculate(); 0160 } 0161 0162 void KChart::AbstractCoordinatePlane::setReferenceCoordinatePlane( AbstractCoordinatePlane * plane ) 0163 { 0164 d->referenceCoordinatePlane = plane; 0165 } 0166 0167 AbstractCoordinatePlane * KChart::AbstractCoordinatePlane::referenceCoordinatePlane( ) const 0168 { 0169 return d->referenceCoordinatePlane; 0170 } 0171 0172 void KChart::AbstractCoordinatePlane::setParent( KChart::Chart* parent ) 0173 { 0174 d->parent = parent; 0175 } 0176 0177 const KChart::Chart* KChart::AbstractCoordinatePlane::parent() const 0178 { 0179 return d->parent; 0180 } 0181 0182 KChart::Chart* KChart::AbstractCoordinatePlane::parent() 0183 { 0184 return d->parent; 0185 } 0186 0187 /* pure virtual in QLayoutItem */ 0188 bool KChart::AbstractCoordinatePlane::isEmpty() const 0189 { 0190 return false; // never empty! 0191 // coordinate planes with no associated diagrams 0192 // are showing a default grid of ()1..10, 1..10) stepWidth 1 0193 } 0194 /* pure virtual in QLayoutItem */ 0195 Qt::Orientations KChart::AbstractCoordinatePlane::expandingDirections() const 0196 { 0197 return Qt::Vertical | Qt::Horizontal; 0198 } 0199 /* pure virtual in QLayoutItem */ 0200 QSize KChart::AbstractCoordinatePlane::maximumSize() const 0201 { 0202 // No maximum size set. Especially not parent()->size(), we are not layouting 0203 // to the parent widget's size when using Chart::paint()! 0204 return QSize(QLAYOUTSIZE_MAX, QLAYOUTSIZE_MAX); 0205 } 0206 /* pure virtual in QLayoutItem */ 0207 QSize KChart::AbstractCoordinatePlane::minimumSize() const 0208 { 0209 return QSize(60, 60); // this default can be overwritten by derived classes 0210 } 0211 /* pure virtual in QLayoutItem */ 0212 QSize KChart::AbstractCoordinatePlane::sizeHint() const 0213 { 0214 // we return our maxiumu (which is the full size of the Chart) 0215 // even if we know the plane will be smaller 0216 return maximumSize(); 0217 } 0218 /* pure virtual in QLayoutItem */ 0219 void KChart::AbstractCoordinatePlane::setGeometry( const QRect& r ) 0220 { 0221 if ( d->geometry != r ) { 0222 // inform the outside word by Signal geometryChanged() 0223 // via a queued connection to internal_geometryChanged() 0224 Q_EMIT internal_geometryChanged( d->geometry, r ); 0225 0226 d->geometry = r; 0227 // Note: We do *not* call update() here 0228 // because it would invoke KChart::update() recursively. 0229 } 0230 } 0231 /* pure virtual in QLayoutItem */ 0232 QRect KChart::AbstractCoordinatePlane::geometry() const 0233 { 0234 return d->geometry; 0235 } 0236 0237 void KChart::AbstractCoordinatePlane::update() 0238 { 0239 //qDebug("KChart::AbstractCoordinatePlane::update() called"); 0240 Q_EMIT needUpdate(); 0241 } 0242 0243 void KChart::AbstractCoordinatePlane::relayout() 0244 { 0245 //qDebug("KChart::AbstractCoordinatePlane::relayout() called"); 0246 Q_EMIT needRelayout(); 0247 } 0248 0249 void KChart::AbstractCoordinatePlane::layoutPlanes() 0250 { 0251 //qDebug("KChart::AbstractCoordinatePlane::relayout() called"); 0252 Q_EMIT needLayoutPlanes(); 0253 } 0254 0255 void KChart::AbstractCoordinatePlane::setRubberBandZoomingEnabled( bool enable ) 0256 { 0257 d->enableRubberBandZooming = enable; 0258 0259 if ( !enable && d->rubberBand != nullptr ) 0260 { 0261 delete d->rubberBand; 0262 d->rubberBand = nullptr; 0263 } 0264 } 0265 0266 bool KChart::AbstractCoordinatePlane::isRubberBandZoomingEnabled() const 0267 { 0268 return d->enableRubberBandZooming; 0269 } 0270 0271 void KChart::AbstractCoordinatePlane::setCornerSpacersEnabled( bool enable ) 0272 { 0273 if ( d->enableCornerSpacers == enable ) return; 0274 0275 d->enableCornerSpacers = enable; 0276 Q_EMIT needRelayout(); 0277 } 0278 0279 bool KChart::AbstractCoordinatePlane::isCornerSpacersEnabled() const 0280 { 0281 return d->enableCornerSpacers; 0282 } 0283 0284 void KChart::AbstractCoordinatePlane::mousePressEvent( QMouseEvent* event ) 0285 { 0286 if ( event->button() == Qt::LeftButton ) 0287 { 0288 if ( d->enableRubberBandZooming && d->rubberBand == nullptr ) 0289 d->rubberBand = new QRubberBand( QRubberBand::Rectangle, qobject_cast< QWidget* >( parent() ) ); 0290 0291 if ( d->rubberBand != nullptr ) 0292 { 0293 d->rubberBandOrigin = event->pos(); 0294 d->rubberBand->setGeometry( QRect( event->pos(), QSize() ) ); 0295 d->rubberBand->show(); 0296 0297 event->accept(); 0298 } 0299 } 0300 else if ( event->button() == Qt::RightButton ) 0301 { 0302 if ( d->enableRubberBandZooming && !d->rubberBandZoomConfigHistory.isEmpty() ) 0303 { 0304 // restore the last config from the stack 0305 ZoomParameters config = d->rubberBandZoomConfigHistory.pop(); 0306 setZoomFactorX( config.xFactor ); 0307 setZoomFactorY( config.yFactor ); 0308 setZoomCenter( config.center() ); 0309 0310 QWidget* const p = qobject_cast< QWidget* >( parent() ); 0311 if ( p != nullptr ) 0312 p->update(); 0313 0314 event->accept(); 0315 } 0316 } 0317 0318 for ( AbstractDiagram * a : qAsConst(d->diagrams) ) 0319 { 0320 a->mousePressEvent( event ); 0321 } 0322 } 0323 0324 void KChart::AbstractCoordinatePlane::mouseDoubleClickEvent( QMouseEvent* event ) 0325 { 0326 if ( event->button() == Qt::RightButton ) 0327 { 0328 // otherwise the second click gets lost 0329 // which is pretty annoying when zooming out fast 0330 mousePressEvent( event ); 0331 } 0332 for ( AbstractDiagram * a : qAsConst(d->diagrams) ) 0333 { 0334 a->mouseDoubleClickEvent( event ); 0335 } 0336 } 0337 0338 void KChart::AbstractCoordinatePlane::mouseReleaseEvent( QMouseEvent* event ) 0339 { 0340 if ( d->rubberBand != nullptr ) 0341 { 0342 // save the old config on the stack 0343 d->rubberBandZoomConfigHistory.push( ZoomParameters( zoomFactorX(), zoomFactorY(), zoomCenter() ) ); 0344 0345 // this is the height/width of the rubber band in pixel space 0346 const qreal rubberWidth = static_cast< qreal >( d->rubberBand->width() ); 0347 const qreal rubberHeight = static_cast< qreal >( d->rubberBand->height() ); 0348 0349 if ( rubberWidth > 0.0 && rubberHeight > 0.0 ) 0350 { 0351 // this is the center of the rubber band in pixel space 0352 const qreal centerX = qFloor( d->rubberBand->geometry().width() / 2.0 + d->rubberBand->geometry().x() ); 0353 const qreal centerY = qCeil( d->rubberBand->geometry().height() / 2.0 + d->rubberBand->geometry().y() ); 0354 0355 const qreal rubberCenterX = static_cast< qreal >( centerX - geometry().x() ); 0356 const qreal rubberCenterY = static_cast< qreal >( centerY - geometry().y() ); 0357 0358 // this is the height/width of the plane in pixel space 0359 const qreal myWidth = static_cast< qreal >( geometry().width() ); 0360 const qreal myHeight = static_cast< qreal >( geometry().height() ); 0361 0362 // this describes the new center of zooming, relative to the plane pixel space 0363 const qreal newCenterX = rubberCenterX / myWidth / zoomFactorX() + zoomCenter().x() - 0.5 / zoomFactorX(); 0364 const qreal newCenterY = rubberCenterY / myHeight / zoomFactorY() + zoomCenter().y() - 0.5 / zoomFactorY(); 0365 0366 // this will be the new zoom factor 0367 const qreal newZoomFactorX = zoomFactorX() * myWidth / rubberWidth; 0368 const qreal newZoomFactorY = zoomFactorY() * myHeight / rubberHeight; 0369 0370 // and this the new center 0371 const QPointF newZoomCenter( newCenterX, newCenterY ); 0372 0373 setZoomFactorX( newZoomFactorX ); 0374 setZoomFactorY( newZoomFactorY ); 0375 setZoomCenter( newZoomCenter ); 0376 } 0377 0378 d->rubberBand->parentWidget()->update(); 0379 delete d->rubberBand; 0380 d->rubberBand = nullptr; 0381 0382 event->accept(); 0383 } 0384 0385 for ( AbstractDiagram * a : qAsConst(d->diagrams) ) 0386 { 0387 a->mouseReleaseEvent( event ); 0388 } 0389 } 0390 0391 void KChart::AbstractCoordinatePlane::mouseMoveEvent( QMouseEvent* event ) 0392 { 0393 if ( d->rubberBand != nullptr ) 0394 { 0395 const QRect normalized = QRect( d->rubberBandOrigin, event->pos() ).normalized(); 0396 d->rubberBand->setGeometry( normalized & geometry() ); 0397 0398 event->accept(); 0399 } 0400 0401 for ( AbstractDiagram * a : qAsConst(d->diagrams) ) 0402 { 0403 a->mouseMoveEvent( event ); 0404 } 0405 } 0406 0407 #if defined(Q_COMPILER_MANGLES_RETURN_TYPE) 0408 const 0409 #endif 0410 bool KChart::AbstractCoordinatePlane::isVisiblePoint( const QPointF& point ) const 0411 { 0412 return d->isVisiblePoint( this, point ); 0413 } 0414 0415 AbstractCoordinatePlane* KChart::AbstractCoordinatePlane::sharedAxisMasterPlane( QPainter* p ) 0416 { 0417 Q_UNUSED( p ); 0418 return this; 0419 } 0420 0421 #if !defined(QT_NO_DEBUG_STREAM) 0422 0423 QDebug KChart::operator<<( QDebug stream, const DataDimension& r ) 0424 { 0425 stream << "DataDimension(" 0426 << " start=" << r.start 0427 << " end=" << r.end 0428 << " sequence=" << KChartEnums::granularitySequenceToString( r.sequence ) 0429 << " isCalculated=" << r.isCalculated 0430 << " calcMode=" << ( r.calcMode == AbstractCoordinatePlane::Logarithmic ? "Logarithmic" : "Linear" ) 0431 << " stepWidth=" << r.stepWidth 0432 << " subStepWidth=" << r.subStepWidth 0433 << " )"; 0434 return stream; 0435 } 0436 #endif 0437 0438 #undef d