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