File indexing completed on 2024-05-12 15:54:11

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