diff --git a/examples/Axis/Labels/mainwindow.cpp b/examples/Axis/Labels/mainwindow.cpp index 8b6ced5..dbafb7d 100644 --- a/examples/Axis/Labels/mainwindow.cpp +++ b/examples/Axis/Labels/mainwindow.cpp @@ -1,127 +1,127 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "mainwindow.h" #include "AdjustedCartesianAxis.h" #include #include #include #include #include #include #include #include #include MainWindow::MainWindow( QWidget* parent ) : QWidget( parent ) { setupUi( this ); QHBoxLayout* chartLayout = new QHBoxLayout( m_chartFrame ); m_chart = new KChart::Chart; chartLayout->addWidget( m_chart ); m_model.loadFromCSV( ":/data" ); // Set up the diagram m_lines = new KChart::LineDiagram(); m_lines->setModel( &m_model ); m_xAxis = new KChart::CartesianAxis( m_lines ); KChart::TextAttributes ta( m_xAxis->textAttributes() ); AdjustedCartesianAxis *yAxis = new AdjustedCartesianAxis( m_lines ); yAxis->setBounds( 3, 6 ); m_xAxis->setPosition( KChart::CartesianAxis::Bottom ); yAxis->setPosition( KChart::CartesianAxis::Left ); // set the following to 0, to see the default Abscissa labels (== X headers, as read from the data file) #if 1 QStringList daysOfWeek; daysOfWeek << "Monday" << "Tuesday" << "Wednesday" << "Thursday" << "Friday" << "Saturday" << "Sunday"; m_xAxis->setLabels( daysOfWeek ); //QStringList shortDays; //shortDays << "Mon" << "Tue" << "Wed" << "Thu" << "Fri" << "Sat" << "Sun"; //m_xAxis->setShortLabels( shortDays ); #endif // Use HTML for drawing the text in the axis labels. #if 0 QStringList htmlStyles; htmlStyles << "Bold" << "Italic" << "Underline" << "Red"; m_xAxis->setLabels( htmlStyles ); ta.setTextDocument(new QTextDocument); #endif // Illustration of custom ticks QList ticks; ticks.append( 0.5 ); ticks.append( 3.5 ); ticks.append( 4.2 ); ticks.append( 6.5 ); m_xAxis->setCustomTicks(ticks); yAxis->setCustomTicks(ticks); // rotate abscissa labels by -60 degrees: ta.setRotation( -60 ); m_xAxis->setTextAttributes( ta ); m_lines->addAxis( m_xAxis ); m_lines->addAxis( yAxis ); m_chart->coordinatePlane()->replaceDiagram( m_lines ); // Set up the legend m_xAxis->setCustomTickLength( 11 ); yAxis->setCustomTickLength( 11 ); m_legend = new KChart::Legend( m_lines, m_chart ); m_legend->setPosition( KChart::Position::East ); m_legend->setAlignment( Qt::AlignTop ); m_chart->addLegend( m_legend ); - connect( m_annotations, SIGNAL( toggled( bool ) ), SLOT( annotationsToggled( bool ) ) ); - connect( m_linesOnAnnotations, SIGNAL( toggled( bool ) ), SLOT( gridLinesOnAnnotationsToggled( bool ) ) ); + connect( m_annotations, SIGNAL(toggled(bool)), SLOT(annotationsToggled(bool)) ); + connect( m_linesOnAnnotations, SIGNAL(toggled(bool)), SLOT(gridLinesOnAnnotationsToggled(bool)) ); } void MainWindow::annotationsToggled( bool showAnnotations ) { QMap< qreal, QString > annotations; if ( showAnnotations ) { // set custom axis labels at custom positions annotations[ 0.5 ] = "Left"; annotations[ 3.5 ] = "Center"; annotations[ 4.2 ] = "Off Center"; annotations[ 6.5 ] = "Right"; } m_xAxis->setAnnotations( annotations ); m_chart->update(); } void MainWindow::gridLinesOnAnnotationsToggled( bool onAnnotations ) { // Draw grid lines where the annotations are KChart::CartesianCoordinatePlane* plane = static_cast< KChart::CartesianCoordinatePlane* >( m_chart->coordinatePlane() ); KChart::GridAttributes ga = plane->gridAttributes( Qt::Horizontal ); ga.setLinesOnAnnotations( onAnnotations ); plane->setGridAttributes( Qt::Horizontal, ga ); m_chart->update(); } diff --git a/examples/DrawIntoPainter/framewidget.cpp b/examples/DrawIntoPainter/framewidget.cpp index b134112..dca4bf4 100644 --- a/examples/DrawIntoPainter/framewidget.cpp +++ b/examples/DrawIntoPainter/framewidget.cpp @@ -1,77 +1,77 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "framewidget.h" #include #include #include FrameWidget::FrameWidget( QWidget* parent, Qt::WindowFlags f ) : QWidget( parent, f ) , mChart( 0 ) { // this bloc left empty intentionally } void FrameWidget::setChart( KChart::Chart* chart ) { mChart = chart; // This is necessary because Chart can't automatically schedule somebody else (this object) to // call its custom paint method. - connect( mChart, SIGNAL( propertiesChanged() ), SLOT( update() ) ); + connect( mChart, SIGNAL(propertiesChanged()), SLOT(update()) ); } void FrameWidget::paintEvent( QPaintEvent* e ) { if ( !mChart ) { QWidget::paintEvent( e ); } else { QPainter painter( this ); const int wid = 64; const QRect r( rect() ); const QPen oldPen( painter.pen() ); const QBrush oldBrush( painter.brush() ); // paint below the chart painter.setPen( QPen( Qt::NoPen ) ); painter.setBrush( QBrush( QColor( 0xd0, 0xd0, 0xff ) ) ); painter.drawEllipse( r.left(), r.top(), wid, wid ); painter.drawEllipse( r.left() + r.width() - wid - 1, r.top(), wid, wid ); painter.drawEllipse( r.left(), r.top() + r.height() - wid - 1, wid, wid ); painter.drawEllipse( r.left() + r.width() - wid - 1, r.top() + r.height() - wid - 1, wid, wid ); painter.setBrush( oldBrush ); painter.setPen( oldPen ); // paint the chart mChart->paint( &painter, QRect( r.left() + wid / 2, r.top() + wid / 2, r.width() - wid, r.height() - wid ) ); // paint over the chart painter.setPen( QPen( Qt::NoPen ) ); painter.setBrush( QBrush( QColor( 0xd0, 0xff, 0xff ) ) ); const int wid2 = 40; const int gap = ( wid - wid2 ) / 2; painter.drawEllipse( r.left() + gap, r.top() + gap, wid2, wid2 ); painter.drawEllipse( r.left() + r.width() - wid + gap - 1, r.top() + gap, wid2, wid2 ); painter.drawEllipse( r.left() + gap, r.top() + r.height() - wid + gap - 1, wid2, wid2 ); painter.drawEllipse( r.left() + r.width() - wid + gap - 1, r.top() + r.height() - wid + gap - 1, wid2, wid2 ); painter.setBrush( oldBrush ); painter.setPen( oldPen ); } } diff --git a/examples/DrawIntoPainter/mainwindow.cpp b/examples/DrawIntoPainter/mainwindow.cpp index 0dd6258..a4d0e58 100644 --- a/examples/DrawIntoPainter/mainwindow.cpp +++ b/examples/DrawIntoPainter/mainwindow.cpp @@ -1,411 +1,411 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "mainwindow.h" #include "framewidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KChart; static QPixmap drawIntoPixmap( const QSize& size, KChart::Chart* chart ) { QPixmap pix( size ); pix.fill( Qt::white ); QPainter painter( &pix ); chart->paint( &painter, QRect( 0, 0, size.width(), size.height() ) ); return pix; } // When set, this example uses FrameWidget which uses Chart::paint to paint itself. // When not set, this example uses a Chart widget directly. #define USE_FRAME_WIDGET 1 MainWindow::MainWindow( QWidget* parent ) : QWidget( parent ) { setupUi( this ); QHBoxLayout* chartLayout = new QHBoxLayout( chartFrame ); #ifdef USE_FRAME_WIDGET FrameWidget* chartFrameWidget = new FrameWidget(); chartLayout->addWidget( chartFrameWidget ); #endif hSBar->setVisible( false ); vSBar->setVisible( false ); m_model.loadFromCSV( ":/data" ); // Set up the diagram m_lines = new LineDiagram(); m_lines->setModel( &m_model ); CartesianAxis *xAxis = new CartesianAxis( m_lines ); CartesianAxis *yAxis = new CartesianAxis( m_lines ); CartesianAxis *axisTop = new CartesianAxis( m_lines ); CartesianAxis *axisRight = new CartesianAxis( m_lines ); xAxis->setPosition( KChart::CartesianAxis::Bottom ); yAxis->setPosition( KChart::CartesianAxis::Left ); axisTop->setPosition( KChart::CartesianAxis::Top ); axisRight->setPosition( KChart::CartesianAxis::Right ); m_lines->addAxis( xAxis ); m_lines->addAxis( yAxis ); m_lines->addAxis( axisTop ); m_lines->addAxis( axisRight ); m_chart = new Chart(); //m_chart->setGlobalLeading(10,10,10,10); // by default there is no leading #ifdef USE_FRAME_WIDGET chartFrameWidget->setChart( m_chart ); // make sure, we re-draw after changing one of the chart's properties - connect( m_chart, SIGNAL( propertiesChanged() ), - chartFrameWidget, SLOT( update() ) ) ; + connect( m_chart, SIGNAL(propertiesChanged()), + chartFrameWidget, SLOT(update()) ) ; #else chartLayout->addWidget( m_chart ); #endif m_chart->coordinatePlane()->replaceDiagram( m_lines ); for ( int iColumn = 0; iColumn < m_lines->model()->columnCount(); ++iColumn ) { QPen pen(m_lines->pen( iColumn )); pen.setWidth(4); m_lines->setPen( iColumn, pen ); } FrameAttributes faChart( m_chart->frameAttributes() ); faChart.setVisible( true ); faChart.setPen( QPen(QColor( 0x60, 0x60, 0xb0 ), 8 ) ); m_chart->setFrameAttributes( faChart ); BackgroundAttributes baChart( m_chart->backgroundAttributes() ); baChart.setVisible( true ); baChart.setBrush( QColor( 0xd0, 0xd0, 0xff ) ); m_chart->setBackgroundAttributes( baChart ); // Set up the legend m_legend = new Legend( m_lines, m_chart ); m_legend->setPosition( Position::South ); m_legend->setAlignment( Qt::AlignRight ); m_legend->setShowLines( false ); m_legend->setTitleText( tr( "Legend" ) ); m_legend->setOrientation( Qt::Horizontal ); // setting the legend frame and background to the same color: const QColor legendColor( 0xff, 0xe0, 0x80 ); FrameAttributes faLegend( m_legend->frameAttributes() ); faLegend.setVisible( true ); faLegend.setPen( QPen( legendColor, 1 ) ); m_legend->setFrameAttributes( faLegend ); BackgroundAttributes baLegend( m_legend->backgroundAttributes() ); baLegend.setVisible( true ); baLegend.setBrush( legendColor ); m_legend->setBackgroundAttributes( baLegend ); m_chart->addLegend( m_legend ); // for illustration we paint the same chart at different sizes: QSize size1 = QSize( 200, 200 ); QSize size2 = QSize( 800, 800 ); m_pix1 = drawIntoPixmap( size1, m_chart ); m_pix2 = drawIntoPixmap( size2, m_chart ); m_pix2 = m_pix2.scaled( size1 ); m_smallChart1 = new QLabel( this ); m_smallChart1->setWindowTitle( "200x200" ); m_smallChart1->setPixmap( m_pix1 ); m_smallChart1->setFixedSize( m_pix1.size() ); m_smallChart1->move( width() - m_pix1.width() * 2, height() / 2 - m_pix1.height() - 5 ); m_smallChart1->show(); m_smallChart2 = new QLabel( this ); m_smallChart2->setWindowTitle( "800x800 scaled down" ); m_smallChart2->setPixmap( m_pix2 ); m_smallChart2->setFixedSize( m_pix2.size() ); m_smallChart2->move( width() - m_pix2.width() * 2, height() / 2 + 5 ); m_smallChart2->show(); faChart.setPen( QPen( QColor( 0xb0, 0xb0, 0xff ), 8 ) ); m_chart->setFrameAttributes( faChart ); // initialize attributes; this is necessary because we need to enable data value attributes before // any of them (e.g. only markers) can be displayed. but if we enable data value attributs, a default // data value text is included, even if we only wanted to set markers. so we enable DVA and then // individually disable the parts we don't want. on_paintValuesCB_toggled( false ); on_paintMarkersCB_toggled( false ); } void MainWindow::on_lineTypeCB_currentIndexChanged( const QString & text ) { if ( text == "Normal" ) m_lines->setType( LineDiagram::Normal ); else if ( text == "Stacked" ) m_lines->setType( LineDiagram::Stacked ); else if ( text == "Percent" ) m_lines->setType( LineDiagram::Percent ); else qWarning (" Does not match any type"); } void MainWindow::on_paintLegendCB_toggled( bool checked ) { KChart::Legend* legend = m_chart->legend(); if ( checked != ( legend != 0 ) ) { if ( checked ) m_chart->addLegend( m_legend ); else m_chart->takeLegend( legend ); } } void MainWindow::on_paintValuesCB_toggled( bool checked ) { const int colCount = m_lines->model()->columnCount(); for ( int iColumn = 0; iColumn < colCount; ++iColumn ) { DataValueAttributes a = m_lines->dataValueAttributes( iColumn ); a.setVisible( true ); TextAttributes ta = a.textAttributes(); ta.setRotation( 0 ); ta.setFont( QFont( "Comic", 10 ) ); ta.setPen( m_lines->brush( iColumn ).color() ); ta.setVisible( checked ); a.setTextAttributes( ta ); m_lines->setDataValueAttributes( iColumn, a ); } } void MainWindow::on_paintMarkersCB_toggled( bool checked ) { paintMarkers( checked, QSize() ); } void MainWindow::on_markersStyleCB_currentIndexChanged( const QString & text ) { Q_UNUSED( text ); on_paintMarkersCB_toggled( paintMarkersCB->isChecked() ); } void MainWindow::on_markersWidthSB_valueChanged( int i ) { Q_UNUSED( i ); markersHeightSB->setValue( markersWidthSB->value() ); on_paintMarkersCB_toggled( paintMarkersCB->isChecked() ); } void MainWindow::on_markersHeightSB_valueChanged( int i ) { Q_UNUSED( i ); markersWidthSB->setValue( markersHeightSB->value() ); on_paintMarkersCB_toggled( paintMarkersCB->isChecked() ); } void MainWindow::on_displayAreasCB_toggled( bool checked ) { const int colCount = m_lines->model()->columnCount(); for ( int iColumn = 0; iColumn < colCount; ++iColumn ) { LineAttributes la( m_lines->lineAttributes( iColumn ) ); la.setDisplayArea( checked ); if ( checked ) la.setTransparency( transparencySB->value() ); m_lines->setLineAttributes( iColumn, la ); } } void MainWindow::on_transparencySB_valueChanged( int alpha ) { Q_UNUSED( alpha ); if ( !displayAreasCB->isChecked() ) displayAreasCB->setChecked( true ); else on_displayAreasCB_toggled( true ); } void MainWindow::on_zoomFactorSB_valueChanged( qreal factor ) { const bool isZoomedIn = factor > 1.0f; hSBar->setVisible( isZoomedIn ); vSBar->setVisible( isZoomedIn ); if ( !isZoomedIn ) { hSBar->setValue( 500 ); vSBar->setValue( 500 ); } m_chart->coordinatePlane()->setZoomFactorX( factor ); m_chart->coordinatePlane()->setZoomFactorY( factor ); } void MainWindow::on_hSBar_valueChanged( int hPos ) { m_chart->coordinatePlane()->setZoomCenter( QPointF( hPos / 1000.0, vSBar->value() / 1000.0 ) ); m_chart->update(); } void MainWindow::on_vSBar_valueChanged( int vPos ) { m_chart->coordinatePlane()->setZoomCenter( QPointF( hSBar->value() / 1000.0, vPos / 1000.0 ) ); } // since DataValue markers have no relative sizing mode we need to scale them for printing void MainWindow::paintMarkers( bool checked, const QSize& printSize ) { MarkerAttributes::MarkerStylesMap map; map.insert( 0, MarkerAttributes::MarkerSquare ); map.insert( 1, MarkerAttributes::MarkerCircle ); map.insert( 2, MarkerAttributes::MarkerRing ); map.insert( 3, MarkerAttributes::MarkerCross ); map.insert( 4, MarkerAttributes::MarkerDiamond ); // next: Specify column- / cell-specific attributes! const int colCount = m_lines->model()->columnCount(); for ( int iColumn = 0; iColumn < colCount; ++iColumn ) { DataValueAttributes dva = m_lines->dataValueAttributes( iColumn ); dva.setVisible( true ); MarkerAttributes ma( dva.markerAttributes() ); switch ( markersStyleCB->currentIndex() ) { case 0: ma.setMarkerStyle( MarkerAttributes::MarkerSquare ); break; case 1: // Column-specific attributes ma.setMarkerStyle( map.value( iColumn ) ); break; case 2: ma.setMarkerStyle( MarkerAttributes::MarkerCircle ); break; case 3: ma.setMarkerStyle( MarkerAttributes::MarkerDiamond ); break; case 4: ma.setMarkerStyle( MarkerAttributes::Marker1Pixel ); break; case 5: ma.setMarkerStyle( MarkerAttributes::Marker4Pixels ); break; case 6: ma.setMarkerStyle( MarkerAttributes::MarkerRing ); break; case 7: ma.setMarkerStyle( MarkerAttributes::MarkerCross ); break; case 8: ma.setMarkerStyle( MarkerAttributes::MarkerFastCross ); break; default: Q_ASSERT( false ); } ma.setVisible( checked ); qreal factorWidth = printSize.isValid() ? ( printSize.width() / m_chart->rect().width() ) : 1.0f; qreal factorHeight = printSize.isValid() ? ( printSize.height() / m_chart->rect().height() ) : 1.0f; ma.setMarkerSize( QSize( markersWidthSB->value() * factorWidth, markersHeightSB->value() * factorHeight ) ); dva.setMarkerAttributes( ma ); m_lines->setDataValueAttributes( iColumn, dva ); // make a special one for certain values DataValueAttributes yellowAttributes( dva ); MarkerAttributes yellowMarker( yellowAttributes.markerAttributes() ); yellowMarker.setMarkerColor( Qt::yellow ); yellowAttributes.setMarkerAttributes( yellowMarker ); const int rowCount = m_lines->model()->rowCount(); for ( int j = 0; j < rowCount; ++j ) { QModelIndex index = m_lines->model()->index( j, iColumn, QModelIndex() ); QBrush brush = m_lines->model()->headerData( iColumn, Qt::Vertical, DatasetBrushRole ).value(); qreal value = m_lines->model()->data( index ).toReal(); /* Set a specific color - marker for a specific value */ if ( value == 13 ) { m_lines->setDataValueAttributes( index, yellowAttributes ); } } } } void MainWindow::on_savePB_clicked() { const QString file = QFileDialog::getSaveFileName( this, tr( "Choose PNG File..." ) ); if ( file.isEmpty() ) return; qDebug() << "Painting into PNG"; QPixmap qpix( 600, 600 ); QPainter painter( &qpix ); painter.setBrush( Qt::white ); painter.fillRect( 0, 0, 600, 600, Qt::white); m_chart->paint( &painter, QRect( 100, 100, 400, 400 ) ); painter.end(); qpix.save( file, "PNG" ); qDebug() << "Painting into PNG - done"; } void MainWindow::on_savePDF_clicked() { const QString file = QFileDialog::getSaveFileName( this, tr( "Choose PDF File..." ) ); if ( file.isEmpty() ) return; qDebug() << "Painting into PDF"; QPrinter printer( QPrinter::HighResolution ); printer.setOrientation( QPrinter::Landscape ); printer.setOutputFormat( QPrinter::PdfFormat ); printer.setOutputFileName( file ); paintMarkers( paintMarkersCB->isChecked(), printer.pageRect().size() ); QPainter painter; painter.begin( &printer ); m_chart->paint( &painter, printer.pageRect() ); painter.end(); paintMarkers( paintMarkersCB->isChecked(), m_chart->geometry().size() ); qDebug() << "Painting into PDF - done"; } void MainWindow::resizeEvent( QResizeEvent * ) { m_smallChart1->move( width() - m_pix1.width() * 2, height() / 2 - m_pix1.height() - 5 ); m_smallChart2->move( width() - m_pix2.width() * 2, height() / 2 + 5 ); } diff --git a/examples/Gantt/legend_example/entrydialog.cpp b/examples/Gantt/legend_example/entrydialog.cpp index 939a95b..08c3096 100644 --- a/examples/Gantt/legend_example/entrydialog.cpp +++ b/examples/Gantt/legend_example/entrydialog.cpp @@ -1,175 +1,175 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KGantt library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "entrydialog.h" #include "ui_entrydialog.h" #include #include #include #include #include Q_DECLARE_METATYPE( QPersistentModelIndex ) EntryDialog::EntryDialog( const QAbstractItemModel* model, QWidget* parent, Qt::WindowFlags f ) : QDialog( parent, f ), indexList( QList() ), ui( new Ui::EntryDialog ) { ui->setupUi( this ); this->model = model; init(); } void EntryDialog::init() { ui->type->addItem( tr( "Event" ), KGantt::TypeEvent ); ui->type->addItem( tr( "Task" ), KGantt::TypeTask ); ui->type->addItem( tr( "Summary" ), KGantt::TypeSummary ); ui->type->addItem( tr( "Multi" ), KGantt::TypeMulti ); for (int row = 0; row < model->rowCount(); ++row ) addDependItem( model, model->index( row, 0 ) ); - connect( ui->startDate, SIGNAL( dateTimeChanged( const QDateTime& ) ), this, SLOT( updateEndDate( const QDateTime& ) ) ); - connect( ui->readOnly, SIGNAL( toggled( bool ) ), this, SLOT( disableEditing( bool ) ) ); - connect( ui->type, SIGNAL( currentIndexChanged( int ) ), this, SLOT( typeChanged( int ) ) ); + connect( ui->startDate, SIGNAL(dateTimeChanged(QDateTime)), this, SLOT(updateEndDate(QDateTime)) ); + connect( ui->readOnly, SIGNAL(toggled(bool)), this, SLOT(disableEditing(bool)) ); + connect( ui->type, SIGNAL(currentIndexChanged(int)), this, SLOT(typeChanged(int)) ); ui->startDate->setDateTime( QDateTime::currentDateTime() ); typeChanged( 0 ); } void EntryDialog::initFrom( const QModelIndex & index, const KGantt::ConstraintModel* constraintModel ) { int row = index.row(); const QModelIndex parent = index.parent(); ui->name->setText( model->data( model->index( row, 0, parent ) ).toString() ); ui->legend->setText( model->data( model->index( row, 5, parent ) ).toString() ); int idx = ui->type->findData( model->data( model->index( row, 1, parent ) ).toInt() ); ui->type->setCurrentIndex( idx ); ui->startDate->setDateTime( model->data( model->index( row, 2, parent ), KGantt::StartTimeRole ).toDateTime() ); ui->endDate->setDateTime( model->data( model->index( row, 3, parent ), KGantt::EndTimeRole ).toDateTime() ); ui->completion->setValue( model->data( model->index( row, 4, parent ) ).toInt() ); ui->readOnly->setChecked( !(model->flags( model->index( row, 0, parent ) ) & Qt::ItemIsEditable) ); QList constraints = constraintModel->constraintsForIndex( model->index( row, 0, parent ) ); if ( constraints.isEmpty() ) return; QModelIndex constraintIndex; for ( int i = 0; i < constraints.size(); ++i ) { KGantt::Constraint constraint = constraints[i]; if ( constraint.endIndex() == index ) { constraintIndex = constraint.startIndex(); break; } } if ( !constraintIndex.isValid() ) return; ui->depends->setCurrentIndex( indexList.indexOf( constraintIndex ) + 1 ); } void EntryDialog::addDependItem( const QAbstractItemModel* model, const QModelIndex & index, int indent) { indexList << QPersistentModelIndex( index ); QString str = QString( "%1%2" ).arg( QString().fill( ' ', indent ) ).arg( model->data( index ).toString() ); ui->depends->addItem( str ); for (int row = 0; row < model->rowCount( index ); ++row ) addDependItem( model, model->index( row, 0, index ), indent + 2 ); } QString EntryDialog::name() const { return ui->name->text(); } int EntryDialog::type() const { return ui->type->itemData( ui->type->currentIndex() ).toInt(); } QDateTime EntryDialog::startDate() const { return ui->startDate->dateTime(); } QDateTime EntryDialog::endDate() const { return ui->endDate->dateTime(); } int EntryDialog::completion() const { return ui->completion->value(); } void EntryDialog::updateEndDate(const QDateTime & startDate) { ui->endDate->setMinimumDate( startDate.date() ); ui->endDate->setMinimumTime( startDate.time() ); } bool EntryDialog::readOnly() const { return ui->readOnly->isChecked(); } QModelIndex EntryDialog::depends() const { if ( ui->depends->currentIndex() == 0 ) return QModelIndex(); QPersistentModelIndex index = indexList[ ui->depends->currentIndex() - 1 ]; if ( index.isValid() ) return index; return QModelIndex(); } QString EntryDialog::legend() const { return ui->legend->text(); } void EntryDialog::disableEditing(bool disable) { ui->name->setEnabled( !disable ); ui->type->setEnabled( !disable ); ui->completion->setEnabled( !disable ); ui->startDate->setEnabled( !disable ); ui->endDate->setEnabled( !disable ); ui->depends->setEnabled( !disable ); } void EntryDialog::typeChanged(int index) { if ( ! index ) { ui->label_EndDate->hide(); ui->endDate->hide(); } else { ui->label_EndDate->show(); ui->endDate->show(); } } diff --git a/examples/Gantt/legend_example/mainwindow.cpp b/examples/Gantt/legend_example/mainwindow.cpp index 0364a86..3a42068 100644 --- a/examples/Gantt/legend_example/mainwindow.cpp +++ b/examples/Gantt/legend_example/mainwindow.cpp @@ -1,390 +1,390 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KGantt library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "mainwindow.h" #include "ui_mainwindow.h" #include "entrydelegate.h" #include "entrydialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include class MyStandardItem : public QStandardItem { public: MyStandardItem( const QVariant& v ) : QStandardItem() { setData( v, Qt::DisplayRole ); } MyStandardItem( const QString& v ) : QStandardItem() { setData( v, Qt::DisplayRole ); } }; MainWindow::MainWindow( QWidget* parent, Qt::WindowFlags flags ) : QMainWindow( parent, flags ), smallLegend( 0 ), detailedLegend( 0 ), ui( new Ui::MainWindow ) { ui->setupUi( this ); initModel(); initActions(); initItemDelegate(); initGrid(); QTreeView* leftView = qobject_cast( ui->ganttView->leftView() ); Q_ASSERT( leftView ); leftView->setColumnHidden( 1, true ); leftView->setColumnHidden( 2, true ); leftView->setColumnHidden( 3, true ); leftView->setColumnHidden( 4, true ); leftView->setColumnHidden( 5, true ); leftView->header()->setStretchLastSection( true ); - connect( ui->ganttView->leftView(), SIGNAL( customContextMenuRequested( const QPoint& ) ), - this, SLOT( showContextMenu( const QPoint& ) ) ); - connect( ui->ganttView->selectionModel(), SIGNAL( selectionChanged( const QItemSelection&, const QItemSelection& ) ), - this, SLOT( enableActions( const QItemSelection& ) ) ); + connect( ui->ganttView->leftView(), SIGNAL(customContextMenuRequested(QPoint)), + this, SLOT(showContextMenu(QPoint)) ); + connect( ui->ganttView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + this, SLOT(enableActions(QItemSelection)) ); } MainWindow::~MainWindow() { } void MainWindow::closeEvent(QCloseEvent *event) { delete smallLegend; delete detailedLegend; event->accept(); } void MainWindow::initModel() { model = new QStandardItemModel( 0, 6, this ); model->setHeaderData( 0, Qt::Horizontal, tr( "Tree View of Entries" ) ); ui->ganttView->setModel( model ); QStandardItemModel* lmodel = new QStandardItemModel; lmodel->appendRow( QList() << new MyStandardItem( QVariant() ) << new MyStandardItem( KGantt::TypeEvent ) << new MyStandardItem( QVariant() ) << new MyStandardItem( QVariant() ) << new MyStandardItem( QVariant() ) << new MyStandardItem( QString::fromLatin1("Event") ) ); lmodel->appendRow( QList() << new MyStandardItem( QVariant() ) << new MyStandardItem( KGantt::TypeTask ) << new MyStandardItem( QVariant() ) << new MyStandardItem( QVariant() ) << new MyStandardItem( QVariant() ) << new MyStandardItem( QString::fromLatin1("Task") ) ); lmodel->appendRow( QList() << new MyStandardItem( QVariant() ) << new MyStandardItem( KGantt::TypeSummary ) << new MyStandardItem( QVariant() ) << new MyStandardItem( QVariant() ) << new MyStandardItem( QVariant() ) << new MyStandardItem( QString::fromLatin1("Summary") ) ); smallLegend = new KGantt::Legend(); smallLegend->setWindowTitle( tr( "Legend" ) ); smallLegend->show(); smallLegend->setModel( lmodel ); detailedLegend = new KGantt::Legend(); detailedLegend->setWindowTitle( tr( "List" ) ); detailedLegend->show(); detailedLegend->setModel( model ); constraintModel = new KGantt::ConstraintModel( this ); ui->ganttView->setConstraintModel( constraintModel ); } void MainWindow::initActions() { newEntryAction = new QAction( tr( "New entry" ), this ); newEntryAction->setShortcut( QKeySequence::New ); - connect( newEntryAction, SIGNAL( triggered() ), this, SLOT( addNewEntry() ) ); + connect( newEntryAction, SIGNAL(triggered()), this, SLOT(addNewEntry()) ); removeEntryAction = new QAction( tr( "Remove entry" ), this ); removeEntryAction->setShortcut( QKeySequence::Delete ); - connect( removeEntryAction, SIGNAL( triggered() ), this, SLOT( removeEntry() ) ); + connect( removeEntryAction, SIGNAL(triggered()), this, SLOT(removeEntry()) ); zoomInAction = new QAction( tr( "Zoom In" ), this ); zoomInAction->setShortcut( QKeySequence::ZoomIn ); - connect( zoomInAction, SIGNAL( triggered() ), this, SLOT( zoomIn() ) ); + connect( zoomInAction, SIGNAL(triggered()), this, SLOT(zoomIn()) ); zoomOutAction = new QAction( tr( "Zoom Out" ), this ); zoomOutAction->setShortcut( QKeySequence::ZoomOut ); - connect( zoomOutAction, SIGNAL( triggered() ), this, SLOT( zoomOut() ) ); + connect( zoomOutAction, SIGNAL(triggered()), this, SLOT(zoomOut()) ); zoomFitAction = new QAction( tr( "Zoom to Fit" ), this ); - connect( zoomFitAction, SIGNAL( triggered() ), this, SLOT( zoomFit() ) ); + connect( zoomFitAction, SIGNAL(triggered()), this, SLOT(zoomFit()) ); ui->ganttView->leftView()->setContextMenuPolicy( Qt::CustomContextMenu ); ui->ganttView->leftView()->addAction( newEntryAction ); ui->ganttView->leftView()->addAction( removeEntryAction ); QMenu* entryMenu = menuBar()->addMenu( tr( "Entry" ) ); entryMenu->addAction( newEntryAction ); entryMenu->addAction( removeEntryAction ); QMenu* zoomMenu = menuBar()->addMenu( tr( "Zoom" ) ); zoomMenu->addAction( zoomInAction ); zoomMenu->addAction( zoomOutAction ); zoomMenu->addAction( zoomFitAction ); QMenu* scaleMenu = menuBar()->addMenu( tr( "Scale" ) ); scaleMenu->addAction( tr( "Auto" ), this, SLOT(scaleAuto()) ); scaleMenu->addAction( tr( "Hour" ), this, SLOT(scaleHour()) ); scaleMenu->addAction( tr( "Day" ), this, SLOT(scaleDay()) ); scaleMenu->addAction( tr( "Week" ), this, SLOT(scaleWeek()) ); scaleMenu->addAction( tr( "Month" ), this, SLOT(scaleMonth()) ); enableActions( QItemSelection() ); } void MainWindow::initItemDelegate() { EntryDelegate* delegate = new EntryDelegate( constraintModel, this ); ui->ganttView->leftView()->setItemDelegate( delegate ); } void MainWindow::initGrid() { grid = new KGantt::DateTimeGrid(); grid->setDayWidth( 70 ); ui->ganttView->setGrid( grid ); } void MainWindow::showContextMenu( const QPoint& pos ) { if ( !ui->ganttView->leftView()->indexAt( pos ).isValid() ) ui->ganttView->selectionModel()->clearSelection(); QMenu menu( ui->ganttView->leftView() ); menu.addAction( newEntryAction ); menu.addAction( removeEntryAction ); menu.exec( ui->ganttView->leftView()->viewport()->mapToGlobal( pos ) ); } void MainWindow::enableActions(const QItemSelection & selected) { if ( selected.indexes().isEmpty() ) { newEntryAction->setEnabled( true ); removeEntryAction->setEnabled( false ); return; } QModelIndex selectedIndex = selected.indexes()[0]; if ( model->data( model->index( selectedIndex.row(), 1 ) ) == KGantt::TypeEvent || model->data( model->index( selectedIndex.row(), 1 ) ) == KGantt::TypeTask ) { newEntryAction->setEnabled( false ); removeEntryAction->setEnabled( true ); return; } newEntryAction->setEnabled( true ); removeEntryAction->setEnabled( true ); } void MainWindow::addNewEntry() { QPointer dialog = new EntryDialog( model ); dialog->setWindowTitle( tr( "New Entry") ); if ( dialog->exec() == QDialog::Rejected || !dialog ) { delete dialog; return; } QModelIndexList selectedIndexes = ui->ganttView->selectionModel()->selectedIndexes(); const QModelIndex parent = selectedIndexes.value( 0 ); if ( !model->insertRow( model->rowCount( parent ), parent ) ) return; int row = model->rowCount( parent ) - 1; if ( row == 0 && parent.isValid() ) model->insertColumns( model->columnCount( parent ), 5, parent ); model->setData( model->index( row, 0, parent ), dialog->name() ); model->setData( model->index( row, 1, parent ), dialog->type() ); if ( dialog->type() != KGantt::TypeSummary ) { model->setData( model->index( row, 2, parent ), dialog->startDate(), KGantt::StartTimeRole ); model->setData( model->index( row, 3, parent ), dialog->endDate(), KGantt::EndTimeRole ); } model->setData( model->index( row, 4, parent ), dialog->completion() ); const QString legend( dialog->legend() ); if ( ! legend.isEmpty() ) model->setData( model->index( row, 5, parent ), legend ); addConstraint( dialog->depends(), model->index( row, 0, parent ) ); setReadOnly( model->index( row, 0, parent ), dialog->readOnly() ); delete dialog; } void MainWindow::setReadOnly(const QModelIndex & index, bool readOnly) { int row = index.row(); const QModelIndex parent = index.parent(); QStandardItem* item; item = model->itemFromIndex( model->index( row, 0, parent ) ); item->setFlags( readOnly ? item->flags() & ~Qt::ItemIsEditable : item->flags() | Qt::ItemIsEditable ); item = model->itemFromIndex( model->index( row, 1, parent ) ); item->setFlags( readOnly ? item->flags() & ~Qt::ItemIsEditable : item->flags() | Qt::ItemIsEditable ); item = model->itemFromIndex( model->index( row, 2, parent ) ); item->setFlags( readOnly ? item->flags() & ~Qt::ItemIsEditable : item->flags() | Qt::ItemIsEditable ); item = model->itemFromIndex( model->index( row, 3, parent ) ); item->setFlags( readOnly ? item->flags() & ~Qt::ItemIsEditable : item->flags() | Qt::ItemIsEditable ); item = model->itemFromIndex( model->index( row, 4, parent ) ); item->setFlags( readOnly ? item->flags() & ~Qt::ItemIsEditable : item->flags() | Qt::ItemIsEditable ); } void MainWindow::addConstraint(const QModelIndex & index1, const QModelIndex & index2) { if ( !index1.isValid() || !index2.isValid() ) return; KGantt::Constraint c( index1, index2 ); ui->ganttView->constraintModel()->addConstraint( c ); } void MainWindow::removeEntry() { QModelIndexList selectedIndexes = ui->ganttView->selectionModel()->selectedIndexes(); QModelIndex index = selectedIndexes.value( 0 ); if ( !index.isValid() ) return; model->removeRow( index.row(), index.parent() ); } void MainWindow::zoomIn() { qreal dayWidth = grid->dayWidth() + 10; if ( dayWidth > 400 ) grid->setScale( KGantt::DateTimeGrid::ScaleHour ); grid->setDayWidth( dayWidth ); } void MainWindow::zoomOut() { qreal dayWidth = grid->dayWidth() - 10; if ( dayWidth < 10 ) dayWidth = 10; if ( dayWidth <= 400 ) grid->setScale( KGantt::DateTimeGrid::ScaleDay ); grid->setDayWidth( dayWidth ); } void MainWindow::zoomFit() { QModelIndexList selectedIndexes = ui->ganttView->selectionModel()->selectedIndexes(); if ( selectedIndexes.isEmpty() ) { return; } KGantt::Span span; Q_FOREACH( QModelIndex idx, selectedIndexes ) { const KGantt::Span s = grid->mapToChart( grid->model()->index( idx.row(), 0 ) ); if ( span.isValid() ) { span = span.expandedTo( s ); } else { span = s; } } span.setLength( span.length()+20 ); span.setStart( span.start()-10 ); qDebug() << selectedIndexes << span; const qreal view_width = ui->ganttView->graphicsView()->viewport()->width(); const QDateTime start = grid->mapFromChart( span.start() ).value(); const QDateTime end = grid->mapFromChart( span.end() ).value(); qreal delta = start.date().daysTo(end.date()); delta += start.time().msecsTo(end.time())/( 1000.*24.*60.*60. ); qDebug() << view_width << "/" << delta; grid->setDayWidth( view_width/( std::max( 1., delta ) ) ); qDebug() << "daywidth set to" << grid->dayWidth(); qDebug() << "start scroll to" << grid->mapToChart( start ); ui->ganttView->graphicsView()->horizontalScrollBar()->setValue( grid->mapToChart( start ) ); } void MainWindow::scaleAuto() { KGantt::DateTimeGrid* grid = static_cast(ui->ganttView->grid()); grid->setScale( KGantt::DateTimeGrid::ScaleAuto ); } void MainWindow::scaleHour() { KGantt::DateTimeGrid* grid = static_cast(ui->ganttView->grid()); grid->setScale( KGantt::DateTimeGrid::ScaleHour ); } void MainWindow::scaleDay() { KGantt::DateTimeGrid* grid = static_cast(ui->ganttView->grid()); grid->setScale( KGantt::DateTimeGrid::ScaleDay ); } void MainWindow::scaleWeek() { KGantt::DateTimeGrid* grid = static_cast(ui->ganttView->grid()); grid->setScale( KGantt::DateTimeGrid::ScaleWeek ); } void MainWindow::scaleMonth() { KGantt::DateTimeGrid* grid = static_cast(ui->ganttView->grid()); grid->setScale( KGantt::DateTimeGrid::ScaleMonth ); } diff --git a/examples/Gantt/project/mainwindow.cpp b/examples/Gantt/project/mainwindow.cpp index 4ae115b..d408fc4 100644 --- a/examples/Gantt/project/mainwindow.cpp +++ b/examples/Gantt/project/mainwindow.cpp @@ -1,478 +1,478 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KGantt library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "mainwindow.h" #include "projectmodel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class ItemTypeComboBox : public QComboBox { Q_OBJECT Q_PROPERTY( KGantt::ItemType itemType READ itemType WRITE setItemType ) public: explicit ItemTypeComboBox( QWidget* parent=0 ); KGantt::ItemType itemType() const; public slots: void setItemType( KGantt::ItemType typ ); }; ItemTypeComboBox::ItemTypeComboBox( QWidget* parent ) : QComboBox( parent ) { addItem( tr( "Task" ), QVariant( KGantt::TypeTask ) ); addItem( tr( "Event" ), QVariant( KGantt::TypeEvent ) ); addItem( tr( "Summary" ), QVariant( KGantt::TypeSummary ) ); } KGantt::ItemType ItemTypeComboBox::itemType() const { return static_cast( itemData( currentIndex() ).toInt() ); } void ItemTypeComboBox::setItemType( KGantt::ItemType typ ) { setCurrentIndex( typ-1 ); } class MyItemDelegate : public KGantt::ItemDelegate { public: explicit MyItemDelegate( QObject* parent=0 ); /*reimp*/ QWidget* createEditor( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& idx ) const Q_DECL_OVERRIDE; /*reimp*/ void setEditorData( QWidget* editor, const QModelIndex& index ) const Q_DECL_OVERRIDE; /*reimp*/ void setModelData( QWidget* editor, QAbstractItemModel* model, const QModelIndex & index ) const Q_DECL_OVERRIDE; protected: /*reimp*/void drawDisplay( QPainter* painter, const QStyleOptionViewItem & option, const QRect& rect, const QString& text ) const Q_DECL_OVERRIDE; }; MyItemDelegate::MyItemDelegate( QObject* parent ) : KGantt::ItemDelegate( parent ) { } QWidget* MyItemDelegate::createEditor( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& idx ) const { qDebug() << "MyItemDelegate::createEditor("<(editor)) && index.isValid() ) { c->setItemType(static_cast(index.data(Qt::EditRole).toInt())); } else { ItemDelegate::setEditorData(editor,index); } } void MyItemDelegate::setModelData ( QWidget* editor, QAbstractItemModel* model, const QModelIndex & index ) const { ItemTypeComboBox* c; if ( (c = qobject_cast(editor)) && index.isValid() ) { model->setData(index,c->itemType()); } else { ItemDelegate::setModelData(editor,model,index); } } void MyItemDelegate::drawDisplay( QPainter* painter, const QStyleOptionViewItem& option, const QRect& rect, const QString& text ) const { //qDebug() << "MyItemDelegate::drawDisplay(" <(text.toInt()); QString str; switch (typ) { case KGantt::TypeTask: str = tr("Task"); break; case KGantt::TypeEvent: str = tr("Event"); break; case KGantt::TypeSummary: str = tr("Summary"); break; default: str = tr("None"); break; } ItemDelegate::drawDisplay(painter,option,rect,str); } /////////////////////////////////////////////////////////////////////////////// // Provide custom background and foreground /////////////////////////////////////////////////////////////////////////////// class DateTimeGrid : public KGantt::DateTimeGrid { public: DateTimeGrid(QObject* parent=0) { setParent(parent); setFreeDays( QSet() ); setFreeDaysBrush( QBrush( Qt::NoBrush ) ); } ~DateTimeGrid() { } //virtual void paintUserDefinedHeader(QPainter* painter, const QRectF& headerRect, const QRectF& exposedRect, qreal offset, const KGantt::DateTimeScaleFormatter* formatter, QWidget* widget = 0); void drawBackground(QPainter* painter, const QRectF& rect) Q_DECL_OVERRIDE; void drawForeground(QPainter* painter, const QRectF& rect) Q_DECL_OVERRIDE; }; void DateTimeGrid::drawBackground(QPainter* painter, const QRectF& rect) { QLinearGradient grad; grad.setCoordinateMode( QGradient::ObjectBoundingMode ); grad.setStart( 0.5, 0.5 ); grad.setFinalStop( 0.5, 0.0 ); grad.setSpread( QGradient::ReflectSpread ); // grad.setCenter( 0.5, 0.5 ); // grad.setFocalPoint( 0.5, 0.5 ); // grad.setRadius( 0.5 ); QColor currentColor = Qt::blue; for ( qreal i = 0; i <= 1.0; i += 0.1 ) { currentColor = currentColor.lighter( 100 + 20 * i ); grad.setColorAt( i, currentColor ); } QBrush brush( grad); //brush.setColor(Qt::lightGray); QRectF r = computeRect(QDateTime::currentDateTime(), QDateTime::currentDateTime().addDays(2), rect); painter->fillRect(r, brush); } void DateTimeGrid::drawForeground(QPainter* painter, const QRectF& rect) { painter->save(); QRectF r = computeRect(QDateTime::currentDateTime(), QDateTime::currentDateTime().addDays(2), rect); static QString text("Holiday"); QFont font = painter->font(); font.setPixelSize(r.width()/5); QFontMetrics fm(font); int width = fm.width(text); int height = fm.boundingRect(text).height(); painter->translate(r.center()); painter->translate(-width/2, height/2); painter->setFont(font); painter->drawText(0, 0, text); painter->restore(); } /* void DateTimeGrid::paintUserDefinedHeader( QPainter* painter, const QRectF& headerRect, const QRectF& exposedRect, qreal offset, const KGantt::DateTimeScaleFormatter* formatter, QWidget* widget) { const QStyle* const style = widget ? widget->style() : QApplication::style(); QDateTime dt = formatter->currentRangeBegin( mapToDateTime( offset + exposedRect.left() ) ).toUTC(); qreal x = mapFromDateTime( dt ); while ( x < exposedRect.right() + offset ) { const QDateTime next = formatter->nextRangeBegin( dt ); const qreal nextx = mapFromDateTime( next ); QStyleOptionHeader opt; if ( widget ) opt.init( widget ); opt.rect = QRectF( x - offset+1, headerRect.top(), qMax( 1., nextx-x-1 ), headerRect.height() ).toAlignedRect(); //opt.state = QStyle::State_Raised | QStyle::State_Enabled; opt.textAlignment = formatter->alignment(); opt.text = formatter->text( dt ); // use white text on black background opt.palette.setColor(QPalette::Window, QColor("black")); opt.palette.setColor(QPalette::ButtonText, QColor("white")); style->drawControl( QStyle::CE_Header, &opt, painter, widget ); dt = next; x = nextx; } } */ MainWindow::MainWindow( QWidget* parent ) : QMainWindow( parent ), m_model( new ProjectModel( this ) ), m_view( new KGantt::View ) { m_view->setModel( m_model ); m_view->setSelectionModel( new QItemSelectionModel(m_model)); // slotToolsNewItem(); m_view->leftView()->setItemDelegateForColumn( 1, new MyItemDelegate( this ) ); m_view->leftView()->setHorizontalScrollBarPolicy( Qt::ScrollBarAsNeeded ); m_view->graphicsView()->setHorizontalScrollBarPolicy( Qt::ScrollBarAsNeeded ); m_view->setGrid(new DateTimeGrid(this)); //QItemEditorCreatorBase *creator = new QItemEditorCreator("itemType"); //QItemEditorFactory* factory = new QItemEditorFactory; //factory->registerEditor( QVariant( KGantt::TypeTask ).type(), creator ); //m_view->itemDelegate()->setItemEditorFactory( factory ); setCentralWidget( m_view ); QMenuBar* mb = menuBar(); QMenu* fileMenu = new QMenu( tr( "&File" ) ); #ifndef QT_NO_PRINTER - fileMenu->addAction( tr( "&Save as PDF..." ), this, SLOT( slotFileSavePdf() ) ); - fileMenu->addAction( tr( "&Print..." ), this, SLOT( slotFilePrint() ) ); + fileMenu->addAction( tr( "&Save as PDF..." ), this, SLOT(slotFileSavePdf()) ); + fileMenu->addAction( tr( "&Print..." ), this, SLOT(slotFilePrint()) ); #endif fileMenu->addSeparator(); - fileMenu->addAction( tr( "&Quit" ), this, SLOT( slotFileQuit() ) ); + fileMenu->addAction( tr( "&Quit" ), this, SLOT(slotFileQuit()) ); mb->addMenu( fileMenu ); QMenu* toolsMenu = new QMenu( tr( "&Tools" ) ); - toolsMenu->addAction( tr( "&New Item" ), this, SLOT( slotToolsNewItem() ) ); - toolsMenu->addAction( tr( "&Add Item" ), this, SLOT( slotToolsAppendItem() ) ); + toolsMenu->addAction( tr( "&New Item" ), this, SLOT(slotToolsNewItem()) ); + toolsMenu->addAction( tr( "&Add Item" ), this, SLOT(slotToolsAppendItem()) ); toolsMenu->addSeparator(); QMenu *alignMenu = toolsMenu->addMenu( tr( "Ali&gn" ) ); - alignMenu->addAction( tr( "&Left" ), this, SLOT( slotAlignLeft() ) ); - alignMenu->addAction( tr( "&Center" ), this, SLOT( slotAlignCenter() ) ); - alignMenu->addAction( tr( "&Right" ), this, SLOT( slotAlignRight() ) ); - alignMenu->addAction( tr( "&Hidden" ), this, SLOT( slotAlignHidden() ) ); + alignMenu->addAction( tr( "&Left" ), this, SLOT(slotAlignLeft()) ); + alignMenu->addAction( tr( "&Center" ), this, SLOT(slotAlignCenter()) ); + alignMenu->addAction( tr( "&Right" ), this, SLOT(slotAlignRight()) ); + alignMenu->addAction( tr( "&Hidden" ), this, SLOT(slotAlignHidden()) ); toolsMenu->addSeparator(); - toolsMenu->addAction( tr( "&Collapse All" ), this, SLOT( slotCollapseAll() ) ); - toolsMenu->addAction( tr( "&Expand All" ), this, SLOT( slotExpandAll() ) ); + toolsMenu->addAction( tr( "&Collapse All" ), this, SLOT(slotCollapseAll()) ); + toolsMenu->addAction( tr( "&Expand All" ), this, SLOT(slotExpandAll()) ); mb->addMenu( toolsMenu ); /* slotToolsNewItem(); slotToolsNewItem(); slotToolsNewItem(); for (int i = 0; i < 3; ++i) { m_model->setData(m_model->index(i,2,QModelIndex()), qVariantFromValue(QDateTime::currentDateTime().addDays(i)), KGantt::StartTimeRole); m_model->setData(m_model->index(i,3,QModelIndex()), qVariantFromValue(QDateTime::currentDateTime().addDays(i+1)), KGantt::EndTimeRole); } m_view->setConstraintModel(new KGantt::ConstraintModel(m_view)); m_view->constraintModel()->addConstraint(KGantt::Constraint(m_model->index(0,0,QModelIndex()),m_model->index(1,0,QModelIndex()))); m_view->constraintModel()->addConstraint(KGantt::Constraint(m_model->index(1,0,QModelIndex()),m_model->index(2,0,QModelIndex()))); */ } SavePdfDialog::SavePdfDialog(QWidget *parent) : QDialog(parent) { setModal(true); setWindowTitle(tr("Save as PDF")); QVBoxLayout *l = new QVBoxLayout(this); setLayout(l); QHBoxLayout *fileLayout = new QHBoxLayout(this); l->addLayout(fileLayout); QLabel *fileLabel = new QLabel(tr("File:"), this); fileLayout->addWidget(fileLabel); m_fileEdit = new QLineEdit(this); fileLabel->setBuddy(m_fileEdit); m_fileEdit->setText(QFileInfo(QDir::homePath(), "gantt.pdf").absoluteFilePath()); fileLayout->addWidget(m_fileEdit); QPushButton *m_fileButton = new QPushButton("...", this); connect(m_fileButton, SIGNAL(clicked()), this, SLOT(fileButtonClicked())); fileLayout->addWidget(m_fileButton); m_rowLabels = new QCheckBox(tr("Row Header"), this); m_rowLabels->setChecked(true); l->addWidget(m_rowLabels); m_columnLabels = new QCheckBox(tr("Column Header"), this); m_columnLabels->setChecked(true); l->addWidget(m_columnLabels); QDialogButtonBox *btnBox = new QDialogButtonBox(this); btnBox->setStandardButtons(QDialogButtonBox::Save | QDialogButtonBox::Cancel); connect(btnBox, SIGNAL(accepted()), this, SLOT(accept())); connect(btnBox, SIGNAL(rejected()), this, SLOT(reject())); l->addWidget(btnBox); resize(QSize(400, 100).expandedTo(minimumSizeHint())); } void SavePdfDialog::fileButtonClicked() { const QString file = QFileDialog::getSaveFileName(this, tr("Choose PDF File..."), QString(), tr("PDF files (*.pdf)")); if (!file.isEmpty()) m_fileEdit->setText(file); } void MainWindow::slotFileSavePdf() { #ifndef QT_NO_PRINTER SavePdfDialog dialog(this); if (dialog.exec() != QDialog::Accepted) return; const QString file = dialog.m_fileEdit->text(); if (file.isEmpty()) return; const bool drawRowLabels = dialog.m_rowLabels->isChecked(); const bool drawColumnLabels = dialog.m_columnLabels->isChecked(); QPrinter printer(QPrinter::HighResolution); printer.setOrientation(QPrinter::Landscape); printer.setColorMode(QPrinter::Color); printer.setPageMargins(0.2, 0.2, 0.2, 0.2, QPrinter::Point); printer.setOutputFormat(QPrinter::PdfFormat); printer.setOutputFileName(file); m_view->print(&printer, drawRowLabels, drawColumnLabels); #endif } void MainWindow::slotFilePrint() { #ifndef QT_NO_PRINTER QPrinter printer(QPrinter::HighResolution); printer.setOrientation(QPrinter::Landscape); printer.setColorMode(QPrinter::Color); QPrintDialog dialog(&printer, this); if (dialog.exec() != QDialog::Accepted) return; m_view->print(&printer); #endif } void MainWindow::slotFileQuit() { // TODO QApplication::instance()->quit(); } void MainWindow::slotToolsNewItem() { QModelIndex idx = m_view->selectionModel()->currentIndex(); if ( idx.isValid() ) { qDebug() << "MainWindow::slotToolsNewItem" << idx; m_model->insertRows( 0, 1, m_model->index( idx.row(),0,idx.parent() ) ); } else { m_model->insertRows( 0, 1, m_view->rootIndex() ); } } void MainWindow::slotToolsAppendItem() { QModelIndex idx = m_view->selectionModel()->currentIndex(); if ( idx.isValid() ) { qDebug() << "MainWindow::slotToolsAppendItem" << idx; m_model->insertRows( m_model->rowCount( idx ), 1, m_model->index( idx.row(),0,idx.parent() ) ); } else { m_model->insertRows( m_model->rowCount( m_view->rootIndex() ), 1, m_view->rootIndex() ); } } void MainWindow::slotCollapseAll() { // don't use the treeview's collapseAll/expandAll methods but use the one provided by the // view cause that one will take care to update everyt6hing as needed. //QTreeView* view = qobject_cast( m_view->leftView() ); //view->collapseAll(); QModelIndex idx = m_view->selectionModel()->currentIndex(); if ( idx.isValid() ) m_view->collapseAll(); } void MainWindow::slotExpandAll() { // don't use the treeview's collapseAll/expandAll methods but use the one provided by the // view cause that one will take care to update everyt6hing as needed. //QTreeView* view = qobject_cast( m_view->leftView() ); //view->expandAll(); QModelIndex idx = m_view->selectionModel()->currentIndex(); if ( idx.isValid() ) m_view->expandAll(); } void MainWindow::slotAlignLeft() { QModelIndex idx = m_view->selectionModel()->currentIndex(); if ( idx.isValid() ) { m_model->setData( idx, KGantt::StyleOptionGanttItem::Left, KGantt::TextPositionRole ); } } void MainWindow::slotAlignCenter() { QModelIndex idx = m_view->selectionModel()->currentIndex(); if ( idx.isValid() ) { m_model->setData( idx, KGantt::StyleOptionGanttItem::Center, KGantt::TextPositionRole ); } } void MainWindow::slotAlignRight() { QModelIndex idx = m_view->selectionModel()->currentIndex(); if ( idx.isValid() ) { m_model->setData( idx, KGantt::StyleOptionGanttItem::Right, KGantt::TextPositionRole ); } } void MainWindow::slotAlignHidden() { QModelIndex idx = m_view->selectionModel()->currentIndex(); if ( idx.isValid() ) { m_model->setData( idx, KGantt::StyleOptionGanttItem::Hidden, KGantt::TextPositionRole ); } } #include "mainwindow.moc" diff --git a/examples/LeveyJennings/Simple/main.cpp b/examples/LeveyJennings/Simple/main.cpp index 79732da..6067ca7 100644 --- a/examples/LeveyJennings/Simple/main.cpp +++ b/examples/LeveyJennings/Simple/main.cpp @@ -1,198 +1,198 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include class SelectionAnimator : public QObject { Q_OBJECT public: SelectionAnimator( QAbstractItemView* view ) : QObject( view ), view( view ) { QTimer* const t = new QTimer( this ); - connect( t, SIGNAL( timeout() ), this, SLOT( animate() ) ); + connect( t, SIGNAL(timeout()), this, SLOT(animate()) ); t->start( 1000 ); } protected Q_SLOTS: void animate() { const int row = ( view->selectionModel()->currentIndex().row() + 1 ) % view->model()->rowCount(); view->selectionModel()->setCurrentIndex( view->model()->index( row, 0 ), QItemSelectionModel::ClearAndSelect ); } private: QAbstractItemView* const view; }; #include "main.moc" int main( int argc, char** argv ) { QApplication app( argc, argv ); QStandardItemModel model( 14, 6 ); model.setHeaderData( 0, Qt::Horizontal, QObject::tr( "Lot" ) ); model.setHeaderData( 1, Qt::Horizontal, QObject::tr( "Value" ) ); model.setHeaderData( 2, Qt::Horizontal, QObject::tr( "OK" ) ); model.setHeaderData( 3, Qt::Horizontal, QObject::tr( "Date/Time" ) ); model.setHeaderData( 4, Qt::Horizontal, QObject::tr( "Mean Value" ) ); model.setHeaderData( 5, Qt::Horizontal, QObject::tr( "Standard Deviation" ) ); // Lot 1 model.setData( model.index( 0, 0 ), 1 ); // lot number model.setData( model.index( 0, 1 ), 210 ); // value model.setData( model.index( 0, 2 ), true ); // QC value ok/not ok model.setData( model.index( 0, 3 ), QDateTime::fromString( "2007-07-06T09:00:00", Qt::ISODate ) ); model.setData( model.index( 1, 0 ), 1 ); model.setData( model.index( 1, 1 ), 9.5 ); model.setData( model.index( 1, 2 ), true ); model.setData( model.index( 1, 3 ), QDateTime::fromString( "2007-07-06T21:00:00", Qt::ISODate ) ); model.setData( model.index( 1, 4 ), 7.5 ); model.setData( model.index( 1, 5 ), 1.0 ); model.setData( model.index( 2, 0 ), 1 ); model.setData( model.index( 2, 1 ), 200 ); model.setData( model.index( 2, 2 ), true ); model.setData( model.index( 2, 3 ), QDateTime::fromString( "2007-07-07T09:00:00", Qt::ISODate ) ); // This values should be missing (lot is needed anyway) model.setData( model.index( 3, 0 ), 1 ); model.setData( model.index( 3, 3 ), QDateTime::fromString( "2007-07-07T21:00:00", Qt::ISODate ) ); model.setData( model.index( 4, 0 ), 1 ); model.setData( model.index( 4, 1 ), 180 ); model.setData( model.index( 4, 2 ), true ); model.setData( model.index( 4, 3 ), QDateTime::fromString( "2007-07-08T09:00:00", Qt::ISODate ) ); // Lot 2 model.setData( model.index( 5, 0 ), 2 ); model.setData( model.index( 5, 1 ), 210 ); model.setData( model.index( 5, 2 ), true ); model.setData( model.index( 5, 3 ), QDateTime::fromString( "2007-07-08T21:00:00", Qt::ISODate ) ); model.setData( model.index( 6, 0 ), 2 ); model.setData( model.index( 6, 1 ), 195 ); model.setData( model.index( 6, 2 ), true ); model.setData( model.index( 6, 3 ), QDateTime::fromString( "2007-07-09T09:00:00", Qt::ISODate ) ); // this value is not OK model.setData( model.index( 7, 0 ), 2 ); model.setData( model.index( 7, 1 ), 200 ); model.setData( model.index( 7, 2 ), false ); model.setData( model.index( 7, 3 ), QDateTime::fromString( "2007-07-09T21:00:00", Qt::ISODate ) ); model.setData( model.index( 8, 0 ), 2 ); model.setData( model.index( 8, 1 ), 210 ); model.setData( model.index( 8, 2 ), true ); model.setData( model.index( 8, 3 ), QDateTime::fromString( "2007-07-10T09:00:00", Qt::ISODate ) ); model.setData( model.index( 9, 0 ), 2 ); model.setData( model.index( 9, 1 ), 180 ); model.setData( model.index( 9, 2 ), true ); model.setData( model.index( 9, 3 ), QDateTime::fromString( "2007-07-10T21:00:00", Qt::ISODate ) ); // this values is completely out of bounds and therefore cut/truncated model.setData( model.index( 10,0 ), 2 ); model.setData( model.index( 10,1 ), 290 ); model.setData( model.index( 10,2 ), true ); model.setData( model.index( 10,3 ), QDateTime::fromString( "2007-07-11T09:00:00", Qt::ISODate ) ); // this value is ok again model.setData( model.index( 11,0 ), 2 ); model.setData( model.index( 11,1 ), 210 ); model.setData( model.index( 11,2 ), true ); model.setData( model.index( 11,3 ), QDateTime::fromString( "2007-07-11T21:00:00", Qt::ISODate ) ); model.setData( model.index( 12,0 ), 2 ); model.setData( model.index( 12,1 ), 205 ); model.setData( model.index( 12,2 ), true ); model.setData( model.index( 12,3 ), QDateTime::fromString( "2007-07-12T09:00:00", Qt::ISODate ) ); model.setData( model.index( 13,0 ), 2 ); model.setData( model.index( 13,1 ), 204 ); model.setData( model.index( 13,2 ), true ); model.setData( model.index( 13,3 ), QDateTime::fromString( "2007-07-12T21:00:00", Qt::ISODate ) ); KChart::Chart* chart = new KChart::Chart(); KChart::LeveyJenningsDiagram* diagram = new KChart::LeveyJenningsDiagram; diagram->setModel( &model ); diagram->setExpectedMeanValue( 200 ); diagram->setExpectedStandardDeviation( 20 ); KChart::LeveyJenningsCoordinatePlane* plane = new KChart::LeveyJenningsCoordinatePlane; chart->replaceCoordinatePlane( plane ); plane->replaceDiagram( diagram ); /*diagram->setLotChangedSymbolPosition( Qt::AlignBottom ); diagram->setSensorChangedSymbolPosition( Qt::AlignTop ); diagram->setFluidicsPackChangedSymbolPosition( Qt::AlignTop );*/ //diagram->setScanLinePen( QPen( Qt::green ) ); //diagram->setSymbol( KChart::LeveyJenningsDiagram::NotOkDataPoint, // diagram->symbol( KChart::LeveyJenningsDiagram::OkDataPoint ) ); diagram->setFluidicsPackChanges( QVector< QDateTime >() << QDateTime::fromString( "2007-07-11T15:00:00", Qt::ISODate ) ); diagram->setSensorChanges( QVector< QDateTime >() << QDateTime::fromString( "2007-07-10T11:00:00", Qt::ISODate ) ); KChart::LeveyJenningsAxis* axis = new KChart::LeveyJenningsAxis( diagram ); axis->setPosition( KChart::CartesianAxis::Left ); diagram->addAxis( axis ); KChart::LeveyJenningsAxis* axis2 = new KChart::LeveyJenningsAxis( diagram ); axis2->setPosition( KChart::CartesianAxis::Right ); axis2->setType( KChart::LeveyJenningsGridAttributes::Calculated ); diagram->addAxis( axis2 ); KChart::CartesianAxis* axis3 = new KChart::LeveyJenningsAxis( diagram ); axis3->setPosition( KChart::CartesianAxis::Bottom ); diagram->addAxis( axis3 ); QTableView* tv = new QTableView; tv->setModel( &model ); tv->setSelectionModel( diagram->selectionModel() ); QSplitter* splitter = new QSplitter; splitter->addWidget( tv ); splitter->addWidget( chart ); splitter->show(); new SelectionAnimator( tv );//diagram ); return app.exec(); } diff --git a/examples/Lines/Advanced/mainwindow.cpp b/examples/Lines/Advanced/mainwindow.cpp index ec1ae88..be8dacd 100644 --- a/examples/Lines/Advanced/mainwindow.cpp +++ b/examples/Lines/Advanced/mainwindow.cpp @@ -1,305 +1,305 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "mainwindow.h" #include #include #include #include #include #include #include using namespace KChart; MainWindow::MainWindow( QWidget* parent ) : QWidget( parent ) { setupUi( this ); m_curColumn = -1; m_curOpacity = 0; QHBoxLayout* chartLayout = new QHBoxLayout( chartFrame ); m_chart = new Chart(); chartLayout->addWidget( m_chart ); m_model.loadFromCSV( ":/data" ); // Set up the diagram m_lines = new LineDiagram( this ); m_lines->setModel( &m_model ); CartesianAxis* xAxis = new CartesianAxis( m_lines ); CartesianAxis* yAxis = new CartesianAxis( m_lines ); xAxis->setPosition( KChart::CartesianAxis::Bottom ); yAxis->setPosition( KChart::CartesianAxis::Left ); m_lines->addAxis( xAxis ); m_lines->addAxis( yAxis ); m_chart->coordinatePlane()->replaceDiagram( m_lines ); m_chart->setGlobalLeading( 20, 20, 20, 20 ); // Instantiate the timer QTimer* timer = new QTimer( this ); - connect( timer, SIGNAL( timeout() ), this, SLOT( slot_timerFired() ) ); + connect( timer, SIGNAL(timeout()), this, SLOT(slot_timerFired()) ); timer->start( 30 ); //Change the cursor to IBeamCursor inside Chart widget. m_chart->setCursor( Qt::IBeamCursor ); //Install event filter on Chart to get the mouse position m_chart->installEventFilter( this ); } /** Event filter for getting mouse position */ bool MainWindow::eventFilter(QObject* target, QEvent* event) { if (target == m_chart) { if (event->type() == QEvent::MouseMove) { // When the mouse is over a data-point then fetch that data-point // that belongs to the mouse-position and print the data value. QMouseEvent* mouseEvent = static_cast< QMouseEvent* >( event ); QPointF pos = mouseEvent->pos(); QModelIndex index = m_lines->indexAt(pos.toPoint()); if (index.isValid()) { qDebug() << "Mouse position" << pos << "Data:" << m_model.data(index).toDouble(); } } } return QWidget::eventFilter(target, event); } void MainWindow::on_lineTypeCB_currentIndexChanged( const QString& text ) { if ( text == "Normal" ) m_lines->setType( LineDiagram::Normal ); else if ( text == "Stacked" ) m_lines->setType( LineDiagram::Stacked ); else if ( text == "Percent" ) m_lines->setType( LineDiagram::Percent ); else qWarning (" Does not match any type"); m_chart->update(); } void MainWindow::on_paintValuesCB_toggled( bool checked ) { const int colCount = m_lines->model()->columnCount( m_lines->rootIndex() ); for ( int iColumn = 0; iColumndataValueAttributes( iColumn ) ); QBrush brush( m_lines->brush( iColumn ) ); TextAttributes ta( a.textAttributes() ); ta.setRotation( 0 ); ta.setFont( QFont( "Comic", 10 ) ); ta.setPen( QPen( brush.color() ) ); if ( checked ) ta.setVisible( true ); else ta.setVisible( false ); a.setVisible( true ); a.setTextAttributes( ta ); m_lines->setDataValueAttributes( iColumn, a ); } m_chart->update(); } void MainWindow::on_centerDataPointsCB_toggled( bool checked ) { m_lines->setCenterDataPoints( checked ); m_chart->update(); } void MainWindow::on_animateAreasCB_toggled( bool checked ) { if ( checked ) { highlightAreaCB->setCheckState( Qt::Unchecked ); m_curRow = 0; m_curColumn = 0; } else { m_curColumn = -1; } highlightAreaCB->setEnabled( !checked ); highlightAreaSB->setEnabled( !checked ); // un-highlight all previously highlighted columns const int rowCount = m_lines->model()->rowCount(); const int colCount = m_lines->model()->columnCount(); for ( int iColumn = 0; iColumnupdate(); m_curOpacity = 0; } void MainWindow::slot_timerFired() { if ( m_curColumn < 0 ) { return; } m_curOpacity += 8; if ( m_curOpacity > 255 ) { setHighlightArea( m_curRow, m_curColumn, 127, false, false ); m_curOpacity = 5; ++m_curRow; if ( m_curRow >= m_lines->model()->rowCount( m_lines->rootIndex() ) ) { m_curRow = 0; ++m_curColumn; if ( m_curColumn >= m_lines->model()->columnCount( m_lines->rootIndex() ) ) m_curColumn = 0; } } setHighlightArea( m_curRow, m_curColumn, m_curOpacity, true, true ); } void MainWindow::setHighlightArea( int row, int column, int opacity, bool checked, bool doUpdate ) { if ( row < 0 ) { // highlight a complete dataset LineAttributes la = m_lines->lineAttributes( column ); if ( checked ) { la.setDisplayArea( true ); la.setTransparency( opacity ); } else { la.setDisplayArea( false ); } m_lines->setLineAttributes( column, la ); } else { // highlight two segments only if ( row ) { QModelIndex cellIndex( m_lines->model()->index( row - 1, column, m_lines->rootIndex() ) ); if ( checked ) { LineAttributes la( m_lines->lineAttributes( cellIndex ) ); la.setDisplayArea( true ); la.setTransparency( 255 - opacity ); // set specific line attribute settings for this cell m_lines->setLineAttributes( cellIndex, la ); } else { // remove any cell-specific line attribute settings // from the indexed cell m_lines->resetLineAttributes( cellIndex ); } } if ( row < m_lines->model()->rowCount(m_lines->rootIndex() ) ) { QModelIndex cellIndex( m_lines->model()->index( row, column, m_lines->rootIndex() ) ); if ( checked ) { LineAttributes la( m_lines->lineAttributes( cellIndex ) ); la.setDisplayArea( true ); la.setTransparency( opacity ); // set specific line attribute settings for this cell m_lines->setLineAttributes( cellIndex, la ); } else { // remove any cell-specific line attribute settings // from the indexed cell m_lines->resetLineAttributes( cellIndex ); } } } if ( doUpdate ) m_chart->update(); } void MainWindow::on_highlightAreaCB_toggled( bool checked ) { setHighlightArea( -1, highlightAreaSB->value(), 127, checked, true ); } void MainWindow::on_highlightAreaSB_valueChanged( int i ) { Q_UNUSED( i ); if ( highlightAreaCB->isChecked() ) on_highlightAreaCB_toggled( true ); else on_highlightAreaCB_toggled( false ); } void MainWindow::on_threeDModeCB_toggled( bool checked ) { ThreeDLineAttributes td( m_lines->threeDLineAttributes() ); td.setDepth( depthSB->value() ); if ( checked ) td.setEnabled( true ); else td.setEnabled( false ); m_lines->setThreeDLineAttributes( td ); m_chart->update(); } void MainWindow::on_depthSB_valueChanged( int i ) { Q_UNUSED( i ); if ( threeDModeCB->isChecked() ) on_threeDModeCB_toggled( true ); } void MainWindow::on_trackAreasCB_toggled( bool checked ) { setTrackedArea( trackAreasSB->value(), checked, true ); } void MainWindow::on_trackAreasSB_valueChanged( int i ) { Q_UNUSED( i ); on_trackAreasCB_toggled( trackAreasCB->isChecked() ); } void MainWindow::setTrackedArea( int column, bool checked, bool doUpdate ) { const int rowCount = m_model.rowCount( m_lines->rootIndex() ); const int columnCount = m_model.columnCount( m_lines->rootIndex() ); for ( int i = 0; i < rowCount; ++i ) { for ( int j = 0; j < columnCount; ++j ) { QModelIndex cellIndex( m_model.index( i, j, m_lines->rootIndex() ) ); ValueTrackerAttributes va( m_lines->valueTrackerAttributes( cellIndex ) ); va.setEnabled( checked && j == column ); va.setAreaBrush( QColor( 255, 255, 0, 50 ) ); va.setOrientations( Qt::Horizontal | Qt::Vertical ); QColor color = Qt::cyan; color.setAlphaF( 0.5 ); va.setAreaBrush( color ); m_lines->setValueTrackerAttributes( cellIndex, va ); } } if ( doUpdate ) m_chart->update(); } void MainWindow::on_reverseHorizontalCB_toggled( bool checked ) { static_cast< KChart::CartesianCoordinatePlane* >( m_chart->coordinatePlane() ) ->setHorizontalRangeReversed( checked ); } void MainWindow::on_reverseVerticalCB_toggled( bool checked ) { static_cast< KChart::CartesianCoordinatePlane* >( m_chart->coordinatePlane() ) ->setVerticalRangeReversed( checked ); } diff --git a/examples/ModelView/TableView/mainwindow.cpp b/examples/ModelView/TableView/mainwindow.cpp index 4ea66d2..048c21d 100644 --- a/examples/ModelView/TableView/mainwindow.cpp +++ b/examples/ModelView/TableView/mainwindow.cpp @@ -1,245 +1,245 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "mainwindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KChart; MainWindow::MainWindow() { QMenu *fileMenu = new QMenu( tr( "&File" ), this ); QAction *openAction = fileMenu->addAction( tr( "&Open..." ) ); openAction->setShortcut( QKeySequence( tr( "Ctrl+O" ) ) ); QAction *saveAction = fileMenu->addAction( tr( "&Save As..." ) ); saveAction->setShortcut( QKeySequence( tr( "Ctrl+S" ) ) ); QAction *quitAction = fileMenu->addAction( tr( "E&xit" ) ); quitAction->setShortcut( QKeySequence( tr( "Ctrl+Q" ) ) ); setupModel(); initializeData(); setupViews(); connect( openAction, SIGNAL(triggered()), this, SLOT(openFile())); connect( saveAction, SIGNAL(triggered()), this, SLOT(saveFile())); connect( quitAction, SIGNAL(triggered()), qApp, SLOT(quit())); - connect( m_selectionModel, SIGNAL( selectionChanged( const QItemSelection &, const QItemSelection & ) ), - this, SLOT( selectionChanged( const QItemSelection &, const QItemSelection & ) ) ); + connect( m_selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + this, SLOT(selectionChanged(QItemSelection,QItemSelection)) ); - connect( m_diagramView, SIGNAL( clicked( const QModelIndex& ) ), SLOT( itemClicked( const QModelIndex& ) ) ); + connect( m_diagramView, SIGNAL(clicked(QModelIndex)), SLOT(itemClicked(QModelIndex)) ); menuBar()->addMenu( fileMenu ); statusBar(); setWindowTitle( tr( "KD Chart used as item viewer together with a QTableView" ) ); resize( 740, 480 ); } void MainWindow::setupModel() { const int rowCount = 8; const int columnCount = 3; m_model = new QStandardItemModel( rowCount, columnCount, this ); m_model->setHeaderData( 0, Qt::Horizontal, tr( "Label" ) ); m_model->setHeaderData( 1, Qt::Horizontal, tr( "Quantity" ) ); m_model->setHeaderData( 2, Qt::Horizontal, tr( "Product A") ); } void MainWindow::initializeData() { openFile(":/Charts/qtdata.cht"); } void MainWindow::setupViews() { m_chart = new Chart(); m_tableView = new QTableView; QSplitter *splitter = new QSplitter( Qt::Vertical ); splitter->addWidget( m_tableView ); splitter->addWidget( m_chart ); splitter->setStretchFactor( 0, 0 ); splitter->setStretchFactor( 1, 1 ); // Set up the diagram m_diagramView = new BarDiagram(); DatasetDescriptionVector columnConfig( m_model->columnCount() - 1 ); for ( int iC = 1; iC <= columnConfig.size(); ++iC ) columnConfig[ iC - 1 ] = iC; qDebug() << "(" << m_model->rowCount() << "," << m_model->columnCount() << ")"; KChart::DatasetProxyModel* dproxy = new KChart::DatasetProxyModel( this ); dproxy->setSourceModel( m_model ); dproxy->setDatasetColumnDescriptionVector( columnConfig ); m_diagramView->setModel( dproxy ); KChart::HeaderFooter* headerFooter = new KChart::HeaderFooter( m_chart ); headerFooter->setText( tr( "You can edit the table data, or select table cells with keyboard/mouse." ) ); headerFooter->setType( HeaderFooter::Header ); headerFooter->setPosition( Position::North ); m_chart->addHeaderFooter( headerFooter ); m_chart->coordinatePlane()->replaceDiagram( m_diagramView ); m_tableView->setModel( m_model ); m_selectionModel = new QItemSelectionModel( m_model ); m_tableView->setSelectionModel( m_selectionModel ); setCentralWidget( splitter ); } void MainWindow::itemClicked( const QModelIndex& index ) { QItemSelectionModel::SelectionFlags command = QItemSelectionModel::Clear | QItemSelectionModel::Select; if ( QApplication::keyboardModifiers() & Qt::ControlModifier ) command = QItemSelectionModel::Toggle; m_selectionModel->setCurrentIndex( static_cast< const QAbstractProxyModel* >( index.model() ) ->mapToSource( index ), command ); } void MainWindow::selectionChanged( const QItemSelection & selected, const QItemSelection & deselected ) { if ( deselected != selected ) { // First we set pItemSelection to the de-selected bars // and we use an invisible pen, then we set it to the selected ones // using a dark blue pen: const QItemSelection * pItemSelection = &deselected; QPen pen( Qt::NoPen ); for ( int iItemSelection = 0; iItemSelection < 2; ++iItemSelection ) { for ( int i = 0; i < pItemSelection->size(); ++i ) { QItemSelectionRange range( pItemSelection->at( i ) ); for ( int iRow = range.topLeft().row(); iRow <= range.bottomRight().row(); ++iRow ) { for ( int iColumn = range.topLeft().column(); iColumn <= range.bottomRight().column(); ++iColumn ) { // ignore the first column: that's just the label texts to be shown in the table view if ( iColumn ) // enable (or disable, resp.) the surrounding line around this bar m_diagramView->setPen( m_diagramView->model() ->index( iRow, iColumn - 1, m_diagramView->rootIndex()), pen ); } } } pItemSelection = &selected; pen.setColor( Qt::darkBlue ); pen.setStyle( Qt::DashLine ); pen.setWidth( 2 ); } m_chart->update(); } } void MainWindow::openFile(const QString &path) { QString fileName; if ( path.isNull() ) fileName = QFileDialog::getOpenFileName( this, tr( "Choose a data file" ), "", "*.cht" ); else fileName = path; if ( !fileName.isEmpty() ) { QFile file(fileName); if ( file.open( QFile::ReadOnly | QFile::Text) ) { QTextStream stream( &file ); QString line; m_model->removeRows( 0, m_model->rowCount( QModelIndex() ), QModelIndex() ); int row = 0; do { line = stream.readLine(); if (!line.isEmpty()) { m_model->insertRows(row, 1, QModelIndex()); QStringList pieces = line.split( ',', QString::SkipEmptyParts ); m_model->setData( m_model->index( row, 0, QModelIndex() ), pieces.value( 0 ) ); m_model->setData( m_model->index(row, 1, QModelIndex() ), pieces.value( 1 ) ); m_model->setData( m_model->index( row, 2, QModelIndex() ), pieces.value( 2 ) ); m_model->setData( m_model->index(row, 0, QModelIndex()), QColor( pieces.value( 3 ) ), Qt::DecorationRole ); ++row; } } while ( !line.isEmpty() ); file.close(); statusBar()->showMessage( tr( "Loaded %1" ).arg( fileName ), 2000 ); } } } void MainWindow::saveFile() { QString fileName = QFileDialog::getSaveFileName(this, tr( "Save file as" ), "", "*.cht" ); if ( !fileName.isEmpty() ) { QFile file( fileName ); QTextStream stream( &file ); if ( file.open(QFile::WriteOnly | QFile::Text) ) { for ( int row = 0; row < m_model->rowCount( QModelIndex() ); ++row ) { QStringList pieces; pieces.append( m_model->data( m_model->index( row, 0, QModelIndex() ), Qt::DisplayRole).toString()); pieces.append( m_model->data( m_model->index( row, 1, QModelIndex() ), Qt::DisplayRole).toString()); pieces.append( m_model->data( m_model->index( row, 2, QModelIndex() ), Qt::DisplayRole).toString()); pieces.append( m_model->data( m_model->index( row, 0, QModelIndex() ), Qt::DecorationRole).toString()); stream << pieces.join( "," ) << "\n"; } } file.close(); statusBar()->showMessage( tr( "Saved %1" ).arg( fileName ), 2000 ); } } diff --git a/examples/Pie/Advanced/mainwindow.cpp b/examples/Pie/Advanced/mainwindow.cpp index a31a2e9..67666e2 100644 --- a/examples/Pie/Advanced/mainwindow.cpp +++ b/examples/Pie/Advanced/mainwindow.cpp @@ -1,148 +1,148 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "mainwindow.h" #include #include #include #include #include #include using namespace KChart; MainWindow::MainWindow( QWidget* parent ) : QWidget( parent ), m_currentFactor( 0 ), m_currentDirection( 1 ), m_currentSlice( 0 ) { setupUi( this ); QHBoxLayout* chartLayout = new QHBoxLayout( chartFrame ); m_chart = new Chart(); m_chart->setGlobalLeadingLeft( 5 ); m_chart->setGlobalLeadingRight( 5 ); chartLayout->addWidget( m_chart ); m_model.loadFromCSV( ":/data" ); explodeDatasetSB->setMaximum( m_model.columnCount() - 1 ); // Set up the diagram PolarCoordinatePlane* polarPlane = new PolarCoordinatePlane( m_chart ); m_chart->replaceCoordinatePlane( polarPlane ); m_pie = new PieDiagram(); m_pie->setModel( &m_model ); m_chart->coordinatePlane()->replaceDiagram( m_pie ); m_timer = new QTimer( this ); - connect( m_timer, SIGNAL( timeout() ), this, SLOT( slotNextFrame() ) ); + connect( m_timer, SIGNAL(timeout()), this, SLOT(slotNextFrame()) ); } void MainWindow::on_startPositionSB_valueChanged( double pos ) { const int intValue = static_cast( pos ); startPositionSL->blockSignals( true ); startPositionSL->setValue( intValue ); startPositionSL->blockSignals( false ); static_cast( m_chart->coordinatePlane() )->setStartPosition( pos ); m_chart->update(); } void MainWindow::on_startPositionSL_valueChanged( int pos ) { qreal qrealValue = static_cast( pos ); startPositionSB->blockSignals( true ); startPositionSB->setValue( qrealValue ); startPositionSB->blockSignals( false ); static_cast( m_chart->coordinatePlane() )->setStartPosition( pos ); m_chart->update(); } void MainWindow::on_explodeSubmitPB_clicked() { setExplodeFactor( explodeDatasetSB->value(), explodeFactorSB->value() ); m_chart->update(); } void MainWindow::setExplodeFactor( int column, qreal value ) { // Note: // We use the per-column getter method here, it will fall back // automatically to return the global (or even the default) settings. PieAttributes attrs( m_pie->pieAttributes( column ) ); attrs.setExplodeFactor( value ); m_pie->setPieAttributes( column, attrs ); m_chart->update(); } void MainWindow::on_animateExplosionCB_toggled( bool toggle ) { if ( toggle ) m_timer->start( 100 ); else m_timer->stop(); } void MainWindow::slotNextFrame() { m_currentFactor += ( 1 * m_currentDirection ); if ( m_currentFactor == 0 || m_currentFactor == 5 ) m_currentDirection = -m_currentDirection; if ( m_currentFactor == 0 ) { setExplodeFactor( m_currentSlice, 0.0 ); m_currentSlice++; if ( m_currentSlice >= m_model.columnCount() ) m_currentSlice = 0; } setExplodeFactor( m_currentSlice, static_cast( m_currentFactor ) / 10.0 ); m_chart->update(); } void MainWindow::on_threeDGB_toggled( bool toggle ) { // note: We use the global getter method here, it will fall back // automatically to return the default settings. ThreeDPieAttributes attrs( m_pie->threeDPieAttributes() ); attrs.setEnabled( toggle ); attrs.setDepth( threeDFactorSB->value() ); m_pie->setThreeDPieAttributes( attrs ); m_chart->update(); } void MainWindow::on_threeDFactorSB_valueChanged( int factor ) { // note: We use the global getter method here, it will fall back // automatically to return the default settings. ThreeDPieAttributes attrs( m_pie->threeDPieAttributes() ); attrs.setEnabled( threeDGB->isChecked() ); attrs.setDepth( factor ); m_pie->setThreeDPieAttributes( attrs ); m_chart->update(); } diff --git a/examples/Plotter/BigDataset/MainWidget.cpp b/examples/Plotter/BigDataset/MainWidget.cpp index 39f2ccc..31e869a 100644 --- a/examples/Plotter/BigDataset/MainWidget.cpp +++ b/examples/Plotter/BigDataset/MainWidget.cpp @@ -1,99 +1,99 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "MainWidget.h" #include "KChartAbstractDiagram_p.h" #include "KChartChart.h" #include "KChartPlotter.h" #include #include MainWidget::MainWidget() : m_controlsContainer( new QWidget( this ) ) { // set up the chart QHBoxLayout* topLayout = new QHBoxLayout( this ); m_controls.setupUi( m_controlsContainer ); topLayout->addWidget( m_controlsContainer ); KChart::Chart* chart = new KChart::Chart; topLayout->addWidget( chart ); m_plotter = new KChart::Plotter; m_plotter->setModel( &m_model ); KChart::AbstractDiagram::Private::get( m_plotter )->doDumpPaintTime = true; chart->coordinatePlane()->replaceDiagram( m_plotter ); KChart::CartesianCoordinatePlane* cPlane = qobject_cast< KChart::CartesianCoordinatePlane* >( chart->coordinatePlane() ); Q_ASSERT( cPlane ); cPlane->setVerticalRange( QPair< qreal, qreal >( -2, 2 ) ); KChart::CartesianAxis* xAxis = new KChart::CartesianAxis( m_plotter ); xAxis->setPosition( KChart::CartesianAxis::Bottom ); xAxis->setTitleText("X"); m_plotter->addAxis( xAxis ); KChart::CartesianAxis* yAxis = new KChart::CartesianAxis( m_plotter ); yAxis->setPosition( KChart::CartesianAxis::Left ); yAxis->setTitleText("Y"); m_plotter->addAxis( yAxis ); // wire up the UI // index of elements in vector must match corresponding Model::Function enum values m_functionSelector << m_controls.sineRadio << m_controls.triangleRadio << m_controls.squareRadio << m_controls.noiseRadio << m_controls.oneDivSineRadio << m_controls.sineOneDivRadio; foreach ( QRadioButton* r, m_functionSelector ) { - connect( r, SIGNAL( toggled( bool ) ), SLOT( functionToggled( bool ) ) ); + connect( r, SIGNAL(toggled(bool)), SLOT(functionToggled(bool)) ); } - connect( m_controls.runButton, SIGNAL( toggled( bool ) ), - &m_model, SLOT( setRunning( bool ) ) ); + connect( m_controls.runButton, SIGNAL(toggled(bool)), + &m_model, SLOT(setRunning(bool)) ); // order matters again m_addPointsButtons << m_controls.add1kButton << m_controls.add10kButton << m_controls.add100kButton; foreach ( QPushButton* b, m_addPointsButtons ) { - connect( b, SIGNAL( clicked( bool ) ), SLOT( addPointsButtonClicked() ) ); + connect( b, SIGNAL(clicked(bool)), SLOT(addPointsButtonClicked()) ); } } // slot void MainWidget::functionToggled( bool checked ) { if ( !checked ) { return; } int idx = m_functionSelector.indexOf( qobject_cast< QRadioButton* >( sender() ) ); Q_ASSERT( idx >= 0 ); m_model.setFunction( static_cast< Model::Function >( idx ) ); } // slot void MainWidget::addPointsButtonClicked() { int idx = m_addPointsButtons.indexOf( qobject_cast< QPushButton* >( sender() ) ); Q_ASSERT( idx >= 0 ); m_model.appendPoints( pow( qreal( 10 ), qreal( idx + 3 ) ) ); } diff --git a/examples/Plotter/BigDataset/Model.cpp b/examples/Plotter/BigDataset/Model.cpp index 20b07cf..85c36a7 100644 --- a/examples/Plotter/BigDataset/Model.cpp +++ b/examples/Plotter/BigDataset/Model.cpp @@ -1,156 +1,156 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "Model.h" #include "KChartMath_p.h" #include #include static const qreal s_stepWidth = 0.1; Model::Model() : m_x( 0 ), m_function( SineFunction ) { m_appendTimer.setInterval( 3 ); - connect( &m_appendTimer, SIGNAL( timeout() ), SLOT( appendPoint() ) ); + connect( &m_appendTimer, SIGNAL(timeout()), SLOT(appendPoint()) ); // pre-fill some values appendPoints( 100 ); } int Model::columnCount( const QModelIndex& parent ) const { Q_UNUSED( parent ); return 2; // row 0: x, row 1: y } int Model::rowCount( const QModelIndex& parent ) const { Q_UNUSED( parent ); return m_data.count(); } QModelIndex Model::index( int row, int column, const QModelIndex& parent ) const { if ( column > 1 || row >= m_data.count() || parent.isValid() ) { return QModelIndex(); } return createIndex( row, column ); } QModelIndex Model::parent( const QModelIndex& index ) const { Q_UNUSED( index ); return QModelIndex(); } QVariant Model::data( const QModelIndex& index, int role ) const { if ( role != Qt::DisplayRole || index.parent().isValid() || index.column() > 1 || index.row() >= m_data.count() ) { return QVariant(); } if ( index.column() == 0 ) { return index.row() * s_stepWidth; } else { return m_data.at( index.row() ); } } // slot void Model::setFunction( Function f ) { m_function = f; } void Model::setRunning( bool running ) { if ( running ) { m_appendTimer.start(); } else { m_appendTimer.stop(); } } // slot void Model::appendPoint() { appendPoints( 1 ); } void Model::appendPoints( int numPoints ) { Q_ASSERT( numPoints >= 1); beginInsertRows( QModelIndex(), m_data.count(), m_data.count() + numPoints - 1 ); for ( int i = 0; i < numPoints; i++ ) { m_data.append( nextFunctionValue() ); } QTime stopWatch; stopWatch.start(); endInsertRows(); // this immediately triggers the signals that cause the diagram to update qDebug() << "Adding" << numPoints << "data points to the existing" << m_data.count() - numPoints << "took" << stopWatch.elapsed() << "milliseconds"; } qreal Model::nextFunctionValue() { qreal fx = 0.0; switch ( m_function ) { case SineFunction: fx = sin( m_x ); break; case TriangleFunction: { qreal x = fmod( m_x + 0.5 * M_PI, 2 * M_PI ); // make it look like sine, only less round if ( x < M_PI ) { fx = -1.0 + x * ( 2.0 / M_PI); } else { fx = 3.0 - x * ( 2.0 / M_PI); } break; } case SquareFunction: { qreal x = fmod( m_x, 2 * M_PI ); fx = x < M_PI ? 1 : -1; break; } case NoiseFunction: fx = -1.0 + qreal( qrand() ) * 2.0 / qreal( RAND_MAX ); break; case SineOneDivFunction: { // we want this repeating and we want negative arguments, too. qreal x = fmod( m_x + 10, 20 ) - 10; if ( qAbs( x ) < 1e-6 ) { break; } fx = sin( 1.0 / x ); break; } case OneDivSineFunction: { qreal s = sin( m_x ); fx = qAbs( s ) > 1e-6 ? 1.0 / s : 0.0; break; } default: Q_ASSERT( false ); } m_x += s_stepWidth; return fx; } diff --git a/examples/Plotter/BubbleChart/mainwindow.cpp b/examples/Plotter/BubbleChart/mainwindow.cpp index d4d7f00..153d998 100644 --- a/examples/Plotter/BubbleChart/mainwindow.cpp +++ b/examples/Plotter/BubbleChart/mainwindow.cpp @@ -1,160 +1,160 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "mainwindow.h" #include #include #include #include #include #include #include using namespace KChart; static const int nBubbles = 7; // we display seven bubbles using the following data structure: // 0: Y // 1: X // 2: size struct DataType{ DataType( qreal x_, qreal y_, qreal size_ ) : x(x_) , y(y_) , size(size_) {} qreal x; qreal y; qreal size; }; static const DataType bubblesData[nBubbles] = { DataType(0.5, 1.0, 100), DataType(1.0, 0.5, 60 ), DataType(1.6, 2.0, 28 ), DataType(0.7, 0.3, 55 ), DataType(1.3, 2.0, 95 ), DataType(2.0, 1.0, 75 ), DataType(1.4, 1.1, 85 ) }; #define ROLE_SIZE Qt::UserRole + 1 MainWindow::MainWindow( QWidget* parent ) : QWidget( parent ), m_model(0) { setupUi( this ); QHBoxLayout* chartLayout = new QHBoxLayout( chartFrame ); m_chart = new Chart(); chartLayout->addWidget( m_chart ); // Set up the data initializeDataModel(); // Set up the diagram m_plotter = new Plotter(); // Register the data model at the diagram m_plotter->setModel( m_model ); // Add axes to the diagram CartesianAxis *xAxis = new CartesianAxis( m_plotter ); CartesianAxis *xAxis2 = new CartesianAxis( m_plotter ); CartesianAxis *yAxis = new CartesianAxis ( m_plotter ); CartesianAxis *yAxis2 = new CartesianAxis ( m_plotter ); xAxis->setPosition ( KChart::CartesianAxis::Bottom ); xAxis2->setPosition( KChart::CartesianAxis::Top ); yAxis->setPosition ( KChart::CartesianAxis::Left ); yAxis2->setPosition( KChart::CartesianAxis::Right ); m_plotter->addAxis( xAxis ); m_plotter->addAxis( xAxis2 ); m_plotter->addAxis( yAxis ); m_plotter->addAxis( yAxis2 ); - connect( threeDEnabled, SIGNAL( toggled(bool) ), this, SLOT( setMarkerAttributes() ) ); + connect( threeDEnabled, SIGNAL(toggled(bool)), this, SLOT(setMarkerAttributes()) ); m_chart->coordinatePlane()->replaceDiagram( m_plotter ); m_chart->setGlobalLeading( 20, 20, 20, 20 ); setMarkerAttributes(); } void MainWindow::initializeDataModel() { m_model = new QStandardItemModel( nBubbles, 2 ); m_model->setHeaderData(0, Qt::Horizontal, tr("Some Bubbles")); for ( int i=0; i < nBubbles; ++i ) { const QModelIndex indexX = m_model->index(i, 0); const QModelIndex indexY = m_model->index(i, 1); m_model->setData(indexX, QVariant( bubblesData[i].x ), Qt::DisplayRole); m_model->setData(indexY, QVariant( bubblesData[i].y ), Qt::DisplayRole); m_model->setData(indexY, bubblesData[i].size, ROLE_SIZE ); } } void MainWindow::setMarkerAttributes() { // disable the connecting line KChart::LineAttributes la = m_plotter->lineAttributes(); la.setVisible(false); m_plotter->setLineAttributes(la); for ( int iRow = 0; iRowmodel()->index(iRow, 0); const QModelIndex indexY = m_plotter->model()->index(iRow, 1); DataValueAttributes dva( m_plotter->dataValueAttributes( indexX ) ); dva.setVisible( true ); TextAttributes ta( dva.textAttributes() ); ta.setVisible( false ); MarkerAttributes ma( dva.markerAttributes() ); ma.setVisible( true ); ma.setMarkerStyle( MarkerAttributes::MarkerCircle ); ma.setThreeD( threeDEnabled->isChecked() ); // set the size const qreal d = m_model->data( indexY, ROLE_SIZE ).toReal(); ma.setMarkerSize( QSizeF(d,d) ); RelativePosition pos( dva.positivePosition() ); pos.setAlignment( Qt::AlignCenter ); pos.setHorizontalPadding(0); dva.setPositivePosition( pos ); dva.setMarkerAttributes( ma ); dva.setTextAttributes( ta ); // note: The KChart::Plotter looks at the X cell // for data value attributes, // any attrs set at the Y cell are ignored. m_plotter->setDataValueAttributes( indexX, dva ); } m_chart->update(); } diff --git a/examples/Plotter/Logarithmic/main.cpp b/examples/Plotter/Logarithmic/main.cpp index 0f4ff5f..39978af 100644 --- a/examples/Plotter/Logarithmic/main.cpp +++ b/examples/Plotter/Logarithmic/main.cpp @@ -1,112 +1,111 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include -#include #include using namespace KChart; int main( int argc, char** argv ) { QApplication app( argc, argv ); const int points = 420; const double xMin = 0; const double xMax = 20; const double step = ( xMax - xMin ) / ( points - 1 ); QStandardItemModel model( points, 6 ); double x = xMin; for ( int n = 0; n < points; ++n, x += step) { QModelIndex index = model.index( n, 0 ); model.setData( index, x ); index = model.index( n, 1 ); model.setData( index, sin( x ) * 100.0 ); index = model.index( n, 2 ); model.setData( index, x ); index = model.index( n, 3 ); model.setData( index, x ); index = model.index( n, 4 ); model.setData( index, x ); index = model.index( n, 5 ); model.setData( index, x * x * x ); } model.setHeaderData( 0, Qt::Horizontal, QString::fromLatin1( "100 * sin(x)" ) ); model.setHeaderData( 2, Qt::Horizontal, QString::fromLatin1( "x" ) ); model.setHeaderData( 4, Qt::Horizontal, QString::fromLatin1( "x^3" ) ); KChart::Chart* chart = new KChart::Chart(); KChart::AbstractCartesianDiagram* diagram = new KChart::Plotter; diagram->setModel( &model ); chart->coordinatePlane()->replaceDiagram( diagram ); KChart::CartesianAxis* xAxis = new KChart::CartesianAxis( diagram ); KChart::CartesianAxis* yAxis = new KChart::CartesianAxis( diagram ); xAxis->setPosition( KChart::CartesianAxis::Bottom ); yAxis->setPosition( KChart::CartesianAxis::Left ); diagram->addAxis( xAxis ); diagram->addAxis( yAxis ); KChart::Legend* legend = new KChart::Legend( diagram, chart ); KChart::FrameAttributes legendAtt = legend->frameAttributes(); legendAtt.setCornerRadius( 9 ); legend->setFrameAttributes( legendAtt ); legend->setPosition( KChart::Position::East ); legend->setAlignment( Qt::AlignCenter ); legend->setTitleText( "Legend" ); chart->addLegend( legend ); KChart::CartesianCoordinatePlane* cart_plane = dynamic_cast(chart->coordinatePlane()); Q_ASSERT(cart_plane); cart_plane->setAxesCalcModeX(KChart::AbstractCoordinatePlane::Logarithmic); cart_plane->setAxesCalcModeY(KChart::AbstractCoordinatePlane::Logarithmic); // Set the vertical range from 15 to 75 - with a logarithmic axis I actually get 1 to 100 //cart_plane->setVerticalRange(QPair( 0.005, 1000 ) ); // Set the horizontal range from 1 to 9 - with a linear axis this works OK cart_plane->setHorizontalRange(QPair( 0.001, 100 ) ); chart->show(); int ret = app.exec(); delete chart; return ret; } diff --git a/examples/Plotter/Timeline/main.cpp b/examples/Plotter/Timeline/main.cpp index 0de09b8..99c4824 100644 --- a/examples/Plotter/Timeline/main.cpp +++ b/examples/Plotter/Timeline/main.cpp @@ -1,148 +1,148 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include "timeaxis.h" #include "timechartmodel.h" class ChartWidget : public QWidget { Q_OBJECT public: explicit ChartWidget(QWidget* parent = 0) : QWidget(parent), m_counter(0) { QSplitter* splitter = new QSplitter(this); QHBoxLayout* l = new QHBoxLayout(this); setLayout(l); l->addWidget( splitter ); QWidget* leftWidget = new QWidget( splitter ); QVBoxLayout* leftLayout = new QVBoxLayout(leftWidget); leftWidget->setLayout(leftLayout); QPushButton* button = new QPushButton("Animate", leftWidget); leftLayout->addWidget( button ); button->setCheckable( true ); connect( button, SIGNAL(toggled(bool)), this, SLOT(buttonToggled(bool)) ); QTreeView* tv = new QTreeView( leftWidget ); leftLayout->addWidget( tv ); m_chart = new KChart::Chart( splitter ); m_model = new QStandardItemModel( 365, 2, this ); for ( int i = 0; i < 365; ++i ) { const QDateTime dt = QDateTime( QDate( 2010, 1, 1 ), QTime() ); m_model->setData( m_model->index( i, 0 ), dt.addDays( i ) ); m_model->setData( m_model->index( i, 1 ), sin( i / 10.0 ) * 10 ); } TimeChartModel* proxy = new TimeChartModel( this ); proxy->setSourceModel( m_model ); proxy->setVisibleRange( QDateTime( QDate( 2010, 2, 1 ), QTime() ), QDateTime( QDate( 2010, 3, 31 ), QTime() ) ); KChart::Plotter* plotter = new KChart::Plotter; m_chart->coordinatePlane()->replaceDiagram( plotter ); tv->setModel( proxy ); tv->show(); TimeAxis* axis = new TimeAxis( plotter ); axis->setPosition( TimeAxis::Bottom ); plotter->addAxis( axis ); plotter->setModel( proxy ); - connect( proxy, SIGNAL( rowsInserted( QModelIndex, int, int ) ), - m_chart->coordinatePlane(), SLOT( adjustRangesToData() ) ); - connect( proxy, SIGNAL( rowsRemoved( QModelIndex, int, int ) ), - m_chart->coordinatePlane(), SLOT( adjustRangesToData() ) ); + connect( proxy, SIGNAL(rowsInserted(QModelIndex,int,int)), + m_chart->coordinatePlane(), SLOT(adjustRangesToData()) ); + connect( proxy, SIGNAL(rowsRemoved(QModelIndex,int,int)), + m_chart->coordinatePlane(), SLOT(adjustRangesToData()) ); proxy->setVisibleRange( QDateTime( QDate( 2010, 3, 15 ), QTime() ), QDateTime( QDate( 2010, 5, 18 ), QTime() ) ); qobject_cast< KChart::CartesianCoordinatePlane* >( m_chart->coordinatePlane() )->adjustRangesToData(); m_timer = new QTimer(this); - connect( m_timer, SIGNAL( timeout() ), this, SLOT( slotTimeout() ) ); + connect( m_timer, SIGNAL(timeout()), this, SLOT(slotTimeout()) ); } private slots: void slotTimeout() { // An ugly hack to prevent the QAbstractItemModel from emitting dataChanged // for every single call to setData which would result in a full relayout // every time. That is horrible for performance and what we do is to prevent // QAbstractItemModel to emit dataChanged, collect what data changed and // emit the signal at the end ourself. m_model->blockSignals(true); QModelIndexList indexes; QVariant v1, v2; for ( int i = 0; i < 365; ++i ) { QModelIndex idx = m_model->index( i, 1 ); indexes.append(idx); m_model->setData( idx, sin( i / 10.0 + m_counter ) * 10 ); } m_model->blockSignals(false); if (!indexes.isEmpty()) { m_model->metaObject()->invokeMethod(m_model, "dataChanged", Qt::DirectConnection, Q_ARG(QModelIndex,indexes.first()), Q_ARG(QModelIndex,indexes.last())); } m_counter += 0.02; } void buttonToggled(bool checked) { if (checked) m_timer->start( 200 ); else m_timer->stop(); } private: KChart::Chart* m_chart; QStandardItemModel* m_model; QTimer* m_timer; qreal m_counter; }; /** * This example demonstrates how to use time-based plots with timestamp-value data points * based on seconds and how to use a proxy model for defining the plotted "window" of the * measurement data. */ int main( int argc, char* argv[] ) { QApplication app( argc, argv ); ChartWidget w; w.show(); return app.exec(); } #include "main.moc" diff --git a/examples/Plotter/Timeline/timeaxis.cpp b/examples/Plotter/Timeline/timeaxis.cpp index b24e9e4..b2c3c0c 100644 --- a/examples/Plotter/Timeline/timeaxis.cpp +++ b/examples/Plotter/Timeline/timeaxis.cpp @@ -1,41 +1,41 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "timeaxis.h" -#include +#include TimeAxis::TimeAxis( KChart::AbstractCartesianDiagram* parent ) : KChart::CartesianAxis( parent ) { // Intentionally left blank } const QString TimeAxis::customizedLabel( const QString& label ) const { // Here we are free to format the value to whatever we want. As example we // could also return a scientific notation with something like the following // both lines; //const int precision = 2; //return QString::number(label.toReal(), 'E', precision); // Format the time-value to a nice string representation. const QDateTime dateTime = QDateTime::fromTime_t( label.toDouble() * 3600.0 ); return dateTime.date().toString(); } diff --git a/examples/RealTime/main.cpp b/examples/RealTime/main.cpp index af3202c..104b44e 100644 --- a/examples/RealTime/main.cpp +++ b/examples/RealTime/main.cpp @@ -1,80 +1,80 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include class ChartWidget : public QWidget { Q_OBJECT public: explicit ChartWidget(QWidget* parent=0) : QWidget(parent) { m_model.insertRows( 0, 2, QModelIndex() ); m_model.insertColumns( 0, 3, QModelIndex() ); for (int row = 0; row < 3; ++row) { for (int column = 0; column < 3; ++column) { QModelIndex index = m_model.index(row, column, QModelIndex()); m_model.setData(index, QVariant(row+1 * column) ); } } KChart::BarDiagram* diagram = new KChart::BarDiagram; diagram->setModel(&m_model); m_chart.coordinatePlane()->replaceDiagram(diagram); QVBoxLayout* l = new QVBoxLayout(this); l->addWidget(&m_chart); setLayout(l); m_timer = new QTimer(this); - connect( m_timer, SIGNAL( timeout() ), - this, SLOT( slotTimeout() ) ); + connect( m_timer, SIGNAL(timeout()), + this, SLOT(slotTimeout()) ); m_timer->start( 200 ); } private slots: void slotTimeout() { QModelIndex index = m_model.index( 0, 1, QModelIndex()); qreal value = ( m_model.data( index ).toInt() % 24 ) +1; m_model.setData( index, value ); } private: KChart::Chart m_chart; QStandardItemModel m_model; QTimer *m_timer; }; int main( int argc, char** argv ) { QApplication app( argc, argv ); ChartWidget w; w.show(); return app.exec(); } #include "main.moc" diff --git a/examples/Stock/Advanced/mainwindow.cpp b/examples/Stock/Advanced/mainwindow.cpp index d9af76c..ebfd0f4 100644 --- a/examples/Stock/Advanced/mainwindow.cpp +++ b/examples/Stock/Advanced/mainwindow.cpp @@ -1,148 +1,148 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "mainwindow.h" #include #include #include #include using namespace KChart; MainWindow::MainWindow( QWidget *parent ) : QWidget( parent ) , m_chart( new Chart() ) , m_diagram( m_chart ) { setupUi( this ); m_HLCModel.loadFromCSV( ":/HLC" ); m_OHLCModel.loadFromCSV( ":/OHLC" ); m_diagram.setType( StockDiagram::HighLowClose ); m_diagram.setModel( &m_HLCModel ); m_chart->coordinatePlane()->replaceDiagram( &m_diagram ); KChart::Legend* legend = new KChart::Legend( &m_diagram, m_chart ); m_chart->addLegend( legend ); QHBoxLayout* chartLayout = new QHBoxLayout( chartFrame ); chartLayout->addWidget( m_chart ); // Abscissa CartesianAxis *leftAxis = new CartesianAxis( &m_diagram ); // Ordinate CartesianAxis *bottomAxis = new CartesianAxis( &m_diagram ); leftAxis->setPosition( CartesianAxis::Left ); TextAttributes attributes = bottomAxis->textAttributes(); attributes.setRotation( 90 ); attributes.setFontSize( Measure( 7.0, KChartEnums::MeasureCalculationModeAbsolute ) ); bottomAxis->setTextAttributes( attributes ); bottomAxis->setPosition( CartesianAxis::Bottom ); m_diagram.addAxis( leftAxis ); m_diagram.addAxis( bottomAxis ); m_diagram.addAxis( bottomAxis ); applyColor( QColor( "chartreuse" ) ); - const bool connected = connect( colorChooser, SIGNAL( clicked() ), SLOT( chooseColor() ) ); + const bool connected = connect( colorChooser, SIGNAL(clicked()), SLOT(chooseColor()) ); Q_ASSERT( connected ); Q_UNUSED( connected ); // Initialize all values for the stock chart to sane defaults initValues(); } void MainWindow::chooseColor() { applyColor( QColorDialog::getColor( m_diagram.brush().color(), this ) ); } void MainWindow::applyColor(const QColor &color) { if ( color.isValid() ) { m_diagram.setPen( 0, QPen( color.darker( 130 ) ) ); m_diagram.setBrush( 0, QBrush( color ) ); QColor inverse( 255 - color.red(), 255 - color.green(), 255 - color.blue() ); m_diagram.setPen( 1, QPen( inverse.darker( 130 ) ) ); m_diagram.setBrush( 1, QBrush( inverse ) ); QPalette pal = colorChooser->palette(); pal.setBrush( QPalette::Button, QBrush( color ) ); colorChooser->setPalette( pal ); } } void MainWindow::initValues() { m_threeDBarAttributes = m_diagram.threeDBarAttributes(); m_threeDBarAttributes.setDepth( 10.0 ); m_threeDBarAttributes.setUseShadowColors( false ); threeDProperties->setChecked( m_threeDBarAttributes.isEnabled() ); perspectiveAngle->setValue( m_threeDBarAttributes.angle() ); perspectiveDepth->setValue( (int)m_threeDBarAttributes.depth() ); useShadowColors->setChecked( m_threeDBarAttributes.useShadowColors() ); m_diagram.setThreeDBarAttributes( m_threeDBarAttributes ); } void MainWindow::on_threeDProperties_toggled( bool checked ) { m_threeDBarAttributes.setEnabled( checked ); m_diagram.setThreeDBarAttributes( m_threeDBarAttributes ); m_chart->update(); } void MainWindow::on_perspectiveAngle_valueChanged( int value ) { m_threeDBarAttributes.setAngle( value ); m_diagram.setThreeDBarAttributes( m_threeDBarAttributes ); m_chart->update(); } void MainWindow::on_perspectiveDepth_valueChanged( int value ) { m_threeDBarAttributes.setDepth( value ); m_diagram.setThreeDBarAttributes( m_threeDBarAttributes ); m_chart->update(); } void MainWindow::on_useShadowColors_toggled( bool checked ) { m_threeDBarAttributes.setUseShadowColors( checked ); m_diagram.setThreeDBarAttributes( m_threeDBarAttributes ); m_chart->update(); } void MainWindow::on_stockTypeCB_currentIndexChanged( const QString &text ) { // FIXME: Workaround for disappearing diagram when setting new model m_chart->coordinatePlane()->takeDiagram( &m_diagram ); if ( text == "High-Low-Close" ) { m_diagram.setType( StockDiagram::HighLowClose ); m_diagram.setModel( &m_HLCModel ); } else if ( text == "Open-High-Low-Close" ) { m_diagram.setType( StockDiagram::OpenHighLowClose ); m_diagram.setModel( &m_OHLCModel ); } else if ( text == "Candlestick" ) { m_diagram.setType( StockDiagram::Candlestick ); m_diagram.setModel( &m_OHLCModel ); } m_chart->coordinatePlane()->replaceDiagram( &m_diagram ); } diff --git a/examples/TernaryCharts/Advanced/mainwindow.cpp b/examples/TernaryCharts/Advanced/mainwindow.cpp index 1f6e900..64a62d4 100644 --- a/examples/TernaryCharts/Advanced/mainwindow.cpp +++ b/examples/TernaryCharts/Advanced/mainwindow.cpp @@ -1,99 +1,99 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include "mainwindow.h" MainWindow::MainWindow( QWidget* parent ) : QDialog( parent ) , m_chart( 0 ) , m_diagram( 0 ) , m_ternaryPlane( 0 ) { setupUi( this ); // make a chart m_chart = new KChart::Chart; // replace the default (cartesian) coordinate plane with a ternary one m_ternaryPlane = new KChart::TernaryCoordinatePlane; m_chart->replaceCoordinatePlane( m_ternaryPlane ); // make a ternary line diagram m_diagram = new KChart::TernaryPointDiagram; // and replace the default diagram with it m_ternaryPlane->replaceDiagram( m_diagram ); // add the three ternary axes, see the positions KChart::TernaryAxis* axisA = new KChart::TernaryAxis( m_diagram ); axisA->setPosition( KChartEnums::PositionSouth ); KChart::TernaryAxis* axisB = new KChart::TernaryAxis( m_diagram ); axisB->setPosition( KChartEnums::PositionWest ); KChart::TernaryAxis* axisC = new KChart::TernaryAxis( m_diagram ); axisC->setPosition( KChartEnums::PositionEast ); QHBoxLayout* chartLayout = new QHBoxLayout( frame ); chartLayout->addWidget( m_chart ); setupModel(); m_diagram->setModel( &m_model ); - connect( m_diagram, SIGNAL( clicked( QModelIndex ) ), - SLOT( indexClicked( QModelIndex ) ) ); + connect( m_diagram, SIGNAL(clicked(QModelIndex)), + SLOT(indexClicked(QModelIndex)) ); } void MainWindow::setupModel() { m_model.insertRows( 0, 5 ); m_model.insertColumns( 0, 3 ); const float column0Share = 1.0f/3.0f; const float column1Share = 1.0f/3.0f; const float column2Share = 1.0f/3.0f; for ( int row = 0; row < m_model.rowCount(); ++row ) { const qreal SkewX = column0Share * ( 1 - 1.0/( 5 * row*row*row + 1 ) ); const qreal SkewY = SkewX; QModelIndex index; index = m_model.index( row, 0 ); m_model.setData( index, QVariant( column0Share - SkewX ) ); index = m_model.index( row, 1 ); m_model.setData( index, QVariant( column1Share + SkewX - SkewY) ); index = m_model.index( row, 2 ); m_model.setData( index, QVariant( column2Share + SkewY ) ); } } void MainWindow::indexClicked( const QModelIndex& index ) { qDebug() << "MainWindow::indexClicked:" << index; if ( index.isValid() ) { QString text; QTextStream stream( &text ); stream << "Row: " << index.row() << " - Column: " << index.column(); label->setText( text ); } } diff --git a/examples/Widget/Advanced/mainwindow.cpp b/examples/Widget/Advanced/mainwindow.cpp index 1d39d1c..9137f8d 100644 --- a/examples/Widget/Advanced/mainwindow.cpp +++ b/examples/Widget/Advanced/mainwindow.cpp @@ -1,87 +1,87 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "mainwindow.h" #include #include #include #include using namespace KChart; MainWindow::MainWindow( QWidget* parent ) : QWidget( parent ), datasetCount( 3 ) { setupUi( this ); QHBoxLayout* chartLayout = new QHBoxLayout( chartFrame ); widget = new Widget( chartFrame ); chartLayout->addWidget( widget ); typeSelector->setCurrentIndex(1); // we start by LineDiagram - connect( typeSelector, SIGNAL( activated( int ) ), SLOT( changeType() ) ); + connect( typeSelector, SIGNAL(activated(int)), SLOT(changeType()) ); - connect( btnAddDataset, SIGNAL( clicked() ), SLOT( addDataset() ) ); + connect( btnAddDataset, SIGNAL(clicked()), SLOT(addDataset()) ); - connect( leadingSelector, SIGNAL( valueChanged( int ) ), - this, SLOT( changeLeading( int ) ) ); + connect( leadingSelector, SIGNAL(valueChanged(int)), + this, SLOT(changeLeading(int)) ); } void MainWindow::changeType() { QString text = typeSelector->currentText(); if ( text == "Widget::Bar" ) widget->setType( Widget::Bar ); else if ( text == "Widget::Line" ) widget->setType( Widget::Line ); else if ( text == "Widget::Pie" ) widget->setType( Widget::Pie ); else if ( text == "Widget::Polar" ) widget->setType( Widget::Polar ); else widget->setType( Widget::NoType ); } void MainWindow::changeLeading( int leading ) { widget->setGlobalLeading( leading, leading, leading, leading ); } void MainWindow::addDataset() { QStringList parts = lineAddDataset->text().split( ';' ); bool ok; QVector< qreal > vec; foreach ( const QString &str, parts ) { const qreal val = str.toDouble( &ok ); if ( ok ) vec.append( val ); } const int rows = widget->diagram()->model()->rowCount(); if ( vec.count() != rows ) { QMessageBox::warning( this, "Wrong number of values entered!", QString( "You have entered %1 values,
but the data model needs %2 ones." "

Note: Use ; to separate the values!" ) .arg(vec.count()).arg(rows)); } else { widget->setDataset( datasetCount++, vec, "user data" ); } } diff --git a/examples/Zoom/ScrollBars/mainwindow.cpp b/examples/Zoom/ScrollBars/mainwindow.cpp index 275b977..c856b55 100644 --- a/examples/Zoom/ScrollBars/mainwindow.cpp +++ b/examples/Zoom/ScrollBars/mainwindow.cpp @@ -1,124 +1,124 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "mainwindow.h" #include #include #include #include #include #include using namespace KChart; MainWindow::MainWindow( QWidget* parent ) : QWidget( parent ) { setupUi( this ); QHBoxLayout* chartLayout = new QHBoxLayout( chartFrame ); m_chart = new Chart(); m_chart->setGlobalLeadingTop( 10 ); m_chart->setGlobalLeadingRight( 10 ); chartLayout->addWidget( m_chart ); hSBar->setVisible( false ); vSBar->setVisible( false ); m_model.loadFromCSV( ":/data" ); // Set up the diagram m_lines = new LineDiagram(); m_lines->setModel( &m_model ); CartesianAxis *xAxis = new CartesianAxis( m_lines ); CartesianAxis *yAxis = new CartesianAxis ( m_lines ); xAxis->setPosition ( KChart::CartesianAxis::Bottom ); yAxis->setPosition ( KChart::CartesianAxis::Left ); xAxis->setTitleText ( "Abscissa axis at the bottom" ); yAxis->setTitleText ( "Ordinate axis at the left side" ); m_lines->addAxis( xAxis ); m_lines->addAxis( yAxis ); m_chart->coordinatePlane()->replaceDiagram( m_lines ); - connect( m_chart, SIGNAL( propertiesChanged() ), SLOT( applyNewZoomParameters() ) ); + connect( m_chart, SIGNAL(propertiesChanged()), SLOT(applyNewZoomParameters()) ); // Set up the legend m_legend = new Legend( m_lines, m_chart ); m_chart->addLegend( m_legend ); m_legend->setPosition( KChartEnums::PositionEast ); m_legend->setAlignment( Qt::AlignTop ); } void MainWindow::on_zoomFactorSB_valueChanged( double factor ) { if ( factor > 1 ) { hSBar->setVisible( true ); vSBar->setVisible( true ); } else { hSBar->setValue( 500 ); vSBar->setValue( 500 ); hSBar->setVisible( false ); vSBar->setVisible( false ); } m_chart->coordinatePlane()->setZoomFactorX( factor ); m_chart->coordinatePlane()->setZoomFactorY( factor ); m_chart->update(); } void MainWindow::on_adjustGridCB_toggled( bool checked ) { static_cast ( m_chart->coordinatePlane() ) ->setAutoAdjustGridToZoom( checked ); m_chart->update(); } void MainWindow::on_rubberBandZoomCB_toggled( bool checked ) { m_chart->coordinatePlane()->setRubberBandZoomingEnabled( checked ); } void MainWindow::on_hSBar_valueChanged( int hPos ) { m_chart->coordinatePlane()->setZoomCenter( QPointF(hPos/1000.0, vSBar->value()/1000.0) ); m_chart->update(); } void MainWindow::on_vSBar_valueChanged( int vPos ) { m_chart->coordinatePlane()->setZoomCenter( QPointF( hSBar->value()/1000.0, vPos/1000.0) ); m_chart->update(); } void MainWindow::applyNewZoomParameters() { hSBar->blockSignals( true ); vSBar->blockSignals( true ); hSBar->setValue( qRound( m_chart->coordinatePlane()->zoomCenter().x() * 1000 ) ); vSBar->setValue( qRound( m_chart->coordinatePlane()->zoomCenter().y() * 1000 ) ); zoomFactorSB->setValue( m_chart->coordinatePlane()->zoomFactorX() ); hSBar->blockSignals( false ); vSBar->blockSignals( false ); } diff --git a/examples/demo/axissettings.cpp b/examples/demo/axissettings.cpp index 26b8db9..5f8d88e 100644 --- a/examples/demo/axissettings.cpp +++ b/examples/demo/axissettings.cpp @@ -1,205 +1,205 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "axissettings.h" #include "ui_axissettings.h" -#include -#include +#include +#include #include #include #include #include #include #include #include using namespace KChart; class AxisSettings::Private : public QObject { Q_OBJECT public: Private( Chart* chart, AxisSettings *q ); void init(); CartesianAxis *m_currentAxis; QHash< CartesianCoordinatePlane*, QVector< CartesianAxis* > > m_axisCache; Chart *m_chart; Ui::AxisSettings ui; AxisSettings *qq; public Q_SLOTS: void currentIndexChanged( int index ); void setVisible( bool value ); void setShowMajorTickMarks( bool value ); void setShowMinorTickMarks( bool value ); private: void updateUiForCurrentAxis(); }; AxisSettings::Private::Private( Chart* chart, AxisSettings *q ) : m_chart( chart ) , qq( q ) { } void AxisSettings::Private::init() { ui.axisSelection->addItem( QIcon(), tr( "Left Y-Axis" ), CartesianAxis::Left ); ui.axisSelection->addItem( QIcon(), tr( "Right Y-Axis" ), CartesianAxis::Right ); ui.axisSelection->addItem( QIcon(), tr( "Bottom X-Axis" ), CartesianAxis::Bottom ); ui.axisSelection->addItem( QIcon(), tr( "Top X-Axis" ), CartesianAxis::Top ); - connect( ui.axisSelection, SIGNAL( currentIndexChanged( int ) ), this, SLOT( currentIndexChanged( int ) ) ); - connect( ui.axisVisibility, SIGNAL( toggled( bool ) ), this, SLOT( setVisible( bool ) ) ); - connect( ui.majorTicks, SIGNAL( toggled( bool ) ), this, SLOT( setShowMajorTickMarks( bool ) ) ); - connect( ui.minorTicks, SIGNAL( toggled( bool ) ), this, SLOT( setShowMinorTickMarks( bool ) ) ); + connect( ui.axisSelection, SIGNAL(currentIndexChanged(int)), this, SLOT(currentIndexChanged(int)) ); + connect( ui.axisVisibility, SIGNAL(toggled(bool)), this, SLOT(setVisible(bool)) ); + connect( ui.majorTicks, SIGNAL(toggled(bool)), this, SLOT(setShowMajorTickMarks(bool)) ); + connect( ui.minorTicks, SIGNAL(toggled(bool)), this, SLOT(setShowMinorTickMarks(bool)) ); ui.axisSelection->setCurrentIndex( 0 ); } void AxisSettings::Private::currentIndexChanged( int index ) { m_currentAxis = 0; const CartesianAxis::Position pos = ( CartesianAxis::Position ) ui.axisSelection->itemData( index ).toInt(); CartesianCoordinatePlane *plane = qobject_cast< CartesianCoordinatePlane* >( m_chart->coordinatePlane() ); AbstractCartesianDiagram *diag = qobject_cast< AbstractCartesianDiagram* >( m_chart->coordinatePlane()->diagram() ); if ( plane && diag ) { QVector< CartesianAxis* > axes = diag->axes().toVector(); Q_FOREACH( CartesianAxis *axis, axes ) { if ( axis->position() == pos ) { m_currentAxis = axis; break; } } } updateUiForCurrentAxis(); } void AxisSettings::Private::updateUiForCurrentAxis() { const bool prevSignalsBlocked = ui.axisVisibility->blockSignals( true ); ui.axisVisibility->setChecked( m_currentAxis ); ui.axisVisibility->blockSignals( prevSignalsBlocked ); ui.majorTicks->setEnabled( m_currentAxis ); ui.minorTicks->setEnabled( m_currentAxis ); if ( m_currentAxis ) { const RulerAttributes rulerAttr = m_currentAxis->rulerAttributes(); ui.majorTicks->setChecked( rulerAttr.showMajorTickMarks() ); ui.minorTicks->setChecked( rulerAttr.showMinorTickMarks() ); } else { ui.majorTicks->setChecked( false ); ui.minorTicks->setChecked( false ); } } void AxisSettings::Private::setVisible( bool value ) { const CartesianAxis::Position pos = ( CartesianAxis::Position ) ui.axisSelection->itemData( ui.axisSelection->currentIndex() ).toInt(); CartesianCoordinatePlane *plane = qobject_cast< CartesianCoordinatePlane* >( m_chart->coordinatePlane() ); AbstractCartesianDiagram *diag = qobject_cast< AbstractCartesianDiagram* >( m_chart->coordinatePlane()->diagram() ); if ( plane && diag ) { QVector< CartesianAxis* > axes = m_axisCache[ plane ]; bool foundAxis = false; Q_FOREACH( CartesianAxis *axis, axes ) { if ( axis->position() == pos ) { foundAxis = true; if ( !value ) { diag->takeAxis( axis ); m_currentAxis = 0; } else { diag->addAxis( axis ); m_currentAxis = axis; } break; } } if ( !foundAxis ) { Q_ASSERT( value ); CartesianAxis *axis = new CartesianAxis( diag ); axis->setPosition( pos ); diag->addAxis( axis ); m_axisCache[ plane ].append( axis ); m_currentAxis = axis; } updateUiForCurrentAxis(); m_chart->update(); } } void AxisSettings::Private::setShowMajorTickMarks( bool value ) { if ( m_currentAxis ) { RulerAttributes attr( m_currentAxis->rulerAttributes() ); attr.setShowMajorTickMarks( value ); m_currentAxis->setRulerAttributes( attr ); m_chart->update(); } } void AxisSettings::Private::setShowMinorTickMarks( bool value ) { if ( m_currentAxis ) { RulerAttributes attr( m_currentAxis->rulerAttributes() ); attr.setShowMinorTickMarks( value ); m_currentAxis->setRulerAttributes( attr ); m_chart->update(); } } AxisSettings::AxisSettings( Chart *chart, QWidget *parent ) : QWidget( parent ) , d( new Private( chart, this ) ) { d->ui.setupUi( this ); d->init(); } AxisSettings::~AxisSettings() { delete d; } void AxisSettings::diagramTypeChanged() { CartesianCoordinatePlane *plane = qobject_cast< CartesianCoordinatePlane* >( d->m_chart->coordinatePlane() ); setEnabled( plane ); if ( plane ) d->currentIndexChanged( d->ui.axisSelection->currentIndex() ); } #include "axissettings.moc" diff --git a/examples/demo/colorslider.cpp b/examples/demo/colorslider.cpp index 8991761..6bfff10 100644 --- a/examples/demo/colorslider.cpp +++ b/examples/demo/colorslider.cpp @@ -1,163 +1,163 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "colorslider.h" -#include -#include -#include +#include +#include +#include #include class ColorSlider::Private { public: Private( ColorSlider *q ); void drawSliderMarker( QPainter *painter ); QColor m_startColor; QColor m_endColor; bool m_dragging; ColorSlider *qq; }; ColorSlider::Private::Private( ColorSlider *q ) : m_dragging( false ) , qq( q ) { } void ColorSlider::Private::drawSliderMarker( QPainter *painter ) { QVector< QPointF > points; points << QPointF( -0.5, 0 ) << QPointF( 0.0, 0.5 ) << QPointF( 0.5, 0 ); QPolygonF triangle( points ); painter->drawPolygon( triangle ); } ColorSlider::ColorSlider( QWidget *parent ) : QAbstractSlider( parent ) , d( new Private( this ) ) { setSizePolicy( QSizePolicy( QSizePolicy::Minimum ,QSizePolicy::Fixed) ); setMinimum( 0 ); setMaximum( 255 ); setValue( 25 ); } ColorSlider::~ColorSlider() { delete d; } QColor ColorSlider::startColor() const { return d->m_startColor; } void ColorSlider::setStartColor( const QColor &color ) { if ( d->m_startColor != color ) { d->m_startColor = color; Q_EMIT startColorChanged(); update(); } } QColor ColorSlider::endColor() const { return d->m_endColor; } void ColorSlider::setEndColor( const QColor &color ) { if ( d->m_endColor != color ) { d->m_endColor = color; Q_EMIT startColorChanged(); update(); } } void ColorSlider::paintEvent( QPaintEvent *event ) { QAbstractSlider::paintEvent( event ); const double percentage = value() / ( double )( maximum() - minimum() ); QPainter painter( this ); QLinearGradient gradient; gradient.setStart( 0, 0 ); gradient.setFinalStop( 1, 0 ); gradient.setCoordinateMode( QGradient::ObjectBoundingMode ); gradient.setColorAt( 0, d->m_startColor ); gradient.setColorAt( 1, d->m_endColor ); painter.setBrush( gradient ); painter.drawRect( event->rect() ); painter.setBrush( Qt::white ); painter.save(); painter.translate( event->rect().width() * percentage, 0 ); QPen oldPen = painter.pen(); QPen linePen = oldPen; linePen.setColor( Qt::white ); painter.setPen( linePen ); painter.drawLine( 0, 0, 0, rect().height() ); painter.setPen( oldPen ); painter.save(); painter.translate( 0, rect().height() ); painter.rotate( 180 ); painter.scale( 15, 15 ); d->drawSliderMarker( &painter ); painter.restore(); painter.scale( 15, 15 ); d->drawSliderMarker( &painter ); painter.restore(); } void ColorSlider::mousePressEvent(QMouseEvent *event ) { if ( event->button() == Qt::LeftButton ) { d->m_dragging = true; const double relPos = qMin( 1.0, qMax( 0.0, event->localPos().x() / rect().width() ) ); setValue( (maximum() - minimum() ) * relPos ); } } void ColorSlider::mouseReleaseEvent(QMouseEvent *event ) { if ( event->button() != Qt::LeftButton ) { d->m_dragging = false; const double relPos = qMin( 1.0, qMax( 0.0, event->localPos().x() / rect().width() ) ); setValue( (maximum() - minimum() ) * relPos ); } } void ColorSlider::mouseMoveEvent( QMouseEvent *event ) { if ( d->m_dragging ) { const double relPos = qMin( 1.0, qMax( 0.0, event->localPos().x() / rect().width() ) ); setValue( (maximum() - minimum() ) * relPos ); } } QSize ColorSlider::sizeHint() const { return QSize( 300, 50 ); } diff --git a/examples/demo/datasetsettings.cpp b/examples/demo/datasetsettings.cpp index 98eaa41..6eaca1b 100644 --- a/examples/demo/datasetsettings.cpp +++ b/examples/demo/datasetsettings.cpp @@ -1,203 +1,203 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "datasetsettings.h" #include "ui_datasetsettings.h" #include #include #include #include #include -#include +#include -#include +#include -#include +#include #include "gradientdialog.h" using namespace KChart; class DatasetSettings::Private : public QObject { Q_OBJECT public: Private( Chart *chart, DatasetSettings *q, QObject *parent ); ~Private(); Ui::DatasetSettings *ui; int m_dataset; int m_datasetCount; KChart::Chart *m_chart; DatasetSettings *qq; public Q_SLOTS: void changeColor(); void changeOutline(); }; DatasetSettings::Private::Private( Chart *chart, DatasetSettings *q, QObject *parent ) : QObject( parent ) , ui( new Ui::DatasetSettings ) , m_dataset( 0 ) , m_datasetCount( 0 ) , m_chart( chart ) , qq( q ) { } DatasetSettings::Private::~Private() { delete ui; } void DatasetSettings::Private::changeColor() { const int index = ui->datasetSelector->currentIndex(); if ( ui->Color->isChecked() ) { QBrush setBrush = m_chart->coordinatePlane()->diagram()->brush( index ); const QColor color = QColorDialog::getColor( setBrush.color(), qq, tr( "Choose new color" ) ); if ( !color.isValid() ) return; m_chart->coordinatePlane()->diagram()->setBrush( index, color ); QPalette palette = ui->colorDisplay->palette(); palette.setBrush( QPalette::Button, color ); ui->colorDisplay->setPalette( palette ); } else if ( ui->textureBtn->isChecked() ) { //QBrush setBrush = m_chart->coordinatePlane()->diagram()->brush( index ); QImage texture; const QString filename = QFileDialog::getOpenFileName( qq, tr( "Choose Texture" ), QString(), tr( "Images (*.png *.xpm *.jpg)" ) ); if ( filename.isEmpty() ) return; texture = QImage( filename ); m_chart->coordinatePlane()->diagram()->setBrush( index, texture ); QPalette palette = ui->colorDisplay->palette(); palette.setBrush( QPalette::Button, QBrush( texture ) ); ui->colorDisplay->setPalette( palette ); } else { QBrush setBrush = m_chart->coordinatePlane()->diagram()->brush( index ); QGradient grad; QLinearGradient lGrad; lGrad.setColorAt( 0, Qt::black ); lGrad.setColorAt( 1, setBrush.color() ); grad = lGrad; if ( setBrush.gradient() ) grad = *setBrush.gradient(); const QGradient &color = GradientDialog::getGradient( grad, qq, tr( "Choose new color" ) ); m_chart->coordinatePlane()->diagram()->setBrush( index, color ); QPalette palette = ui->colorDisplay->palette(); palette.setBrush( QPalette::Button, QBrush( color ) ); ui->colorDisplay->setPalette( palette ); } } void DatasetSettings::Private::changeOutline() { const int index = ui->datasetSelector->currentIndex(); if ( ui->Color->isChecked() ) { QPen pen = m_chart->coordinatePlane()->diagram()->pen( index ); const QColor color = QColorDialog::getColor( pen.color(), qq, tr( "Choose new color" ) ); if ( !color.isValid() ) return; pen.setColor( color ); m_chart->coordinatePlane()->diagram()->setPen( index, pen ); QPalette palette = ui->outlineBtn->palette(); palette.setBrush( QPalette::Button, color ); ui->outlineBtn->setPalette( palette ); } } DatasetSettings::DatasetSettings( Chart *chart, QWidget *parent ) : QWidget( parent ) , d( new Private( chart, this, this ) ) { d->ui->setupUi(this); #ifdef Q_OS_LINUX d->ui->colorDisplay->setStyle( QStyleFactory::create( QStringLiteral( "cleanlooks" ) ) ); d->ui->outlineBtn->setStyle( QStyleFactory::create( QStringLiteral( "cleanlooks" ) ) ); #endif - connect( d->ui->datasetSelector, SIGNAL( currentIndexChanged( int ) ), this, SLOT( indexChanged( int ) ) ); - connect( d->ui->colorDisplay, SIGNAL( clicked() ), d, SLOT( changeColor() ) ); - connect( d->ui->outlineBtn, SIGNAL( clicked() ), d, SLOT( changeOutline() ) ); + connect( d->ui->datasetSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(indexChanged(int)) ); + connect( d->ui->colorDisplay, SIGNAL(clicked()), d, SLOT(changeColor()) ); + connect( d->ui->outlineBtn, SIGNAL(clicked()), d, SLOT(changeOutline()) ); } DatasetSettings::~DatasetSettings() { delete d; } int DatasetSettings::datasetCount() const { return d->m_datasetCount; } void DatasetSettings::setDatasetCount( int value ) { if ( d->m_datasetCount != value ) { d->m_datasetCount = value; QStringList list; for ( int i = 0; i < value; ++i ) { list << tr( "Dataset %1" ).arg( i ); } d->ui->datasetSelector->clear(); d->ui->datasetSelector->addItems( list ); Q_EMIT datasetCountChanged(); } } void DatasetSettings::indexChanged( int index ) { if ( d->m_chart && index >= 0 ) { const QBrush setBrush = d->m_chart->coordinatePlane()->diagram()->brush( index ); QPalette palette = d->ui->colorDisplay->palette(); if ( setBrush.gradient() ) d->ui->radioButton_2->setChecked( true ); else if ( !setBrush.textureImage().isNull() ) d->ui->textureBtn->setChecked( true ); else d->ui->Color->setChecked( true ); palette.setBrush( QPalette::Button, setBrush ); d->ui->colorDisplay->setPalette( palette ); const QPen pen = d->m_chart->coordinatePlane()->diagram()->pen( index ); QPalette penPalette = d->ui->outlineBtn->palette(); penPalette.setBrush( QPalette::Button, pen.color() ); d->ui->outlineBtn->setPalette( penPalette ); } } void DatasetSettings::diagramTypeChanged() { } #include "datasetsettings.moc" diff --git a/examples/demo/datavaluesettings.cpp b/examples/demo/datavaluesettings.cpp index 15fb8aa..ee4669a 100644 --- a/examples/demo/datavaluesettings.cpp +++ b/examples/demo/datavaluesettings.cpp @@ -1,488 +1,488 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "datavaluesettings.h" #include "ui_datavaluesettings.h" #include #include #include #include #include #include #include #include #include #include using namespace KChart; class DataValueSettings::Private : public QObject { Q_OBJECT public: Private( KChart::Chart *chart, DataValueSettings *q ); DataValueSettings *qq; void init(); public Q_SLOTS: void on_scopeOneBarRB_toggled( bool checked ); void on_scopeBarDatasetSB_valueChanged( int i ); void on_scopeBarItemSB_valueChanged( int i ); void on_scopeDatasetRB_toggled( bool checked ); void on_scopeDatasetSB_valueChanged( int i ); void on_scopeCommonRB_toggled( bool checked ); void on_paintValuesCB_toggled( bool checked ); void on_fontCombo_currentIndexChanged( const QString & text ); void on_relativeSizeSB_valueChanged( int i ); void on_minimumSizeSB_valueChanged( int i ); void on_rotationSB_valueChanged( int i ); void on_posPosCombo_currentIndexChanged( const QString & text ); void on_posAlignCombo_currentIndexChanged( const QString & text ); void on_posPadHoriSB_valueChanged( int i ); void on_posPadVertSB_valueChanged( int i ); void on_negPosCombo_currentIndexChanged( const QString & text ); void on_negAlignCombo_currentIndexChanged( const QString & text ); void on_negPadHoriSB_valueChanged( int i ); void on_negPadVertSB_valueChanged( int i ); void on_labelLE_textEdited( const QString & text ); void on_prefixLE_textEdited( const QString & text ); void on_suffixLE_textEdited( const QString & text ); public: const QModelIndex currentIndex() const; const KChart::DataValueAttributes attributes() const; const char* positionToScreenName( const KChart::Position& pos ) const; const Qt::Alignment alignmentFromScreeName( const QString& name ) const; const QString alignmentToScreenName( const Qt::Alignment& align ) const; void populateWidgets(); void setAttributes( const KChart::DataValueAttributes& da ); void connectWidgets(); KChart::Chart* m_chart; Ui::DataValueSettings *ui; }; DataValueSettings::Private::Private( KChart::Chart *chart, DataValueSettings *q ) : qq ( q ) , m_chart( chart ) , ui( new Ui::DataValueSettings ) { } void DataValueSettings::Private::init() { ui->setupUi( qq ); ui->scopeCommonRB->setFocus( Qt::OtherFocusReason ); ui->scopeCommonRB->setChecked( true ); ui->paintValuesCB->setChecked( true ); populateWidgets(); connectWidgets(); } void DataValueSettings::Private::connectWidgets() { - connect( ui->scopeBarDatasetSB, SIGNAL( valueChanged( int ) ), this, SLOT( on_scopeBarDatasetSB_valueChanged( int ) ) ); - connect( ui->scopeBarItemSB, SIGNAL( valueChanged( int ) ), this, SLOT( on_scopeBarItemSB_valueChanged( int ) ) ); - connect( ui->scopeDatasetSB, SIGNAL( valueChanged( int ) ), this, SLOT( on_scopeDatasetSB_valueChanged( int ) ) ); - connect( ui->scopeOneBarRB, SIGNAL( toggled( bool ) ), this, SLOT( on_scopeOneBarRB_toggled( bool ) ) ); - connect( ui->scopeDatasetRB, SIGNAL( toggled( bool ) ), this, SLOT( on_scopeDatasetRB_toggled( bool ) ) ); - connect( ui->scopeCommonRB, SIGNAL( toggled( bool ) ), this, SLOT( on_scopeCommonRB_toggled( bool ) ) ); - connect( ui->paintValuesCB, SIGNAL( toggled( bool ) ), this, SLOT( on_paintValuesCB_toggled( bool ) ) ); - connect( ui->fontCombo, SIGNAL( currentIndexChanged( QString ) ), this , SLOT( on_fontCombo_currentIndexChanged( QString ) ) ); - connect( ui->relativeSizeSB, SIGNAL( valueChanged( int ) ), this, SLOT( on_relativeSizeSB_valueChanged( int ) ) ); - connect( ui->minimumSizeSB, SIGNAL( valueChanged( int ) ), this, SLOT( on_minimumSizeSB_valueChanged( int ) ) ); - connect( ui->rotationSB, SIGNAL( valueChanged( int ) ), this, SLOT( on_rotationSB_valueChanged( int ) ) ); - connect( ui->posPosCombo, SIGNAL( currentIndexChanged( QString ) ), this, SLOT( on_posPosCombo_currentIndexChanged( QString ) ) ); - connect( ui->posAlignCombo, SIGNAL( currentIndexChanged( QString ) ), this, SLOT( on_posAlignCombo_currentIndexChanged( QString ) ) ); - connect( ui->posPadHoriSB, SIGNAL( valueChanged(int) ), this, SLOT( on_posPadHoriSB_valueChanged(int) ) ); - connect( ui->posPadVertSB, SIGNAL( valueChanged(int) ), this, SLOT( on_posPadVertSB_valueChanged(int) ) ); - connect( ui->negPosCombo, SIGNAL( currentIndexChanged( QString ) ), this, SLOT( on_negPosCombo_currentIndexChanged( QString ) ) ); - connect( ui->negAlignCombo, SIGNAL( currentIndexChanged( QString ) ), this, SLOT( on_negAlignCombo_currentIndexChanged( QString ) ) ); - connect( ui->negPadHoriSB, SIGNAL( valueChanged( int ) ), this, SLOT( on_negPadHoriSB_valueChanged( int ) ) ); - connect( ui->negPadVertSB, SIGNAL( valueChanged( int ) ), this, SLOT( on_negPadVertSB_valueChanged( int ) ) ); - connect( ui->labelLE, SIGNAL( textEdited( QString ) ), this, SLOT( on_labelLE_textEdited( QString ) ) ); - connect( ui->prefixLE, SIGNAL( textEdited( QString ) ), this, SLOT( on_prefixLE_textEdited( QString ) ) ); - connect( ui->suffixLE, SIGNAL( textEdited( QString ) ), this, SLOT( on_suffixLE_textEdited( QString ) ) ); + connect( ui->scopeBarDatasetSB, SIGNAL(valueChanged(int)), this, SLOT(on_scopeBarDatasetSB_valueChanged(int)) ); + connect( ui->scopeBarItemSB, SIGNAL(valueChanged(int)), this, SLOT(on_scopeBarItemSB_valueChanged(int)) ); + connect( ui->scopeDatasetSB, SIGNAL(valueChanged(int)), this, SLOT(on_scopeDatasetSB_valueChanged(int)) ); + connect( ui->scopeOneBarRB, SIGNAL(toggled(bool)), this, SLOT(on_scopeOneBarRB_toggled(bool)) ); + connect( ui->scopeDatasetRB, SIGNAL(toggled(bool)), this, SLOT(on_scopeDatasetRB_toggled(bool)) ); + connect( ui->scopeCommonRB, SIGNAL(toggled(bool)), this, SLOT(on_scopeCommonRB_toggled(bool)) ); + connect( ui->paintValuesCB, SIGNAL(toggled(bool)), this, SLOT(on_paintValuesCB_toggled(bool)) ); + connect( ui->fontCombo, SIGNAL(currentIndexChanged(QString)), this , SLOT(on_fontCombo_currentIndexChanged(QString)) ); + connect( ui->relativeSizeSB, SIGNAL(valueChanged(int)), this, SLOT(on_relativeSizeSB_valueChanged(int)) ); + connect( ui->minimumSizeSB, SIGNAL(valueChanged(int)), this, SLOT(on_minimumSizeSB_valueChanged(int)) ); + connect( ui->rotationSB, SIGNAL(valueChanged(int)), this, SLOT(on_rotationSB_valueChanged(int)) ); + connect( ui->posPosCombo, SIGNAL(currentIndexChanged(QString)), this, SLOT(on_posPosCombo_currentIndexChanged(QString)) ); + connect( ui->posAlignCombo, SIGNAL(currentIndexChanged(QString)), this, SLOT(on_posAlignCombo_currentIndexChanged(QString)) ); + connect( ui->posPadHoriSB, SIGNAL(valueChanged(int)), this, SLOT(on_posPadHoriSB_valueChanged(int)) ); + connect( ui->posPadVertSB, SIGNAL(valueChanged(int)), this, SLOT(on_posPadVertSB_valueChanged(int)) ); + connect( ui->negPosCombo, SIGNAL(currentIndexChanged(QString)), this, SLOT(on_negPosCombo_currentIndexChanged(QString)) ); + connect( ui->negAlignCombo, SIGNAL(currentIndexChanged(QString)), this, SLOT(on_negAlignCombo_currentIndexChanged(QString)) ); + connect( ui->negPadHoriSB, SIGNAL(valueChanged(int)), this, SLOT(on_negPadHoriSB_valueChanged(int)) ); + connect( ui->negPadVertSB, SIGNAL(valueChanged(int)), this, SLOT(on_negPadVertSB_valueChanged(int)) ); + connect( ui->labelLE, SIGNAL(textEdited(QString)), this, SLOT(on_labelLE_textEdited(QString)) ); + connect( ui->prefixLE, SIGNAL(textEdited(QString)), this, SLOT(on_prefixLE_textEdited(QString)) ); + connect( ui->suffixLE, SIGNAL(textEdited(QString)), this, SLOT(on_suffixLE_textEdited(QString)) ); } DataValueSettings::DataValueSettings( KChart::Chart *chart, QWidget* parent ) : QWidget( parent ) , d( new Private( chart, this ) ) { d->init(); } DataValueSettings::~DataValueSettings() { delete d; } void DataValueSettings::refresh() { d->populateWidgets(); } void DataValueSettings::Private::on_scopeOneBarRB_toggled( bool checked ) { if ( checked ) { ui->scopeBarDatasetSB->setDisabled( false ); ui->scopeBarItemSB->setDisabled( false ); ui->scopeDatasetSB->setDisabled( true ); populateWidgets(); } } void DataValueSettings::Private::on_scopeBarDatasetSB_valueChanged( int i ) { Q_UNUSED(i) populateWidgets(); } void DataValueSettings::Private::on_scopeBarItemSB_valueChanged( int i ) { Q_UNUSED(i) populateWidgets(); } void DataValueSettings::Private::on_scopeDatasetRB_toggled( bool checked ) { if ( checked ) { ui->scopeBarDatasetSB->setDisabled( true ); ui->scopeBarItemSB->setDisabled( true ); ui->scopeDatasetSB->setDisabled( false ); populateWidgets(); } } void DataValueSettings::Private::on_scopeDatasetSB_valueChanged( int i ) { Q_UNUSED(i) populateWidgets(); } void DataValueSettings::Private::on_scopeCommonRB_toggled( bool checked ) { if ( checked ) { ui->scopeBarDatasetSB->setDisabled( true ); ui->scopeBarItemSB->setDisabled( true ); ui->scopeDatasetSB->setDisabled( true ); populateWidgets(); } } void DataValueSettings::Private::on_paintValuesCB_toggled( bool checked ) { DataValueAttributes da( attributes() ); da.setVisible( checked ); setAttributes( da ); m_chart->update(); } void DataValueSettings::Private::on_fontCombo_currentIndexChanged( const QString & text ) { DataValueAttributes da( attributes() ); TextAttributes ta( da.textAttributes() ); QFont font( text ); ta.setFont( font ); da.setTextAttributes( ta ); setAttributes( da ); m_chart->update(); } void DataValueSettings::Private::on_relativeSizeSB_valueChanged( int i ) { DataValueAttributes da( attributes() ); TextAttributes ta( da.textAttributes() ); Measure fs( ta.fontSize() ); fs.setValue( i ); ta.setFontSize( i ); da.setTextAttributes( ta ); setAttributes( da ); m_chart->update(); } void DataValueSettings::Private::on_minimumSizeSB_valueChanged( int i ) { DataValueAttributes da( attributes() ); TextAttributes ta( da.textAttributes() ); Measure fs( ta.fontSize() ); fs.setValue( i ); ta.setMinimalFontSize( i ); da.setTextAttributes( ta ); setAttributes( da ); m_chart->update(); } void DataValueSettings::Private::on_rotationSB_valueChanged( int i ) { DataValueAttributes da( attributes() ); TextAttributes ta( da.textAttributes() ); ta.setRotation( i ); da.setTextAttributes( ta ); setAttributes( da ); m_chart->update(); } void DataValueSettings::Private::on_posPosCombo_currentIndexChanged( const QString & text ) { DataValueAttributes da( attributes() ); RelativePosition relPos( da.positivePosition() ); relPos.setReferencePosition( Position::fromName( qPrintable( text ) ) ); da.setPositivePosition( relPos ); setAttributes( da ); m_chart->update(); } void DataValueSettings::Private::on_posAlignCombo_currentIndexChanged( const QString & text ) { DataValueAttributes da( attributes() ); RelativePosition relPos( da.positivePosition() ); relPos.setAlignment( alignmentFromScreeName( text ) ); da.setPositivePosition( relPos ); setAttributes( da ); m_chart->update(); } void DataValueSettings::Private::on_posPadHoriSB_valueChanged( int i ) { DataValueAttributes da( attributes() ); RelativePosition relPos( da.positivePosition() ); Measure pad( relPos.horizontalPadding() ); pad.setValue( i ); relPos.setHorizontalPadding( pad ); da.setPositivePosition( relPos ); setAttributes( da ); m_chart->update(); } void DataValueSettings::Private::on_posPadVertSB_valueChanged( int i ) { DataValueAttributes da( attributes() ); RelativePosition relPos( da.positivePosition() ); Measure pad( relPos.verticalPadding() ); pad.setValue( i ); relPos.setVerticalPadding( pad ); da.setPositivePosition( relPos ); setAttributes( da ); m_chart->update(); } void DataValueSettings::Private::on_negPosCombo_currentIndexChanged( const QString & text ) { DataValueAttributes da( attributes() ); RelativePosition relPos( da.negativePosition() ); relPos.setReferencePosition( Position::fromName( qPrintable( text ) ) ); da.setNegativePosition( relPos ); setAttributes( da ); m_chart->update(); } void DataValueSettings::Private::on_negAlignCombo_currentIndexChanged( const QString & text ) { DataValueAttributes da( attributes() ); RelativePosition relPos( da.negativePosition() ); relPos.setAlignment( alignmentFromScreeName( text ) ); da.setNegativePosition( relPos ); setAttributes( da ); m_chart->update(); } void DataValueSettings::Private::on_negPadHoriSB_valueChanged( int i ) { DataValueAttributes da( attributes() ); RelativePosition relPos( da.negativePosition() ); Measure pad( relPos.horizontalPadding() ); pad.setValue( i ); relPos.setHorizontalPadding( pad ); da.setNegativePosition( relPos ); setAttributes( da ); m_chart->update(); } void DataValueSettings::Private::on_negPadVertSB_valueChanged( int i ) { DataValueAttributes da( attributes() ); RelativePosition relPos( da.negativePosition() ); Measure pad( relPos.verticalPadding() ); pad.setValue( i ); relPos.setVerticalPadding( pad ); da.setNegativePosition( relPos ); setAttributes( da ); m_chart->update(); } void DataValueSettings::Private::on_labelLE_textEdited( const QString & text ) { DataValueAttributes da( attributes() ); da.setDataLabel( text.isEmpty() ? QString() : text ); setAttributes( da ); m_chart->update(); } void DataValueSettings::Private::on_prefixLE_textEdited( const QString & text ) { DataValueAttributes da( attributes() ); da.setPrefix( text.isEmpty() ? QString() : text ); setAttributes( da ); m_chart->update(); } void DataValueSettings::Private::on_suffixLE_textEdited( const QString & text ) { DataValueAttributes da( attributes() ); da.setSuffix( text.isEmpty() ? QString() : text ); setAttributes( da ); m_chart->update(); } const QModelIndex DataValueSettings::Private::currentIndex() const { const int dataset = ui->scopeBarDatasetSB->value(); const int item = ui->scopeBarItemSB->value(); Q_ASSERT( m_chart ); Q_ASSERT( m_chart->coordinatePlane() ); AbstractDiagram *diag = m_chart->coordinatePlane()->diagram(); return diag->model()->index( item, dataset, QModelIndex() ); } const KChart::DataValueAttributes DataValueSettings::Private::attributes() const { Q_ASSERT( m_chart ); Q_ASSERT( m_chart->coordinatePlane() ); AbstractDiagram *diag = m_chart->coordinatePlane()->diagram(); DataValueAttributes attr = diag->dataValueAttributes(); if ( ui->scopeOneBarRB->isChecked() ) { attr = diag->dataValueAttributes( currentIndex() ); } else if ( ui->scopeDatasetRB->isChecked() ) { attr = diag->dataValueAttributes( ui->scopeDatasetSB->value() ); } attr.setShowRepetitiveDataLabels( true ); return attr; } void DataValueSettings::Private::setAttributes( const KChart::DataValueAttributes& da ) { Q_ASSERT( m_chart ); Q_ASSERT( m_chart->coordinatePlane() ); AbstractDiagram *diag = m_chart->coordinatePlane()->diagram(); if ( ui->scopeOneBarRB->isChecked() ) diag->setDataValueAttributes( currentIndex(), da ); else if ( ui->scopeDatasetRB->isChecked() ) diag->setDataValueAttributes( ui->scopeDatasetSB->value(), da ); else diag->setDataValueAttributes( da ); } // just a convenience method: // In the combo box we have the text "( Default Value )" instead of "Unknown Position" // because by setting a position to unknown we get KD Chart to use the // diagram-specific default positions. const char* DataValueSettings::Private::positionToScreenName( const Position& pos ) const { static const char* defaultPositionName = "( Default Value )"; if ( pos.isUnknown() ) return defaultPositionName; return pos.name(); } const Qt::Alignment DataValueSettings::Private::alignmentFromScreeName( const QString& name ) const { if ( name == "Center" ) return Qt::AlignCenter; if ( name == "BottomLeft" ) return Qt::AlignLeft | Qt::AlignBottom; if ( name == "Bottom" ) return Qt::AlignHCenter | Qt::AlignBottom; if ( name == "BottomRight" ) return Qt::AlignRight | Qt::AlignBottom; if ( name == "Right" ) return Qt::AlignRight | Qt::AlignVCenter; if ( name == "TopRight" ) return Qt::AlignRight | Qt::AlignTop; if ( name == "Top" ) return Qt::AlignHCenter | Qt::AlignTop; if ( name == "TopLeft" ) return Qt::AlignLeft | Qt::AlignTop; if ( name == "Left" ) return Qt::AlignLeft | Qt::AlignVCenter; return Qt::AlignCenter; } const QString DataValueSettings::Private::alignmentToScreenName( const Qt::Alignment& align ) const { if ( align == Qt::AlignCenter ) return "Center"; if ( align == (Qt::AlignLeft | Qt::AlignBottom) ) return "BottomLeft"; if ( align == (Qt::AlignHCenter | Qt::AlignBottom) ) return "Bottom"; if ( align == (Qt::AlignRight | Qt::AlignBottom) ) return "BottomRight"; if ( align == (Qt::AlignRight | Qt::AlignVCenter) ) return "Right"; if ( align == (Qt::AlignRight | Qt::AlignTop) ) return "TopRight"; if ( align == (Qt::AlignHCenter | Qt::AlignTop) ) return "Top"; if ( align == (Qt::AlignLeft | Qt::AlignTop) ) return "TopLeft"; if ( align == (Qt::AlignLeft | Qt::AlignVCenter) ) return "Left"; return "Center"; } void DataValueSettings::Private::populateWidgets() { const DataValueAttributes da( attributes() ); const TextAttributes ta( da.textAttributes() ); const RelativePosition posPos( da.positivePosition() ); const RelativePosition negPos( da.negativePosition() ); ui->paintValuesCB->setChecked( da.isVisible() && ta.isVisible() ); ui->fontCombo->setCurrentFont( ta.font() ); ui->relativeSizeSB->setValue( static_cast(ta.fontSize().value()) ); ui->minimumSizeSB->setValue( static_cast(ta.minimalFontSize().value()) ); ui->rotationSB->setValue( static_cast(ta.rotation()) ); ui->posPosCombo->setCurrentIndex( ui->posPosCombo->findText( positionToScreenName( posPos.referencePosition() ) ) ); ui->posAlignCombo->setCurrentIndex( ui->posAlignCombo->findText( alignmentToScreenName( posPos.alignment() ) ) ); ui->posPadHoriSB->setValue( static_cast(posPos.horizontalPadding().value()) ); ui->posPadVertSB->setValue( static_cast(posPos.verticalPadding().value()) ); ui->negPosCombo->setCurrentIndex( ui->negPosCombo->findText( positionToScreenName( negPos.referencePosition() ) ) ); ui->negAlignCombo->setCurrentIndex( ui->negAlignCombo->findText( alignmentToScreenName( negPos.alignment() ) ) ); ui->negPadHoriSB->setValue( static_cast(negPos.horizontalPadding().value()) ); ui->negPadVertSB->setValue( static_cast(negPos.verticalPadding().value()) ); ui->labelLE->setText( da.dataLabel() ); ui->prefixLE->setText( da.prefix() ); ui->suffixLE->setText( da.suffix() ); } #include "datavaluesettings.moc" diff --git a/examples/demo/diagramsettings.cpp b/examples/demo/diagramsettings.cpp index 720fd57..03a7ff3 100644 --- a/examples/demo/diagramsettings.cpp +++ b/examples/demo/diagramsettings.cpp @@ -1,257 +1,257 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "diagramsettings.h" #include "ui_diagramsettings.h" #include "gradientdialog.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace KChart; class DiagramSettings::Private : public QObject { Q_OBJECT public: Private( Chart *chart = 0, DiagramSettings *q = 0 ); ~Private(); void init(); void setThreeDData( const AbstractThreeDAttributes &attr ); Chart *m_chart; Ui::DiagramSettings *ui; DiagramSettings *qq; public Q_SLOTS: void changeThreeD(); void changeBackgroundColor(); void changeBackgroundVisibility(); void changeAutoGradient(); }; DiagramSettings::Private::Private( Chart *chart, DiagramSettings *q ) : QObject( q ) , m_chart( chart ) , ui(new Ui::DiagramSettings) , qq( q ) { } DiagramSettings::Private::~Private() { } void DiagramSettings::Private::changeAutoGradient() { changeThreeD(); } void DiagramSettings::Private::changeThreeD() { if ( m_chart && m_chart->coordinatePlane() && m_chart->coordinatePlane()->diagram() ) { BarDiagram *bars = qobject_cast< BarDiagram* >( m_chart->coordinatePlane()->diagram() ); LineDiagram *lines = qobject_cast< LineDiagram* >( m_chart->coordinatePlane()->diagram() ); PieDiagram *pie = qobject_cast< PieDiagram* >( m_chart->coordinatePlane()->diagram() ); if ( bars ) { ThreeDBarAttributes td( bars->threeDBarAttributes() ); td.setEnabled( ui->threeDSelector->checkState() == Qt::Checked ); td.setUseShadowColors( true ); td.setDepth( ui->barHeightInput->value() ); td.setThreeDBrushEnabled( ui->autoGradient->checkState() == Qt::Checked ); bars->setThreeDBarAttributes( td ); m_chart->update(); } else if ( lines ) { ThreeDLineAttributes td( lines->threeDLineAttributes() ); td.setEnabled( ui->threeDSelector->checkState() == Qt::Checked ); td.setDepth( ui->barHeightInput->value() ); td.setThreeDBrushEnabled( ui->autoGradient->checkState() == Qt::Checked ); lines->setThreeDLineAttributes( td ); m_chart->update(); } else if ( pie ) { ThreeDPieAttributes td( pie->threeDPieAttributes() ); td.setEnabled( ui->threeDSelector->checkState() == Qt::Checked ); td.setDepth( ui->barHeightInput->value() ); td.setThreeDBrushEnabled( ui->autoGradient->checkState() == Qt::Checked ); pie->setThreeDPieAttributes( td ); m_chart->update(); } } } void DiagramSettings::Private::changeBackgroundColor() { if ( m_chart && m_chart->coordinatePlane() && m_chart->coordinatePlane()->diagram() ) { BackgroundAttributes bat = m_chart->coordinatePlane()->backgroundAttributes(); bat.setVisible( true ); ui->visibleBtn->setChecked( true ); if ( ui->color->isChecked() ) { QBrush setBrush = bat.brush(); const QColor color = QColorDialog::getColor( setBrush.color(), qq, tr( "Choose new color" ) ); if ( !color.isValid() ) return; bat.setBrush( color ); QPalette palette = ui->diagramBackground->palette(); palette.setBrush( QPalette::Button, color ); ui->diagramBackground->setPalette( palette ); } else if ( ui->textureBtn->isChecked() ) { //QBrush setBrush = m_chart->coordinatePlane()->diagram()->brush( index ); QImage texture; const QString filename = QFileDialog::getOpenFileName( qq, tr( "Choose Texture" ), QString(), tr( "Images (*.png *.xpm *.jpg)" ) ); if ( filename.isEmpty() ) return; texture = QImage( filename ); bat.setBrush( texture ); QPalette palette = ui->diagramBackground->palette(); palette.setBrush( QPalette::Button, QBrush( texture ) ); ui->diagramBackground->setPalette( palette ); } else { QBrush setBrush = bat.brush(); QGradient grad; QLinearGradient lGrad; lGrad.setColorAt( 0, Qt::black ); lGrad.setColorAt( 1, setBrush.color() ); grad = lGrad; if ( setBrush.gradient() ) grad = *setBrush.gradient(); const QGradient &color = GradientDialog::getGradient( grad, qq, tr( "Choose new color" ) ); bat.setBrush( color ); QPalette palette = ui->diagramBackground->palette(); palette.setBrush( QPalette::Button, QBrush( color ) ); ui->diagramBackground->setPalette( palette ); } bat.setVisible( true ); m_chart->coordinatePlane()->setBackgroundAttributes( bat ); qq->update(); } } void DiagramSettings::Private::changeBackgroundVisibility() { if ( m_chart && m_chart->coordinatePlane() && m_chart->coordinatePlane()->diagram() ) { BackgroundAttributes bat = m_chart->coordinatePlane()->backgroundAttributes(); bat.setVisible( ui->visibleBtn->checkState() == Qt::Checked ); m_chart->coordinatePlane()->setBackgroundAttributes( bat ); m_chart->update(); } } void DiagramSettings::Private::init() { ui->setupUi( qq ); #ifdef Q_OS_LINUX ui->diagramBackground->setStyle( QStyleFactory::create( QStringLiteral( "cleanlooks" ) ) ); #endif - connect( ui->threeDSelector, SIGNAL( toggled( bool ) ), this, SLOT( changeThreeD() ) ); - connect( ui->diagramBackground, SIGNAL( clicked() ), this, SLOT( changeBackgroundColor() ) ); - connect( ui->visibleBtn, SIGNAL( toggled( bool ) ), this, SLOT( changeBackgroundVisibility() ) ); - connect( ui->barHeightInput, SIGNAL( valueChanged( int ) ), this, SLOT( changeThreeD() ) ); - connect( ui->autoGradient, SIGNAL( toggled( bool ) ), this, SLOT( changeAutoGradient() ) ); + connect( ui->threeDSelector, SIGNAL(toggled(bool)), this, SLOT(changeThreeD()) ); + connect( ui->diagramBackground, SIGNAL(clicked()), this, SLOT(changeBackgroundColor()) ); + connect( ui->visibleBtn, SIGNAL(toggled(bool)), this, SLOT(changeBackgroundVisibility()) ); + connect( ui->barHeightInput, SIGNAL(valueChanged(int)), this, SLOT(changeThreeD()) ); + connect( ui->autoGradient, SIGNAL(toggled(bool)), this, SLOT(changeAutoGradient()) ); qq->refreshSettings(); } void DiagramSettings::Private::setThreeDData( const AbstractThreeDAttributes &attr ) { ui->threeDSelector->setCheckState( attr.isEnabled() ? Qt::Checked : Qt::Unchecked ); ui->autoGradient->setCheckState( attr.isThreeDBrushEnabled() ? Qt::Checked : Qt::Unchecked ); ui->barHeightInput->setValue( attr.depth() ); } DiagramSettings::DiagramSettings( Chart *chart, QWidget *parent ) : QWidget( parent ) , d( new Private( chart, this ) ) { d->init(); } DiagramSettings::~DiagramSettings() { delete d; } void DiagramSettings::refreshSettings() { if ( d->m_chart && d->m_chart->coordinatePlane() && d->m_chart->coordinatePlane()->diagram() ) { BarDiagram *bars = qobject_cast< BarDiagram* >( d->m_chart->coordinatePlane()->diagram() ); LineDiagram *lines = qobject_cast< LineDiagram* >( d->m_chart->coordinatePlane()->diagram() ); PieDiagram *pie = qobject_cast< PieDiagram* >( d->m_chart->coordinatePlane()->diagram() ); if ( bars ) { const AbstractThreeDAttributes &td = bars->threeDBarAttributes(); d->setThreeDData( td ); } else if ( lines ) { const AbstractThreeDAttributes &td = lines->threeDLineAttributes(); d->setThreeDData( td ); } else if ( pie ) { const AbstractThreeDAttributes &td = pie->threeDPieAttributes(); d->setThreeDData( td ); } BackgroundAttributes bat = d->m_chart->coordinatePlane()->backgroundAttributes(); QBrush setBrush = bat.brush(); QPalette palette = d->ui->diagramBackground->palette(); if ( bat.isVisible() ) palette.setBrush( QPalette::Button, setBrush ); else palette.setBrush( QPalette::Button, this->palette().brush( QPalette::Button ) ); d->ui->diagramBackground->setPalette( palette ); } } #include "diagramsettings.moc" diff --git a/examples/demo/diagramtypedialog.cpp b/examples/demo/diagramtypedialog.cpp index 4f69bff..fd1450f 100644 --- a/examples/demo/diagramtypedialog.cpp +++ b/examples/demo/diagramtypedialog.cpp @@ -1,197 +1,197 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "diagramtypedialog.h" #include "ui_diagramtypedialog.h" #include #include #include #include #include #include #include #include #include using namespace KChart; class DiagramTypeDialog::Private : public QObject { Q_OBJECT public: Private( KChart::Chart *chart, DiagramTypeDialog * q ); void init(); void createPlanes(); int lastIndex; Chart *m_chart; DiagramTypeDialog::DiagramType type; DiagramTypeDialog::Subtype subType; QHash< DiagramTypeDialog::DiagramType, QAbstractItemModel* > m_defaultModels; QHash< DiagramTypeDialog::DiagramType, AbstractCoordinatePlane* > m_planes; QHash< DiagramTypeDialog::DiagramType, DiagramTypeDialog::Subtype > m_typemap; DiagramTypeDialog *qq; Ui::DiagramTypeDialog ui; public Q_SLOTS: void typeChanged( int index ); void subtypeChanged( int index ); }; DiagramTypeDialog::Private::Private( KChart::Chart *chart, DiagramTypeDialog * q ) : lastIndex( 0 ) , m_chart( chart ) , type( DiagramTypeDialog::Bar ) , subType( DiagramTypeDialog::Normal ) , qq( q ) { } void DiagramTypeDialog::Private::init() { ui.setupUi( qq ); ui.typeSelector->addItem( QIcon(), tr( "BarDiagram" ), DiagramTypeDialog::Bar ); ui.typeSelector->addItem( QIcon(), tr( "Lying BarDiagram" ), DiagramTypeDialog::LyingBar ); ui.typeSelector->addItem( QIcon(), tr( "LineDiagram" ), DiagramTypeDialog::Line ); ui.typeSelector->addItem( QIcon(), tr( "Plotter" ), DiagramTypeDialog::Plotter ); ui.typeSelector->addItem( QIcon(), tr( "PieDiagram" ), DiagramTypeDialog::Pie ); ui.subtypeSelector->addItem( QIcon(), tr( "Normal" ), DiagramTypeDialog::Normal ); ui.subtypeSelector->addItem( QIcon(), tr( "Stacked" ), DiagramTypeDialog::Stacked ); ui.subtypeSelector->addItem( QIcon(), tr( "Percent" ), DiagramTypeDialog::Percent ); createPlanes(); m_typemap[ DiagramTypeDialog::Bar ] = DiagramTypeDialog::Normal; m_typemap[ DiagramTypeDialog::LyingBar ] = DiagramTypeDialog::Normal; m_typemap[ DiagramTypeDialog::Line ] = DiagramTypeDialog::Normal; m_typemap[ DiagramTypeDialog::Plotter ] = DiagramTypeDialog::Normal; - connect( ui.typeSelector, SIGNAL( currentIndexChanged( int ) ), this, SLOT( typeChanged( int ) ) ); - connect( ui.subtypeSelector, SIGNAL( currentIndexChanged( int ) ), this, SLOT( subtypeChanged( int ) ) ); + connect( ui.typeSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(typeChanged(int)) ); + connect( ui.subtypeSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(subtypeChanged(int)) ); } void DiagramTypeDialog::Private::createPlanes() { m_planes[ DiagramTypeDialog::Bar ] = m_chart->coordinatePlane(); m_planes[ DiagramTypeDialog::LyingBar ] = m_chart->coordinatePlane(); CartesianCoordinatePlane *linePlane = new CartesianCoordinatePlane; linePlane->addDiagram( new LineDiagram ); m_planes[ DiagramTypeDialog::Line ] = linePlane; CartesianCoordinatePlane *plotterPlane = new CartesianCoordinatePlane; plotterPlane->addDiagram( new KChart::Plotter ); m_planes[ DiagramTypeDialog::Plotter ] = plotterPlane; PolarCoordinatePlane *piePlane = new PolarCoordinatePlane; piePlane->addDiagram( new PieDiagram ); m_planes[ DiagramTypeDialog::Pie ] = piePlane; } void DiagramTypeDialog::Private::typeChanged( int index ) { if ( index == lastIndex ) return; const DiagramTypeDialog::DiagramType type = static_cast< DiagramTypeDialog::DiagramType >( ui.typeSelector->itemData( index ).toInt() ); const DiagramTypeDialog::DiagramType lastType = static_cast< DiagramTypeDialog::DiagramType >( ui.typeSelector->itemData( lastIndex ).toInt() ); if ( m_planes.contains( type ) ) { ui.subtypeSelector->setEnabled( true ); if ( type == DiagramTypeDialog::LyingBar ) { BarDiagram * diag = qobject_cast< BarDiagram* >( m_planes[ type ]->diagram() ); diag->setOrientation( Qt::Horizontal ); } else if ( type == DiagramTypeDialog::Bar ) { BarDiagram * diag = qobject_cast< BarDiagram* >( m_planes[ type ]->diagram() ); diag->setOrientation( Qt::Vertical ); } else if ( type == DiagramTypeDialog::Pie ) ui.subtypeSelector->setEnabled( false ); this->type = type; ui.subtypeSelector->setCurrentIndex( m_typemap[ type ] ); m_chart->takeCoordinatePlane( m_planes[ lastType ] ); m_chart->addCoordinatePlane( m_planes[ type ] ); lastIndex = index; Q_EMIT qq->diagramTypeChanged( type, subType ); } else { ui.typeSelector->setCurrentIndex( lastIndex ); } } void DiagramTypeDialog::Private::subtypeChanged( int index ) { const DiagramTypeDialog::DiagramType type = static_cast< DiagramTypeDialog::DiagramType >( ui.typeSelector->itemData( ui.typeSelector->currentIndex() ).toInt() ); switch ( type ) { case( DiagramTypeDialog::Bar ): case( DiagramTypeDialog::LyingBar ): { BarDiagram *bar = qobject_cast< BarDiagram* >( m_planes[ type ]->diagram() ); Q_ASSERT( bar ); bar->setType( static_cast< BarDiagram::BarType >( index ) ); m_typemap[ type ] = static_cast< DiagramTypeDialog::Subtype >( index ); break; } case( DiagramTypeDialog::Line ): { LineDiagram *line = qobject_cast< LineDiagram* >( m_planes[ type ]->diagram() ); Q_ASSERT( line ); line->setType( static_cast< LineDiagram::LineType >( index ) ); m_typemap[ type ] = static_cast< DiagramTypeDialog::Subtype >( index ); break; break; } case( DiagramTypeDialog::Pie ): break; default: Q_ASSERT( false ); } } DiagramTypeDialog::DiagramTypeDialog( KChart::Chart *chart, QWidget *parent ) : QDialog( parent ) , d( new Private( chart, this ) ) { d->init(); } DiagramTypeDialog::~DiagramTypeDialog() { delete d; } void DiagramTypeDialog::setDefaultModels( QHash< DiagramType, QAbstractItemModel* > models ) { d->m_defaultModels = models; for ( QHash< DiagramType, AbstractCoordinatePlane* >::iterator it = d->m_planes.begin(); it != d->m_planes.end(); ++it ) { AbstractDiagram * diagram = it.value()->diagram(); diagram->setModel( d->m_defaultModels[ it.key() ] ); } } #include "diagramtypedialog.moc" diff --git a/examples/demo/diagramtypedialog.h b/examples/demo/diagramtypedialog.h index 5202e2a..9a1a936 100644 --- a/examples/demo/diagramtypedialog.h +++ b/examples/demo/diagramtypedialog.h @@ -1,51 +1,51 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef DIAGRAMTYPEDIALOG_H #define DIAGRAMTYPEDIALOG_H #include -#include +#include class QAbstractItemModel; namespace KChart { class Chart; } class DiagramTypeDialog : public QDialog { Q_OBJECT public: enum DiagramType{ Bar, LyingBar, Line, Plotter, Pie }; enum Subtype{ Normal, Percent, Stacked }; explicit DiagramTypeDialog( KChart::Chart *chart, QWidget *parent = 0 ); ~DiagramTypeDialog(); void setDefaultModels( QHash< DiagramType, QAbstractItemModel* > models ); Q_SIGNALS: void diagramTypeChanged( DiagramType, Subtype ); private: class Private; Private *d; }; #endif // DIAGRAMTYPEDIALOG_H diff --git a/examples/demo/gradientdialog.cpp b/examples/demo/gradientdialog.cpp index c45d64c..82c2f61 100644 --- a/examples/demo/gradientdialog.cpp +++ b/examples/demo/gradientdialog.cpp @@ -1,277 +1,277 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "gradientdialog.h" #include "ui_gradientdialog.h" #include "colorslider.h" #include #include #include #include class GradientDialog::Private : public QObject { Q_OBJECT public: Private( GradientDialog *qq ); ~Private(); void init(); ColorSlider *redSlider; ColorSlider *greenSlider; ColorSlider *blueSlider; QVector< QGradientStop > m_gradient; int m_currStopNr; Ui::GradientDialog *ui; GradientDialog *q; public Q_SLOTS: void changedIndex( int index ); void changeStopPosition( double value ); void insertItem(); void deleteItem(); void resetColors(); void updateGradientDisplay(); }; GradientDialog::Private::Private( GradientDialog *qq ) : m_currStopNr( 2 ) , ui( new Ui::GradientDialog ) , q( qq ) { m_gradient << qMakePair( 0.0, QColor( Qt::red ) ); m_gradient << qMakePair( 1.0, QColor( Qt::blue ) ); } GradientDialog::Private::~Private() { delete ui; } void GradientDialog::Private::changeStopPosition( double value ) { m_gradient[ ui->stopSelector->currentIndex() ].first = value; } void GradientDialog::Private::changedIndex( int index ) { if ( index >= 0 ) { ui->stopPosition->setValue( m_gradient[ index ].first ); QColor stopColor = m_gradient[ index ].second; redSlider->setValue( stopColor.red() ); greenSlider->setValue( stopColor.green() ); blueSlider->setValue( stopColor.blue() ); } } void GradientDialog::Private::insertItem() { QGradientStop newStop; newStop.first = 0.5; newStop.second = Qt::white; const int index = ui->stopSelector->currentIndex() + 1; ui->stopSelector->setCurrentIndex( - 1 ); m_gradient.insert( index, newStop ); QStringList newItems; newItems << tr( "stop %1" ).arg( m_currStopNr ); ui->stopSelector->insertItems( index, newItems ); ui->stopSelector->setCurrentIndex( index ); updateGradientDisplay(); } void GradientDialog::Private::deleteItem() { } void GradientDialog::Private::updateGradientDisplay() { QLinearGradient gradient; gradient.setStart( 0, 0 ); gradient.setStart( 1, 0 ); gradient.setCoordinateMode( QGradient::ObjectBoundingMode ); Q_FOREACH( const QGradientStop &stop, m_gradient ) gradient.setColorAt( stop.first, stop.second ); QPalette palette = ui->gradientDisplay->palette(); palette.setBrush( QPalette::Background, gradient ); ui->gradientDisplay->setPalette( palette ); } void GradientDialog::Private::init() { ui->setupUi( q ); QStringList list; list << tr( "stop1" ) << tr( "stop2" ); ui->stopSelector->addItems( list ); QHBoxLayout *redLayout = new QHBoxLayout; dynamic_cast< QVBoxLayout* >( ui->gradientStopBox->layout() )->addLayout( redLayout ); QLabel *redLabel = new QLabel( "R" ); QFont redFont( redLabel->font() ); redFont.setUnderline( true ); redLabel->setFont( redFont ); redLayout->addWidget( redLabel ); redSlider = new ColorSlider( q ); redSlider->setStartColor( Qt::black ); redSlider->setEndColor( Qt::red ); QSpinBox *redSpin = new QSpinBox( q ); redSpin->setMinimum( 0 ); redSpin->setMaximum( 255 ); redSpin->setAccelerated( true ); redSpin->setValue( redSlider->value() ); - connect( redSpin, SIGNAL( valueChanged( int ) ), redSlider, SLOT( setValue( int ) ) ); - connect( redSlider, SIGNAL( valueChanged( int ) ), redSpin, SLOT( setValue( int ) ) ); + connect( redSpin, SIGNAL(valueChanged(int)), redSlider, SLOT(setValue(int)) ); + connect( redSlider, SIGNAL(valueChanged(int)), redSpin, SLOT(setValue(int)) ); redLayout->addWidget( redSlider ); redLayout->addWidget( redSpin ); QHBoxLayout *greenLayout = new QHBoxLayout; dynamic_cast< QVBoxLayout* >( ui->gradientStopBox->layout() )->addLayout( greenLayout ); QLabel *greenLabel = new QLabel( "G" ); QFont greenFont( greenLabel->font() ); greenFont.setUnderline( true ); greenLabel->setFont( greenFont ); greenLayout->addWidget( greenLabel ); greenSlider = new ColorSlider( q ); greenSlider->setStartColor( Qt::black ); greenSlider->setEndColor( Qt::green ); QSpinBox *greenSpin = new QSpinBox( q ); greenSpin->setMinimum( 0 ); greenSpin->setMaximum( 255 ); greenSpin->setAccelerated( true ); greenSpin->setValue( greenSlider->value() ); - connect( greenSpin, SIGNAL( valueChanged( int ) ), greenSlider, SLOT( setValue( int ) ) ); - connect( greenSlider, SIGNAL( valueChanged( int ) ), greenSpin, SLOT( setValue( int ) ) ); + connect( greenSpin, SIGNAL(valueChanged(int)), greenSlider, SLOT(setValue(int)) ); + connect( greenSlider, SIGNAL(valueChanged(int)), greenSpin, SLOT(setValue(int)) ); greenLayout->addWidget( greenSlider ); greenLayout->addWidget( greenSpin ); QHBoxLayout *blueLayout = new QHBoxLayout; dynamic_cast< QVBoxLayout* >( ui->gradientStopBox->layout() )->addLayout( blueLayout ); QLabel *blueLabel = new QLabel( "B" ); QFont blueFont( blueLabel->font() ); blueFont.setUnderline( true ); blueLabel->setFont( blueFont ); blueLayout->addWidget( blueLabel ); blueSlider = new ColorSlider( q ); blueSlider->setStartColor( Qt::black ); blueSlider->setEndColor( Qt::blue ); QSpinBox *blueSpin = new QSpinBox( q ); blueSpin->setMinimum( 0 ); blueSpin->setMaximum( 255 ); blueSpin->setAccelerated( true ); blueSpin->setValue( blueSlider->value() ); - connect( blueSpin, SIGNAL( valueChanged( int ) ), blueSlider, SLOT( setValue( int ) ) ); - connect( blueSlider, SIGNAL( valueChanged( int ) ), blueSpin, SLOT( setValue( int ) ) ); + connect( blueSpin, SIGNAL(valueChanged(int)), blueSlider, SLOT(setValue(int)) ); + connect( blueSlider, SIGNAL(valueChanged(int)), blueSpin, SLOT(setValue(int)) ); blueLayout->addWidget( blueSlider ); blueLayout->addWidget( blueSpin ); updateGradientDisplay(); - connect( redSlider, SIGNAL( valueChanged( int ) ), this, SLOT( resetColors() ) ); - connect( greenSlider, SIGNAL( valueChanged( int ) ), this, SLOT( resetColors() ) ); - connect( blueSlider, SIGNAL( valueChanged( int ) ), this, SLOT( resetColors() ) ); + connect( redSlider, SIGNAL(valueChanged(int)), this, SLOT(resetColors()) ); + connect( greenSlider, SIGNAL(valueChanged(int)), this, SLOT(resetColors()) ); + connect( blueSlider, SIGNAL(valueChanged(int)), this, SLOT(resetColors()) ); - connect( ui->newStop, SIGNAL( clicked() ), this, SLOT( insertItem() ) ); - connect( ui->deleteStop, SIGNAL( clicked() ), this, SLOT( deleteItem() ) ); - connect( ui->stopSelector, SIGNAL( currentIndexChanged( int ) ), this, SLOT( changedIndex( int ) ) ); + connect( ui->newStop, SIGNAL(clicked()), this, SLOT(insertItem()) ); + connect( ui->deleteStop, SIGNAL(clicked()), this, SLOT(deleteItem()) ); + connect( ui->stopSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(changedIndex(int)) ); - connect( ui->stopPosition, SIGNAL( valueChanged( double ) ), this, SLOT( changeStopPosition( double ) ) ); + connect( ui->stopPosition, SIGNAL(valueChanged(double)), this, SLOT(changeStopPosition(double)) ); } GradientDialog::GradientDialog( QWidget *parent ) : QDialog( parent ) , d( new Private( this ) ) { d->init(); } GradientDialog::~GradientDialog() { delete d; } void GradientDialog::Private::resetColors() { const int red = redSlider->value(); const int green = greenSlider->value(); const int blue = blueSlider->value(); QColor redSliderStartColor( 0, green, blue ); QColor redSliderStopColor( 255, green, blue ); redSlider->setStartColor( redSliderStartColor ); redSlider->setEndColor( redSliderStopColor ); m_gradient[ ui->stopSelector->currentIndex() ].second.setRed( red ); QColor greenSliderStartColor( red, 0, blue ); QColor greenSliderStopColor( red, 255, blue ); greenSlider->setStartColor( greenSliderStartColor ); greenSlider->setEndColor( greenSliderStopColor ); m_gradient[ ui->stopSelector->currentIndex() ].second.setGreen( green ); QColor blueSliderStartColor( red, green, 0 ); QColor blueSliderStopColor( red, green, 255 ); blueSlider->setStartColor( blueSliderStartColor ); blueSlider->setEndColor( blueSliderStopColor ); m_gradient[ ui->stopSelector->currentIndex() ].second.setBlue( blue ); updateGradientDisplay(); } QGradient GradientDialog::gradient() const { QLinearGradient gradient; gradient.setStart( 0, 0 ); gradient.setStart( 1, 0 ); gradient.setCoordinateMode( QGradient::ObjectBoundingMode ); Q_FOREACH( const QGradientStop &stop, d->m_gradient ) gradient.setColorAt( stop.first, stop.second ); return gradient; } void GradientDialog::setGradient( const QGradient &gradient ) { d->m_gradient.clear(); d->ui->stopSelector->clear(); const QGradientStops stops = gradient.stops(); Q_FOREACH( const QGradientStop &stop, stops ) { d->m_gradient.append( stop ); } QStringList newEntries; for ( int i = 0; i < stops.count(); ++i ) newEntries << tr( "stop %1" ).arg( i ); d->ui->stopSelector->addItems( newEntries ); d->m_currStopNr = newEntries.count(); d->updateGradientDisplay(); } QGradient GradientDialog::getGradient( const QGradient &gradient, QWidget *parent, const QString &title ) { GradientDialog dialog( parent ); dialog.setGradient( gradient ); dialog.setWindowTitle( title ); dialog.exec(); return dialog.gradient(); } #include "gradientdialog.moc" diff --git a/examples/demo/main.cpp b/examples/demo/main.cpp index 78db7c0..242fe5c 100644 --- a/examples/demo/main.cpp +++ b/examples/demo/main.cpp @@ -1,80 +1,80 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include //class ChartWidget : public QWidget { // Q_OBJECT //public: // explicit ChartWidget(QWidget* parent=0) // : QWidget(parent) // { // m_model.insertRows( 0, 2, QModelIndex() ); // m_model.insertColumns( 0, 3, QModelIndex() ); // for (int row = 0; row < 3; ++row) { // for (int column = 0; column < 3; ++column) { // QModelIndex index = m_model.index(row, column, QModelIndex()); // m_model.setData(index, QVariant(row+1 * column) ); // } // } // KChart::BarDiagram* diagram = new KChart::BarDiagram; // diagram->setModel(&m_model); // m_chart.coordinatePlane()->replaceDiagram(diagram); // QVBoxLayout* l = new QVBoxLayout(this); // l->addWidget(&m_chart); // setLayout(l); // m_timer = new QTimer(this); -// connect( m_timer, SIGNAL( timeout() ), -// this, SLOT( slotTimeout() ) ); +// connect( m_timer, SIGNAL(timeout()), +// this, SLOT(slotTimeout()) ); // m_timer->start( 200 ); // } //private slots: // void slotTimeout() { // QModelIndex index = m_model.index( 0, 1, QModelIndex()); // qreal value = ( m_model.data( index ).toInt() % 24 ) +1; // m_model.setData( index, value ); // } //private: // KChart::Chart m_chart; // QStandardItemModel m_model; // QTimer *m_timer; //}; #include "mainwindow.h" int main( int argc, char** argv ) { QApplication app( argc, argv ); //ChartWidget w; MainWindow w; w.show(); return app.exec(); } //#include "main.moc" diff --git a/examples/demo/mainwindow.cpp b/examples/demo/mainwindow.cpp index a7fa379..bee7c8a 100644 --- a/examples/demo/mainwindow.cpp +++ b/examples/demo/mainwindow.cpp @@ -1,165 +1,165 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "mainwindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "datasetsettings.h" #include "diagramsettings.h" #include "datavaluesettings.h" #include "diagramtypedialog.h" #include "axissettings.h" using namespace KChart; class MainWindow::Private : public QObject { Q_OBJECT Q_PROPERTY( int datasetCount READ datasetCount NOTIFY datasetCountChanged ) public: int datasetCount() const; enum ChartType{ Bar, Line, Plotter, Polar, Pie }; Private( MainWindow *q = 0 ); Chart *m_chartWin; CartesianCoordinatePlane *m_cartPlane; TableModel *m_model; AbstractDiagram *m_curDiagram; ChartType m_type; QHash< DiagramTypeDialog::DiagramType, QAbstractItemModel* > m_modelTable; MainWindow *qq; Q_SIGNALS: void datasetCountChanged( int ); public Q_SLOTS: void changedChartType(); }; MainWindow::Private::Private( MainWindow *q ) : m_chartWin( new Chart( q ) ) , m_cartPlane( new CartesianCoordinatePlane ) , m_model( new TableModel( q ) ) , m_curDiagram( new BarDiagram( q, m_cartPlane ) ) , m_type( Bar ) , qq( q ) { m_chartWin->replaceCoordinatePlane( m_cartPlane ); m_model->loadFromCSV( ":/data/barSimple.csv" ); m_cartPlane->replaceDiagram( m_curDiagram ); m_curDiagram->setModel( m_model ); m_modelTable[ DiagramTypeDialog::Bar ] = m_model; m_modelTable[ DiagramTypeDialog::LyingBar ] = m_model; TableModel *lineModel = new TableModel( q ); lineModel->loadFromCSV( ":/data/lineSimple.csv" ); m_modelTable[ DiagramTypeDialog::Line ] = lineModel; m_modelTable[ DiagramTypeDialog::Plotter ] = lineModel; TableModel *pieModel = new TableModel( q ); pieModel->loadFromCSV( ":/data/pieSimple.csv" ); m_modelTable[ DiagramTypeDialog::Pie ] = pieModel; DiagramTypeDialog *diagramTypeSettings = new DiagramTypeDialog( m_chartWin, qq ); diagramTypeSettings->setDefaultModels( m_modelTable ); QDockWidget *diagramTypeSettingsDock = new QDockWidget( tr( "DiagramType" ), qq ); diagramTypeSettingsDock->setWidget( diagramTypeSettings ); diagramTypeSettingsDock->setFloating( false ); - connect( diagramTypeSettings, SIGNAL( diagramTypeChanged( DiagramType, Subtype ) ), this, SLOT( changedChartType() ) ); + connect( diagramTypeSettings, SIGNAL(diagramTypeChanged(DiagramType,Subtype)), this, SLOT(changedChartType()) ); q->addDockWidget( Qt::LeftDockWidgetArea, diagramTypeSettingsDock ); AxisSettings *axisSettings = new AxisSettings( m_chartWin, qq ); QDockWidget *axisSettingsDock = new QDockWidget( tr( "AxisSettings" ), qq ); axisSettingsDock->setWidget( axisSettings ); axisSettingsDock->setFloating( false ); q->addDockWidget( Qt::LeftDockWidgetArea, axisSettingsDock ); - connect( diagramTypeSettings, SIGNAL( diagramTypeChanged( DiagramType, Subtype ) ), axisSettings, SLOT( diagramTypeChanged() ) ); + connect( diagramTypeSettings, SIGNAL(diagramTypeChanged(DiagramType,Subtype)), axisSettings, SLOT(diagramTypeChanged()) ); DatasetSettings *setSettings = new DatasetSettings( m_chartWin, qq ); QDockWidget *setSettingsDock = new QDockWidget( tr( "DatasetSettings" ), qq ); setSettingsDock->setWidget( setSettings ); setSettingsDock->setFloating( false ); q->addDockWidget( Qt::LeftDockWidgetArea, setSettingsDock ); - connect( this, SIGNAL( datasetCountChanged( int ) ), setSettings, SLOT( setDatasetCount( int ) ) ); - connect( diagramTypeSettings, SIGNAL( diagramTypeChanged( DiagramType, Subtype ) ), setSettings, SLOT( diagramTypeChanged() ) ); + connect( this, SIGNAL(datasetCountChanged(int)), setSettings, SLOT(setDatasetCount(int)) ); + connect( diagramTypeSettings, SIGNAL(diagramTypeChanged(DiagramType,Subtype)), setSettings, SLOT(diagramTypeChanged()) ); setSettings->setDatasetCount( m_model->columnCount() ); DiagramSettings *diagSettings = new DiagramSettings( m_chartWin, qq ); QDockWidget *diagSettingsDock = new QDockWidget( tr( "DiagramSettings" ), qq ); diagSettingsDock->setWidget( diagSettings ); diagSettingsDock->setFloating( false ); q->addDockWidget( Qt::LeftDockWidgetArea, diagSettingsDock ); - connect( diagramTypeSettings, SIGNAL( diagramTypeChanged( DiagramType, Subtype ) ), diagSettings, SLOT( refreshSettings() ) ); + connect( diagramTypeSettings, SIGNAL(diagramTypeChanged(DiagramType,Subtype)), diagSettings, SLOT(refreshSettings()) ); DataValueSettings *dataValueSettings = new DataValueSettings( m_chartWin, qq ); QDockWidget *dataValueSettingsDock = new QDockWidget( tr( "DataValueSettings" ), qq ); dataValueSettingsDock->setWidget( dataValueSettings ); dataValueSettingsDock->setFloating( false ); q->addDockWidget( Qt::RightDockWidgetArea, dataValueSettingsDock ); - connect( diagramTypeSettings, SIGNAL( diagramTypeChanged( DiagramType, Subtype ) ), dataValueSettings, SLOT( refresh() ) ); + connect( diagramTypeSettings, SIGNAL(diagramTypeChanged(DiagramType,Subtype)), dataValueSettings, SLOT(refresh()) ); } int MainWindow::Private::datasetCount() const { if ( m_chartWin->coordinatePlane() && m_chartWin->coordinatePlane()->diagram() ) { const AbstractDiagram *diag = m_chartWin->coordinatePlane()->diagram(); if ( !diag->model() ) return 0; return diag->model()->columnCount() / diag->datasetDimension(); } return 0; } void MainWindow::Private::changedChartType() { Q_EMIT datasetCountChanged( datasetCount() ); } MainWindow::MainWindow( QWidget *parent ) : QMainWindow( parent ) , d( new Private( this ) ) { setCentralWidget( d->m_chartWin ); } MainWindow::~MainWindow() { delete d; } #include "mainwindow.moc" diff --git a/qtests/DrawIntoPainter/framewidget.cpp b/qtests/DrawIntoPainter/framewidget.cpp index c473fa2..0c353d7 100644 --- a/qtests/DrawIntoPainter/framewidget.cpp +++ b/qtests/DrawIntoPainter/framewidget.cpp @@ -1,77 +1,77 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "framewidget.h" #include #include #include FrameWidget::FrameWidget( QWidget* parent, Qt::WindowFlags f ) : QWidget( parent, f ) , mChart( 0 ) { // this bloc left empty intentionally } void FrameWidget::setChart( KChart::Chart* chart ) { mChart = chart; // This is necessary because Chart can't automatically schedule somebody else (this object) to // call its custom paint method. - connect( mChart, SIGNAL( propertiesChanged() ), SLOT( update() ) ); + connect( mChart, SIGNAL(propertiesChanged()), SLOT(update()) ); } void FrameWidget::paintEvent( QPaintEvent* e ) { if ( !mChart ) { QWidget::paintEvent( e ); } else { QPainter painter( this ); const int wid = 64; const QRect r( rect() ); const QPen oldPen( painter.pen() ); const QBrush oldBrush( painter.brush() ); // paint below the chart painter.setPen( QPen(Qt::NoPen) ); painter.setBrush( QBrush(QColor(0xd0,0xd0,0xff)) ); painter.drawEllipse(r.left(), r.top(), wid,wid); painter.drawEllipse(r.left()+r.width()-wid-1, r.top(), wid,wid); painter.drawEllipse(r.left(), r.top()+r.height()-wid-1, wid,wid); painter.drawEllipse(r.left()+r.width()-wid-1, r.top()+r.height()-wid-1, wid,wid); painter.setBrush( oldBrush ); painter.setPen( oldPen ); // paint the chart mChart->paint( &painter, QRect( r.left()+wid/2, r.top()+wid/2, r.width()-wid, r.height()-wid ) ); // paint over the chart painter.setPen( QPen(Qt::NoPen) ); painter.setBrush( QBrush(QColor(0xd0,0xff,0xff)) ); const int wid2=40; const int gap=(wid-wid2)/2; painter.drawEllipse(r.left()+gap, r.top()+gap, wid2,wid2); painter.drawEllipse(r.left()+r.width()-wid+gap-1, r.top()+gap, wid2,wid2); painter.drawEllipse(r.left()+gap, r.top()+r.height()-wid+gap-1, wid2,wid2); painter.drawEllipse(r.left()+r.width()-wid+gap-1, r.top()+r.height()-wid+gap-1, wid2,wid2); painter.setBrush( oldBrush ); painter.setPen( oldPen ); } } diff --git a/qtests/DrawIntoPainter/mainwindow.cpp b/qtests/DrawIntoPainter/mainwindow.cpp index f5b6508..6ffd500 100644 --- a/qtests/DrawIntoPainter/mainwindow.cpp +++ b/qtests/DrawIntoPainter/mainwindow.cpp @@ -1,424 +1,424 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "mainwindow.h" #include "framewidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KChart; static QPixmap drawIntoPixmap( const QSize& size, KChart::Chart* chart ) { QPixmap pix( size ); pix.fill( Qt::white ); QPainter painter( &pix ); chart->paint( &painter, QRect( 0, 0, size.width(), size.height() ) ); return pix; } // When set, this example uses FrameWidget which uses Chart::paint to paint itself. // When not set, this example uses a Chart widget directly. #define USE_FRAME_WIDGET 1 MainWindow::MainWindow( QWidget* parent ) : QWidget( parent ) { setupUi( this ); QHBoxLayout* chartLayout = new QHBoxLayout( chartFrame ); #ifdef USE_FRAME_WIDGET FrameWidget* chartFrameWidget = new FrameWidget(); chartLayout->addWidget( chartFrameWidget ); #endif hSBar->setVisible( false ); vSBar->setVisible( false ); m_model.loadFromCSV( ":/empty" ); // Set up the diagram m_lines = new LineDiagram(); m_lines->setModel( &m_model ); CartesianAxis *xAxis = new CartesianAxis( m_lines ); CartesianAxis *yAxis = new CartesianAxis ( m_lines ); CartesianAxis *axisTop = new CartesianAxis ( m_lines ); CartesianAxis *axisRight = new CartesianAxis ( m_lines ); xAxis->setPosition ( KChart::CartesianAxis::Bottom ); yAxis->setPosition ( KChart::CartesianAxis::Left ); axisTop->setPosition( KChart::CartesianAxis::Top ); axisRight->setPosition( KChart::CartesianAxis::Right ); m_lines->addAxis( xAxis ); m_lines->addAxis( yAxis ); m_lines->addAxis( axisTop ); m_lines->addAxis( axisRight ); m_chart = new Chart(); //m_chart->setGlobalLeading(10,10,10,10); // by default there is no leading #ifdef USE_FRAME_WIDGET chartFrameWidget->setChart( m_chart ); // make sure, we re-draw after changing one of the chart's properties - connect( m_chart, SIGNAL( propertiesChanged() ), - chartFrameWidget, SLOT( update() ) ) ; + connect( m_chart, SIGNAL(propertiesChanged()), + chartFrameWidget, SLOT(update()) ) ; #else chartLayout->addWidget( m_chart ); #endif m_chart->coordinatePlane()->replaceDiagram( m_lines ); for ( int iColumn = 0; iColumnmodel()->columnCount(); ++iColumn ) { QPen pen(m_lines->pen( iColumn )); pen.setWidth(4); m_lines->setPen( iColumn, pen ); } FrameAttributes faChart( m_chart->frameAttributes() ); faChart.setVisible( true ); faChart.setPen( QPen(QColor(0x60,0x60,0xb0), 8) ); m_chart->setFrameAttributes( faChart ); BackgroundAttributes baChart( m_chart->backgroundAttributes() ); baChart.setVisible( true ); baChart.setBrush( QColor(0xd0,0xd0,0xff) ); m_chart->setBackgroundAttributes( baChart ); // Set up the legend m_legend = new Legend( m_lines, m_chart ); m_legend->setPosition( Position::South ); m_legend->setAlignment( Qt::AlignRight ); m_legend->setShowLines( false ); m_legend->setTitleText( tr( "Legend" ) ); m_legend->setOrientation( Qt::Horizontal ); // setting the legend frame and background to the same color: const QColor legendColor(0xff,0xe0,0x80); FrameAttributes faLegend( m_legend->frameAttributes() ); faLegend.setVisible( true ); faLegend.setPen( QPen(legendColor, 1) ); m_legend->setFrameAttributes( faLegend ); BackgroundAttributes baLegend( m_legend->backgroundAttributes() ); baLegend.setVisible( true ); baLegend.setBrush( legendColor ); m_legend->setBackgroundAttributes( baLegend ); m_chart->addLegend( m_legend ); // for illustration we paint the same chart at different sizes: QSize size1 = QSize( 200, 200 ); QSize size2 = QSize( 800, 800 ); m_pix1 = drawIntoPixmap( size1, m_chart ); m_pix2 = drawIntoPixmap( size2, m_chart ); m_pix2 = m_pix2.scaled( size1 ); m_smallChart1 = new QLabel( this ); m_smallChart1->setWindowTitle( "200x200" ); m_smallChart1->setPixmap( m_pix1 ); m_smallChart1->setFixedSize( m_pix1.size() ); m_smallChart1->move( width() - m_pix1.width()*2, height()/2 - m_pix1.height()-5 ); m_smallChart1->show(); m_smallChart2 = new QLabel( this ); m_smallChart2->setWindowTitle( "800x800 scaled down" ); m_smallChart2->setPixmap( m_pix2 ); m_smallChart2->setFixedSize( m_pix2.size() ); m_smallChart2->move( width() - m_pix2.width()*2, height()/2 + 5 ); m_smallChart2->show(); faChart.setPen( QPen(QColor(0xb0,0xb0,0xff), 8) ); m_chart->setFrameAttributes( faChart ); // initialize attributes; this is necessary because we need to enable data value attributes before // any of them (e.g. only markers) can be displayed. but if we enable data value attributs, a default // data value text is included, even if we only wanted to set markers. so we enable DVA and then // individually disable the parts we don't want. on_paintValuesCB_toggled( false ); on_paintMarkersCB_toggled( false ); } void MainWindow::updateData(QString data) { QTime t; t.start(); m_model.loadFromCSV( data ); qDebug("Time for loading data %s: %d ms", data.toLatin1().constData(), t.elapsed()); t.restart(); QSize size1 = QSize( 200, 200 ); QSize size2 = QSize( 800, 800 ); m_pix1 = drawIntoPixmap( size1, m_chart ); m_pix2 = drawIntoPixmap( size2, m_chart ); qDebug("Time for drawing pixmap %s: %d ms", data.toLatin1().constData(), t.elapsed()); t.restart(); m_lines->setModel( &m_model ); qDebug("Time for setting model %s: %d ms", data.toLatin1().constData(), t.elapsed()); t.restart(); m_smallChart1->setPixmap( m_pix1 ); m_smallChart2->setPixmap( m_pix2 ); m_smallChart1->show(); m_smallChart2->show(); qDebug("Time for setting pixmap %s: %d ms", data.toLatin1().constData(), t.elapsed()); t.restart(); } void MainWindow::on_lineTypeCB_currentIndexChanged( const QString & text ) { if ( text == "Normal" ) m_lines->setType( LineDiagram::Normal ); else if ( text == "Stacked" ) m_lines->setType( LineDiagram::Stacked ); else if ( text == "Percent" ) m_lines->setType( LineDiagram::Percent ); else qWarning (" Does not match any type"); } void MainWindow::on_paintLegendCB_toggled( bool checked ) { KChart::Legend* legend = m_chart->legend(); if ( checked != ( legend != 0 ) ) { if ( checked ) m_chart->addLegend( m_legend ); else m_chart->takeLegend( legend ); } } void MainWindow::on_paintValuesCB_toggled( bool checked ) { const int colCount = m_lines->model()->columnCount(); for ( int iColumn = 0; iColumndataValueAttributes( iColumn ); a.setVisible( true ); TextAttributes ta = a.textAttributes(); ta.setRotation( 0 ); ta.setFont( QFont( "Comic", 10 ) ); ta.setPen( m_lines->brush( iColumn ).color() ); ta.setVisible( checked ); a.setTextAttributes( ta ); m_lines->setDataValueAttributes( iColumn, a); } } void MainWindow::on_paintMarkersCB_toggled( bool checked ) { paintMarkers( checked, QSize() ); } void MainWindow::on_markersStyleCB_currentIndexChanged( const QString & text ) { Q_UNUSED( text ); on_paintMarkersCB_toggled( paintMarkersCB->isChecked() ); } void MainWindow::on_markersWidthSB_valueChanged( int i ) { Q_UNUSED( i ); markersHeightSB->setValue( markersWidthSB->value() ); on_paintMarkersCB_toggled( paintMarkersCB->isChecked() ); } void MainWindow::on_markersHeightSB_valueChanged( int i ) { Q_UNUSED( i ); markersWidthSB->setValue( markersHeightSB->value() ); on_paintMarkersCB_toggled( paintMarkersCB->isChecked() ); } void MainWindow::on_displayAreasCB_toggled( bool checked ) { const int colCount = m_lines->model()->columnCount(); for ( int iColumn = 0; iColumnlineAttributes( iColumn ) ); la.setDisplayArea( checked ); if ( checked ) la.setTransparency( transparencySB->value() ); m_lines->setLineAttributes( iColumn, la ); } } void MainWindow::on_transparencySB_valueChanged( int alpha ) { Q_UNUSED( alpha ); if ( !displayAreasCB->isChecked() ) displayAreasCB->setChecked( true ); else on_displayAreasCB_toggled( true ); } void MainWindow::on_zoomFactorSB_valueChanged( qreal factor ) { const bool isZoomedIn = factor > 1.0f; hSBar->setVisible( isZoomedIn ); vSBar->setVisible( isZoomedIn ); if ( !isZoomedIn ) { hSBar->setValue( 500 ); vSBar->setValue( 500 ); } m_chart->coordinatePlane()->setZoomFactorX( factor ); m_chart->coordinatePlane()->setZoomFactorY( factor ); } void MainWindow::on_hSBar_valueChanged( int hPos ) { m_chart->coordinatePlane()->setZoomCenter( QPointF(hPos/1000.0, vSBar->value()/1000.0) ); m_chart->update(); } void MainWindow::on_vSBar_valueChanged( int vPos ) { m_chart->coordinatePlane()->setZoomCenter( QPointF( hSBar->value()/1000.0, vPos/1000.0) ); } // since DataValue markers have no relative sizing mode we need to scale them for printing void MainWindow::paintMarkers( bool checked, const QSize& printSize ) { MarkerAttributes::MarkerStylesMap map; map.insert( 0, MarkerAttributes::MarkerSquare ); map.insert( 1, MarkerAttributes::MarkerCircle ); map.insert( 2, MarkerAttributes::MarkerRing ); map.insert( 3, MarkerAttributes::MarkerCross ); map.insert( 4, MarkerAttributes::MarkerDiamond ); // next: Specify column- / cell-specific attributes! const int colCount = m_lines->model()->columnCount(); for ( int iColumn = 0; iColumndataValueAttributes( iColumn ); dva.setVisible( true ); MarkerAttributes ma( dva.markerAttributes() ); switch ( markersStyleCB->currentIndex() ) { case 0: ma.setMarkerStyle( MarkerAttributes::MarkerSquare ); break; case 1: // Column-specific attributes ma.setMarkerStyle( map.value( iColumn ) ); break; case 2: ma.setMarkerStyle( MarkerAttributes::MarkerCircle ); break; case 3: ma.setMarkerStyle( MarkerAttributes::MarkerDiamond ); break; case 4: ma.setMarkerStyle( MarkerAttributes::Marker1Pixel ); break; case 5: ma.setMarkerStyle( MarkerAttributes::Marker4Pixels ); break; case 6: ma.setMarkerStyle( MarkerAttributes::MarkerRing ); break; case 7: ma.setMarkerStyle( MarkerAttributes::MarkerCross ); break; case 8: ma.setMarkerStyle( MarkerAttributes::MarkerFastCross ); break; default: Q_ASSERT( false ); } ma.setVisible( checked ); qreal factorWidth = printSize.isValid() ? ( printSize.width() / m_chart->rect().width() ) : 1.0f; qreal factorHeight = printSize.isValid() ? ( printSize.height() / m_chart->rect().height() ) : 1.0f; ma.setMarkerSize( QSize( markersWidthSB->value() * factorWidth, markersHeightSB->value() * factorHeight ) ); dva.setMarkerAttributes( ma ); m_lines->setDataValueAttributes( iColumn, dva ); // make a special one for certain values DataValueAttributes yellowAttributes( dva ); MarkerAttributes yellowMarker( yellowAttributes.markerAttributes() ); yellowMarker.setMarkerColor( Qt::yellow ); yellowAttributes.setMarkerAttributes( yellowMarker ); const int rowCount = m_lines->model()->rowCount(); for ( int j=0; j< rowCount; ++j ) { QModelIndex index = m_lines->model()->index( j, iColumn, QModelIndex() ); QBrush brush = m_lines->model()->headerData( iColumn, Qt::Vertical, DatasetBrushRole ).value(); qreal value = m_lines->model()->data( index ).toReal(); /* Set a specific color - marker for a specific value */ if ( value == 13 ) { m_lines->setDataValueAttributes( index, yellowAttributes ); } } } } void MainWindow::on_savePB_clicked() { qDebug() << "Painting into PNG"; QPixmap qpix(600,600); QPainter painter(&qpix); painter.setBrush(Qt::white); painter.fillRect( 0, 0, 600, 600, Qt::white); m_chart->paint( &painter, QRect(100, 100, 400, 400) ); painter.end(); qpix.save("kchart-demo.png", "PNG"); qDebug() << "Painting into PNG - done"; } void MainWindow::resizeEvent ( QResizeEvent * ) { m_smallChart1->move( width() - m_pix1.width()*2, height()/2 - m_pix1.height()-5 ); m_smallChart2->move( width() - m_pix2.width()*2, height()/2 + 5 ); } diff --git a/src/KChart/Cartesian/DiagramFlavors/KChartNormalLineDiagram_p.cpp b/src/KChart/Cartesian/DiagramFlavors/KChartNormalLineDiagram_p.cpp index 6663eff..a9c1cf0 100644 --- a/src/KChart/Cartesian/DiagramFlavors/KChartNormalLineDiagram_p.cpp +++ b/src/KChart/Cartesian/DiagramFlavors/KChartNormalLineDiagram_p.cpp @@ -1,153 +1,153 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#include "KChartNormalLineDiagram_p.h" #include #include #include "KChartBarDiagram.h" #include "KChartLineDiagram.h" #include "KChartTextAttributes.h" #include "KChartAttributesModel.h" #include "KChartAbstractCartesianDiagram.h" -#include "KChartNormalLineDiagram_p.h" #include "PaintingHelpers_p.h" using namespace KChart; using namespace std; NormalLineDiagram::NormalLineDiagram( LineDiagram* d ) : LineDiagramType( d ) { } LineDiagram::LineType NormalLineDiagram::type() const { return LineDiagram::Normal; } const QPair< QPointF, QPointF > NormalLineDiagram::calculateDataBoundaries() const { return compressor().dataBoundaries(); } void NormalLineDiagram::paint( PaintContext* ctx ) { reverseMapper().clear(); Q_ASSERT( dynamic_cast( ctx->coordinatePlane() ) ); CartesianCoordinatePlane* plane = static_cast( ctx->coordinatePlane() ); const int columnCount = compressor().modelDataColumns(); const int rowCount = compressor().modelDataRows(); if ( columnCount == 0 || rowCount == 0 ) return; // maybe blank out the area? // Reverse order of data sets? bool rev = diagram()->reverseDatasetOrder(); LabelPaintCache lpc; LineAttributesInfoList lineList; const int step = rev ? -1 : 1; const int end = rev ? -1 : columnCount; for ( int column = rev ? columnCount - 1 : 0; column != end; column += step ) { LineAttributes laPreviousCell; CartesianDiagramDataCompressor::DataPoint lastPoint; qreal lastAreaBoundingValue = 0; // Get min. y value, used as lower or upper bounding for area highlighting const qreal minYValue = qMin(plane->visibleDataRange().bottom(), plane->visibleDataRange().top()); CartesianDiagramDataCompressor::CachePosition previousCellPosition; for ( int row = 0; row < rowCount; ++row ) { const CartesianDiagramDataCompressor::CachePosition position( row, column ); // get where to draw the line from: CartesianDiagramDataCompressor::DataPoint point = compressor().data( position ); if ( point.hidden ) { continue; } const QModelIndex sourceIndex = attributesModel()->mapToSource( point.index ); const LineAttributes laCell = diagram()->lineAttributes( sourceIndex ); const LineAttributes::MissingValuesPolicy policy = laCell.missingValuesPolicy(); // lower or upper bounding for the highlighted area qreal areaBoundingValue; if ( laCell.areaBoundingDataset() != -1 ) { const CartesianDiagramDataCompressor::CachePosition areaBoundingCachePosition( row, laCell.areaBoundingDataset() ); areaBoundingValue = compressor().data( areaBoundingCachePosition ).value; } else { // Use min. y value (i.e. zero line in most cases) if no bounding dataset is set areaBoundingValue = minYValue; } if ( ISNAN( point.value ) ) { switch ( policy ) { case LineAttributes::MissingValuesAreBridged: // we just bridge both values continue; case LineAttributes::MissingValuesShownAsZero: // set it to zero point.value = 0.0; break; case LineAttributes::MissingValuesHideSegments: // they're just hidden break; default: break; // hm.... } } if ( !ISNAN( point.value ) ) { // area corners, a + b are the line ends: const qreal offset = diagram()->centerDataPoints() ? 0.5 : 0; const QPointF a( plane->translate( QPointF( lastPoint.key + offset, lastPoint.value ) ) ); const QPointF b( plane->translate( QPointF( point.key + offset, point.value ) ) ); const QPointF c( plane->translate( QPointF( lastPoint.key + offset, lastAreaBoundingValue ) ) ); const QPointF d( plane->translate( QPointF( point.key + offset, areaBoundingValue ) ) ); const PositionPoints pts = PositionPoints( b, a, d, c ); // add label m_private->addLabel( &lpc, sourceIndex, &position, pts, Position::NorthWest, Position::NorthWest, point.value ); // add line and area, if switched on and we have a current and previous value if ( !ISNAN( lastPoint.value ) ) { lineList.append( LineAttributesInfo( sourceIndex, a, b ) ); if ( laCell.displayArea() ) { QList areas; areas << ( QPolygonF() << a << b << d << c ); PaintingHelpers::paintAreas( m_private, ctx, attributesModel()->mapToSource( lastPoint.index ), areas, laCell.transparency() ); } } } previousCellPosition = position; laPreviousCell = laCell; lastAreaBoundingValue = areaBoundingValue; lastPoint = point; } } // paint the lines PaintingHelpers::paintElements( m_private, ctx, lpc, lineList ); } diff --git a/src/KChart/Cartesian/DiagramFlavors/KChartStackedBarDiagram_p.cpp b/src/KChart/Cartesian/DiagramFlavors/KChartStackedBarDiagram_p.cpp index 9928a8d..86f3dd9 100644 --- a/src/KChart/Cartesian/DiagramFlavors/KChartStackedBarDiagram_p.cpp +++ b/src/KChart/Cartesian/DiagramFlavors/KChartStackedBarDiagram_p.cpp @@ -1,211 +1,211 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#include "KChartStackedBarDiagram_p.h" #include #include "KChartBarDiagram.h" #include "KChartTextAttributes.h" #include "KChartAttributesModel.h" #include "KChartAbstractCartesianDiagram.h" -#include "KChartStackedBarDiagram_p.h" using namespace KChart; StackedBarDiagram::StackedBarDiagram( BarDiagram* d ) : BarDiagramType( d ) { } BarDiagram::BarType StackedBarDiagram::type() const { return BarDiagram::Stacked; } const QPair StackedBarDiagram::calculateDataBoundaries() const { const int rowCount = compressor().modelDataRows(); const int colCount = compressor().modelDataColumns(); const qreal xMin = 0.0; const qreal xMax = rowCount; qreal yMin = 0.0; qreal yMax = 0.0; bool isFirst = true; for ( int row = 0; row < rowCount; ++row ) { // calculate sum of values per column - Find out stacked Min/Max qreal stackedValues = 0.0; qreal negativeStackedValues = 0.0; for ( int col = 0; col < colCount; ++col ) { const CartesianDiagramDataCompressor::CachePosition position( row, col ); const CartesianDiagramDataCompressor::DataPoint point = compressor().data( position ); const double value = ISNAN( point.value ) ? 0.0 : point.value; if ( value > 0.0 ) { stackedValues += value; } else { negativeStackedValues += value; } // this is always true yMin can be 0 in case all values // are the same // same for yMax it can be zero if all values are negative if ( isFirst ) { yMin = negativeStackedValues < 0.0 ? negativeStackedValues : stackedValues; yMax = stackedValues > 0.0 ? stackedValues : negativeStackedValues; isFirst = false; } else { yMin = qMin( qMin( yMin, stackedValues ), negativeStackedValues ); yMax = qMax( qMax( yMax, stackedValues ), negativeStackedValues ); } } } // special cases if ( yMax == yMin ) { if ( yMin == 0.0 ) { yMax = 0.1; // we need at least a range } else if ( yMax < 0.0 ) { yMax = 0.0; // extend the range to zero } else if ( yMin > 0.0 ) { yMin = 0.0; // dito } } return QPair< QPointF, QPointF >( QPointF( xMin, yMin ), QPointF( xMax, yMax ) ); } void StackedBarDiagram::paint( PaintContext* ctx ) { reverseMapper().clear(); const QPair boundaries = diagram()->dataBoundaries(); // cached const QPointF boundLeft = ctx->coordinatePlane()->translate( boundaries.first ) ; const QPointF boundRight = ctx->coordinatePlane()->translate( boundaries.second ); const int rowCount = compressor().modelDataRows(); const int colCount = compressor().modelDataColumns(); BarAttributes ba = diagram()->barAttributes(); qreal barWidth = 0; qreal maxDepth = 0; qreal width = boundRight.x() - boundLeft.x(); qreal groupWidth = width / rowCount; qreal spaceBetweenBars = 0; qreal spaceBetweenGroups = 0; if ( ba.useFixedBarWidth() ) { barWidth = ba.fixedBarWidth(); groupWidth += barWidth; // Pending Michel set a min and max value for the groupWidth // related to the area.width if ( groupWidth < 0 ) groupWidth = 0; if ( groupWidth * rowCount > width ) groupWidth = width / rowCount; } // maxLimit: allow the space between bars to be larger until area.width() // is covered by the groups. qreal maxLimit = rowCount * (groupWidth + ((colCount-1) * ba.fixedDataValueGap()) ); //Pending Michel: FixMe if ( ba.useFixedDataValueGap() ) { if ( width > maxLimit ) spaceBetweenBars += ba.fixedDataValueGap(); else spaceBetweenBars = ((width/rowCount) - groupWidth)/(colCount-1); } if ( ba.useFixedValueBlockGap() ) spaceBetweenGroups += ba.fixedValueBlockGap(); calculateValueAndGapWidths( rowCount, colCount,groupWidth, barWidth, spaceBetweenBars, spaceBetweenGroups ); LabelPaintCache lpc; for ( int col = 0; col < colCount; ++col ) { qreal offset = spaceBetweenGroups; if ( ba.useFixedBarWidth() ) offset -= ba.fixedBarWidth(); if ( offset < 0 ) offset = 0; for ( int row = 0; row < rowCount; ++row ) { const CartesianDiagramDataCompressor::CachePosition position( row, col ); const CartesianDiagramDataCompressor::DataPoint p = compressor().data( position ); const QModelIndex index = attributesModel()->mapToSource( p.index ); ThreeDBarAttributes threeDAttrs = diagram()->threeDBarAttributes( index ); const qreal value = p.value; qreal stackedValues = 0.0; qreal key = 0.0; if ( threeDAttrs.isEnabled() ) { if ( barWidth > 0 ) barWidth = (width - ((offset+(threeDAttrs.depth()))*rowCount))/ rowCount; if ( barWidth <= 0 ) { barWidth = 0; maxDepth = offset - (width/rowCount); } } else { barWidth = (width - (offset*rowCount))/ rowCount ; } for ( int k = col; k >= 0; --k ) { const CartesianDiagramDataCompressor::CachePosition position( row, k ); const CartesianDiagramDataCompressor::DataPoint point = compressor().data( position ); if ( !ISNAN( point.value ) && (( p.value >= 0.0 && point.value >= 0.0 ) || ( p.value < 0.0 && point.value < 0.0 )) ) stackedValues += point.value; key = point.key; } if (!ISNAN( value )) { const qreal usedDepth = threeDAttrs.depth(); QPointF point = ctx->coordinatePlane()->translate( QPointF( key, stackedValues ) ); const qreal dy = point.y() - usedDepth; if ( dy < 0 ) { threeDAttrs.setDepth( point.y() - 1 ); diagram()->setThreeDBarAttributes( threeDAttrs ); } point.rx() += offset / 2; const QPointF previousPoint = ctx->coordinatePlane()->translate( QPointF( key, stackedValues - value ) ); const qreal barHeight = previousPoint.y() - point.y(); const QRectF rect( point, QSizeF( barWidth , barHeight ) ); m_private->addLabel( &lpc, index, 0, PositionPoints( rect ), Position::North, Position::South, value ); paintBars( ctx, index, rect, maxDepth ); } } } m_private->paintDataValueTextsAndMarkers( ctx, lpc, false ); } diff --git a/src/KChart/Cartesian/DiagramFlavors/KChartStackedLyingBarDiagram_p.cpp b/src/KChart/Cartesian/DiagramFlavors/KChartStackedLyingBarDiagram_p.cpp index 1a5b003..495c6e9 100644 --- a/src/KChart/Cartesian/DiagramFlavors/KChartStackedLyingBarDiagram_p.cpp +++ b/src/KChart/Cartesian/DiagramFlavors/KChartStackedLyingBarDiagram_p.cpp @@ -1,200 +1,200 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#include "KChartStackedLyingBarDiagram_p.h" #include #include "KChartBarDiagram.h" #include "KChartTextAttributes.h" #include "KChartAttributesModel.h" #include "KChartAbstractCartesianDiagram.h" -#include "KChartStackedLyingBarDiagram_p.h" using namespace KChart; StackedLyingBarDiagram::StackedLyingBarDiagram( BarDiagram* d ) : BarDiagramType( d ) { } BarDiagram::BarType StackedLyingBarDiagram::type() const { return BarDiagram::Stacked; } const QPair StackedLyingBarDiagram::calculateDataBoundaries() const { const int rowCount = compressor().modelDataRows(); const int colCount = compressor().modelDataColumns(); const qreal xMin = 0; const qreal xMax = rowCount; qreal yMin = 0; qreal yMax = 0; bool isFirst = true; for ( int row = 0; row < rowCount; ++row ) { // calculate sum of values per column - Find out stacked Min/Max qreal stackedValues = 0.0; qreal negativeStackedValues = 0.0; for ( int col = 0; col < colCount; ++col ) { const CartesianDiagramDataCompressor::CachePosition position( row, col ); const CartesianDiagramDataCompressor::DataPoint point = compressor().data( position ); if ( point.value > 0.0 ) stackedValues += point.value; else negativeStackedValues += point.value; // this is always true yMin can be 0 in case all values // are the same // same for yMax it can be zero if all values are negative if ( isFirst ) { yMin = negativeStackedValues < 0.0 ? negativeStackedValues : stackedValues; yMax = stackedValues > 0.0 ? stackedValues : negativeStackedValues; isFirst = false; } else { yMin = qMin( qMin( yMin, stackedValues ), negativeStackedValues ); yMax = qMax( qMax( yMax, stackedValues ), negativeStackedValues ); } } } // special cases if ( yMax == yMin ) { if ( yMin == 0.0 ) { yMax = 0.1; // we need at least a range } else if ( yMax < 0.0 ) { yMax = 0.0; // extend the range to zero } else if ( yMin > 0.0 ) { yMin = 0.0; // dito } } return QPair< QPointF, QPointF >( QPointF( yMin, xMin ), QPointF( yMax, xMax ) ); } void StackedLyingBarDiagram::paint( PaintContext* ctx ) { reverseMapper().clear(); const QPair boundaries = diagram()->dataBoundaries(); // cached const QPointF boundLeft = ctx->coordinatePlane()->translate( boundaries.first ) ; const QPointF boundRight = ctx->coordinatePlane()->translate( boundaries.second ); const int rowCount = compressor().modelDataRows(); const int colCount = compressor().modelDataColumns(); BarAttributes ba = diagram()->barAttributes(); qreal barWidth = 0; qreal maxDepth = 0; qreal width = boundLeft.y() - boundRight.y(); qreal groupWidth = width / rowCount; qreal spaceBetweenBars = 0; qreal spaceBetweenGroups = 0; if ( ba.useFixedBarWidth() ) { barWidth = ba.fixedBarWidth(); groupWidth += barWidth; // Pending Michel set a min and max value for the groupWidth // related to the area.width if ( groupWidth < 0 ) groupWidth = 0; if ( groupWidth * rowCount > width ) groupWidth = width / rowCount; } // maxLimit: allow the space between bars to be larger until area.width() // is covered by the groups. qreal maxLimit = rowCount * (groupWidth + ((colCount-1) * ba.fixedDataValueGap()) ); //Pending Michel: FixMe if ( ba.useFixedDataValueGap() ) { if ( ctx->rectangle().width() > maxLimit ) spaceBetweenBars += ba.fixedDataValueGap(); else spaceBetweenBars = ((width/rowCount) - groupWidth)/(colCount-1); } if ( ba.useFixedValueBlockGap() ) spaceBetweenGroups += ba.fixedValueBlockGap(); calculateValueAndGapWidths( rowCount, colCount,groupWidth, barWidth, spaceBetweenBars, spaceBetweenGroups ); LabelPaintCache lpc; for ( int row = rowCount - 1; row >= 0; --row ) { qreal offset = spaceBetweenGroups; if ( ba.useFixedBarWidth() ) offset -= ba.fixedBarWidth(); if ( offset < 0 ) offset = 0; for ( int col = 0; col < colCount; ++col ) { qreal threeDOffset = 0.0; const CartesianDiagramDataCompressor::CachePosition position( row, col ); const CartesianDiagramDataCompressor::DataPoint p = compressor().data( position ); const QModelIndex index = attributesModel()->mapToSource( p.index ); ThreeDBarAttributes threeDAttrs = diagram()->threeDBarAttributes( index ); const qreal value = p.value; qreal stackedValues = 0.0; qreal key = 0.0; if ( threeDAttrs.isEnabled() ) { if ( barWidth > 0 ) { barWidth = (width - ((offset+(threeDAttrs.depth()))*rowCount))/ rowCount; threeDOffset = threeDAttrs.depth(); } if ( barWidth <= 0 ) { barWidth = 0.1; threeDOffset = (width - (offset*rowCount))/ rowCount; } } else { barWidth = (width - (offset*rowCount))/ rowCount; } for ( int k = col; k >= 0; --k ) { const CartesianDiagramDataCompressor::CachePosition position( row, k ); const CartesianDiagramDataCompressor::DataPoint point = compressor().data( position ); if ( ( p.value >= 0.0 && point.value >= 0.0 ) || ( p.value < 0.0 && point.value < 0.0 ) ) stackedValues += point.value; key = point.key; } QPointF point = ctx->coordinatePlane()->translate( QPointF( stackedValues, key + 1 ) ); point.ry() += offset / 2 + threeDOffset; const QPointF previousPoint = ctx->coordinatePlane()->translate( QPointF( stackedValues - value, key + 1 ) ); const qreal barHeight = point.x() - previousPoint.x(); point.rx() -= barHeight; const QRectF rect = QRectF( point, QSizeF( barHeight , barWidth ) ).translated( 1, 0 ); m_private->addLabel( &lpc, index, 0, PositionPoints( rect ), Position::North, Position::South, value ); paintBars( ctx, index, rect, maxDepth ); } } m_private->paintDataValueTextsAndMarkers( ctx, lpc, false ); } diff --git a/src/KChart/Cartesian/KChartAbstractCartesianDiagram.cpp b/src/KChart/Cartesian/KChartAbstractCartesianDiagram.cpp index 3e3a786..32b959e 100644 --- a/src/KChart/Cartesian/KChartAbstractCartesianDiagram.cpp +++ b/src/KChart/Cartesian/KChartAbstractCartesianDiagram.cpp @@ -1,196 +1,196 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "KChartAbstractCartesianDiagram.h" #include "KChartAbstractCartesianDiagram_p.h" #include "KChartMath_p.h" using namespace KChart; AbstractCartesianDiagram::Private::Private() : referenceDiagram( 0 ) { } AbstractCartesianDiagram::Private::~Private() { } bool AbstractCartesianDiagram::compare( const AbstractCartesianDiagram* other ) const { if ( other == this ) return true; if ( ! other ) { return false; } return // compare the base class ( static_cast(this)->compare( other ) ) && // compare own properties (referenceDiagram() == other->referenceDiagram()) && ((! referenceDiagram()) || (referenceDiagramOffset() == other->referenceDiagramOffset())); } #define d d_func() AbstractCartesianDiagram::AbstractCartesianDiagram ( QWidget* parent, CartesianCoordinatePlane* plane ) : AbstractDiagram ( new Private(), parent, plane ) { init(); } KChart::AbstractCartesianDiagram::~AbstractCartesianDiagram() { Q_FOREACH( CartesianAxis* axis, d->axesList ) { axis->deleteObserver( this ); } d->axesList.clear(); } void AbstractCartesianDiagram::init() { d->compressor.setModel( attributesModel() ); - connect( this, SIGNAL( layoutChanged( AbstractDiagram* ) ), - &d->compressor, SLOT( slotDiagramLayoutChanged( AbstractDiagram* ) ) ); - connect( this, SIGNAL( attributesModelAboutToChange( AttributesModel*, AttributesModel* ) ), - this, SLOT( connectAttributesModel( AttributesModel* ) ) ); + connect( this, SIGNAL(layoutChanged(AbstractDiagram*)), + &d->compressor, SLOT(slotDiagramLayoutChanged(AbstractDiagram*)) ); + connect( this, SIGNAL(attributesModelAboutToChange(AttributesModel*,AttributesModel*)), + this, SLOT(connectAttributesModel(AttributesModel*)) ); if ( d->plane ) { - connect( d->plane, SIGNAL( viewportCoordinateSystemChanged() ), - this, SIGNAL( viewportCoordinateSystemChanged() ) ); + connect( d->plane, SIGNAL(viewportCoordinateSystemChanged()), + this, SIGNAL(viewportCoordinateSystemChanged()) ); } } void AbstractCartesianDiagram::addAxis( CartesianAxis *axis ) { if ( !d->axesList.contains( axis ) ) { d->axesList.append( axis ); axis->createObserver( this ); layoutPlanes(); } } void AbstractCartesianDiagram::takeAxis( CartesianAxis *axis ) { const int idx = d->axesList.indexOf( axis ); if ( idx != -1 ) d->axesList.takeAt( idx ); axis->deleteObserver( this ); axis->setParentWidget( 0 ); layoutPlanes(); } KChart::CartesianAxisList AbstractCartesianDiagram::axes( ) const { return d->axesList; } void KChart::AbstractCartesianDiagram::layoutPlanes() { AbstractCoordinatePlane* plane = coordinatePlane(); if ( plane ) { plane->layoutPlanes(); } } void KChart::AbstractCartesianDiagram::setCoordinatePlane( AbstractCoordinatePlane* plane ) { if ( coordinatePlane() ) { - disconnect( attributesModel(), SIGNAL( rowsRemoved( const QModelIndex&, int, int ) ), - coordinatePlane(), SLOT( relayout() ) ); - disconnect( attributesModel(), SIGNAL( rowsInserted( const QModelIndex&, int, int ) ), - coordinatePlane(), SLOT( relayout() ) ); - disconnect( attributesModel(), SIGNAL( columnsRemoved( const QModelIndex&, int, int ) ), - coordinatePlane(), SLOT( relayout() ) ); - disconnect( attributesModel(), SIGNAL( columnsInserted( const QModelIndex&, int, int ) ), - coordinatePlane(), SLOT( relayout() ) ); + disconnect( attributesModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), + coordinatePlane(), SLOT(relayout()) ); + disconnect( attributesModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), + coordinatePlane(), SLOT(relayout()) ); + disconnect( attributesModel(), SIGNAL(columnsRemoved(QModelIndex,int,int)), + coordinatePlane(), SLOT(relayout()) ); + disconnect( attributesModel(), SIGNAL(columnsInserted(QModelIndex,int,int)), + coordinatePlane(), SLOT(relayout()) ); disconnect( coordinatePlane() ); } AbstractDiagram::setCoordinatePlane(plane); if ( plane ) { // Readjust the layout when the dataset count changes - connect( attributesModel(), SIGNAL( rowsRemoved( const QModelIndex&, int, int ) ), - plane, SLOT( relayout() ) ); - connect( attributesModel(), SIGNAL( rowsInserted( const QModelIndex&, int, int ) ), - plane, SLOT( relayout() ) ); - connect( attributesModel(), SIGNAL( columnsRemoved( const QModelIndex&, int, int ) ), - plane, SLOT( relayout() ) ); - connect( attributesModel(), SIGNAL( columnsInserted( const QModelIndex&, int, int ) ), - plane, SLOT( relayout() ) ); - connect( plane, SIGNAL( viewportCoordinateSystemChanged() ), - this, SIGNAL( viewportCoordinateSystemChanged() ) ); - connect( plane, SIGNAL( viewportCoordinateSystemChanged() ), this, SLOT( update() ) ); + connect( attributesModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), + plane, SLOT(relayout()) ); + connect( attributesModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), + plane, SLOT(relayout()) ); + connect( attributesModel(), SIGNAL(columnsRemoved(QModelIndex,int,int)), + plane, SLOT(relayout()) ); + connect( attributesModel(), SIGNAL(columnsInserted(QModelIndex,int,int)), + plane, SLOT(relayout()) ); + connect( plane, SIGNAL(viewportCoordinateSystemChanged()), + this, SIGNAL(viewportCoordinateSystemChanged()) ); + connect( plane, SIGNAL(viewportCoordinateSystemChanged()), this, SLOT(update()) ); } } void AbstractCartesianDiagram::setReferenceDiagram( AbstractCartesianDiagram* diagram, const QPointF& offset ) { d->referenceDiagram = diagram; d->referenceDiagramOffset = offset; } AbstractCartesianDiagram* AbstractCartesianDiagram::referenceDiagram() const { return d->referenceDiagram; } QPointF AbstractCartesianDiagram::referenceDiagramOffset() const { return d->referenceDiagramOffset; } void AbstractCartesianDiagram::setRootIndex( const QModelIndex& index ) { d->compressor.setRootIndex( attributesModel()->mapFromSource( index ) ); AbstractDiagram::setRootIndex( index ); } void AbstractCartesianDiagram::setModel( QAbstractItemModel* m ) { if ( m == model() ) { return; } AbstractDiagram::setModel( m ); } void AbstractCartesianDiagram::setAttributesModel( AttributesModel* model ) { if ( model == attributesModel() ) { return; } AbstractDiagram::setAttributesModel( model ); } void AbstractCartesianDiagram::connectAttributesModel( AttributesModel* newModel ) { // The compressor must receive model signals before the diagram because the diagram will ask the // compressor for data upon receiving dataChanged() et al. from the model, at which point the // compressor must be up to date already. // Starting from Qt 4.6, the invocation order of slots is guaranteed to be equal to connection // order (and probably was before). // This is our opportunity to connect to the AttributesModel before the AbstractDiagram does. // ### A better design would be to properly recognize that the compressor is the real data interface // for Cartesian diagrams and make diagrams listen to updates from the *compressor*, not the model. // However, this would change the outside interface of AbstractCartesianDiagram which would be bad. // So we're stuck with the complication of this slot and the corresponding signal. d->compressor.setModel( newModel ); } diff --git a/src/KChart/Cartesian/KChartCartesianAxis.cpp b/src/KChart/Cartesian/KChartCartesianAxis.cpp index 2ba8e45..1ba6237 100644 --- a/src/KChart/Cartesian/KChartCartesianAxis.cpp +++ b/src/KChart/Cartesian/KChartCartesianAxis.cpp @@ -1,1173 +1,1173 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "KChartCartesianAxis.h" #include "KChartCartesianAxis_p.h" #include #include #include #include #include #include #include "KChartPaintContext.h" #include "KChartChart.h" #include "KChartAbstractCartesianDiagram.h" #include "KChartAbstractDiagram_p.h" #include "KChartAbstractGrid.h" #include "KChartPainterSaver_p.h" #include "KChartLayoutItems.h" #include "KChartBarDiagram.h" #include "KChartStockDiagram.h" #include "KChartLineDiagram.h" #include "KChartPrintingParameters.h" using namespace KChart; #define d (d_func()) static qreal slightlyLessThan( qreal r ) { if ( r == 0.0 ) { // scale down the epsilon somewhat arbitrarily return r - std::numeric_limits< qreal >::epsilon() * 1e-6; } // scale the epsilon so that it (hopefully) changes at least the least significant bit of r qreal diff = qAbs( r ) * std::numeric_limits< qreal >::epsilon() * 2.0; return r - diff; } static int numSignificantDecimalPlaces( qreal floatNumber ) { static const int maxPlaces = 15; QString sample = QString::number( floatNumber, 'f', maxPlaces ).section( QLatin1Char('.'), 1, 2 ); int ret = maxPlaces; for ( ; ret > 0; ret-- ) { if ( sample[ ret - 1 ] != QLatin1Char( '0' ) ) { break; } } return ret; } // Feature idea: In case of numeric labels, consider limiting the possible values of majorThinningFactor // to something like {1, 2, 5} * 10^n. Or even better, something that achieves round values in the // remaining labels. // ensure we take the const-overload of any following function, esp. required for strict iterators template static const T& constify(T &v) { return v; } TickIterator::TickIterator( CartesianAxis* a, CartesianCoordinatePlane* plane, uint majorThinningFactor, bool omitLastTick ) : m_axis( a ), m_majorThinningFactor( majorThinningFactor ), m_majorLabelCount( 0 ), m_type( NoTick ) { // deal with the things that are specific to axes (like annotations), before the generic init(). const CartesianAxis::Private *axisPriv = CartesianAxis::Private::get( a ); XySwitch xy( axisPriv->isVertical() ); m_dimension = xy( plane->gridDimensionsList().first(), plane->gridDimensionsList().last() ); if ( omitLastTick ) { // In bar and stock charts the last X tick is a fencepost with no associated value, which is // convenient for grid painting. Here we have to manually exclude it to avoid overpainting. m_dimension.end -= m_dimension.stepWidth; } m_annotations = axisPriv->annotations; m_customTicks = axisPriv->customTicksPositions; const qreal inf = std::numeric_limits< qreal >::infinity(); if ( m_customTicks.count() ) { qSort( m_customTicks.begin(), m_customTicks.end() ); m_customTickIndex = 0; m_customTick = m_customTicks.at( m_customTickIndex ); } else { m_customTickIndex = -1; m_customTick = inf; } if ( m_majorThinningFactor > 1 && hasShorterLabels() ) { m_manualLabelTexts = m_axis->shortLabels(); } else { m_manualLabelTexts = m_axis->labels(); } m_manualLabelIndex = m_manualLabelTexts.isEmpty() ? -1 : 0; if ( !m_dimension.isCalculated ) { // ### depending on the data, it is difficult to impossible to choose anchors (where ticks // corresponding to the header labels are) on the ordinate or even the abscissa with // 2-dimensional data. this should be somewhat mitigated by isCalculated only being false // when header data labels should work, at least that seems to be what the code that sets up // the dimensions is trying to do. QStringList dataHeaderLabels; AbstractDiagram* const dia = plane->diagram(); dataHeaderLabels = dia->itemRowLabels(); if ( !dataHeaderLabels.isEmpty() ) { AttributesModel* model = dia->attributesModel(); const int anchorCount = model->rowCount( QModelIndex() ); if ( anchorCount == dataHeaderLabels.count() ) { for ( int i = 0; i < anchorCount; i++ ) { // ### ordinal number as anchor point generally only works for 1-dimensional data m_dataHeaderLabels.insert( qreal( i ), dataHeaderLabels.at( i ) ); } } } } bool hasMajorTicks = m_axis->rulerAttributes().showMajorTickMarks(); bool hasMinorTicks = m_axis->rulerAttributes().showMinorTickMarks(); init( xy.isY, hasMajorTicks, hasMinorTicks, plane ); } static QMap< qreal, QString > allAxisAnnotations( const AbstractCoordinatePlane *plane, bool isY ) { QMap< qreal, QString > annotations; Q_FOREACH( const AbstractDiagram *diagram, plane->diagrams() ) { const AbstractCartesianDiagram *cd = qobject_cast< const AbstractCartesianDiagram* >( diagram ); if ( !cd ) { continue; } Q_FOREACH( const CartesianAxis *axis, cd->axes() ) { const CartesianAxis::Private *axisPriv = CartesianAxis::Private::get( axis ); if ( axisPriv->isVertical() == isY ) { annotations.unite( axisPriv->annotations ); } } } return annotations; } TickIterator::TickIterator( bool isY, const DataDimension& dimension, bool useAnnotationsForTicks, bool hasMajorTicks, bool hasMinorTicks, CartesianCoordinatePlane* plane ) : m_axis( 0 ), m_dimension( dimension ), m_majorThinningFactor( 1 ), m_majorLabelCount( 0 ), m_customTickIndex( -1 ), m_manualLabelIndex( -1 ), m_type( NoTick ), m_customTick( std::numeric_limits< qreal >::infinity() ) { if ( useAnnotationsForTicks ) { m_annotations = allAxisAnnotations( plane, isY ); } init( isY, hasMajorTicks, hasMinorTicks, plane ); } void TickIterator::init( bool isY, bool hasMajorTicks, bool hasMinorTicks, CartesianCoordinatePlane* plane ) { Q_ASSERT( std::numeric_limits< qreal >::has_infinity ); m_isLogarithmic = m_dimension.calcMode == AbstractCoordinatePlane::Logarithmic; // sanity check against infinite loops hasMajorTicks = hasMajorTicks && ( m_dimension.stepWidth > 0 || m_isLogarithmic ); hasMinorTicks = hasMinorTicks && ( m_dimension.subStepWidth > 0 || m_isLogarithmic ); XySwitch xy( isY ); GridAttributes gridAttributes = plane->gridAttributes( xy( Qt::Horizontal, Qt::Vertical ) ); m_isLogarithmic = m_dimension.calcMode == AbstractCoordinatePlane::Logarithmic; if ( !m_isLogarithmic ) { // adjustedLowerUpperRange() is intended for use with linear scaling; specifically it would // round lower bounds < 1 to 0. const bool fixedRange = xy( plane->autoAdjustHorizontalRangeToData(), plane->autoAdjustVerticalRangeToData() ) >= 100; const bool adjustLower = gridAttributes.adjustLowerBoundToGrid() && !fixedRange; const bool adjustUpper = gridAttributes.adjustUpperBoundToGrid() && !fixedRange; m_dimension = AbstractGrid::adjustedLowerUpperRange( m_dimension, adjustLower, adjustUpper ); m_decimalPlaces = numSignificantDecimalPlaces( m_dimension.stepWidth ); } else { // the number of significant decimal places for each label naturally varies with logarithmic scaling m_decimalPlaces = -1; } const qreal inf = std::numeric_limits< qreal >::infinity(); // try to place m_position just in front of the first tick to be drawn so that operator++() // can be used to find the first tick if ( m_isLogarithmic ) { if ( ISNAN( m_dimension.start ) || ISNAN( m_dimension.end ) ) { // this can happen in a spurious paint operation before everything is set up; // just bail out to avoid an infinite loop in that case. m_dimension.start = 0.0; m_dimension.end = 0.0; m_position = inf; m_majorTick = inf; m_minorTick = inf; } else if ( m_dimension.start >= 0 ) { m_position = m_dimension.start ? pow( 10.0, floor( log10( m_dimension.start ) ) - 1.0 ) : 1e-6; m_majorTick = hasMajorTicks ? m_position : inf; m_minorTick = hasMinorTicks ? m_position * 20.0 : inf; } else { m_position = -pow( 10.0, ceil( log10( -m_dimension.start ) ) + 1.0 ); m_majorTick = hasMajorTicks ? m_position : inf; m_minorTick = hasMinorTicks ? m_position * 0.09 : inf; } } else { m_majorTick = hasMajorTicks ? m_dimension.start : inf; m_minorTick = hasMinorTicks ? m_dimension.start : inf; m_position = slightlyLessThan( m_dimension.start ); } ++( *this ); } bool TickIterator::areAlmostEqual( qreal r1, qreal r2 ) const { if ( !m_isLogarithmic ) { qreal span = m_dimension.end - m_dimension.start; if ( span == 0 ) { // When start == end, we still want to show one tick if possible, // which needs this function to perform a reasonable comparison. span = qFuzzyIsNull( m_dimension.start) ? 1 : qAbs( m_dimension.start ); } return qAbs( r2 - r1 ) < ( span ) * 1e-6; } else { return qAbs( r2 - r1 ) < qMax( qAbs( r1 ), qAbs( r2 ) ) * 0.01; } } bool TickIterator::isHigherPrecedence( qreal importantTick, qreal unimportantTick ) const { return importantTick != std::numeric_limits< qreal >::infinity() && ( importantTick <= unimportantTick || areAlmostEqual( importantTick, unimportantTick ) ); } void TickIterator::computeMajorTickLabel( int decimalPlaces ) { if ( m_manualLabelIndex >= 0 ) { m_text = m_manualLabelTexts[ m_manualLabelIndex++ ]; if ( m_manualLabelIndex >= m_manualLabelTexts.count() ) { // manual label texts repeat if there are less label texts than ticks on an axis m_manualLabelIndex = 0; } m_type = m_majorThinningFactor > 1 ? MajorTickManualShort : MajorTickManualLong; } else { // if m_axis is null, we are dealing with grid lines. grid lines never need labels. if ( m_axis && ( m_majorLabelCount++ % m_majorThinningFactor ) == 0 ) { QMap< qreal, QString >::ConstIterator it = constify(m_dataHeaderLabels).lowerBound( slightlyLessThan( m_position ) ); if ( it != m_dataHeaderLabels.constEnd() && areAlmostEqual( it.key(), m_position ) ) { m_text = it.value(); m_type = MajorTickHeaderDataLabel; } else { // 'f' to avoid exponential notation for large numbers, consistent with data value text if ( decimalPlaces < 0 ) { decimalPlaces = numSignificantDecimalPlaces( m_position ); } m_text = QString::number( m_position, 'f', decimalPlaces ); m_type = MajorTick; } } else { m_text.clear(); m_type = MajorTick; } } } void TickIterator::operator++() { if ( isAtEnd() ) { return; } const qreal inf = std::numeric_limits< qreal >::infinity(); // make sure to find the next tick at a value strictly greater than m_position if ( !m_annotations.isEmpty() ) { QMap< qreal, QString >::ConstIterator it = constify(m_annotations).upperBound( m_position ); if ( it != m_annotations.constEnd() ) { m_position = it.key(); m_text = it.value(); m_type = CustomTick; } else { m_position = inf; } } else if ( !m_isLogarithmic && m_dimension.stepWidth * 1e6 < qMax( qAbs( m_dimension.start ), qAbs( m_dimension.end ) ) ) { // If the step width is too small to increase m_position at all, we get an infinite loop. // This usually happens when m_dimension.start == m_dimension.end and both are very large. // When start == end, the step width defaults to 1, and it doesn't scale with start or end. // So currently, we bail and show no tick at all for empty ranges > 10^6, but at least we don't hang. m_position = inf; } else { // advance the calculated ticks if ( m_isLogarithmic ) { while ( m_majorTick <= m_position ) { m_majorTick *= m_position >= 0 ? 10 : 0.1; } while ( m_minorTick <= m_position ) { // the next major tick position should be greater than this m_minorTick += m_majorTick * ( m_position >= 0 ? 0.1 : 1.0 ); } } else { while ( m_majorTick <= m_position ) { m_majorTick += m_dimension.stepWidth; } while ( m_minorTick <= m_position ) { m_minorTick += m_dimension.subStepWidth; } } while ( m_customTickIndex >= 0 && m_customTick <= m_position ) { if ( ++m_customTickIndex >= m_customTicks.count() ) { m_customTickIndex = -1; m_customTick = inf; break; } m_customTick = m_customTicks.at( m_customTickIndex ); } // now see which kind of tick we'll have if ( isHigherPrecedence( m_customTick, m_majorTick ) && isHigherPrecedence( m_customTick, m_minorTick ) ) { m_position = m_customTick; computeMajorTickLabel( -1 ); // override the MajorTick type here because those tick's labels are collision-tested, which we don't want // for custom ticks. they may be arbitrarily close to other ticks, causing excessive label thinning. if ( m_type == MajorTick ) { m_type = CustomTick; } } else if ( isHigherPrecedence( m_majorTick, m_minorTick ) ) { m_position = m_majorTick; if ( m_minorTick != inf ) { // realign minor to major m_minorTick = m_majorTick; } computeMajorTickLabel( m_decimalPlaces ); } else if ( m_minorTick != inf ) { m_position = m_minorTick; m_text.clear(); m_type = MinorTick; } else { m_position = inf; } } if ( m_position > m_dimension.end || ISNAN( m_position ) ) { m_position = inf; // make isAtEnd() return true m_text.clear(); m_type = NoTick; } } CartesianAxis::CartesianAxis( AbstractCartesianDiagram* diagram ) : AbstractAxis ( new Private( diagram, this ), diagram ) { init(); } CartesianAxis::~CartesianAxis() { // when we remove the first axis it will unregister itself and // propagate the next one to the primary, thus the while loop while ( d->mDiagram ) { AbstractCartesianDiagram *cd = qobject_cast< AbstractCartesianDiagram* >( d->mDiagram ); cd->takeAxis( this ); } Q_FOREACH( AbstractDiagram *diagram, d->secondaryDiagrams ) { AbstractCartesianDiagram *cd = qobject_cast< AbstractCartesianDiagram* >( diagram ); cd->takeAxis( this ); } } void CartesianAxis::init() { d->customTickLength = 3; d->position = Bottom; setCachedSizeDirty(); - connect( this, SIGNAL( coordinateSystemChanged() ), SLOT( coordinateSystemChanged() ) ); + connect( this, SIGNAL(coordinateSystemChanged()), SLOT(coordinateSystemChanged()) ); } bool CartesianAxis::compare( const CartesianAxis* other ) const { if ( other == this ) { return true; } if ( !other ) { return false; } return AbstractAxis::compare( other ) && ( position() == other->position() ) && ( titleText() == other->titleText() ) && ( titleTextAttributes() == other->titleTextAttributes() ); } void CartesianAxis::coordinateSystemChanged() { layoutPlanes(); } void CartesianAxis::setTitleText( const QString& text ) { d->titleText = text; setCachedSizeDirty(); layoutPlanes(); } QString CartesianAxis::titleText() const { return d->titleText; } void CartesianAxis::setTitleTextAttributes( const TextAttributes &a ) { d->titleTextAttributes = a; d->useDefaultTextAttributes = false; setCachedSizeDirty(); layoutPlanes(); } TextAttributes CartesianAxis::titleTextAttributes() const { if ( hasDefaultTitleTextAttributes() ) { TextAttributes ta( textAttributes() ); Measure me( ta.fontSize() ); me.setValue( me.value() * 1.5 ); ta.setFontSize( me ); return ta; } return d->titleTextAttributes; } void CartesianAxis::resetTitleTextAttributes() { d->useDefaultTextAttributes = true; setCachedSizeDirty(); layoutPlanes(); } bool CartesianAxis::hasDefaultTitleTextAttributes() const { return d->useDefaultTextAttributes; } void CartesianAxis::setPosition( Position p ) { if ( d->position == p ) { return; } d->position = p; // Invalidating size is not always necessary if both old and new positions are horizontal or both // vertical, but in practice there could be small differences due to who-knows-what, so always adapt // to the possibly new size. Changing position is expensive anyway. setCachedSizeDirty(); layoutPlanes(); } #if defined(Q_COMPILER_MANGLES_RETURN_TYPE) const #endif CartesianAxis::Position CartesianAxis::position() const { return d->position; } void CartesianAxis::layoutPlanes() { if ( ! d->diagram() || ! d->diagram()->coordinatePlane() ) { return; } AbstractCoordinatePlane* plane = d->diagram()->coordinatePlane(); if ( plane ) { plane->layoutPlanes(); } } static bool referenceDiagramIsBarDiagram( const AbstractDiagram * diagram ) { const AbstractCartesianDiagram * dia = qobject_cast< const AbstractCartesianDiagram * >( diagram ); if ( dia && dia->referenceDiagram() ) dia = dia->referenceDiagram(); return qobject_cast< const BarDiagram* >( dia ) != 0; } static bool referenceDiagramNeedsCenteredAbscissaTicks( const AbstractDiagram *diagram ) { const AbstractCartesianDiagram * dia = qobject_cast< const AbstractCartesianDiagram * >( diagram ); if ( dia && dia->referenceDiagram() ) dia = dia->referenceDiagram(); if ( qobject_cast< const BarDiagram* >( dia ) ) return true; if ( qobject_cast< const StockDiagram* >( dia ) ) return true; const LineDiagram * lineDiagram = qobject_cast< const LineDiagram* >( dia ); return lineDiagram && lineDiagram->centerDataPoints(); } bool CartesianAxis::isAbscissa() const { const Qt::Orientation diagramOrientation = referenceDiagramIsBarDiagram( d->diagram() ) ? ( ( BarDiagram* )( d->diagram() ) )->orientation() : Qt::Vertical; return diagramOrientation == Qt::Vertical ? position() == Bottom || position() == Top : position() == Left || position() == Right; } bool CartesianAxis::isOrdinate() const { return !isAbscissa(); } void CartesianAxis::paint( QPainter* painter ) { if ( !d->diagram() || !d->diagram()->coordinatePlane() ) { return; } PaintContext ctx; ctx.setPainter ( painter ); AbstractCoordinatePlane *const plane = d->diagram()->coordinatePlane(); ctx.setCoordinatePlane( plane ); ctx.setRectangle( QRectF( areaGeometry() ) ); PainterSaver painterSaver( painter ); // enable clipping only when required due to zoom, because it slows down painting // (the alternative to clipping when zoomed in requires much more work to paint just the right area) const qreal zoomFactor = d->isVertical() ? plane->zoomFactorY() : plane->zoomFactorX(); if ( zoomFactor > 1.0 ) { painter->setClipRegion( areaGeometry().adjusted( - d->amountOfLeftOverlap - 1, - d->amountOfTopOverlap - 1, d->amountOfRightOverlap + 1, d->amountOfBottomOverlap + 1 ) ); } paintCtx( &ctx ); } const TextAttributes CartesianAxis::Private::titleTextAttributesWithAdjustedRotation() const { TextAttributes titleTA( titleTextAttributes ); int rotation = titleTA.rotation(); if ( position == Left || position == Right ) { rotation += 270; } if ( rotation >= 360 ) { rotation -= 360; } // limit the allowed values to 0, 90, 180, 270 rotation = ( rotation / 90 ) * 90; titleTA.setRotation( rotation ); return titleTA; } QString CartesianAxis::Private::customizedLabelText( const QString& text, Qt::Orientation orientation, qreal value ) const { // ### like in the old code, using int( value ) as column number... QString withUnits = diagram()->unitPrefix( int( value ), orientation, true ) + text + diagram()->unitSuffix( int( value ), orientation, true ); return axis()->customizedLabel( withUnits ); } void CartesianAxis::setTitleSpace( qreal axisTitleSpace ) { d->axisTitleSpace = axisTitleSpace; } qreal CartesianAxis::titleSpace() const { return d->axisTitleSpace; } void CartesianAxis::setTitleSize( qreal value ) { Q_UNUSED( value ) // ### remove me } qreal CartesianAxis::titleSize() const { // ### remove me return 1.0; } void CartesianAxis::Private::drawTitleText( QPainter* painter, CartesianCoordinatePlane* plane, const QRect& geoRect ) const { const TextAttributes titleTA( titleTextAttributesWithAdjustedRotation() ); if ( titleTA.isVisible() ) { TextLayoutItem titleItem( titleText, titleTA, plane->parent(), KChartEnums::MeasureOrientationMinimum, Qt::AlignHCenter | Qt::AlignVCenter ); QPointF point; QSize size = titleItem.sizeHint(); switch ( position ) { case Top: point.setX( geoRect.left() + geoRect.width() / 2 ); point.setY( geoRect.top() + ( size.height() / 2 ) / axisTitleSpace ); size.setWidth( qMin( size.width(), axis()->geometry().width() ) ); break; case Bottom: point.setX( geoRect.left() + geoRect.width() / 2 ); point.setY( geoRect.bottom() - ( size.height() / 2 ) / axisTitleSpace ); size.setWidth( qMin( size.width(), axis()->geometry().width() ) ); break; case Left: point.setX( geoRect.left() + ( size.width() / 2 ) / axisTitleSpace ); point.setY( geoRect.top() + geoRect.height() / 2 ); size.setHeight( qMin( size.height(), axis()->geometry().height() ) ); break; case Right: point.setX( geoRect.right() - ( size.width() / 2 ) / axisTitleSpace ); point.setY( geoRect.top() + geoRect.height() / 2 ); size.setHeight( qMin( size.height(), axis()->geometry().height() ) ); break; } const PainterSaver painterSaver( painter ); painter->setClipping( false ); painter->translate( point ); titleItem.setGeometry( QRect( QPoint( -size.width() / 2, -size.height() / 2 ), size ) ); titleItem.paint( painter ); } } bool CartesianAxis::Private::isVertical() const { return axis()->isAbscissa() == AbstractDiagram::Private::get( diagram() )->isTransposed(); } void CartesianAxis::paintCtx( PaintContext* context ) { Q_ASSERT_X ( d->diagram(), "CartesianAxis::paint", "Function call not allowed: The axis is not assigned to any diagram." ); CartesianCoordinatePlane* plane = dynamic_cast( context->coordinatePlane() ); Q_ASSERT_X ( plane, "CartesianAxis::paint", "Bad function call: PaintContext::coordinatePlane() NOT a cartesian plane." ); // note: Not having any data model assigned is no bug // but we can not draw an axis then either. if ( !d->diagram()->model() ) { return; } const bool centerTicks = referenceDiagramNeedsCenteredAbscissaTicks( d->diagram() ) && isAbscissa(); XySwitch geoXy( d->isVertical() ); QPainter* const painter = context->painter(); // determine the position of the axis (also required for labels) and paint it qreal transversePosition = signalingNaN; // in data space // the next one describes an additional shift in screen space; it is unfortunately required to // make axis sharing work, which uses the areaGeometry() to override the position of the axis. qreal transverseScreenSpaceShift = signalingNaN; { // determine the unadulterated position in screen space DataDimension dimX = plane->gridDimensionsList().first(); DataDimension dimY = plane->gridDimensionsList().last(); QPointF start( dimX.start, dimY.start ); QPointF end( dimX.end, dimY.end ); // consider this: you can turn a diagonal line into a horizontal or vertical line on any // edge by changing just one of its four coordinates. switch ( position() ) { case CartesianAxis::Bottom: end.setY( dimY.start ); break; case CartesianAxis::Top: start.setY( dimY.end ); break; case CartesianAxis::Left: end.setX( dimX.start ); break; case CartesianAxis::Right: start.setX( dimX.end ); break; } transversePosition = geoXy( start.y(), start.x() ); QPointF transStart = plane->translate( start ); QPointF transEnd = plane->translate( end ); // an externally set areaGeometry() moves the axis position transversally; the shift is // nonzero only when this is a shared axis const QRect geo = areaGeometry(); switch ( position() ) { case CartesianAxis::Bottom: transverseScreenSpaceShift = geo.top() - transStart.y(); break; case CartesianAxis::Top: transverseScreenSpaceShift = geo.bottom() - transStart.y(); break; case CartesianAxis::Left: transverseScreenSpaceShift = geo.right() - transStart.x(); break; case CartesianAxis::Right: transverseScreenSpaceShift = geo.left() - transStart.x(); break; } geoXy.lvalue( transStart.ry(), transStart.rx() ) += transverseScreenSpaceShift; geoXy.lvalue( transEnd.ry(), transEnd.rx() ) += transverseScreenSpaceShift; if ( rulerAttributes().showRulerLine() ) { bool clipSaved = context->painter()->hasClipping(); painter->setClipping( false ); painter->drawLine( transStart, transEnd ); painter->setClipping( clipSaved ); } } // paint ticks and labels TextAttributes labelTA = textAttributes(); RulerAttributes rulerAttr = rulerAttributes(); int labelThinningFactor = 1; // TODO: label thinning also when grid line distance < 4 pixels, not only when labels collide TextLayoutItem *tickLabel = new TextLayoutItem( QString(), labelTA, plane->parent(), KChartEnums::MeasureOrientationMinimum, Qt::AlignLeft ); TextLayoutItem *prevTickLabel = new TextLayoutItem( QString(), labelTA, plane->parent(), KChartEnums::MeasureOrientationMinimum, Qt::AlignLeft ); QPointF prevTickLabelPos; enum { Layout = 0, Painting, Done }; for ( int step = labelTA.isVisible() ? Layout : Painting; step < Done; step++ ) { bool skipFirstTick = !rulerAttr.showFirstTick(); bool isFirstLabel = true; for ( TickIterator it( this, plane, labelThinningFactor, centerTicks ); !it.isAtEnd(); ++it ) { if ( skipFirstTick ) { skipFirstTick = false; continue; } const qreal drawPos = it.position() + ( centerTicks ? 0.5 : 0. ); QPointF onAxis = plane->translate( geoXy( QPointF( drawPos, transversePosition ) , QPointF( transversePosition, drawPos ) ) ); geoXy.lvalue( onAxis.ry(), onAxis.rx() ) += transverseScreenSpaceShift; const bool isOutwardsPositive = position() == Bottom || position() == Right; // paint the tick mark QPointF tickEnd = onAxis; qreal tickLen = it.type() == TickIterator::CustomTick ? d->customTickLength : tickLength( it.type() == TickIterator::MinorTick ); geoXy.lvalue( tickEnd.ry(), tickEnd.rx() ) += isOutwardsPositive ? tickLen : -tickLen; // those adjustments are required to paint the ticks exactly on the axis and of the right length if ( position() == Top ) { onAxis.ry() += 1; tickEnd.ry() += 1; } else if ( position() == Left ) { tickEnd.rx() += 1; } if ( step == Painting ) { painter->save(); if ( rulerAttr.hasTickMarkPenAt( it.position() ) ) { painter->setPen( rulerAttr.tickMarkPen( it.position() ) ); } else { painter->setPen( it.type() == TickIterator::MinorTick ? rulerAttr.minorTickMarkPen() : rulerAttr.majorTickMarkPen() ); } painter->drawLine( onAxis, tickEnd ); painter->restore(); } if ( it.text().isEmpty() || !labelTA.isVisible() ) { // the following code in the loop is only label painting, so skip it continue; } // paint the label QString text = it.text(); if ( it.type() == TickIterator::MajorTick ) { // add unit prefixes and suffixes, then customize text = d->customizedLabelText( text, geoXy( Qt::Horizontal, Qt::Vertical ), it.position() ); } else if ( it.type() == TickIterator::MajorTickHeaderDataLabel ) { // unit prefixes and suffixes have already been added in this case - only customize text = customizedLabel( text ); } tickLabel->setText( text ); QSizeF size = QSizeF( tickLabel->sizeHint() ); QPolygon labelPoly = tickLabel->boundingPolygon(); Q_ASSERT( labelPoly.count() == 4 ); // for alignment, find the label polygon edge "most parallel" and closest to the axis int axisAngle = 0; switch ( position() ) { case Bottom: axisAngle = 0; break; case Top: axisAngle = 180; break; case Right: axisAngle = 270; break; case Left: axisAngle = 90; break; default: Q_ASSERT( false ); } // the left axis is not actually pointing down and the top axis not actually pointing // left, but their corresponding closest edges of a rectangular unrotated label polygon are. int relAngle = axisAngle - labelTA.rotation() + 45; if ( relAngle < 0 ) { relAngle += 360; } int polyCorner1 = relAngle / 90; QPoint p1 = labelPoly.at( polyCorner1 ); QPoint p2 = labelPoly.at( polyCorner1 == 3 ? 0 : ( polyCorner1 + 1 ) ); QPointF labelPos = tickEnd; qreal labelMargin = rulerAttr.labelMargin(); if ( labelMargin < 0 ) { labelMargin = QFontMetricsF( tickLabel->realFont() ).height() * 0.5; } labelMargin -= tickLabel->marginWidth(); // make up for the margin that's already there switch ( position() ) { case Left: labelPos += QPointF( -size.width() - labelMargin, -0.45 * size.height() - 0.5 * ( p1.y() + p2.y() ) ); break; case Right: labelPos += QPointF( labelMargin, -0.45 * size.height() - 0.5 * ( p1.y() + p2.y() ) ); break; case Top: labelPos += QPointF( -0.45 * size.width() - 0.5 * ( p1.x() + p2.x() ), -size.height() - labelMargin ); break; case Bottom: labelPos += QPointF( -0.45 * size.width() - 0.5 * ( p1.x() + p2.x() ), labelMargin ); break; } tickLabel->setGeometry( QRect( labelPos.toPoint(), size.toSize() ) ); if ( step == Painting ) { tickLabel->paint( painter ); } // collision check the current label against the previous one // like in the old code, we don't shorten or decimate labels if they are already the // manual short type, or if they are the manual long type and on the vertical axis // ### they can still collide though, especially when they're rotated! if ( step == Layout ) { int spaceSavingRotation = geoXy( 270, 0 ); bool canRotate = labelTA.autoRotate() && labelTA.rotation() != spaceSavingRotation; const bool canShortenLabels = !geoXy.isY && it.type() == TickIterator::MajorTickManualLong && it.hasShorterLabels(); bool collides = false; if ( it.type() == TickIterator::MajorTick || it.type() == TickIterator::MajorTickHeaderDataLabel || canShortenLabels || canRotate ) { if ( isFirstLabel ) { isFirstLabel = false; } else { collides = tickLabel->intersects( *prevTickLabel, labelPos, prevTickLabelPos ); qSwap( prevTickLabel, tickLabel ); } prevTickLabelPos = labelPos; } if ( collides ) { // to make room, we try in order: shorten, rotate, decimate if ( canRotate && !canShortenLabels ) { labelTA.setRotation( spaceSavingRotation ); // tickLabel will be reused in the next round tickLabel->setTextAttributes( labelTA ); } else { labelThinningFactor++; } step--; // relayout break; } } } } delete tickLabel; tickLabel = 0; delete prevTickLabel; prevTickLabel = 0; if ( ! titleText().isEmpty() ) { d->drawTitleText( painter, plane, geometry() ); } } /* pure virtual in QLayoutItem */ bool CartesianAxis::isEmpty() const { return false; // if the axis exists, it has some (perhaps default) content } /* pure virtual in QLayoutItem */ Qt::Orientations CartesianAxis::expandingDirections() const { Qt::Orientations ret; switch ( position() ) { case Bottom: case Top: ret = Qt::Horizontal; break; case Left: case Right: ret = Qt::Vertical; break; default: Q_ASSERT( false ); break; }; return ret; } void CartesianAxis::setCachedSizeDirty() const { d->cachedMaximumSize = QSize(); } /* pure virtual in QLayoutItem */ QSize CartesianAxis::maximumSize() const { if ( ! d->cachedMaximumSize.isValid() ) d->cachedMaximumSize = d->calculateMaximumSize(); return d->cachedMaximumSize; } QSize CartesianAxis::Private::calculateMaximumSize() const { if ( !diagram() ) { return QSize(); } CartesianCoordinatePlane* plane = dynamic_cast< CartesianCoordinatePlane* >( diagram()->coordinatePlane() ); Q_ASSERT( plane ); QObject* refArea = plane->parent(); const bool centerTicks = referenceDiagramNeedsCenteredAbscissaTicks( diagram() ) && axis()->isAbscissa(); // we ignore: // - label thinning (expensive, not worst case and we want worst case) // - label autorotation (expensive, obscure feature(?)) // - axis length (it is determined by the plane / diagram / chart anyway) // - the title's influence on axis length; this one might be TODO. See KDCH-863. XySwitch geoXy( isVertical() ); qreal size = 0; // this is the size transverse to the axis direction // the following variables describe how much the first and last label stick out over the axis // area, so that the geometry of surrounding layout items can be adjusted to make room. qreal startOverhang = 0.0; qreal endOverhang = 0.0; if ( mAxis->textAttributes().isVisible() ) { // these four are used just to calculate startOverhang and endOverhang qreal lowestLabelPosition = signalingNaN; qreal highestLabelPosition = signalingNaN; qreal lowestLabelLongitudinalSize = signalingNaN; qreal highestLabelLongitudinalSize = signalingNaN; TextLayoutItem tickLabel( QString(), mAxis->textAttributes(), refArea, KChartEnums::MeasureOrientationMinimum, Qt::AlignLeft ); const RulerAttributes rulerAttr = mAxis->rulerAttributes(); bool showFirstTick = rulerAttr.showFirstTick(); for ( TickIterator it( axis(), plane, 1, centerTicks ); !it.isAtEnd(); ++it ) { const qreal drawPos = it.position() + ( centerTicks ? 0.5 : 0. ); if ( !showFirstTick ) { showFirstTick = true; continue; } qreal labelSizeTransverse = 0.0; qreal labelMargin = 0.0; QString text = it.text(); if ( !text.isEmpty() ) { QPointF labelPosition = plane->translate( QPointF( geoXy( drawPos, 1.0 ), geoXy( 1.0, drawPos ) ) ); highestLabelPosition = geoXy( labelPosition.x(), labelPosition.y() ); if ( it.type() == TickIterator::MajorTick ) { // add unit prefixes and suffixes, then customize text = customizedLabelText( text, geoXy( Qt::Horizontal, Qt::Vertical ), it.position() ); } else if ( it.type() == TickIterator::MajorTickHeaderDataLabel ) { // unit prefixes and suffixes have already been added in this case - only customize text = axis()->customizedLabel( text ); } tickLabel.setText( text ); QSize sz = tickLabel.sizeHint(); highestLabelLongitudinalSize = geoXy( sz.width(), sz.height() ); if ( ISNAN( lowestLabelLongitudinalSize ) ) { lowestLabelLongitudinalSize = highestLabelLongitudinalSize; lowestLabelPosition = highestLabelPosition; } labelSizeTransverse = geoXy( sz.height(), sz.width() ); labelMargin = rulerAttr.labelMargin(); if ( labelMargin < 0 ) { labelMargin = QFontMetricsF( tickLabel.realFont() ).height() * 0.5; } labelMargin -= tickLabel.marginWidth(); // make up for the margin that's already there } qreal tickLength = it.type() == TickIterator::CustomTick ? customTickLength : axis()->tickLength( it.type() == TickIterator::MinorTick ); size = qMax( size, tickLength + labelMargin + labelSizeTransverse ); } const DataDimension dimX = plane->gridDimensionsList().first(); const DataDimension dimY = plane->gridDimensionsList().last(); QPointF pt = plane->translate( QPointF( dimX.start, dimY.start ) ); const qreal lowestPosition = geoXy( pt.x(), pt.y() ); pt = plane->translate( QPointF( dimX.end, dimY.end ) ); const qreal highestPosition = geoXy( pt.x(), pt.y() ); // the geoXy( 1.0, -1.0 ) here is necessary because Qt's y coordinate is inverted startOverhang = qMax( 0.0, ( lowestPosition - lowestLabelPosition ) * geoXy( 1.0, -1.0 ) + lowestLabelLongitudinalSize * 0.5 ); endOverhang = qMax( 0.0, ( highestLabelPosition - highestPosition ) * geoXy( 1.0, -1.0 ) + highestLabelLongitudinalSize * 0.5 ); } amountOfLeftOverlap = geoXy( startOverhang, 0.0 ); amountOfRightOverlap = geoXy( endOverhang, 0.0 ); amountOfBottomOverlap = geoXy( 0.0, startOverhang ); amountOfTopOverlap = geoXy( 0.0, endOverhang ); const TextAttributes titleTA = titleTextAttributesWithAdjustedRotation(); if ( titleTA.isVisible() && !axis()->titleText().isEmpty() ) { TextLayoutItem title( axis()->titleText(), titleTA, refArea, KChartEnums::MeasureOrientationMinimum, Qt::AlignHCenter | Qt::AlignVCenter ); QFontMetricsF titleFM( title.realFont(), GlobalMeasureScaling::paintDevice() ); size += geoXy( titleFM.height() * 0.33, titleFM.averageCharWidth() * 0.55 ); // spacing size += geoXy( title.sizeHint().height(), title.sizeHint().width() ); } // the size parallel to the axis direction is not determined by us, so we just return 1 return QSize( geoXy( 1, int( size ) ), geoXy( int ( size ), 1 ) ); } /* pure virtual in QLayoutItem */ QSize CartesianAxis::minimumSize() const { return maximumSize(); } /* pure virtual in QLayoutItem */ QSize CartesianAxis::sizeHint() const { return maximumSize(); } /* pure virtual in QLayoutItem */ void CartesianAxis::setGeometry( const QRect& r ) { if ( d->geometry != r ) { d->geometry = r; setCachedSizeDirty(); } } /* pure virtual in QLayoutItem */ QRect CartesianAxis::geometry() const { return d->geometry; } void CartesianAxis::setCustomTickLength( int value ) { if ( d->customTickLength == value ) { return; } d->customTickLength = value; setCachedSizeDirty(); layoutPlanes(); } int CartesianAxis::customTickLength() const { return d->customTickLength; } int CartesianAxis::tickLength( bool subUnitTicks ) const { const RulerAttributes& rulerAttr = rulerAttributes(); return subUnitTicks ? rulerAttr.minorTickMarkLength() : rulerAttr.majorTickMarkLength(); } QMap< qreal, QString > CartesianAxis::annotations() const { return d->annotations; } void CartesianAxis::setAnnotations( const QMap< qreal, QString >& annotations ) { if ( d->annotations == annotations ) return; d->annotations = annotations; setCachedSizeDirty(); layoutPlanes(); } QList< qreal > CartesianAxis::customTicks() const { return d->customTicksPositions; } void CartesianAxis::setCustomTicks( const QList< qreal >& customTicksPositions ) { if ( d->customTicksPositions == customTicksPositions ) return; d->customTicksPositions = customTicksPositions; setCachedSizeDirty(); layoutPlanes(); } #if !defined(QT_NO_DEBUG_STREAM) QDebug operator<<(QDebug dbg, KChart::CartesianAxis::Position pos) { switch (pos) { case KChart::CartesianAxis::Bottom: dbg << "KChart::CartesianAxis::Bottom"; break; case KChart::CartesianAxis::Top: dbg << "KChart::CartesianAxis::Top"; break; case KChart::CartesianAxis::Left: dbg << "KChart::CartesianAxis::Left"; break; case KChart::CartesianAxis::Right: dbg << "KChart::CartesianAxis::Right"; break; default: dbg << "KChart::CartesianAxis::Invalid"; break; } return dbg; } #endif diff --git a/src/KChart/Cartesian/KChartCartesianCoordinatePlane.cpp b/src/KChart/Cartesian/KChartCartesianCoordinatePlane.cpp index 629029f..056875d 100644 --- a/src/KChart/Cartesian/KChartCartesianCoordinatePlane.cpp +++ b/src/KChart/Cartesian/KChartCartesianCoordinatePlane.cpp @@ -1,920 +1,920 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "KChartCartesianCoordinatePlane.h" #include "KChartCartesianCoordinatePlane_p.h" #include "KChartAbstractDiagram.h" #include "KChartAbstractDiagram_p.h" #include "KChartAbstractCartesianDiagram.h" #include "CartesianCoordinateTransformation.h" #include "KChartGridAttributes.h" #include "KChartPaintContext.h" #include "KChartPainterSaver_p.h" #include "KChartBarDiagram.h" #include "KChartStockDiagram.h" #include #include #include #include #include #include using namespace KChart; #define d d_func() CartesianCoordinatePlane::Private::Private() : AbstractCoordinatePlane::Private() , bPaintIsRunning( false ) , hasOwnGridAttributesHorizontal( false ) , hasOwnGridAttributesVertical( false ) , isometricScaling( false ) , horizontalMin( 0 ) , horizontalMax( 0 ) , verticalMin( 0 ) , verticalMax( 0 ) , autoAdjustHorizontalRangeToData( 67 ) , autoAdjustVerticalRangeToData( 67 ) , autoAdjustGridToZoom( true ) , fixedDataCoordinateSpaceRelation( false ) , xAxisStartAtZero( true ) , reverseVerticalPlane( false ) , reverseHorizontalPlane( false ) { } CartesianCoordinatePlane::CartesianCoordinatePlane( Chart* parent ) : AbstractCoordinatePlane( new Private(), parent ) { // this bloc left empty intentionally } CartesianCoordinatePlane::~CartesianCoordinatePlane() { // this bloc left empty intentionally } void CartesianCoordinatePlane::init() { // this bloc left empty intentionally } void CartesianCoordinatePlane::addDiagram( AbstractDiagram* diagram ) { Q_ASSERT_X( dynamic_cast( diagram ), "CartesianCoordinatePlane::addDiagram", "Only cartesian " "diagrams can be added to a cartesian coordinate plane!" ); AbstractCoordinatePlane::addDiagram( diagram ); - connect( diagram, SIGNAL( layoutChanged ( AbstractDiagram* ) ), - SLOT( slotLayoutChanged( AbstractDiagram* ) ) ); + connect( diagram, SIGNAL(layoutChanged(AbstractDiagram*)), + SLOT(slotLayoutChanged(AbstractDiagram*)) ); - connect( diagram, SIGNAL( propertiesChanged() ), this, SIGNAL( propertiesChanged() ) ); + connect( diagram, SIGNAL(propertiesChanged()), this, SIGNAL(propertiesChanged()) ); } void CartesianCoordinatePlane::paint( QPainter* painter ) { // prevent recursive call: if ( d->bPaintIsRunning ) { return; } d->bPaintIsRunning = true; AbstractDiagramList diags = diagrams(); if ( !diags.isEmpty() ) { PaintContext ctx; ctx.setPainter ( painter ); ctx.setCoordinatePlane ( this ); const QRectF drawArea( drawingArea() ); ctx.setRectangle ( drawArea ); // enabling clipping so that we're not drawing outside PainterSaver painterSaver( painter ); QRect clipRect = drawArea.toRect().adjusted( -1, -1, 1, 1 ); QRegion clipRegion( clipRect ); painter->setClipRegion( clipRegion ); // paint the coordinate system rulers: d->grid->drawGrid( &ctx ); // paint the diagrams: for ( int i = 0; i < diags.size(); i++ ) { if ( diags[i]->isHidden() ) { continue; } bool doDumpPaintTime = AbstractDiagram::Private::get( diags[ i ] )->doDumpPaintTime; QTime stopWatch; if ( doDumpPaintTime ) { stopWatch.start(); } PainterSaver diagramPainterSaver( painter ); diags[i]->paint( &ctx ); if ( doDumpPaintTime ) { qDebug() << "Painting diagram" << i << "took" << stopWatch.elapsed() << "milliseconds"; } } } d->bPaintIsRunning = false; } void CartesianCoordinatePlane::slotLayoutChanged( AbstractDiagram* ) { layoutDiagrams(); } QRectF CartesianCoordinatePlane::getRawDataBoundingRectFromDiagrams() const { // determine unit of the rectangles of all involved diagrams: qreal minX = 0; qreal maxX = 0; qreal minY = 0; qreal maxY = 0; bool bStarting = true; Q_FOREACH( const AbstractDiagram* diagram, diagrams() ) { QPair dataBoundariesPair = diagram->dataBoundaries(); //qDebug() << "CartesianCoordinatePlane::getRawDataBoundingRectFromDiagrams()\ngets diagram->dataBoundaries: " << dataBoundariesPair.first << dataBoundariesPair.second; if ( bStarting || dataBoundariesPair.first.x() < minX ) minX = dataBoundariesPair.first.x(); if ( bStarting || dataBoundariesPair.first.y() < minY ) minY = dataBoundariesPair.first.y(); if ( bStarting || dataBoundariesPair.second.x() > maxX ) maxX = dataBoundariesPair.second.x(); if ( bStarting || dataBoundariesPair.second.y() > maxY ) maxY = dataBoundariesPair.second.y(); bStarting = false; } //qDebug() << "CartesianCoordinatePlane::getRawDataBoundingRectFromDiagrams()\nreturns data boundaries: " << QRectF( QPointF(minX, minY), QSizeF(maxX - minX, maxY - minY) ); QRectF dataBoundingRect; dataBoundingRect.setBottomLeft( QPointF( minX, minY ) ); dataBoundingRect.setTopRight( QPointF( maxX, maxY ) ); return dataBoundingRect; } QRectF CartesianCoordinatePlane::adjustedToMaxEmptyInnerPercentage( const QRectF& r, unsigned int percentX, unsigned int percentY ) const { QRectF ret = r; if ( ( axesCalcModeX() != Logarithmic || r.left() < 0.0 ) && percentX > 0 && percentX != 100 ) { const bool isPositive = r.left() >= 0; if ( ( r.right() >= 0 ) == isPositive ) { qreal upperBound = qMax( r.left(), r.right() ); qreal lowerBound = qMin( r.left(), r.right() ); qreal innerBound = isPositive ? lowerBound : upperBound; qreal outerBound = isPositive ? upperBound : lowerBound; if ( innerBound / outerBound * 100 <= percentX && d->xAxisStartAtZero ) { if ( isPositive ) { ret.setLeft( 0.0 ); } else { ret.setRight( 0.0 ); } } } } // ### this doesn't seem to take into account that Qt's y coordinate is inverted if ( ( axesCalcModeY() != Logarithmic || r.bottom() < 0.0 ) && percentY > 0 && percentY != 100 ) { const bool isPositive = r.bottom() >= 0; if ( ( r.top() >= 0 ) == isPositive ) { qreal upperBound = qMax( r.top(), r.bottom() ); qreal lowerBound = qMin( r.top(), r.bottom() ); const qreal innerBound = isPositive ? lowerBound : upperBound; const qreal outerBound = isPositive ? upperBound : lowerBound; if ( innerBound / outerBound * 100 <= percentY ) { if ( isPositive ) { ret.setBottom( 0.0 ); } else { ret.setTop( 0.0 ); } } } } return ret; } QRectF CartesianCoordinatePlane::calculateRawDataBoundingRect() const { // are manually set ranges to be applied? const bool bAutoAdjustHorizontalRange = d->autoAdjustHorizontalRangeToData < 100; const bool bAutoAdjustVerticalRange = d->autoAdjustVerticalRangeToData < 100; const bool bHardHorizontalRange = (!bAutoAdjustHorizontalRange) && (d->horizontalMin != d->horizontalMax || (ISNAN(d->horizontalMin) != ISNAN(d->horizontalMax))); const bool bHardVerticalRange = (!bAutoAdjustVerticalRange) && (d->verticalMin != d->verticalMax || (ISNAN(d->verticalMin) != ISNAN(d->verticalMax))); QRectF dataBoundingRect; // if custom boundaries are set on the plane, use them if ( bHardHorizontalRange && bHardVerticalRange ) { dataBoundingRect.setLeft( d->horizontalMin ); dataBoundingRect.setRight( d->horizontalMax ); dataBoundingRect.setBottom( d->verticalMin ); dataBoundingRect.setTop( d->verticalMax ); } else { // determine unit of the rectangles of all involved diagrams: dataBoundingRect = getRawDataBoundingRectFromDiagrams(); if ( bHardHorizontalRange ) { if (!ISNAN(d->horizontalMin)) dataBoundingRect.setLeft( d->horizontalMin ); if (!ISNAN(d->horizontalMax)) dataBoundingRect.setRight( d->horizontalMax ); } if ( bHardVerticalRange ) { if (!ISNAN(d->verticalMin)) dataBoundingRect.setBottom( d->verticalMin ); if (!ISNAN(d->verticalMax)) dataBoundingRect.setTop( d->verticalMax ); } } // recalculate the bounds, if automatic adjusting of ranges is desired AND // both bounds are at the same side of the zero line dataBoundingRect = adjustedToMaxEmptyInnerPercentage( dataBoundingRect, d->autoAdjustHorizontalRangeToData, d->autoAdjustVerticalRangeToData ); if ( bAutoAdjustHorizontalRange ) { const_cast( this )->d->horizontalMin = dataBoundingRect.left(); const_cast( this )->d->horizontalMax = dataBoundingRect.right(); } if ( bAutoAdjustVerticalRange ) { const_cast( this )->d->verticalMin = dataBoundingRect.bottom(); const_cast( this )->d->verticalMax = dataBoundingRect.top(); } // qDebug() << Q_FUNC_INFO << dataBoundingRect; return dataBoundingRect; } DataDimensionsList CartesianCoordinatePlane::getDataDimensionsList() const { const AbstractCartesianDiagram* dgr = diagrams().isEmpty() ? 0 : qobject_cast< const AbstractCartesianDiagram* >( diagrams().first() ); if ( dgr && dgr->referenceDiagram() ) { dgr = dgr->referenceDiagram(); } const BarDiagram *barDiagram = qobject_cast< const BarDiagram* >( dgr ); const StockDiagram *stockDiagram = qobject_cast< const StockDiagram* >( dgr ); // note: // It does make sense to retrieve the orientation from the first diagram. This is because // a coordinate plane can either be for horizontal *or* for vertical diagrams. Both at the // same time won't work, and thus the orientation for all diagrams is the same as for the first one. const Qt::Orientation diagramOrientation = barDiagram != 0 ? barDiagram->orientation() : Qt::Vertical; const bool diagramIsVertical = diagramOrientation == Qt::Vertical; DataDimensionsList l; if ( dgr ) { const QRectF r( calculateRawDataBoundingRect() ); // We do not access d->gridAttributesHorizontal/Vertical here, but we use the getter function, // to get the global attrs, if no special ones have been set for the given orientation. const GridAttributes gaH( gridAttributes( Qt::Horizontal ) ); const GridAttributes gaV( gridAttributes( Qt::Vertical ) ); // append the first dimension: for Abscissa axes l.append( DataDimension( r.left(), r.right(), diagramIsVertical ? ( !stockDiagram && dgr->datasetDimension() > 1 ) : true, axesCalcModeX(), gaH.gridGranularitySequence(), gaH.gridStepWidth(), gaH.gridSubStepWidth() ) ); // append the second dimension: for Ordinate axes l.append( DataDimension( r.bottom(), r.top(), diagramIsVertical ? true : ( dgr->datasetDimension() > 1 ), axesCalcModeY(), gaV.gridGranularitySequence(), gaV.gridStepWidth(), gaV.gridSubStepWidth() ) ); } else { l.append( DataDimension() ); // This gets us the default 1..0 / 1..0 grid l.append( DataDimension() ); // shown, if there is no diagram on this plane. } return l; } QRectF CartesianCoordinatePlane::drawingArea() const { // the rectangle the diagrams cover in the *plane*: // We reserve 1px on each side for antialiased drawing, and respect the way QPainter calculates // the width of a painted rect (the size is the rectangle size plus the pen width). The latter // accounts for another pixel that we subtract from height and width. // This way, most clipping for regular pens should be avoided. When pens with a width larger // than 1 are used, this may not be sufficient. return QRectF( areaGeometry() ).adjusted( 1.0, 1.0, -2.0, -2.0 ); } QRectF CartesianCoordinatePlane::logicalArea() const { if ( d->dimensions.isEmpty() ) return QRectF(); const DataDimension dimX = d->dimensions.first(); const DataDimension dimY = d->dimensions.last(); const QPointF pt( qMin( dimX.start, dimX.end ), qMax( dimY.start, dimY.end ) ); const QSizeF siz( qAbs( dimX.distance() ), -qAbs( dimY.distance() ) ); const QRectF dataBoundingRect( pt, siz ); // determine logical top left, taking the "reverse" options into account const QPointF topLeft( d->reverseHorizontalPlane ? dataBoundingRect.right() : dataBoundingRect.left(), d->reverseVerticalPlane ? dataBoundingRect.bottom() : dataBoundingRect.top() ); const qreal width = dataBoundingRect.width() * ( d->reverseHorizontalPlane ? -1.0 : 1.0 ); const qreal height = dataBoundingRect.height() * ( d->reverseVerticalPlane ? -1.0 : 1.0 ); return QRectF( topLeft, QSizeF( width, height ) ); } QRectF CartesianCoordinatePlane::diagramArea() const { const QRectF logArea( logicalArea() ); QPointF physicalTopLeft = d->coordinateTransformation.translate( logArea.topLeft() ); QPointF physicalBottomRight = d->coordinateTransformation.translate( logArea.bottomRight() ); return QRectF( physicalTopLeft, physicalBottomRight ).normalized(); } QRectF CartesianCoordinatePlane::visibleDiagramArea() const { return diagramArea().intersected( drawingArea() ); } void CartesianCoordinatePlane::layoutDiagrams() { d->dimensions = gridDimensionsList(); Q_ASSERT_X ( d->dimensions.count() == 2, "CartesianCoordinatePlane::layoutDiagrams", "Error: gridDimensionsList() did not return exactly two dimensions." ); // physical area of the plane const QRectF physicalArea( drawingArea() ); // .. in contrast to the logical area const QRectF logArea( logicalArea() ); // TODO: isometric scaling for zooming? // the plane area might have changed, so the zoom values might also be changed handleFixedDataCoordinateSpaceRelation( physicalArea ); d->coordinateTransformation.updateTransform( logArea, physicalArea ); update(); } void CartesianCoordinatePlane::setFixedDataCoordinateSpaceRelation( bool fixed ) { d->fixedDataCoordinateSpaceRelation = fixed; d->fixedDataCoordinateSpaceRelationPinnedSize = QSize(); handleFixedDataCoordinateSpaceRelation( drawingArea() ); } bool CartesianCoordinatePlane::hasFixedDataCoordinateSpaceRelation() const { return d->fixedDataCoordinateSpaceRelation; } void CartesianCoordinatePlane::setXAxisStartAtZero(bool fixedStart) { if (d->xAxisStartAtZero == fixedStart) return; d->xAxisStartAtZero = fixedStart; } bool CartesianCoordinatePlane::xAxisStartAtZero() const { return d->xAxisStartAtZero; } void CartesianCoordinatePlane::handleFixedDataCoordinateSpaceRelation( const QRectF& geometry ) { if ( !d->fixedDataCoordinateSpaceRelation ) { return; } // is the new geometry ok? if ( !geometry.isValid() ) { return; } // note that the pinned size can be invalid even after setting it, if geometry wasn't valid. // this is relevant for the cooperation between this method, setFixedDataCoordinateSpaceRelation(), // and handleFixedDataCoordinateSpaceRelation(). if ( !d->fixedDataCoordinateSpaceRelationPinnedSize.isValid() ) { d->fixedDataCoordinateSpaceRelationPinnedSize = geometry.size(); d->fixedDataCoordinateSpaceRelationPinnedZoom = ZoomParameters( zoomFactorX(), zoomFactorY(), zoomCenter() ); return; } // if the plane size was changed, change zoom factors to keep the diagram size constant if ( d->fixedDataCoordinateSpaceRelationPinnedSize != geometry.size() ) { const qreal widthScaling = d->fixedDataCoordinateSpaceRelationPinnedSize.width() / geometry.width(); const qreal heightScaling = d->fixedDataCoordinateSpaceRelationPinnedSize.height() / geometry.height(); const qreal newZoomX = d->fixedDataCoordinateSpaceRelationPinnedZoom.xFactor * widthScaling; const qreal newZoomY = d->fixedDataCoordinateSpaceRelationPinnedZoom.yFactor * heightScaling; const QPointF newCenter = QPointF( d->fixedDataCoordinateSpaceRelationPinnedZoom.xCenter / widthScaling, d->fixedDataCoordinateSpaceRelationPinnedZoom.yCenter / heightScaling ); // Use these internal methods to avoid sending the propertiesChanged signal more than once bool changed = false; if ( doneSetZoomFactorY( newZoomY ) ) changed = true; if ( doneSetZoomFactorX( newZoomX ) ) changed = true; if ( doneSetZoomCenter( newCenter ) ) changed = true; if ( changed ) emit propertiesChanged(); } } const QPointF CartesianCoordinatePlane::translate( const QPointF& diagramPoint ) const { // Note: We do not test if the point lays inside of the data area, // but we just apply the transformation calculations to the point. // This allows for basic calculations done by the user, see e.g. // the file examples/Lines/BubbleChart/mainwindow.cpp return d->coordinateTransformation.translate( diagramPoint ); } const QPointF CartesianCoordinatePlane::translateBack( const QPointF& screenPoint ) const { return d->coordinateTransformation.translateBack( screenPoint ); } void CartesianCoordinatePlane::setIsometricScaling( bool isOn ) { if ( d->isometricScaling != isOn ) { d->isometricScaling = isOn; layoutDiagrams(); emit propertiesChanged(); } } bool CartesianCoordinatePlane::doesIsometricScaling() const { return d->isometricScaling; } bool CartesianCoordinatePlane::doneSetZoomFactorX( qreal factor ) { if ( d->coordinateTransformation.zoom.xFactor == factor ) { return false; } d->coordinateTransformation.zoom.xFactor = factor; if ( d->autoAdjustGridToZoom ) { d->grid->setNeedRecalculate(); } return true; } bool CartesianCoordinatePlane::doneSetZoomFactorY( qreal factor ) { if ( d->coordinateTransformation.zoom.yFactor == factor ) { return false; } d->coordinateTransformation.zoom.yFactor = factor; if ( d->autoAdjustGridToZoom ) { d->grid->setNeedRecalculate(); } return true; } bool CartesianCoordinatePlane::doneSetZoomCenter( const QPointF& point ) { if ( d->coordinateTransformation.zoom.center() == point ) { return false; } d->coordinateTransformation.zoom.setCenter( point ); if ( d->autoAdjustGridToZoom ) { d->grid->setNeedRecalculate(); } return true; } void CartesianCoordinatePlane::setZoomFactors( qreal factorX, qreal factorY ) { if ( doneSetZoomFactorX( factorX ) || doneSetZoomFactorY( factorY ) ) { d->coordinateTransformation.updateTransform( logicalArea(), drawingArea() ); emit propertiesChanged(); } } void CartesianCoordinatePlane::setZoomFactorX( qreal factor ) { if ( doneSetZoomFactorX( factor ) ) { d->coordinateTransformation.updateTransform( logicalArea(), drawingArea() ); emit propertiesChanged(); } } void CartesianCoordinatePlane::setZoomFactorY( qreal factor ) { if ( doneSetZoomFactorY( factor ) ) { d->coordinateTransformation.updateTransform( logicalArea(), drawingArea() ); emit propertiesChanged(); } } void CartesianCoordinatePlane::setZoomCenter( const QPointF& point ) { if ( doneSetZoomCenter( point ) ) { d->coordinateTransformation.updateTransform( logicalArea(), drawingArea() ); emit propertiesChanged(); } } QPointF CartesianCoordinatePlane::zoomCenter() const { return d->coordinateTransformation.zoom.center(); } qreal CartesianCoordinatePlane::zoomFactorX() const { return d->coordinateTransformation.zoom.xFactor; } qreal CartesianCoordinatePlane::zoomFactorY() const { return d->coordinateTransformation.zoom.yFactor; } CartesianCoordinatePlane::AxesCalcMode CartesianCoordinatePlane::axesCalcModeY() const { return d->coordinateTransformation.axesCalcModeY; } CartesianCoordinatePlane::AxesCalcMode CartesianCoordinatePlane::axesCalcModeX() const { return d->coordinateTransformation.axesCalcModeX; } void CartesianCoordinatePlane::setAxesCalcModes( AxesCalcMode mode ) { if ( d->coordinateTransformation.axesCalcModeY != mode || d->coordinateTransformation.axesCalcModeX != mode ) { d->coordinateTransformation.axesCalcModeY = mode; d->coordinateTransformation.axesCalcModeX = mode; emit propertiesChanged(); emit viewportCoordinateSystemChanged(); Q_FOREACH( AbstractDiagram* diag, diagrams() ) slotLayoutChanged( diag ); } } void CartesianCoordinatePlane::setAxesCalcModeY( AxesCalcMode mode ) { if ( d->coordinateTransformation.axesCalcModeY != mode ) { d->coordinateTransformation.axesCalcModeY = mode; emit propertiesChanged(); setGridNeedsRecalculate(); emit viewportCoordinateSystemChanged(); } } void CartesianCoordinatePlane::setAxesCalcModeX( AxesCalcMode mode ) { if ( d->coordinateTransformation.axesCalcModeX != mode ) { d->coordinateTransformation.axesCalcModeX = mode; emit propertiesChanged(); emit viewportCoordinateSystemChanged(); } } namespace { inline bool fuzzyCompare( qreal a, qreal b ) { if ( ISNAN(a) && ISNAN(b) ) return true; if ( qFuzzyIsNull(a) && qFuzzyIsNull(b) ) return true; return qFuzzyCompare( a, b ); } } void CartesianCoordinatePlane::setHorizontalRange( const QPair< qreal, qreal > & range ) { if ( !fuzzyCompare(d->horizontalMin, range.first) || !fuzzyCompare(d->horizontalMax, range.second) ) { d->autoAdjustHorizontalRangeToData = 100; d->horizontalMin = range.first; d->horizontalMax = range.second; layoutDiagrams(); emit propertiesChanged(); emit boundariesChanged(); } } void CartesianCoordinatePlane::setVerticalRange( const QPair< qreal, qreal > & range ) { if ( !fuzzyCompare(d->verticalMin, range.first) || !fuzzyCompare(d->verticalMax, range.second) ) { d->autoAdjustVerticalRangeToData = 100; d->verticalMin = range.first; d->verticalMax = range.second; layoutDiagrams(); emit propertiesChanged(); emit boundariesChanged(); } } QPair< qreal, qreal > CartesianCoordinatePlane::horizontalRange() const { return QPair( d->horizontalMin, d->horizontalMax ); } QPair< qreal, qreal > CartesianCoordinatePlane::verticalRange() const { return QPair( d->verticalMin, d->verticalMax ); } void CartesianCoordinatePlane::adjustRangesToData() { const QRectF dataBoundingRect( getRawDataBoundingRectFromDiagrams() ); d->horizontalMin = dataBoundingRect.left(); d->horizontalMax = dataBoundingRect.right(); d->verticalMin = dataBoundingRect.top(); d->verticalMax = dataBoundingRect.bottom(); layoutDiagrams(); emit propertiesChanged(); } void CartesianCoordinatePlane::adjustHorizontalRangeToData() { const QRectF dataBoundingRect( getRawDataBoundingRectFromDiagrams() ); d->horizontalMin = dataBoundingRect.left(); d->horizontalMax = dataBoundingRect.right(); layoutDiagrams(); emit propertiesChanged(); } void CartesianCoordinatePlane::adjustVerticalRangeToData() { const QRectF dataBoundingRect( getRawDataBoundingRectFromDiagrams() ); d->verticalMin = dataBoundingRect.bottom(); d->verticalMax = dataBoundingRect.top(); layoutDiagrams(); emit propertiesChanged(); } void CartesianCoordinatePlane::setAutoAdjustHorizontalRangeToData( unsigned int percentEmpty ) { if ( d->autoAdjustHorizontalRangeToData != percentEmpty ) { d->autoAdjustHorizontalRangeToData = percentEmpty; d->horizontalMin = 0.0; d->horizontalMax = 0.0; layoutDiagrams(); emit propertiesChanged(); } } void CartesianCoordinatePlane::setAutoAdjustVerticalRangeToData( unsigned int percentEmpty ) { if ( d->autoAdjustVerticalRangeToData != percentEmpty ) { d->autoAdjustVerticalRangeToData = percentEmpty; d->verticalMin = 0.0; d->verticalMax = 0.0; layoutDiagrams(); emit propertiesChanged(); } } unsigned int CartesianCoordinatePlane::autoAdjustHorizontalRangeToData() const { return d->autoAdjustHorizontalRangeToData; } unsigned int CartesianCoordinatePlane::autoAdjustVerticalRangeToData() const { return d->autoAdjustVerticalRangeToData; } void CartesianCoordinatePlane::setGridAttributes( Qt::Orientation orientation, const GridAttributes& a ) { if ( orientation == Qt::Horizontal ) d->gridAttributesHorizontal = a; else d->gridAttributesVertical = a; setHasOwnGridAttributes( orientation, true ); update(); emit propertiesChanged(); } void CartesianCoordinatePlane::resetGridAttributes( Qt::Orientation orientation ) { setHasOwnGridAttributes( orientation, false ); update(); } const GridAttributes CartesianCoordinatePlane::gridAttributes( Qt::Orientation orientation ) const { if ( hasOwnGridAttributes( orientation ) ) { if ( orientation == Qt::Horizontal ) return d->gridAttributesHorizontal; else return d->gridAttributesVertical; } else { return globalGridAttributes(); } } void CartesianCoordinatePlane::setHasOwnGridAttributes( Qt::Orientation orientation, bool on ) { if ( orientation == Qt::Horizontal ) d->hasOwnGridAttributesHorizontal = on; else d->hasOwnGridAttributesVertical = on; emit propertiesChanged(); } bool CartesianCoordinatePlane::hasOwnGridAttributes( Qt::Orientation orientation ) const { return orientation == Qt::Horizontal ? d->hasOwnGridAttributesHorizontal : d->hasOwnGridAttributesVertical; } void CartesianCoordinatePlane::setAutoAdjustGridToZoom( bool autoAdjust ) { if ( d->autoAdjustGridToZoom != autoAdjust ) { d->autoAdjustGridToZoom = autoAdjust; d->grid->setNeedRecalculate(); emit propertiesChanged(); } } #if defined(Q_COMPILER_MANGLES_RETURN_TYPE) const #endif bool CartesianCoordinatePlane::autoAdjustGridToZoom() const { return d->autoAdjustGridToZoom; } AbstractCoordinatePlane* CartesianCoordinatePlane::sharedAxisMasterPlane( QPainter* painter ) { CartesianCoordinatePlane* plane = this; AbstractCartesianDiagram* diag = dynamic_cast< AbstractCartesianDiagram* >( plane->diagram() ); const CartesianAxis* sharedAxis = 0; if ( diag != 0 ) { const CartesianAxisList axes = diag->axes(); Q_FOREACH( const CartesianAxis* a, axes ) { CartesianCoordinatePlane* p = const_cast< CartesianCoordinatePlane* >( dynamic_cast< const CartesianCoordinatePlane* >( a->coordinatePlane() ) ); if ( p != 0 && p != this ) { plane = p; sharedAxis = a; } } } if ( plane == this || painter == 0 ) return plane; const QPointF zero = QPointF( 0, 0 ); const QPointF tenX = QPointF( 10, 0 ); const QPointF tenY = QPointF( 0, 10 ); if ( sharedAxis->isOrdinate() ) { painter->translate( translate( zero ).x(), 0.0 ); const qreal factor = (translate( tenX ) - translate( zero ) ).x() / ( plane->translate( tenX ) - plane->translate( zero ) ).x(); painter->scale( factor, 1.0 ); painter->translate( -plane->translate( zero ).x(), 0.0 ); } if ( sharedAxis->isAbscissa() ) { painter->translate( 0.0, translate( zero ).y() ); const qreal factor = (translate( tenY ) - translate( zero ) ).y() / ( plane->translate( tenY ) - plane->translate( zero ) ).y(); painter->scale( 1.0, factor ); painter->translate( 0.0, -plane->translate( zero ).y() ); } return plane; } void CartesianCoordinatePlane::setHorizontalRangeReversed( bool reverse ) { if ( d->reverseHorizontalPlane == reverse ) return; d->reverseHorizontalPlane = reverse; layoutDiagrams(); emit propertiesChanged(); } bool CartesianCoordinatePlane::isHorizontalRangeReversed() const { return d->reverseHorizontalPlane; } void CartesianCoordinatePlane::setVerticalRangeReversed( bool reverse ) { if ( d->reverseVerticalPlane == reverse ) return; d->reverseVerticalPlane = reverse; layoutDiagrams(); emit propertiesChanged(); } bool CartesianCoordinatePlane::isVerticalRangeReversed() const { return d->reverseVerticalPlane; } QRectF CartesianCoordinatePlane::visibleDataRange() const { QRectF result; const QRectF drawArea = drawingArea(); result.setTopLeft( translateBack( drawArea.topLeft() ) ); result.setBottomRight( translateBack( drawArea.bottomRight() ) ); return result; } void CartesianCoordinatePlane::setGeometry( const QRect& rectangle ) { if ( rectangle == geometry() ) { return; } d->geometry = rectangle; if ( d->isometricScaling ) { const int hfw = heightForWidth( rectangle.width() ); // same scaling for x and y means a fixed aspect ratio, which is enforced here // always shrink the too large dimension if ( hfw < rectangle.height() ) { d->geometry.setHeight( hfw ); } else { d->geometry.setWidth( qRound( qreal( rectangle.width() ) * qreal( rectangle.height() ) / qreal( hfw ) ) ); } } AbstractCoordinatePlane::setGeometry( d->geometry ); Q_FOREACH( AbstractDiagram* diagram, diagrams() ) { diagram->resize( d->geometry.size() ); } } Qt::Orientations CartesianCoordinatePlane::expandingDirections() const { // not completely sure why this is required for isometric scaling... return d->isometricScaling ? Qt::Horizontal : ( Qt::Horizontal | Qt::Vertical ); } bool CartesianCoordinatePlane::hasHeightForWidth() const { return d->isometricScaling; } int CartesianCoordinatePlane::heightForWidth( int w ) const { // ### using anything for dataRect that depends on geometry will close a feedback loop which // prevents the geometry from stabilizing. specifically, visibleDataRange() depends on // drawingArea(), and no good will come out of using it here. QRectF dataRect = logicalArea(); return qRound( qreal( w ) * qAbs( qreal( dataRect.height() ) / qreal( dataRect.width() ) ) ); } QSize CartesianCoordinatePlane::sizeHint() const { QSize sh = AbstractCoordinatePlane::sizeHint(); if ( d->isometricScaling ) { // not sure why the next line doesn't cause an infinite loop, but it improves initial size allocation sh = d->geometry.size(); sh.setHeight( heightForWidth( sh.width() ) ); } return sh; } diff --git a/src/KChart/Cartesian/KChartCartesianDiagramDataCompressor_p.cpp b/src/KChart/Cartesian/KChartCartesianDiagramDataCompressor_p.cpp index 735483a..2644e12 100644 --- a/src/KChart/Cartesian/KChartCartesianDiagramDataCompressor_p.cpp +++ b/src/KChart/Cartesian/KChartCartesianDiagramDataCompressor_p.cpp @@ -1,660 +1,660 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "KChartCartesianDiagramDataCompressor_p.h" #include #include #include "KChartAbstractCartesianDiagram.h" #include "KChartMath_p.h" using namespace KChart; using namespace std; CartesianDiagramDataCompressor::CartesianDiagramDataCompressor( QObject* parent ) : QObject( parent ) , m_mode( Precise ) , m_xResolution( 0 ) , m_yResolution( 0 ) , m_sampleStep( 0 ) , m_datasetDimension( 1 ) { calculateSampleStepWidth(); m_data.resize( 0 ); } static bool contains( const CartesianDiagramDataCompressor::AggregatedDataValueAttributes& aggregated, const DataValueAttributes& attributes ) { CartesianDiagramDataCompressor::AggregatedDataValueAttributes::const_iterator it = aggregated.constBegin(); for ( ; it != aggregated.constEnd(); ++it ) { if ( it.value() == attributes ) { return true; } } return false; } CartesianDiagramDataCompressor::AggregatedDataValueAttributes CartesianDiagramDataCompressor::aggregatedAttrs( const AbstractDiagram* diagram, const QModelIndex & index, const CachePosition& position ) const { // return cached attrs, if any DataValueAttributesCache::const_iterator i = m_dataValueAttributesCache.constFind( position ); if ( i != m_dataValueAttributesCache.constEnd() ) { return i.value(); } // aggregate attributes from all indices in the same CachePosition as index CartesianDiagramDataCompressor::AggregatedDataValueAttributes aggregated; Q_FOREACH( const QModelIndex& neighborIndex, mapToModel( position ) ) { DataValueAttributes attrs = diagram->dataValueAttributes( neighborIndex ); // only store visible and unique attributes if ( !attrs.isVisible() ) { continue; } if ( !contains( aggregated, attrs ) ) { aggregated[ neighborIndex ] = attrs; } } // if none of the attributes had the visible flag set, we just take the one set for the index // to avoid returning an empty list (### why not return an empty list?) if ( aggregated.isEmpty() ) { aggregated[index] = diagram->dataValueAttributes( index ); } m_dataValueAttributesCache[position] = aggregated; return aggregated; } bool CartesianDiagramDataCompressor::prepareDataChange( const QModelIndex& parent, bool isRows, int* start, int* end ) { if ( parent != m_rootIndex ) { return false; } Q_ASSERT( *start <= *end ); CachePosition startPos = isRows ? mapToCache( *start, 0 ) : mapToCache( 0, *start ); CachePosition endPos = isRows ? mapToCache( *end, 0 ) : mapToCache( 0, *end ); static const CachePosition nullPosition; if ( startPos == nullPosition ) { rebuildCache(); startPos = isRows ? mapToCache( *start, 0 ) : mapToCache( 0, *start ); endPos = isRows ? mapToCache( *end, 0 ) : mapToCache( 0, *end ); // The start position still isn't valid, // means that no resolution was set yet or we're about to add the first rows if ( startPos == nullPosition ) { return false; } } *start = isRows ? startPos.row : startPos.column; *end = isRows ? endPos.row : endPos.column; return true; } void CartesianDiagramDataCompressor::slotRowsAboutToBeInserted( const QModelIndex& parent, int start, int end ) { if ( !prepareDataChange( parent, true, &start, &end ) ) { return; } for ( int i = 0; i < m_data.size(); ++i ) { Q_ASSERT( start >= 0 && start <= m_data[ i ].size() ); m_data[ i ].insert( start, end - start + 1, DataPoint() ); } } void CartesianDiagramDataCompressor::slotRowsInserted( const QModelIndex& parent, int start, int end ) { if ( !prepareDataChange( parent, true, &start, &end ) ) { return; } for ( int i = 0; i < m_data.size(); ++i ) { for ( int j = start; j < m_data[i].size(); ++j ) { retrieveModelData( CachePosition( j, i ) ); } } } void CartesianDiagramDataCompressor::slotColumnsAboutToBeInserted( const QModelIndex& parent, int start, int end ) { if ( !prepareDataChange( parent, false, &start, &end ) ) { return; } const int rowCount = qMin( m_model ? m_model->rowCount( m_rootIndex ) : 0, m_xResolution ); Q_ASSERT( start >= 0 && start <= m_data.size() ); m_data.insert( start, end - start + 1, QVector< DataPoint >( rowCount ) ); } void CartesianDiagramDataCompressor::slotColumnsInserted( const QModelIndex& parent, int start, int end ) { if ( !prepareDataChange( parent, false, &start, &end ) ) { return; } for ( int i = start; i < m_data.size(); ++i ) { for (int j = 0; j < m_data[i].size(); ++j ) { retrieveModelData( CachePosition( j, i ) ); } } } void CartesianDiagramDataCompressor::slotRowsAboutToBeRemoved( const QModelIndex& parent, int start, int end ) { if ( !prepareDataChange( parent, true, &start, &end ) ) { return; } for ( int i = 0; i < m_data.size(); ++i ) { m_data[ i ].remove( start, end - start + 1 ); } } void CartesianDiagramDataCompressor::slotRowsRemoved( const QModelIndex& parent, int start, int end ) { if ( parent != m_rootIndex ) return; Q_ASSERT( start <= end ); Q_UNUSED( end ) CachePosition startPos = mapToCache( start, 0 ); static const CachePosition nullPosition; if ( startPos == nullPosition ) { // Since we should already have rebuilt the cache, it won't help to rebuild it again. // Do not Q_ASSERT() though, since the resolution might simply not be set or we might now have 0 rows return; } for ( int i = 0; i < m_data.size(); ++i ) { for (int j = startPos.row; j < m_data[i].size(); ++j ) { retrieveModelData( CachePosition( j, i ) ); } } } void CartesianDiagramDataCompressor::slotColumnsAboutToBeRemoved( const QModelIndex& parent, int start, int end ) { if ( !prepareDataChange( parent, false, &start, &end ) ) { return; } m_data.remove( start, end - start + 1 ); } void CartesianDiagramDataCompressor::slotColumnsRemoved( const QModelIndex& parent, int start, int end ) { if ( parent != m_rootIndex ) return; Q_ASSERT( start <= end ); Q_UNUSED( end ); const CachePosition startPos = mapToCache( 0, start ); static const CachePosition nullPosition; if ( startPos == nullPosition ) { // Since we should already have rebuilt the cache, it won't help to rebuild it again. // Do not Q_ASSERT() though, since the resolution might simply not be set or we might now have 0 columns return; } for ( int i = startPos.column; i < m_data.size(); ++i ) { for ( int j = 0; j < m_data[i].size(); ++j ) { retrieveModelData( CachePosition( j, i ) ); } } } void CartesianDiagramDataCompressor::slotModelHeaderDataChanged( Qt::Orientation orientation, int first, int last ) { if ( orientation != Qt::Vertical ) return; if ( m_model->rowCount( m_rootIndex ) > 0 ) { const QModelIndex firstRow = m_model->index( 0, first, m_rootIndex ); // checked const QModelIndex lastRow = m_model->index( m_model->rowCount( m_rootIndex ) - 1, last, m_rootIndex ); // checked slotModelDataChanged( firstRow, lastRow ); } } void CartesianDiagramDataCompressor::slotModelDataChanged( const QModelIndex& topLeftIndex, const QModelIndex& bottomRightIndex ) { if ( topLeftIndex.parent() != m_rootIndex ) return; Q_ASSERT( topLeftIndex.parent() == bottomRightIndex.parent() ); Q_ASSERT( topLeftIndex.row() <= bottomRightIndex.row() ); Q_ASSERT( topLeftIndex.column() <= bottomRightIndex.column() ); CachePosition topleft = mapToCache( topLeftIndex ); CachePosition bottomright = mapToCache( bottomRightIndex ); for ( int row = topleft.row; row <= bottomright.row; ++row ) for ( int column = topleft.column; column <= bottomright.column; ++column ) invalidate( CachePosition( row, column ) ); } void CartesianDiagramDataCompressor::slotModelLayoutChanged() { rebuildCache(); calculateSampleStepWidth(); } void CartesianDiagramDataCompressor::slotDiagramLayoutChanged( AbstractDiagram* diagramBase ) { AbstractCartesianDiagram* diagram = qobject_cast< AbstractCartesianDiagram* >( diagramBase ); Q_ASSERT( diagram ); if ( diagram->datasetDimension() != m_datasetDimension ) { setDatasetDimension( diagram->datasetDimension() ); } } int CartesianDiagramDataCompressor::modelDataColumns() const { Q_ASSERT( m_datasetDimension != 0 ); // only operational if there is a model and a resolution if ( m_model ) { const int effectiveDimension = m_datasetDimension == 2 ? 2 : 1; const int columns = m_model->columnCount( m_rootIndex ) / effectiveDimension; Q_ASSERT( columns == m_data.size() ); return columns; } else { return 0; } } int CartesianDiagramDataCompressor::modelDataRows() const { // only operational if there is a model, columns, and a resolution if ( m_model && m_model->columnCount( m_rootIndex ) > 0 && m_xResolution > 0 ) { return m_data.isEmpty() ? 0 : m_data.first().size(); } else { return 0; } } void CartesianDiagramDataCompressor::setModel( QAbstractItemModel* model ) { if ( model == m_model ) { return; } if ( m_model != 0 ) { - disconnect( m_model, SIGNAL( headerDataChanged( Qt::Orientation, int, int ) ), - this, SLOT( slotModelHeaderDataChanged( Qt::Orientation, int, int ) ) ); - disconnect( m_model, SIGNAL( dataChanged( QModelIndex, QModelIndex ) ), - this, SLOT( slotModelDataChanged( QModelIndex, QModelIndex ) ) ); - disconnect( m_model, SIGNAL( layoutChanged() ), - this, SLOT( slotModelLayoutChanged() ) ); - disconnect( m_model, SIGNAL( rowsAboutToBeInserted( QModelIndex, int, int ) ), - this, SLOT( slotRowsAboutToBeInserted( QModelIndex, int, int ) ) ); - disconnect( m_model, SIGNAL( rowsInserted( QModelIndex, int, int ) ), - this, SLOT( slotRowsInserted( QModelIndex, int, int ) ) ); - disconnect( m_model, SIGNAL( rowsAboutToBeRemoved( QModelIndex, int, int ) ), - this, SLOT( slotRowsAboutToBeRemoved( QModelIndex, int, int ) ) ); - disconnect( m_model, SIGNAL( rowsRemoved( QModelIndex, int, int ) ), - this, SLOT( slotRowsRemoved( QModelIndex, int, int ) ) ); - disconnect( m_model, SIGNAL( columnsAboutToBeInserted( QModelIndex, int, int ) ), - this, SLOT( slotColumnsAboutToBeInserted( QModelIndex, int, int ) ) ); - disconnect( m_model, SIGNAL( columnsInserted( QModelIndex, int, int ) ), - this, SLOT( slotColumnsInserted( QModelIndex, int, int ) ) ); - disconnect( m_model, SIGNAL( columnsRemoved( QModelIndex, int, int ) ), - this, SLOT( slotColumnsRemoved( QModelIndex, int, int ) ) ); - disconnect( m_model, SIGNAL( columnsAboutToBeRemoved( QModelIndex, int, int ) ), - this, SLOT( slotColumnsAboutToBeRemoved( QModelIndex, int, int ) ) ); - disconnect( m_model, SIGNAL( modelReset() ), - this, SLOT( rebuildCache() ) ); + disconnect( m_model, SIGNAL(headerDataChanged(Qt::Orientation,int,int)), + this, SLOT(slotModelHeaderDataChanged(Qt::Orientation,int,int)) ); + disconnect( m_model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(slotModelDataChanged(QModelIndex,QModelIndex)) ); + disconnect( m_model, SIGNAL(layoutChanged()), + this, SLOT(slotModelLayoutChanged()) ); + disconnect( m_model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(slotRowsAboutToBeInserted(QModelIndex,int,int)) ); + disconnect( m_model, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(slotRowsInserted(QModelIndex,int,int)) ); + disconnect( m_model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int)) ); + disconnect( m_model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(slotRowsRemoved(QModelIndex,int,int)) ); + disconnect( m_model, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(slotColumnsAboutToBeInserted(QModelIndex,int,int)) ); + disconnect( m_model, SIGNAL(columnsInserted(QModelIndex,int,int)), + this, SLOT(slotColumnsInserted(QModelIndex,int,int)) ); + disconnect( m_model, SIGNAL(columnsRemoved(QModelIndex,int,int)), + this, SLOT(slotColumnsRemoved(QModelIndex,int,int)) ); + disconnect( m_model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(slotColumnsAboutToBeRemoved(QModelIndex,int,int)) ); + disconnect( m_model, SIGNAL(modelReset()), + this, SLOT(rebuildCache()) ); m_model = 0; } m_modelCache.setModel( model ); if ( model != 0 ) { m_model = model; - connect( m_model, SIGNAL( headerDataChanged( Qt::Orientation, int, int ) ), - SLOT( slotModelHeaderDataChanged( Qt::Orientation, int, int ) ) ); - connect( m_model, SIGNAL( dataChanged( QModelIndex, QModelIndex ) ), - SLOT( slotModelDataChanged( QModelIndex, QModelIndex ) ) ); - connect( m_model, SIGNAL( layoutChanged() ), - SLOT( slotModelLayoutChanged() ) ); - connect( m_model, SIGNAL( rowsAboutToBeInserted( QModelIndex, int, int ) ), - SLOT( slotRowsAboutToBeInserted( QModelIndex, int, int ) ) ); - connect( m_model, SIGNAL( rowsInserted( QModelIndex, int, int ) ), - SLOT( slotRowsInserted( QModelIndex, int, int ) ) ); - connect( m_model, SIGNAL( rowsAboutToBeRemoved( QModelIndex, int, int ) ), - SLOT( slotRowsAboutToBeRemoved( QModelIndex, int, int ) ) ); - connect( m_model, SIGNAL( rowsRemoved( QModelIndex, int, int ) ), - SLOT( slotRowsRemoved( QModelIndex, int, int ) ) ); - connect( m_model, SIGNAL( columnsAboutToBeInserted( QModelIndex, int, int ) ), - SLOT( slotColumnsAboutToBeInserted( QModelIndex, int, int ) ) ); - connect( m_model, SIGNAL( columnsInserted( QModelIndex, int, int ) ), - SLOT( slotColumnsInserted( QModelIndex, int, int ) ) ); - connect( m_model, SIGNAL( columnsRemoved( QModelIndex, int, int ) ), - SLOT( slotColumnsRemoved( QModelIndex, int, int ) ) ); - connect( m_model, SIGNAL( columnsAboutToBeRemoved( QModelIndex, int, int ) ), - SLOT( slotColumnsAboutToBeRemoved( QModelIndex, int, int ) ) ); - connect( m_model, SIGNAL( modelReset() ), SLOT( rebuildCache() ) ); + connect( m_model, SIGNAL(headerDataChanged(Qt::Orientation,int,int)), + SLOT(slotModelHeaderDataChanged(Qt::Orientation,int,int)) ); + connect( m_model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + SLOT(slotModelDataChanged(QModelIndex,QModelIndex)) ); + connect( m_model, SIGNAL(layoutChanged()), + SLOT(slotModelLayoutChanged()) ); + connect( m_model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), + SLOT(slotRowsAboutToBeInserted(QModelIndex,int,int)) ); + connect( m_model, SIGNAL(rowsInserted(QModelIndex,int,int)), + SLOT(slotRowsInserted(QModelIndex,int,int)) ); + connect( m_model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int)) ); + connect( m_model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + SLOT(slotRowsRemoved(QModelIndex,int,int)) ); + connect( m_model, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)), + SLOT(slotColumnsAboutToBeInserted(QModelIndex,int,int)) ); + connect( m_model, SIGNAL(columnsInserted(QModelIndex,int,int)), + SLOT(slotColumnsInserted(QModelIndex,int,int)) ); + connect( m_model, SIGNAL(columnsRemoved(QModelIndex,int,int)), + SLOT(slotColumnsRemoved(QModelIndex,int,int)) ); + connect( m_model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), + SLOT(slotColumnsAboutToBeRemoved(QModelIndex,int,int)) ); + connect( m_model, SIGNAL(modelReset()), SLOT(rebuildCache()) ); } rebuildCache(); calculateSampleStepWidth(); } void CartesianDiagramDataCompressor::setRootIndex( const QModelIndex& root ) { if ( m_rootIndex != root ) { Q_ASSERT( root.model() == m_model || !root.isValid() ); m_rootIndex = root; m_modelCache.setRootIndex( root ); rebuildCache(); calculateSampleStepWidth(); } } void CartesianDiagramDataCompressor::recalcResolution() { setResolution( m_xResolution, m_yResolution ); } void CartesianDiagramDataCompressor::setResolution( int x, int y ) { if ( setResolutionInternal( x, y ) ) { rebuildCache(); calculateSampleStepWidth(); } } bool CartesianDiagramDataCompressor::setResolutionInternal( int x, int y ) { const int oldXRes = m_xResolution; const int oldYRes = m_yResolution; if ( m_datasetDimension != 1 ) { // just ignore the X resolution in that case m_xResolution = m_model ? m_model->rowCount( m_rootIndex ) : 0; } else { m_xResolution = qMax( 0, x ); } m_yResolution = qMax( 0, y ); return m_xResolution != oldXRes || m_yResolution != oldYRes; } void CartesianDiagramDataCompressor::clearCache() { for ( int column = 0; column < m_data.size(); ++column ) m_data[column].fill( DataPoint() ); } void CartesianDiagramDataCompressor::rebuildCache() { Q_ASSERT( m_datasetDimension != 0 ); m_data.clear(); setResolutionInternal( m_xResolution, m_yResolution ); const int columnDivisor = m_datasetDimension == 2 ? 2 : 1; const int columnCount = m_model ? m_model->columnCount( m_rootIndex ) / columnDivisor : 0; const int rowCount = qMin( m_model ? m_model->rowCount( m_rootIndex ) : 0, m_xResolution ); m_data.resize( columnCount ); for ( int i = 0; i < columnCount; ++i ) { m_data[i].resize( rowCount ); } // also empty the attrs cache m_dataValueAttributesCache.clear(); } const CartesianDiagramDataCompressor::DataPoint& CartesianDiagramDataCompressor::data( const CachePosition& position ) const { static DataPoint nullDataPoint; if ( ! mapsToModelIndex( position ) ) { return nullDataPoint; } if ( ! isCached( position ) ) { retrieveModelData( position ); } return m_data.at( position.column ).at( position.row ); } QPair< QPointF, QPointF > CartesianDiagramDataCompressor::dataBoundaries() const { const int colCount = modelDataColumns(); qreal xMin = std::numeric_limits< qreal >::quiet_NaN(); qreal xMax = std::numeric_limits< qreal >::quiet_NaN(); qreal yMin = std::numeric_limits< qreal >::quiet_NaN(); qreal yMax = std::numeric_limits< qreal >::quiet_NaN(); for ( int column = 0; column < colCount; ++column ) { const DataPointVector& data = m_data.at( column ); int row = 0; for ( DataPointVector::const_iterator it = data.begin(); it != data.end(); ++it, ++row ) { const DataPoint& p = *it; if ( !p.index.isValid() ) retrieveModelData( CachePosition( row, column ) ); if ( ISNAN( p.key ) || ISNAN( p.value ) ) { continue; } if ( ISNAN( xMin ) ) { xMin = p.key; xMax = p.key; yMin = p.value; yMax = p.value; } else { xMin = qMin( xMin, p.key ); xMax = qMax( xMax, p.key ); yMin = qMin( yMin, p.value ); yMax = qMax( yMax, p.value ); } } } const QPointF bottomLeft( xMin, yMin ); const QPointF topRight( xMax, yMax ); return qMakePair( bottomLeft, topRight ); } void CartesianDiagramDataCompressor::retrieveModelData( const CachePosition& position ) const { Q_ASSERT( mapsToModelIndex( position ) ); DataPoint result; result.hidden = true; switch ( m_mode ) { case Precise: { const QModelIndexList indexes = mapToModel( position ); if ( m_datasetDimension == 2 ) { Q_ASSERT( indexes.count() == 2 ); const QModelIndex& xIndex = indexes.at( 0 ); result.index = xIndex; result.key = m_modelCache.data( xIndex ); result.value = m_modelCache.data( indexes.at( 1 ) ); } else { if ( indexes.isEmpty() ) { break; } result.value = std::numeric_limits< qreal >::quiet_NaN(); result.key = 0.0; Q_FOREACH( const QModelIndex& index, indexes ) { const qreal value = m_modelCache.data( index ); if ( !ISNAN( value ) ) { result.value = ISNAN( result.value ) ? value : result.value + value; } result.key += index.row(); } result.index = indexes.at( 0 ); result.key /= indexes.size(); result.value /= indexes.size(); } Q_FOREACH( const QModelIndex& index, indexes ) { // the DataPoint point is visible if any of the underlying, aggregated points is visible if ( m_model->data( index, DataHiddenRole ).value() == false ) { result.hidden = false; } } break; } case SamplingSeven: break; } m_data[ position.column ][ position.row ] = result; Q_ASSERT( isCached( position ) ); } CartesianDiagramDataCompressor::CachePosition CartesianDiagramDataCompressor::mapToCache( const QModelIndex& index ) const { Q_ASSERT( m_datasetDimension != 0 ); static const CachePosition nullPosition; if ( !index.isValid() ) { return nullPosition; } return mapToCache( index.row(), index.column() ); } CartesianDiagramDataCompressor::CachePosition CartesianDiagramDataCompressor::mapToCache( int row, int column ) const { Q_ASSERT( m_datasetDimension != 0 ); if ( m_data.size() == 0 || m_data.at( 0 ).size() == 0 ) { return mapToCache( QModelIndex() ); } // assumption: indexes per column == 1 if ( indexesPerPixel() == 0 ) { return mapToCache( QModelIndex() ); } return CachePosition( int( row / indexesPerPixel() ), column / m_datasetDimension ); } QModelIndexList CartesianDiagramDataCompressor::mapToModel( const CachePosition& position ) const { QModelIndexList indexes; if ( !mapsToModelIndex( position ) ) { return indexes; } Q_ASSERT( position.column < modelDataColumns() ); if ( m_datasetDimension == 2 ) { indexes << m_model->index( position.row, position.column * 2, m_rootIndex ); // checked indexes << m_model->index( position.row, position.column * 2 + 1, m_rootIndex ); // checked } else { // here, indexes per column is usually but not always 1 (e.g. stock diagrams can have three // or four dimensions: High-Low-Close or Open-High-Low-Close) const qreal ipp = indexesPerPixel(); const int baseRow = floor( position.row * ipp ); // the following line needs to work for the last row(s), too... const int endRow = floor( ( position.row + 1 ) * ipp ); for ( int row = baseRow; row < endRow; ++row ) { Q_ASSERT( row < m_model->rowCount( m_rootIndex ) ); const QModelIndex index = m_model->index( row, position.column, m_rootIndex ); if ( index.isValid() ) { indexes << index; } } } return indexes; } qreal CartesianDiagramDataCompressor::indexesPerPixel() const { if ( !m_model || m_data.size() == 0 || m_data.at( 0 ).size() == 0 ) { return 0; } return qreal( m_model->rowCount( m_rootIndex ) ) / qreal( m_data.at( 0 ).size() ); } bool CartesianDiagramDataCompressor::mapsToModelIndex( const CachePosition& position ) const { return m_model && m_data.size() > 0 && m_data.at( 0 ).size() > 0 && position.column >= 0 && position.column < m_data.size() && position.row >=0 && position.row < m_data.at( 0 ).size(); } void CartesianDiagramDataCompressor::invalidate( const CachePosition& position ) { if ( mapsToModelIndex( position ) ) { m_data[ position.column ][ position.row ] = DataPoint(); // Also invalidate the data value attributes at "position". // Otherwise the user overwrites the attributes without us noticing // it because we keep reading what's in the cache. m_dataValueAttributesCache.remove( position ); } } bool CartesianDiagramDataCompressor::isCached( const CachePosition& position ) const { Q_ASSERT( mapsToModelIndex( position ) ); const DataPoint& p = m_data.at( position.column ).at( position.row ); return p.index.isValid(); } void CartesianDiagramDataCompressor::calculateSampleStepWidth() { if ( m_mode == Precise ) { m_sampleStep = 1; return; } static unsigned int SomePrimes[] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 151, 211, 313, 401, 503, 607, 701, 811, 911, 1009, 10037, 12911, 16001, 20011, 50021, 100003, 137867, 199999, 500009, 707753, 1000003, 0 }; // ... after that, having a model at all becomes impractical // we want at least 17 samples per data point, using a prime step width const qreal WantedSamples = 17; if ( WantedSamples > indexesPerPixel() ) { m_sampleStep = 1; } else { int i; for ( i = 0; SomePrimes[i] != 0; ++i ) { if ( WantedSamples * SomePrimes[i+1] > indexesPerPixel() ) { break; } } m_sampleStep = SomePrimes[i]; if ( SomePrimes[i] == 0 ) { m_sampleStep = SomePrimes[i-1]; } else { m_sampleStep = SomePrimes[i]; } } } void CartesianDiagramDataCompressor::setDatasetDimension( int dimension ) { if ( dimension != m_datasetDimension ) { m_datasetDimension = dimension; rebuildCache(); calculateSampleStepWidth(); } } diff --git a/src/KChart/Cartesian/KChartLeveyJenningsDiagram.cpp b/src/KChart/Cartesian/KChartLeveyJenningsDiagram.cpp index 6f9fa5b..a481d29 100644 --- a/src/KChart/Cartesian/KChartLeveyJenningsDiagram.cpp +++ b/src/KChart/Cartesian/KChartLeveyJenningsDiagram.cpp @@ -1,732 +1,732 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "KChartLeveyJenningsDiagram.h" #include "KChartLeveyJenningsDiagram_p.h" #include "KChartChart.h" #include "KChartTextAttributes.h" #include "KChartAbstractGrid.h" #include "KChartPainterSaver_p.h" #include #include #include #include #include using namespace KChart; using namespace std; LeveyJenningsDiagram::Private::Private() { } LeveyJenningsDiagram::Private::~Private() {} #define d d_func() LeveyJenningsDiagram::LeveyJenningsDiagram( QWidget* parent, LeveyJenningsCoordinatePlane* plane ) : LineDiagram( new Private(), parent, plane ) { init(); } void LeveyJenningsDiagram::init() { d->lotChangedPosition = Qt::AlignTop; d->fluidicsPackChangedPosition = Qt::AlignBottom; d->sensorChangedPosition = Qt::AlignBottom; d->scanLinePen = QPen( Qt::blue ); setPen( d->scanLinePen ); d->expectedMeanValue = 0.0; d->expectedStandardDeviation = 0.0; d->diagram = this; d->icons[ LotChanged ] = QString::fromLatin1( ":/KDE/kchart/LeveyJennings/karo_black.svg" ); d->icons[ SensorChanged ] = QString::fromLatin1( ":/KDE/kchart/LeveyJennings/karo_red.svg" ); d->icons[ FluidicsPackChanged ] = QString::fromLatin1( ":/KDE/kchart/LeveyJennings/karo_blue.svg" ); d->icons[ OkDataPoint ] = QString::fromLatin1( ":/KDE/kchart/LeveyJennings/circle_blue.svg" ); d->icons[ NotOkDataPoint ] = QString::fromLatin1( ":/KDE/kchart/LeveyJennings/circle_blue_red.svg" ); setSelectionMode( QAbstractItemView::SingleSelection ); } LeveyJenningsDiagram::~LeveyJenningsDiagram() { } /** * Creates an exact copy of this diagram. */ LineDiagram * LeveyJenningsDiagram::clone() const { LeveyJenningsDiagram* newDiagram = new LeveyJenningsDiagram( new Private( *d ) ); return newDiagram; } bool LeveyJenningsDiagram::compare( const LeveyJenningsDiagram* other ) const { if ( other == this ) return true; if ( ! other ) { return false; } /* qDebug() <<"\n LineDiagram::compare():"; // compare own properties qDebug() << (type() == other->type()); */ return // compare the base class ( static_cast(this)->compare( other ) ); } /** * Sets the position of the lot change symbol to \a pos. * Valid values are: Qt::AlignTop (default), Qt::AlignBottom. */ void LeveyJenningsDiagram::setLotChangedSymbolPosition( Qt::Alignment pos ) { if ( d->lotChangedPosition == pos ) return; d->lotChangedPosition = pos; update(); } /** * Returns the position of the lot change symbol. */ Qt::Alignment LeveyJenningsDiagram::lotChangedSymbolPosition() const { return d->lotChangedPosition; } /** * Sets the position of the fluidics pack changed symbol to \a pos. * Valid values are: Qt::AlignBottom (default), Qt::AlignTop. */ void LeveyJenningsDiagram::setFluidicsPackChangedSymbolPosition( Qt::Alignment pos ) { if ( d->fluidicsPackChangedPosition == pos ) return; d->fluidicsPackChangedPosition = pos; update(); } /** * Returns the position of the fluidics pack changed symbol. */ Qt::Alignment LeveyJenningsDiagram::fluidicsPackChangedSymbolPosition() const { return d->fluidicsPackChangedPosition; } /** * Sets the position of the sensor changed symbol to \a pos. * Valid values are: Qt::AlignBottom (default), Qt::AlignTop. */ void LeveyJenningsDiagram::setSensorChangedSymbolPosition( Qt::Alignment pos ) { if ( d->sensorChangedPosition == pos ) return; d->sensorChangedPosition = pos; update(); } /** * Returns the position of the sensor changed symbol. */ Qt::Alignment LeveyJenningsDiagram::sensorChangedSymbolPosition() const { return d->sensorChangedPosition; } /** * Sets the date/time of all fluidics pack changes to \a changes. */ void LeveyJenningsDiagram::setFluidicsPackChanges( const QVector< QDateTime >& changes ) { if ( d->fluidicsPackChanges == changes ) return; d->fluidicsPackChanges = changes; update(); } /** * Returns the list of all fluidics pack changes. */ QVector< QDateTime > LeveyJenningsDiagram::fluidicsPackChanges() const { return d->fluidicsPackChanges; } /** * Sets the date/time of all sensor changes to \a changes. */ void LeveyJenningsDiagram::setSensorChanges( const QVector< QDateTime >& changes ) { if ( d->sensorChanges == changes ) return; d->sensorChanges = changes; update(); } /** * Sets the pen used for drawing the scan line to \a pen */ void LeveyJenningsDiagram::setScanLinePen( const QPen& pen ) { if ( d->scanLinePen == pen ) return; d->scanLinePen = pen; update(); } /** * Returns the pen being used for drawing the scan line. */ QPen LeveyJenningsDiagram::scanLinePen() const { return d->scanLinePen; } /** * Returns the SVG file name usef for \a symbol */ QString LeveyJenningsDiagram::symbol( Symbol symbol ) const { return d->icons[ symbol ]; } /** * Sets the symbol being used for \a symbol to a SVG file \a filename. */ void LeveyJenningsDiagram::setSymbol( Symbol symbol, const QString& filename ) { if ( d->icons[ symbol ] == filename ) return; delete d->iconRenderer[ symbol ]; d->iconRenderer[ symbol ] = 0; d->icons[ symbol ] = filename; update(); } /** * Returns the list of all sensor changes. */ QVector< QDateTime > LeveyJenningsDiagram::sensorChanges() const { return d->sensorChanges; } /** * Sets the expected mean value over all QC values to \a meanValue. */ void LeveyJenningsDiagram::setExpectedMeanValue( float meanValue ) { if ( d->expectedMeanValue == meanValue ) return; d->expectedMeanValue = meanValue; d->setYAxisRange(); update(); } /** * Returns the expected mean values over all QC values. */ float LeveyJenningsDiagram::expectedMeanValue() const { return d->expectedMeanValue; } /** * Sets the expected standard deviaction over all QC values to \a sd. */ void LeveyJenningsDiagram::setExpectedStandardDeviation( float sd ) { if ( d->expectedStandardDeviation == sd ) return; d->expectedStandardDeviation = sd; d->setYAxisRange(); update(); } /** * Returns the expected standard deviation over all QC values. */ float LeveyJenningsDiagram::expectedStandardDeviation() const { return d->expectedStandardDeviation; } /** * Returns the calculated mean values over all QC values. */ float LeveyJenningsDiagram::calculatedMeanValue() const { return d->calculatedMeanValue; } /** * Returns the calculated standard deviation over all QC values. */ float LeveyJenningsDiagram::calculatedStandardDeviation() const { return d->calculatedStandardDeviation; } void LeveyJenningsDiagram::setModel( QAbstractItemModel* model ) { if ( this->model() != 0 ) { - disconnect( this->model(), SIGNAL( dataChanged( const QModelIndex&, const QModelIndex& ) ), - this, SLOT( calculateMeanAndStandardDeviation() ) ); - disconnect( this->model(), SIGNAL( rowsInserted( const QModelIndex&, int, int ) ), - this, SLOT( calculateMeanAndStandardDeviation() ) ); - disconnect( this->model(), SIGNAL( rowsRemoved( const QModelIndex&, int, int ) ), - this, SLOT( calculateMeanAndStandardDeviation() ) ); - disconnect( this->model(), SIGNAL( columnsInserted( const QModelIndex&, int, int ) ), - this, SLOT( calculateMeanAndStandardDeviation() ) ); - disconnect( this->model(), SIGNAL( columnsRemoved( const QModelIndex&, int, int ) ), - this, SLOT( calculateMeanAndStandardDeviation() ) ); - disconnect( this->model(), SIGNAL( modelReset() ), - this, SLOT( calculateMeanAndStandardDeviation() ) ); - disconnect( this->model(), SIGNAL( layoutChanged() ), - this, SLOT( calculateMeanAndStandardDeviation() ) ); + disconnect( this->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(calculateMeanAndStandardDeviation()) ); + disconnect( this->model(), SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(calculateMeanAndStandardDeviation()) ); + disconnect( this->model(), SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(calculateMeanAndStandardDeviation()) ); + disconnect( this->model(), SIGNAL(columnsInserted(QModelIndex,int,int)), + this, SLOT(calculateMeanAndStandardDeviation()) ); + disconnect( this->model(), SIGNAL(columnsRemoved(QModelIndex,int,int)), + this, SLOT(calculateMeanAndStandardDeviation()) ); + disconnect( this->model(), SIGNAL(modelReset()), + this, SLOT(calculateMeanAndStandardDeviation()) ); + disconnect( this->model(), SIGNAL(layoutChanged()), + this, SLOT(calculateMeanAndStandardDeviation()) ); } LineDiagram::setModel( model ); if ( this->model() != 0 ) { - connect( this->model(), SIGNAL( dataChanged( const QModelIndex&, const QModelIndex& ) ), - this, SLOT( calculateMeanAndStandardDeviation() ) ); - connect( this->model(), SIGNAL( rowsInserted( const QModelIndex&, int, int ) ), - this, SLOT( calculateMeanAndStandardDeviation() ) ); - connect( this->model(), SIGNAL( rowsRemoved( const QModelIndex&, int, int ) ), - this, SLOT( calculateMeanAndStandardDeviation() ) ); - connect( this->model(), SIGNAL( columnsInserted( const QModelIndex&, int, int ) ), - this, SLOT( calculateMeanAndStandardDeviation() ) ); - connect( this->model(), SIGNAL( columnsRemoved( const QModelIndex&, int, int ) ), - this, SLOT( calculateMeanAndStandardDeviation() ) ); - connect( this->model(), SIGNAL( modelReset() ), - this, SLOT( calculateMeanAndStandardDeviation() ) ); - connect( this->model(), SIGNAL( layoutChanged() ), - this, SLOT( calculateMeanAndStandardDeviation() ) ); + connect( this->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(calculateMeanAndStandardDeviation()) ); + connect( this->model(), SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(calculateMeanAndStandardDeviation()) ); + connect( this->model(), SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(calculateMeanAndStandardDeviation()) ); + connect( this->model(), SIGNAL(columnsInserted(QModelIndex,int,int)), + this, SLOT(calculateMeanAndStandardDeviation()) ); + connect( this->model(), SIGNAL(columnsRemoved(QModelIndex,int,int)), + this, SLOT(calculateMeanAndStandardDeviation()) ); + connect( this->model(), SIGNAL(modelReset()), + this, SLOT(calculateMeanAndStandardDeviation()) ); + connect( this->model(), SIGNAL(layoutChanged()), + this, SLOT(calculateMeanAndStandardDeviation()) ); calculateMeanAndStandardDeviation(); } } // TODO: This is the 'easy' solution // evaluate whether this is enough or we need some better one or even boost here void LeveyJenningsDiagram::calculateMeanAndStandardDeviation() const { QVector< qreal > values; // first fetch all values const QAbstractItemModel& m = *model(); const int rowCount = m.rowCount( rootIndex() ); for ( int row = 0; row < rowCount; ++row ) { const QVariant var = m.data( m.index( row, 1, rootIndex() ) ); if ( !var.isValid() ) continue; const qreal value = var.toReal(); if ( ISNAN( value ) ) continue; values << value; } qreal sum = 0.0; qreal sumSquares = 0.0; Q_FOREACH( qreal value, values ) { sum += value; sumSquares += value * value; } const int N = values.count(); d->calculatedMeanValue = sum / N; d->calculatedStandardDeviation = sqrt( ( static_cast< qreal >( N ) * sumSquares - sum * sum ) / ( N * ( N - 1 ) ) ); } // calculates the largest QDate not greater than \a dt. static QDate floorDay( const QDateTime& dt ) { return dt.date(); } // calculates the smallest QDate not less than \a dt. static QDate ceilDay( const QDateTime& dt ) { QDate result = dt.date(); if ( QDateTime( result, QTime() ) < dt ) result = result.addDays( 1 ); return result; } // calculates the largest QDateTime like xx:00 not greater than \a dt. static QDateTime floorHour( const QDateTime& dt ) { return QDateTime( dt.date(), QTime( dt.time().hour(), 0 ) ); } // calculates the smallest QDateTime like xx:00 not less than \a dt. static QDateTime ceilHour( const QDateTime& dt ) { QDateTime result( dt.date(), QTime( dt.time().hour(), 0 ) ); if ( result < dt ) result = result.addSecs( 3600 ); return result; } /** \reimpl */ const QPair LeveyJenningsDiagram::calculateDataBoundaries() const { const qreal yMin = d->expectedMeanValue - 4 * d->expectedStandardDeviation; const qreal yMax = d->expectedMeanValue + 4 * d->expectedStandardDeviation; d->setYAxisRange(); // rounded down/up to the prev/next midnight (at least that's the default) const QPair< QDateTime, QDateTime > range = timeRange(); const unsigned int minTime = range.first.toTime_t(); const unsigned int maxTime = range.second.toTime_t(); const qreal xMin = minTime / static_cast< qreal >( 24 * 60 * 60 ); const qreal xMax = maxTime / static_cast< qreal >( 24 * 60 * 60 ) - xMin; const QPointF bottomLeft( QPointF( 0, yMin ) ); const QPointF topRight( QPointF( xMax, yMax ) ); return QPair< QPointF, QPointF >( bottomLeft, topRight ); } /** * Returns the timerange of the diagram's data. */ QPair< QDateTime, QDateTime > LeveyJenningsDiagram::timeRange() const { if ( d->timeRange != QPair< QDateTime, QDateTime >() ) return d->timeRange; const QAbstractItemModel& m = *model(); const int rowCount = m.rowCount( rootIndex() ); const QDateTime begin = m.data( m.index( 0, 3, rootIndex() ) ).toDateTime(); const QDateTime end = m.data( m.index( rowCount - 1, 3, rootIndex() ) ).toDateTime(); if ( begin.secsTo( end ) > 86400 ) { // if begin to end is more than 24h // round down/up to the prev/next midnight const QDate min = floorDay( begin ); const QDate max = ceilDay( end ); return QPair< QDateTime, QDateTime >( QDateTime( min ), QDateTime( max ) ); } else if ( begin.secsTo( end ) > 3600 ) { // more than 1h: rond down up to the prex/next hour // if begin to end is more than 24h const QDateTime min = floorHour( begin ); const QDateTime max = ceilHour( end ); return QPair< QDateTime, QDateTime >( min, max ); } return QPair< QDateTime, QDateTime >( begin, end ); } /** * Sets the \a timeRange visible on the x axis. Set it to QPair< QDateTime, QDateTime >() * to use the default auto calculation. */ void LeveyJenningsDiagram::setTimeRange( const QPair< QDateTime, QDateTime >& timeRange ) { if ( d->timeRange == timeRange ) return; d->timeRange = timeRange; update(); } /** * Draws the fluidics pack and sensor changed symbols. */ void LeveyJenningsDiagram::drawChanges( PaintContext* ctx ) { const unsigned int minTime = timeRange().first.toTime_t(); Q_FOREACH( const QDateTime& dt, d->fluidicsPackChanges ) { const qreal xValue = ( dt.toTime_t() - minTime ) / static_cast< qreal >( 24 * 60 * 60 ); const QPointF point( xValue, 0.0 ); drawFluidicsPackChangedSymbol( ctx, point ); } Q_FOREACH( const QDateTime& dt, d->sensorChanges ) { const qreal xValue = ( dt.toTime_t() - minTime ) / static_cast< qreal >( 24 * 60 * 60 ); const QPointF point( xValue, 0.0 ); drawSensorChangedSymbol( ctx, point ); } } /** \reimpl */ void LeveyJenningsDiagram::paint( PaintContext* ctx ) { d->reverseMapper.clear(); // note: Not having any data model assigned is no bug // but we can not draw a diagram then either. if ( !checkInvariants( true ) ) return; if ( !AbstractGrid::isBoundariesValid(dataBoundaries()) ) return; QPainter* const painter = ctx->painter(); const PainterSaver p( painter ); if ( model()->rowCount( rootIndex() ) == 0 || model()->columnCount( rootIndex() ) < 4 ) return; // nothing to paint for us AbstractCoordinatePlane* const plane = ctx->coordinatePlane(); ctx->setCoordinatePlane( plane->sharedAxisMasterPlane( painter ) ); const QAbstractItemModel& m = *model(); const int rowCount = m.rowCount( rootIndex() ); const unsigned int minTime = timeRange().first.toTime_t(); painter->setRenderHint( QPainter::Antialiasing, true ); int prevLot = -1; QPointF prevPoint; bool hadMissingValue = false; for ( int row = 0; row < rowCount; ++row ) { const QModelIndex lotIndex = m.index( row, 0, rootIndex() ); const QModelIndex valueIndex = m.index( row, 1, rootIndex() ); const QModelIndex okIndex = m.index( row, 2, rootIndex() ); const QModelIndex timeIndex = m.index( row, 3, rootIndex() ); const QModelIndex expectedMeanIndex = m.index( row, 4, rootIndex() ); const QModelIndex expectedSDIndex = m.index( row, 5, rootIndex() ); painter->setPen( pen( lotIndex ) ); QVariant vValue = m.data( valueIndex ); qreal value = vValue.toReal(); const int lot = m.data( lotIndex ).toInt(); const bool ok = m.data( okIndex ).toBool(); const QDateTime time = m.data( timeIndex ).toDateTime(); const qreal xValue = ( time.toTime_t() - minTime ) / static_cast< qreal >( 24 * 60 * 60 ); QVariant vExpectedMean = m.data( expectedMeanIndex ); const qreal expectedMean = vExpectedMean.toReal(); QVariant vExpectedSD = m.data( expectedSDIndex ); const qreal expectedSD = vExpectedSD.toReal(); QPointF point = ctx->coordinatePlane()->translate( QPointF( xValue, value ) ); if ( vValue.isNull() ) { hadMissingValue = true; } else { if ( !vExpectedMean.isNull() && !vExpectedSD.isNull() ) { // this calculates the 'logical' value relative to the expected mean and SD of this point value -= expectedMean; value /= expectedSD; value *= d->expectedStandardDeviation; value += d->expectedMeanValue; point = ctx->coordinatePlane()->translate( QPointF( xValue, value ) ); } if ( prevLot == lot ) { const QPen pen = painter->pen(); QPen newPen = pen; if ( hadMissingValue ) { newPen.setDashPattern( QVector< qreal >() << 4.0 << 4.0 ); } painter->setPen( newPen ); painter->drawLine( prevPoint, point ); painter->setPen( pen ); // d->reverseMapper.addLine( valueIndex.row(), valueIndex.column(), prevPoint, point ); } else if ( row > 0 ) { drawLotChangeSymbol( ctx, QPointF( xValue, value ) ); } if ( value <= d->expectedMeanValue + 4 * d->expectedStandardDeviation && value >= d->expectedMeanValue - 4 * d->expectedStandardDeviation ) { const QPointF location( xValue, value ); drawDataPointSymbol( ctx, location, ok ); d->reverseMapper.addCircle( valueIndex.row(), valueIndex.column(), ctx->coordinatePlane()->translate( location ), iconRect().size() ); } prevLot = lot; prevPoint = point; hadMissingValue = false; } const QModelIndex current = selectionModel()->currentIndex(); if ( selectionModel()->rowIntersectsSelection( lotIndex.row(), lotIndex.parent() ) || current.sibling( current.row(), 0 ) == lotIndex ) { const QPen pen = ctx->painter()->pen(); painter->setPen( d->scanLinePen ); painter->drawLine( ctx->coordinatePlane()->translate( QPointF( xValue, d->expectedMeanValue - 4 * d->expectedStandardDeviation ) ), ctx->coordinatePlane()->translate( QPointF( xValue, d->expectedMeanValue + 4 * d->expectedStandardDeviation ) ) ); painter->setPen( pen ); } } drawChanges( ctx ); ctx->setCoordinatePlane( plane ); } /** * Draws a data point symbol for the data point at \a pos. * @param ok True, when the data point is ok, false otherwise (different symbol) * @param ctx The PaintContext being used */ void LeveyJenningsDiagram::drawDataPointSymbol( PaintContext* ctx, const QPointF& pos, bool ok ) { const Symbol type = ok ? OkDataPoint : NotOkDataPoint; QPainter* const painter = ctx->painter(); const PainterSaver ps( painter ); const QPointF transPos = ctx->coordinatePlane()->translate( pos ).toPoint(); painter->translate( transPos ); painter->setClipping( false ); iconRenderer( type )->render( painter, iconRect() ); } /** * Draws a lot changed symbol for the data point at \a pos. * @param ctx The PaintContext being used * \sa lotChangedSymbolPosition */ void LeveyJenningsDiagram::drawLotChangeSymbol( PaintContext* ctx, const QPointF& pos ) { const QPointF transPos = ctx->coordinatePlane()->translate( QPointF( pos.x(), d->lotChangedPosition & Qt::AlignTop ? d->expectedMeanValue + 4 * d->expectedStandardDeviation : d->expectedMeanValue - 4 * d->expectedStandardDeviation ) ); QPainter* const painter = ctx->painter(); const PainterSaver ps( painter ); painter->setClipping( false ); painter->translate( transPos ); iconRenderer( LotChanged )->render( painter, iconRect() ); } /** * Draws a sensor changed symbol for the data point at \a pos. * @param ctx The PaintContext being used * \sa sensorChangedSymbolPosition */ void LeveyJenningsDiagram::drawSensorChangedSymbol( PaintContext* ctx, const QPointF& pos ) { const QPointF transPos = ctx->coordinatePlane()->translate( QPointF( pos.x(), d->sensorChangedPosition & Qt::AlignTop ? d->expectedMeanValue + 4 * d->expectedStandardDeviation : d->expectedMeanValue - 4 * d->expectedStandardDeviation ) ); QPainter* const painter = ctx->painter(); const PainterSaver ps( painter ); painter->setClipping( false ); painter->translate( transPos ); iconRenderer( SensorChanged )->render( painter, iconRect() ); } /** * Draws a fluidics pack changed symbol for the data point at \a pos. * @param ctx The PaintContext being used * \sa fluidicsPackChangedSymbolPosition */ void LeveyJenningsDiagram::drawFluidicsPackChangedSymbol( PaintContext* ctx, const QPointF& pos ) { const QPointF transPos = ctx->coordinatePlane()->translate( QPointF( pos.x(), d->fluidicsPackChangedPosition & Qt::AlignTop ? d->expectedMeanValue + 4 * d->expectedStandardDeviation : d->expectedMeanValue - 4 * d->expectedStandardDeviation ) ); QPainter* const painter = ctx->painter(); const PainterSaver ps( painter ); painter->setClipping( false ); painter->translate( transPos ); iconRenderer( FluidicsPackChanged )->render( painter, iconRect() ); } /** * Returns the rectangle being used for drawing the icons */ QRectF LeveyJenningsDiagram::iconRect() const { const Measure m( 12.5, KChartEnums::MeasureCalculationModeAuto, KChartEnums::MeasureOrientationAuto ); TextAttributes test; test.setFontSize( m ); const QFontMetrics fm( test.calculatedFont( coordinatePlane()->parent(), KChartEnums::MeasureOrientationAuto ) ); const qreal height = fm.height() / 1.2; return QRectF( -height / 2.0, -height / 2.0, height, height ); } /** * Returns the SVG icon renderer for \a symbol */ QSvgRenderer* LeveyJenningsDiagram::iconRenderer( Symbol symbol ) { if ( d->iconRenderer[ symbol ] == 0 ) d->iconRenderer[ symbol ] = new QSvgRenderer( d->icons[ symbol ], this ); return d->iconRenderer[ symbol ]; } diff --git a/src/KChart/Cartesian/KChartLeveyJenningsDiagram_p.cpp b/src/KChart/Cartesian/KChartLeveyJenningsDiagram_p.cpp index 32ac78d..df6748c 100644 --- a/src/KChart/Cartesian/KChartLeveyJenningsDiagram_p.cpp +++ b/src/KChart/Cartesian/KChartLeveyJenningsDiagram_p.cpp @@ -1,49 +1,49 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#include "KChartLeveyJenningsDiagram_p.h" + #include "KChartLeveyJenningsDiagram.h" #include "KChartDataValueAttributes.h" -#include "KChartLeveyJenningsDiagram_p.h" - using namespace KChart; LeveyJenningsDiagram::Private::Private( const Private& rhs ) : LineDiagram::Private( rhs ), lotChangedPosition( rhs.lotChangedPosition ), fluidicsPackChangedPosition( rhs.fluidicsPackChangedPosition ), sensorChangedPosition( rhs.sensorChangedPosition ), fluidicsPackChanges( rhs.fluidicsPackChanges ), sensorChanges( rhs.sensorChanges ), scanLinePen( rhs.scanLinePen ), icons( rhs.icons ), expectedMeanValue( rhs.expectedMeanValue ), expectedStandardDeviation( rhs.expectedStandardDeviation ) { } void LeveyJenningsDiagram::Private::setYAxisRange() const { CartesianCoordinatePlane* const plane = static_cast< CartesianCoordinatePlane* >( diagram->coordinatePlane() ); if ( plane == 0 ) return; plane->setVerticalRange( QPair< qreal, qreal >( expectedMeanValue - 4 * expectedStandardDeviation, expectedMeanValue + 4 * expectedStandardDeviation ) ); } diff --git a/src/KChart/Cartesian/KChartLineDiagram_p.cpp b/src/KChart/Cartesian/KChartLineDiagram_p.cpp index f33e48d..81e6c44 100644 --- a/src/KChart/Cartesian/KChartLineDiagram_p.cpp +++ b/src/KChart/Cartesian/KChartLineDiagram_p.cpp @@ -1,103 +1,103 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#include "KChartLineDiagram_p.h" #include "KChartLineDiagram.h" #include "KChartDataValueAttributes.h" -#include "KChartLineDiagram_p.h" #include "KChartPainterSaver_p.h" #include "PaintingHelpers_p.h" using namespace KChart; using namespace std; LineDiagram::Private::Private( const Private& rhs ) : AbstractCartesianDiagram::Private( rhs ) { } AttributesModel* LineDiagram::LineDiagramType::attributesModel() const { return m_private->attributesModel; } QModelIndex LineDiagram::LineDiagramType::attributesModelRootIndex() const { return diagram()->attributesModelRootIndex(); } int LineDiagram::LineDiagramType::datasetDimension() const { return m_private->datasetDimension; } ReverseMapper& LineDiagram::LineDiagramType::reverseMapper() { return m_private->reverseMapper; } LineDiagram* LineDiagram::LineDiagramType::diagram() const { return static_cast< LineDiagram* >( m_private->diagram ); } qreal LineDiagram::LineDiagramType::valueForCell( int row, int column ) const { return diagram()->valueForCell( row, column ); } CartesianDiagramDataCompressor& LineDiagram::LineDiagramType::compressor() const { return m_private->compressor; } qreal LineDiagram::LineDiagramType::interpolateMissingValue( const CartesianDiagramDataCompressor::CachePosition& pos ) const { qreal leftValue = std::numeric_limits< qreal >::quiet_NaN(); qreal rightValue = std::numeric_limits< qreal >::quiet_NaN(); int missingCount = 1; const int column = pos.column; const int row = pos.row; const int rowCount = compressor().modelDataRows(); // iterate back and forth to find valid values for ( int r1 = row - 1; r1 > 0; --r1 ) { const CartesianDiagramDataCompressor::CachePosition position( r1, column ); const CartesianDiagramDataCompressor::DataPoint point = compressor().data( position ); leftValue = point.value; if ( !ISNAN( point.value ) ) break; ++missingCount; } for ( int r2 = row + 1; r2 < rowCount; ++r2 ) { const CartesianDiagramDataCompressor::CachePosition position( r2, column ); const CartesianDiagramDataCompressor::DataPoint point = compressor().data( position ); rightValue = point.value; if ( !ISNAN( point.value ) ) break; ++missingCount; } if ( !ISNAN( leftValue ) && !ISNAN( rightValue ) ) return leftValue + ( rightValue - leftValue ) / ( missingCount + 1 ); else return std::numeric_limits< qreal >::quiet_NaN(); } diff --git a/src/KChart/Cartesian/KChartPlotter.cpp b/src/KChart/Cartesian/KChartPlotter.cpp index 64a2268..8c2709d 100644 --- a/src/KChart/Cartesian/KChartPlotter.cpp +++ b/src/KChart/Cartesian/KChartPlotter.cpp @@ -1,488 +1,488 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "KChartPlotter.h" #include "KChartPlotter_p.h" #include "KChartAbstractGrid.h" #include "KChartPainterSaver_p.h" #include "KChartMath_p.h" #include "KChartNormalPlotter_p.h" #include "KChartPercentPlotter_p.h" #include "KChartStackedPlotter_p.h" using namespace KChart; Plotter::Private::Private() : implementor( 0 ) , normalPlotter( 0 ) , percentPlotter( 0 ) , stackedPlotter( 0 ) { } Plotter::Private::~Private() { delete normalPlotter; delete percentPlotter; delete stackedPlotter; } #define d d_func() Plotter::Plotter( QWidget* parent, CartesianCoordinatePlane* plane ) : AbstractCartesianDiagram( new Private(), parent, plane ) { init(); } void Plotter::init() { d->diagram = this; d->normalPlotter = new NormalPlotter( this ); d->percentPlotter = new PercentPlotter( this ); d->stackedPlotter = new StackedPlotter( this ); d->implementor = d->normalPlotter; QObject* test = d->implementor->plotterPrivate(); - connect( this, SIGNAL( boundariesChanged() ), test, SLOT( changedProperties() ) ); + connect( this, SIGNAL(boundariesChanged()), test, SLOT(changedProperties()) ); // The signal is connected to the superclass's slot at this point because the connection happened // in its constructor when "its type was not Plotter yet". - disconnect( this, SIGNAL( attributesModelAboutToChange( AttributesModel*, AttributesModel* ) ), - this, SLOT( connectAttributesModel( AttributesModel* ) ) ); - connect( this, SIGNAL( attributesModelAboutToChange( AttributesModel*, AttributesModel* ) ), - this, SLOT( connectAttributesModel( AttributesModel* ) ) ); + disconnect( this, SIGNAL(attributesModelAboutToChange(AttributesModel*,AttributesModel*)), + this, SLOT(connectAttributesModel(AttributesModel*)) ); + connect( this, SIGNAL(attributesModelAboutToChange(AttributesModel*,AttributesModel*)), + this, SLOT(connectAttributesModel(AttributesModel*)) ); setDatasetDimensionInternal( 2 ); } Plotter::~Plotter() { } /** * Creates an exact copy of this diagram. */ Plotter* Plotter::clone() const { Plotter* newDiagram = new Plotter( new Private( *d ) ); newDiagram->setType( type() ); return newDiagram; } bool Plotter::compare( const Plotter* other ) const { if ( other == this ) return true; if ( other == 0 ) return false; return // compare the base class ( static_cast< const AbstractCartesianDiagram* >( this )->compare( other ) ) && // compare own properties ( type() == other->type() ); } void Plotter::connectAttributesModel( AttributesModel* newModel ) { // Order of setting the AttributesModel in compressor and diagram is very important due to slot // invocation order. Refer to the longer comment in // AbstractCartesianDiagram::connectAttributesModel() for details. if ( useDataCompression() == Plotter::NONE ) { d->plotterCompressor.setModel( 0 ); AbstractCartesianDiagram::connectAttributesModel( newModel ); } else { d->compressor.setModel( 0 ); if ( attributesModel() != d->plotterCompressor.model() ) { d->plotterCompressor.setModel( attributesModel() ); - connect( &d->plotterCompressor, SIGNAL( boundariesChanged() ), this, SLOT(setDataBoundariesDirty() ) ); + connect( &d->plotterCompressor, SIGNAL(boundariesChanged()), this, SLOT(setDataBoundariesDirty()) ); if ( useDataCompression() != Plotter::SLOPE ) { - connect( coordinatePlane(), SIGNAL( internal_geometryChanged( QRect,QRect ) ), - this, SLOT( setDataBoundariesDirty() ) ); - connect( coordinatePlane(), SIGNAL( geometryChanged( QRect,QRect ) ), - this, SLOT( setDataBoundariesDirty() ) ); + connect( coordinatePlane(), SIGNAL(internal_geometryChanged(QRect,QRect)), + this, SLOT(setDataBoundariesDirty()) ); + connect( coordinatePlane(), SIGNAL(geometryChanged(QRect,QRect)), + this, SLOT(setDataBoundariesDirty()) ); calcMergeRadius(); } } } } Plotter::CompressionMode Plotter::useDataCompression() const { return d->implementor->useCompression(); } void Plotter::setUseDataCompression( Plotter::CompressionMode value ) { if ( useDataCompression() != value ) { d->implementor->setUseCompression( value ); if ( useDataCompression() != Plotter::NONE ) { d->compressor.setModel( NULL ); if ( attributesModel() != d->plotterCompressor.model() ) d->plotterCompressor.setModel( attributesModel() ); } } } qreal Plotter::maxSlopeChange() const { return d->plotterCompressor.maxSlopeChange(); } void Plotter::setMaxSlopeChange( qreal value ) { d->plotterCompressor.setMaxSlopeChange( value ); } qreal Plotter::mergeRadiusPercentage() const { return d->mergeRadiusPercentage; } void Plotter::setMergeRadiusPercentage( qreal value ) { if ( d->mergeRadiusPercentage != value ) { d->mergeRadiusPercentage = value; //d->plotterCompressor.setMergeRadiusPercentage( value ); //update(); } } /** * Sets the plotter's type to \a type */ void Plotter::setType( const PlotType type ) { if ( d->implementor->type() == type ) { return; } if ( datasetDimension() != 2 ) { Q_ASSERT_X ( false, "setType()", "This line chart type can only be used with two-dimensional data." ); return; } switch ( type ) { case Normal: d->implementor = d->normalPlotter; break; case Percent: d->implementor = d->percentPlotter; break; case Stacked: d->implementor = d->stackedPlotter; break; default: Q_ASSERT_X( false, "Plotter::setType", "unknown plotter subtype" ); } - bool connection = connect( this, SIGNAL( boundariesChanged() ), - d->implementor->plotterPrivate(), SLOT( changedProperties() ) ); + bool connection = connect( this, SIGNAL(boundariesChanged()), + d->implementor->plotterPrivate(), SLOT(changedProperties()) ); Q_ASSERT( connection ); Q_UNUSED( connection ); // d->lineType = type; Q_ASSERT( d->implementor->type() == type ); setDataBoundariesDirty(); emit layoutChanged( this ); emit propertiesChanged(); } /** * @return the type of the plotter */ Plotter::PlotType Plotter::type() const { return d->implementor->type(); } /** * Sets the global line attributes to \a la */ void Plotter::setLineAttributes( const LineAttributes& la ) { d->attributesModel->setModelData( qVariantFromValue( la ), LineAttributesRole ); emit propertiesChanged(); } /** * Sets the line attributes of data set \a column to \a la */ void Plotter::setLineAttributes( int column, const LineAttributes& la ) { d->setDatasetAttrs( column, qVariantFromValue( la ), LineAttributesRole ); emit propertiesChanged(); } /** * Resets the line attributes of data set \a column */ void Plotter::resetLineAttributes( int column ) { d->resetDatasetAttrs( column, LineAttributesRole ); emit propertiesChanged(); } /** * Sets the line attributes for the model index \a index to \a la */ void Plotter::setLineAttributes( const QModelIndex & index, const LineAttributes& la ) { d->attributesModel->setData( d->attributesModel->mapFromSource( index ), qVariantFromValue( la ), LineAttributesRole ); emit propertiesChanged(); } /** * Remove any explicit line attributes settings that might have been specified before. */ void Plotter::resetLineAttributes( const QModelIndex & index ) { d->attributesModel->resetData( d->attributesModel->mapFromSource(index), LineAttributesRole ); emit propertiesChanged(); } /** * @return the global line attribute set */ LineAttributes Plotter::lineAttributes() const { return d->attributesModel->data( KChart::LineAttributesRole ).value(); } /** * @return the line attribute set of data set \a column */ LineAttributes Plotter::lineAttributes( int column ) const { const QVariant attrs( d->datasetAttrs( column, LineAttributesRole ) ); if ( attrs.isValid() ) return attrs.value(); return lineAttributes(); } /** * @return the line attribute set of the model index \a index */ LineAttributes Plotter::lineAttributes( const QModelIndex& index ) const { return d->attributesModel->data( d->attributesModel->mapFromSource( index ), KChart::LineAttributesRole ).value(); } /** * Sets the global 3D line attributes to \a la */ void Plotter::setThreeDLineAttributes( const ThreeDLineAttributes& la ) { setDataBoundariesDirty(); d->attributesModel->setModelData( qVariantFromValue( la ), ThreeDLineAttributesRole ); emit propertiesChanged(); } /** * Sets the 3D line attributes of data set \a column to \a la */ void Plotter::setThreeDLineAttributes( int column, const ThreeDLineAttributes& la ) { setDataBoundariesDirty(); d->setDatasetAttrs( column, qVariantFromValue( la ), ThreeDLineAttributesRole ); emit propertiesChanged(); } /** * Sets the 3D line attributes of model index \a index to \a la */ void Plotter::setThreeDLineAttributes( const QModelIndex& index, const ThreeDLineAttributes& la ) { setDataBoundariesDirty(); d->attributesModel->setData( d->attributesModel->mapFromSource( index ), qVariantFromValue( la ), ThreeDLineAttributesRole ); emit propertiesChanged(); } /** * @return the global 3D line attributes */ ThreeDLineAttributes Plotter::threeDLineAttributes() const { return d->attributesModel->data( KChart::ThreeDLineAttributesRole ).value(); } /** * @return the 3D line attributes of data set \a column */ ThreeDLineAttributes Plotter::threeDLineAttributes( int column ) const { const QVariant attrs( d->datasetAttrs( column, ThreeDLineAttributesRole ) ); if ( attrs.isValid() ) { return attrs.value(); } return threeDLineAttributes(); } /** * @return the 3D line attributes of the model index \a index */ ThreeDLineAttributes Plotter::threeDLineAttributes( const QModelIndex& index ) const { return d->attributesModel->data( d->attributesModel->mapFromSource( index ), KChart::ThreeDLineAttributesRole ).value(); } qreal Plotter::threeDItemDepth( const QModelIndex & index ) const { return threeDLineAttributes( index ).validDepth(); } qreal Plotter::threeDItemDepth( int column ) const { return threeDLineAttributes( column ).validDepth(); } /** * Sets the value tracker attributes of the model index \a index to \a va */ void Plotter::setValueTrackerAttributes( const QModelIndex & index, const ValueTrackerAttributes & va ) { d->attributesModel->setData( d->attributesModel->mapFromSource( index ), qVariantFromValue( va ), KChart::ValueTrackerAttributesRole ); emit propertiesChanged(); } /** * Returns the value tracker attributes of the model index \a index */ ValueTrackerAttributes Plotter::valueTrackerAttributes( const QModelIndex & index ) const { return d->attributesModel->data( d->attributesModel->mapFromSource( index ), KChart::ValueTrackerAttributesRole ).value(); } void Plotter::resizeEvent ( QResizeEvent* ) { } const QPair< QPointF, QPointF > Plotter::calculateDataBoundaries() const { if ( !checkInvariants( true ) ) return QPair< QPointF, QPointF >( QPointF( 0, 0 ), QPointF( 0, 0 ) ); // note: calculateDataBoundaries() is ignoring the hidden flags. // That's not a bug but a feature: Hiding data does not mean removing them. // For totally removing data from KD Chart's view people can use e.g. a proxy model ... // calculate boundaries for different line types Normal - Stacked - Percent - Default Normal return d->implementor->calculateDataBoundaries(); } void Plotter::paintEvent ( QPaintEvent*) { QPainter painter ( viewport() ); PaintContext ctx; ctx.setPainter ( &painter ); ctx.setRectangle ( QRectF ( 0, 0, width(), height() ) ); paint ( &ctx ); } void Plotter::paint( PaintContext* ctx ) { // note: Not having any data model assigned is no bug // but we can not draw a diagram then either. if ( !checkInvariants( true ) ) return; AbstractCoordinatePlane* const plane = ctx->coordinatePlane(); if ( ! plane ) return; d->setCompressorResolution( size(), plane ); if ( !AbstractGrid::isBoundariesValid(dataBoundaries()) ) return; const PainterSaver p( ctx->painter() ); if ( model()->rowCount( rootIndex() ) == 0 || model()->columnCount( rootIndex() ) == 0 ) return; // nothing to paint for us ctx->setCoordinatePlane( plane->sharedAxisMasterPlane( ctx->painter() ) ); // paint different line types Normal - Stacked - Percent - Default Normal d->implementor->paint( ctx ); ctx->setCoordinatePlane( plane ); } void Plotter::resize ( const QSizeF& size ) { d->setCompressorResolution( size, coordinatePlane() ); if ( useDataCompression() == Plotter::BOTH || useDataCompression() == Plotter::DISTANCE ) { d->plotterCompressor.cleanCache(); calcMergeRadius(); } setDataBoundariesDirty(); AbstractCartesianDiagram::resize( size ); } void Plotter::setDataBoundariesDirty() { AbstractCartesianDiagram::setDataBoundariesDirty(); if ( useDataCompression() == Plotter::DISTANCE || useDataCompression() == Plotter::BOTH ) { calcMergeRadius(); //d->plotterCompressor.setMergeRadiusPercentage( d->mergeRadiusPercentage ); } } void Plotter::calcMergeRadius() { CartesianCoordinatePlane *plane = dynamic_cast< CartesianCoordinatePlane* >( coordinatePlane() ); Q_ASSERT( plane ); //Q_ASSERT( plane->translate( plane->translateBack( plane->visibleDiagramArea().topLeft() ) ) == plane->visibleDiagramArea().topLeft() ); QRectF range = plane->visibleDataRange(); //qDebug() << range; const qreal radius = std::sqrt( ( range.x() + range.width() ) * ( range.y() + range.height() ) ); //qDebug() << radius; //qDebug() << radius * d->mergeRadiusPercentage; //qDebug() << d->mergeRadiusPercentage; d->plotterCompressor.setMergeRadius( radius * d->mergeRadiusPercentage ); } #if defined(Q_COMPILER_MANGLES_RETURN_TYPE) const #endif int Plotter::numberOfAbscissaSegments () const { return d->attributesModel->rowCount( attributesModelRootIndex() ); } #if defined(Q_COMPILER_MANGLES_RETURN_TYPE) const #endif int Plotter::numberOfOrdinateSegments () const { return d->attributesModel->columnCount( attributesModelRootIndex() ); } diff --git a/src/KChart/Cartesian/KChartPlotterDiagramCompressor.cpp b/src/KChart/Cartesian/KChartPlotterDiagramCompressor.cpp index fea8871..a4ef678 100644 --- a/src/KChart/Cartesian/KChartPlotterDiagramCompressor.cpp +++ b/src/KChart/Cartesian/KChartPlotterDiagramCompressor.cpp @@ -1,933 +1,933 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "KChartPlotterDiagramCompressor.h" #include "KChartPlotterDiagramCompressor_p.h" #include "KChartMath_p.h" -#include +#include using namespace KChart; qreal calculateSlope( const PlotterDiagramCompressor::DataPoint &lhs, const PlotterDiagramCompressor::DataPoint & rhs ) { return ( rhs.value - lhs.value ) / ( rhs.key - lhs.key ); } PlotterDiagramCompressor::Iterator::Iterator( int dataSet, PlotterDiagramCompressor *parent ) : m_parent( parent ) , m_index( 0 ) , m_dataset( dataSet ) , m_bufferIndex( 0 ) , m_rebuffer( true ) { if ( m_parent ) { if ( parent->rowCount() > m_dataset && parent->rowCount() > 0 ) { m_buffer.append( parent->data( CachePosition( m_index, m_dataset ) ) ); } } else { m_dataset = - 1; m_index = - 1; } } PlotterDiagramCompressor::Iterator::Iterator( int dataSet, PlotterDiagramCompressor *parent, QVector< DataPoint > buffer ) : m_parent( parent ) , m_buffer( buffer ) , m_index( 0 ) , m_dataset( dataSet ) , m_bufferIndex( 0 ) , m_rebuffer( false ) , m_timeOfCreation( QDateTime::currentDateTime() ) { if ( !m_parent ) { m_dataset = -1 ; m_index = - 1; } else { // buffer needs to be filled if ( parent->datasetCount() > m_dataset && parent->rowCount() > 0 && m_buffer.isEmpty() ) { m_buffer.append( parent->data( CachePosition( m_index, m_dataset ) ) ); m_rebuffer = true; } } } PlotterDiagramCompressor::Iterator::~Iterator() { if ( m_parent ) { if ( m_parent.data()->d->m_timeOfLastInvalidation < m_timeOfCreation ) m_parent.data()->d->m_bufferlist[ m_dataset ] = m_buffer; } } bool PlotterDiagramCompressor::Iterator::isValid() const { if ( m_parent == 0 ) return false; return m_dataset >= 0 && m_index >= 0 && m_parent.data()->rowCount() > m_index; } //PlotterDiagramCompressor::Iterator& PlotterDiagramCompressor::Iterator::operator++() //{ // ++m_index; // ++m_bufferIndex; // // the version that checks dataBoundaries is separated here, this is to avoid the runtime cost // // of checking every time the boundaries if thats not necessary // if ( m_parent.data()->d->forcedBoundaries( Qt::Vertical ) || m_parent.data()->d->forcedBoundaries( Qt::Vertical ) ) // { // if ( m_bufferIndex >= m_buffer.count() && m_rebuffer ) // { // if ( m_index < m_parent.data()->rowCount() ) // { // PlotterDiagramCompressor::DataPoint dp = m_parent.data()->data( CachePosition( m_index, m_dataset ) ); // if ( m_parent.data()->d->inBoundaries( Qt::Vertical, dp ) && m_parent.data()->d->inBoundaries( Qt::Horizontal, dp ) ) // { // m_buffer.append( dp ); // } // else // { // if ( m_index + 1 < m_parent.data()->rowCount() ) // { // PlotterDiagramCompressor::DataPoint dp1 = m_parent.data()->data( CachePosition( m_index, m_dataset ) ); // if ( m_parent.data()->d->inBoundaries( Qt::Vertical, dp1 ) && m_parent.data()->d->inBoundaries( Qt::Horizontal, dp1 ) ) // { // m_buffer.append( dp ); // } // } // } // } // } // else // { // if ( m_bufferIndex == m_buffer.count() ) // m_index = - 1; // return *this; // } // PlotterDiagramCompressor::DataPoint dp; // if ( isValid() ) // dp = m_parent.data()->data( CachePosition( m_index - 1, m_dataset ) ); // if ( m_parent ) // { // if ( m_index >= m_parent.data()->rowCount() ) // m_index = -1; // else // { // const qreal mergeRadius = m_parent.data()->d->m_mergeRadius; // PlotterDiagramCompressor::DataPoint newdp = m_parent.data()->data( CachePosition( m_index, m_dataset ) ); // while ( dp.distance( newdp ) <= mergeRadius // || !( m_parent.data()->d->inBoundaries( Qt::Vertical, dp ) || m_parent.data()->d->inBoundaries( Qt::Horizontal, dp ) ) ) // { // ++m_index; // if ( m_index >= m_parent.data()->rowCount() ) // { // m_index = - 1; // break; // } // newdp = m_parent.data()->data( CachePosition( m_index, m_dataset ) ); // } // } // } // } // else // { // // we have a new point in the buffer // if ( m_bufferIndex >= m_buffer.count() && m_rebuffer ) // { // if ( m_index < m_parent.data()->rowCount() ) // m_buffer.append( m_parent.data()->data( CachePosition( m_index, m_dataset ) ) ); // } // else // { // if ( m_bufferIndex == m_buffer.count() ) // m_index = - 1; // return *this; // } // PlotterDiagramCompressor::DataPoint dp; // if ( isValid() ) // dp = m_parent.data()->data( CachePosition( m_index - 1, m_dataset ) ); // // make sure we switch to the next point which would be in the buffer // if ( m_parent ) // { // PlotterDiagramCompressor *parent = m_parent.data(); // if ( m_index >= parent->rowCount() ) // m_index = -1; // else // { // switch ( parent->d->m_mode ) // { // case( PlotterDiagramCompressor::DISTANCE ): // { // const qreal mergeRadius = m_parent.data()->d->m_mergeRadius; // PlotterDiagramCompressor::DataPoint newdp = m_parent.data()->data( CachePosition( m_index, m_dataset ) ); // while ( dp.distance( newdp ) <= mergeRadius ) // { // ++m_index; // if ( m_index >= m_parent.data()->rowCount() ) // { // m_index = - 1; // break; // } // newdp = m_parent.data()->data( CachePosition( m_index, m_dataset ) ); // } // } // break; // case( PlotterDiagramCompressor::BOTH ): // { // const qreal mergeRadius = m_parent.data()->d->m_mergeRadius; // PlotterDiagramCompressor::DataPoint newdp = m_parent.data()->data( CachePosition( m_index, m_dataset ) ); // while ( dp.distance( newdp ) <= mergeRadius ) // { // ++m_index; // if ( m_index >= m_parent.data()->rowCount() ) // { // m_index = - 1; // break; // } // newdp = m_parent.data()->data( CachePosition( m_index, m_dataset ) ); // } // } // break; // case ( PlotterDiagramCompressor::SLOPE ): // { // const qreal mergedist = parent->d->m_maxSlopeRadius; // qreal oldSlope = 0; // qreal newSlope = 0; // PlotterDiagramCompressor::DataPoint newdp = m_parent.data()->data( CachePosition( m_index, m_dataset ) ); // PlotterDiagramCompressor::DataPoint olddp = PlotterDiagramCompressor::DataPoint(); // if ( m_bufferIndex > 1 ) // { // oldSlope = calculateSlope( m_buffer[ m_bufferIndex - 2 ], m_buffer[ m_bufferIndex - 1 ] ); // newSlope = calculateSlope( m_buffer[ m_bufferIndex - 1 ], newdp ); // } // bool first = true; // while ( qAbs( newSlope - oldSlope ) < mergedist ) // { // ++m_index; // if ( m_index >= m_parent.data()->rowCount() ) // { // m_index = - 1; // break; // } // if ( first ) // { // oldSlope = newSlope; // first = false; // } // olddp = newdp; // newdp = m_parent.data()->data( CachePosition( m_index, m_dataset ) ); // newSlope = calculateSlope( olddp, newdp ); // } // } // break; // default: // Q_ASSERT( false ); // } // } // } // } // return *this; //} void PlotterDiagramCompressor::Iterator::handleSlopeForward( const DataPoint &dp ) { PlotterDiagramCompressor* parent = m_parent.data(); const qreal mergedist = parent->d->m_maxSlopeRadius; qreal oldSlope = 0; qreal newSlope = 0; PlotterDiagramCompressor::DataPoint newdp = dp; PlotterDiagramCompressor::DataPoint olddp = PlotterDiagramCompressor::DataPoint(); if ( m_bufferIndex > 1 ) { //oldSlope = calculateSlope( m_buffer[ m_bufferIndex - 2 ], m_buffer[ m_bufferIndex - 1 ] ); //newSlope = calculateSlope( m_buffer[ m_bufferIndex - 1 ], newdp ); oldSlope = calculateSlope( parent->data( CachePosition( m_index - 2, m_dataset ) ) , parent->data( CachePosition( m_index - 1, m_dataset ) ) ); newSlope = calculateSlope( parent->data( CachePosition( m_index - 1, m_dataset ) ), newdp ); qreal accumulatedDist = qAbs( newSlope - oldSlope ); qreal olddist = accumulatedDist; qreal newdist; int counter = 0; while ( accumulatedDist < mergedist ) { ++m_index; if ( m_index >= m_parent.data()->rowCount() ) { m_index = - 1; if ( m_buffer.last() != parent->data( CachePosition( parent->rowCount() -1, m_dataset ) ) ) m_index = parent->rowCount(); break; } oldSlope = newSlope; olddp = newdp; newdp = parent->data( CachePosition( m_index, m_dataset ) ); newSlope = calculateSlope( olddp, newdp ); newdist = qAbs( newSlope - oldSlope ); if ( olddist == newdist ) { ++counter; } else { if ( counter > 10 ) break; } accumulatedDist += newdist; olddist = newdist; } m_buffer.append( newdp ); } else m_buffer.append( dp ); } PlotterDiagramCompressor::Iterator& PlotterDiagramCompressor::Iterator::operator++() { PlotterDiagramCompressor* parent = m_parent.data(); Q_ASSERT( parent ); const int count = parent->rowCount(); //increment the indexes ++m_index; ++m_bufferIndex; //if the index reached the end of the datamodel make this iterator an enditerator //and make sure the buffer was not already build, if thats the case its not necessary //to rebuild it and it would be hard to extend it as we had to know where m_index was if ( m_index >= count || ( !m_rebuffer && m_bufferIndex == m_buffer.count() ) ) { if ( m_bufferIndex == m_buffer.count() ) { if ( m_buffer.last() != parent->data( CachePosition( parent->rowCount() -1, m_dataset ) ) ) m_index = parent->rowCount(); else m_index = - 1; ++m_bufferIndex; } else m_index = -1; } //if we reached the end of the buffer continue filling the buffer if ( m_bufferIndex == m_buffer.count() && m_index >= 0 && m_rebuffer ) { PlotterDiagramCompressor::DataPoint dp = parent->data( CachePosition( m_index, m_dataset ) ); if ( parent->d->inBoundaries( Qt::Vertical, dp ) && parent->d->inBoundaries( Qt::Horizontal, dp ) ) { if ( parent->d->m_mode == PlotterDiagramCompressor::SLOPE ) handleSlopeForward( dp ); } else { m_index = -1; } } return *this; } PlotterDiagramCompressor::Iterator PlotterDiagramCompressor::Iterator::operator++( int ) { Iterator result = *this; ++result; return result; } PlotterDiagramCompressor::Iterator& PlotterDiagramCompressor::Iterator::operator += ( int value ) { for ( int index = m_index; index + value != m_index; ++( *this ) ) {}; return *this; } PlotterDiagramCompressor::Iterator& PlotterDiagramCompressor::Iterator::operator--() { --m_index; --m_bufferIndex; return *this; } PlotterDiagramCompressor::Iterator PlotterDiagramCompressor::Iterator::operator--( int ) { Iterator result = *this; --result; return result; } PlotterDiagramCompressor::Iterator& PlotterDiagramCompressor::Iterator::operator-=( int value ) { m_index -= value; return *this; } PlotterDiagramCompressor::DataPoint PlotterDiagramCompressor::Iterator::operator*() { if ( !m_parent ) return PlotterDiagramCompressor::DataPoint(); Q_ASSERT( m_parent ); if ( m_index == m_parent.data()->rowCount() ) return m_parent.data()->data( CachePosition( m_parent.data()->rowCount() - 1 , m_dataset ) ); return m_buffer[ m_bufferIndex ]; } bool PlotterDiagramCompressor::Iterator::operator==( const PlotterDiagramCompressor::Iterator &other ) const { return m_parent.data() == other.m_parent.data() && m_index == other.m_index && m_dataset == other.m_dataset; } bool PlotterDiagramCompressor::Iterator::operator!=( const PlotterDiagramCompressor::Iterator &other ) const { return ! ( *this == other ); } void PlotterDiagramCompressor::Iterator::invalidate() { m_dataset = - 1; } PlotterDiagramCompressor::Private::Private( PlotterDiagramCompressor *parent ) : m_parent( parent ) , m_model( 0 ) , m_mergeRadius( 0.1 ) , m_maxSlopeRadius( 0.1 ) , m_boundary( qMakePair( QPointF( std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN() ) , QPointF( std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN() ) ) ) , m_forcedXBoundaries( qMakePair( std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN() ) ) , m_forcedYBoundaries( qMakePair( std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN() ) ) , m_mode( PlotterDiagramCompressor::SLOPE ) { } void PlotterDiagramCompressor::Private::setModelToZero() { m_model = 0; } inline bool inBoundary( const QPair< qreal, qreal > &bounds, qreal value ) { return bounds.first <= value && value <= bounds.second; } bool PlotterDiagramCompressor::Private::inBoundaries( Qt::Orientation orient, const PlotterDiagramCompressor::DataPoint &dp ) const { if ( orient == Qt::Vertical && forcedBoundaries( Qt::Vertical ) ) { return inBoundary( m_forcedYBoundaries, dp.value ); } else if ( forcedBoundaries( Qt::Horizontal ) ) { return inBoundary( m_forcedXBoundaries, dp.key ); } return true; } //// TODO this is not threadsafe do never try to invoke the painting in a different thread than this //// method //void PlotterDiagramCompressor::Private::rowsInserted( const QModelIndex& /*parent*/, int start, int end ) //{ // if ( m_bufferlist.count() > 0 && !m_bufferlist[ 0 ].isEmpty() && start < m_bufferlist[ 0 ].count() ) // { // calculateDataBoundaries(); // clearBuffer(); // return; // } // // we are handling appends only here, a prepend might be added, insert is expensive if not needed // qreal minX = std::numeric_limits< qreal >::max(); // qreal minY = std::numeric_limits< qreal >::max(); // qreal maxX = std::numeric_limits< qreal >::min(); // qreal maxY = std::numeric_limits< qreal >::min(); // for ( int dataset = 0; dataset < m_bufferlist.size(); ++dataset ) // { // PlotterDiagramCompressor::DataPoint predecessor = m_bufferlist[ dataset ].isEmpty() ? DataPoint() : m_bufferlist[ dataset ].last(); // qreal oldSlope = 0; // qreal newSlope = 0; // PlotterDiagramCompressor::DataPoint newdp = m_parent->data( CachePosition( start, dataset ) ); // PlotterDiagramCompressor::DataPoint olddp = PlotterDiagramCompressor::DataPoint(); // const int datacount = m_bufferlist[ dataset ].count(); // if ( m_mode != PlotterDiagramCompressor::DISTANCE && m_bufferlist[ dataset ].count() > 1 ) // { // oldSlope = calculateSlope( m_bufferlist[ dataset ][ datacount - 2 ], m_bufferlist[ dataset ][ datacount - 1 ] ); // newSlope = calculateSlope( m_bufferlist[ dataset ][ datacount - 1 ], newdp ); // } // bool first = true; // for ( int row = start; row <= end; ++row ) // { // PlotterDiagramCompressor::DataPoint curdp = m_parent->data( CachePosition( row, dataset ) ); // const bool checkcur = inBoundaries( Qt::Vertical, curdp ) && inBoundaries( Qt::Horizontal, curdp ); // const bool checkpred = inBoundaries( Qt::Vertical, predecessor ) && inBoundaries( Qt::Horizontal, predecessor ); // const bool check = checkcur || checkpred; // switch ( m_mode ) // { // case( PlotterDiagramCompressor::BOTH ): // { // if ( predecessor.distance( curdp ) > m_mergeRadius && check ) // { // if ( start > m_bufferlist[ dataset ].count() && !m_bufferlist[ dataset ].isEmpty() ) // { // m_bufferlist[ dataset ].append( curdp ); // } // else if ( !m_bufferlist[ dataset ].isEmpty() ) // { // m_bufferlist[ dataset ].insert( row, curdp ); // } // predecessor = curdp; // minX = qMin( curdp.key, m_boundary.first.x() ); // minY = qMin( curdp.value, m_boundary.first.y() ); // maxX = qMax( curdp.key, m_boundary.second.x() ); // maxY = qMax( curdp.value, m_boundary.second.y() ); // } // } // break; // case ( PlotterDiagramCompressor::DISTANCE ): // { // if ( predecessor.distance( curdp ) > m_mergeRadius && check ) // { // if ( start > m_bufferlist[ dataset ].count() && !m_bufferlist[ dataset ].isEmpty() ) // { // m_bufferlist[ dataset ].append( curdp ); // } // else if ( !m_bufferlist[ dataset ].isEmpty() ) // { // m_bufferlist[ dataset ].insert( row, curdp ); // } // predecessor = curdp; // minX = qMin( curdp.key, m_boundary.first.x() ); // minY = qMin( curdp.value, m_boundary.first.y() ); // maxX = qMax( curdp.key, m_boundary.second.x() ); // maxY = qMax( curdp.value, m_boundary.second.y() ); // } // } // break; // case( PlotterDiagramCompressor::SLOPE ): // { // if ( check && qAbs( newSlope - oldSlope ) >= m_maxSlopeRadius ) // { // if ( start > m_bufferlist[ dataset ].count() && !m_bufferlist[ dataset ].isEmpty() ) // { // m_bufferlist[ dataset ].append( curdp ); // oldSlope = newSlope; // } // else if ( !m_bufferlist[ dataset ].isEmpty() ) // { // m_bufferlist[ dataset ].insert( row, curdp ); // oldSlope = newSlope; // } // predecessor = curdp; // minX = qMin( curdp.key, m_boundary.first.x() ); // minY = qMin( curdp.value, m_boundary.first.y() ); // maxX = qMax( curdp.key, m_boundary.second.x() ); // maxY = qMax( curdp.value, m_boundary.second.y() ); // if ( first ) // { // oldSlope = newSlope; // first = false; // } // olddp = newdp; // newdp = m_parent->data( CachePosition( row, dataset ) ); // newSlope = calculateSlope( olddp, newdp ); // } // } // break; // } // } // } // setBoundaries( qMakePair( QPointF( minX, minY ), QPointF( maxX, maxY ) ) ); // emit m_parent->rowCountChanged(); //} #include // TODO this is not threadsafe do never try to invoke the painting in a different thread than this // method void PlotterDiagramCompressor::Private::rowsInserted( const QModelIndex& /*parent*/, int start, int end ) { //Q_ASSERT( std::numeric_limits::quiet_NaN() < 5 || std::numeric_limits::quiet_NaN() > 5 ); //Q_ASSERT( 5 == qMin( std::numeric_limits::quiet_NaN(), 5.0 ) ); //Q_ASSERT( 5 == qMax( 5.0, std::numeric_limits::quiet_NaN() ) ); if ( m_bufferlist.count() > 0 && !m_bufferlist[ 0 ].isEmpty() && start < m_bufferlist[ 0 ].count() ) { calculateDataBoundaries(); clearBuffer(); return; } // we are handling appends only here, a prepend might be added, insert is expensive if not needed qreal minX = m_boundary.first.x(); qreal minY = m_boundary.first.y(); qreal maxX = m_boundary.second.x(); qreal maxY = m_boundary.second.y(); for ( int dataset = 0; dataset < m_bufferlist.size(); ++dataset ) { if ( m_mode == PlotterDiagramCompressor::SLOPE ) { PlotterDiagramCompressor::DataPoint predecessor = m_bufferlist[ dataset ].isEmpty() ? DataPoint() : m_bufferlist[ dataset ].last(); qreal oldSlope = 0; qreal newSlope = 0; int counter = 0; PlotterDiagramCompressor::DataPoint newdp = m_parent->data( CachePosition( start, dataset ) ); PlotterDiagramCompressor::DataPoint olddp = PlotterDiagramCompressor::DataPoint(); if ( start > 1 ) { oldSlope = calculateSlope( m_parent->data( CachePosition( start - 2, dataset ) ), m_parent->data( CachePosition( start - 1, dataset ) ) ); olddp = m_parent->data( CachePosition( start - 1, dataset ) ); } else { m_bufferlist[ dataset ].append( newdp ); minX = qMin( minX, newdp.key ); minY = qMin( minY, newdp.value ); maxX = qMax( newdp.key, maxX ); maxY = qMax( newdp.value, maxY ); continue; } qreal olddist = 0; qreal newdist = 0; for ( int row = start; row <= end; ++row ) { PlotterDiagramCompressor::DataPoint curdp = m_parent->data( CachePosition( row, dataset ) ); newdp = curdp; newSlope = calculateSlope( olddp, newdp ); olddist = newdist; newdist = qAbs( newSlope - oldSlope ); m_accumulatedDistances[ dataset ] += newdist; const bool checkcur = inBoundaries( Qt::Vertical, curdp ) && inBoundaries( Qt::Horizontal, curdp ); const bool checkpred = inBoundaries( Qt::Vertical, predecessor ) && inBoundaries( Qt::Horizontal, predecessor ); const bool check = checkcur || checkpred; if ( m_accumulatedDistances[ dataset ] >= m_maxSlopeRadius && check ) { if ( start > m_bufferlist[ dataset ].count() && !m_bufferlist[ dataset ].isEmpty() ) { m_bufferlist[ dataset ].append( curdp ); } else if ( !m_bufferlist[ dataset ].isEmpty() ) { m_bufferlist[ dataset ].insert( row, curdp ); } predecessor = curdp; m_accumulatedDistances[ dataset ] = 0; } minX = qMin( minX, curdp.key ); minY = qMin( minY, curdp.value ); maxX = qMax( curdp.key, maxX ); maxY = qMax( curdp.value, maxY ); oldSlope = newSlope; olddp = newdp; if ( olddist == newdist ) { ++counter; } else { if ( counter > 10 ) { m_bufferlist[ dataset ].append( curdp ); predecessor = curdp; m_accumulatedDistances[ dataset ] = 0; } } } setBoundaries( qMakePair( QPointF( minX, minY ), QPointF( maxX, maxY ) ) ); } else { PlotterDiagramCompressor::DataPoint predecessor = m_bufferlist[ dataset ].isEmpty() ? DataPoint() : m_bufferlist[ dataset ].last(); for ( int row = start; row <= end; ++row ) { PlotterDiagramCompressor::DataPoint curdp = m_parent->data( CachePosition( row, dataset ) ); const bool checkcur = inBoundaries( Qt::Vertical, curdp ) && inBoundaries( Qt::Horizontal, curdp ); const bool checkpred = inBoundaries( Qt::Vertical, predecessor ) && inBoundaries( Qt::Horizontal, predecessor ); const bool check = checkcur || checkpred; if ( predecessor.distance( curdp ) > m_mergeRadius && check ) { if ( start > m_bufferlist[ dataset ].count() && !m_bufferlist[ dataset ].isEmpty() ) { m_bufferlist[ dataset ].append( curdp ); } else if ( !m_bufferlist[ dataset ].isEmpty() ) { m_bufferlist[ dataset ].insert( row, curdp ); } predecessor = curdp; qreal minX = qMin( curdp.key, m_boundary.first.x() ); qreal minY = qMin( curdp.value, m_boundary.first.y() ); qreal maxX = qMax( curdp.key, m_boundary.second.x() ); qreal maxY = qMax( curdp.value, m_boundary.second.y() ); setBoundaries( qMakePair( QPointF( minX, minY ), QPointF( maxX, maxY ) ) ); } } } } emit m_parent->rowCountChanged(); } void PlotterDiagramCompressor::setCompressionModel( CompressionMode value ) { Q_ASSERT( d ); if ( d->m_mode != value ) { d->m_mode = value; d->clearBuffer(); emit rowCountChanged(); } } void PlotterDiagramCompressor::Private::setBoundaries( const Boundaries & bound ) { if ( bound != m_boundary ) { m_boundary = bound; emit m_parent->boundariesChanged(); } } void PlotterDiagramCompressor::Private::calculateDataBoundaries() { if ( !forcedBoundaries( Qt::Vertical ) || !forcedBoundaries( Qt::Horizontal ) ) { qreal minX = std::numeric_limits::quiet_NaN(); qreal minY = std::numeric_limits::quiet_NaN(); qreal maxX = std::numeric_limits::quiet_NaN(); qreal maxY = std::numeric_limits::quiet_NaN(); for ( int dataset = 0; dataset < m_parent->datasetCount(); ++dataset ) { for ( int row = 0; row < m_parent->rowCount(); ++ row ) { PlotterDiagramCompressor::DataPoint dp = m_parent->data( CachePosition( row, dataset ) ); minX = qMin( minX, dp.key ); minY = qMin( minY, dp.value ); maxX = qMax( dp.key, maxX ); maxY = qMax( dp.value, maxY ); Q_ASSERT( !ISNAN( minX ) ); Q_ASSERT( !ISNAN( minY ) ); Q_ASSERT( !ISNAN( maxX ) ); Q_ASSERT( !ISNAN( maxY ) ); } } if ( forcedBoundaries( Qt::Vertical ) ) { minY = m_forcedYBoundaries.first; maxY = m_forcedYBoundaries.second; } if ( forcedBoundaries( Qt::Horizontal ) ) { minX = m_forcedXBoundaries.first; maxX = m_forcedXBoundaries.second; } setBoundaries( qMakePair( QPointF( minX, minY ), QPointF( maxX, maxY ) ) ); } } QModelIndexList PlotterDiagramCompressor::Private::mapToModel( const CachePosition &pos ) { QModelIndexList indexes; QModelIndex index; index = m_model->index( pos.first, pos.second * 2, QModelIndex() ); Q_ASSERT( index.isValid() ); indexes << index; index = m_model->index( pos.first, pos.second * 2 + 1, QModelIndex() ); Q_ASSERT( index.isValid() ); indexes << index; return indexes; } bool PlotterDiagramCompressor::Private::forcedBoundaries( Qt::Orientation orient ) const { if ( orient == Qt::Vertical ) return !ISNAN( m_forcedYBoundaries.first ) && !ISNAN( m_forcedYBoundaries.second ); else return !ISNAN( m_forcedXBoundaries.first ) && !ISNAN( m_forcedXBoundaries.second ); } void PlotterDiagramCompressor::Private::clearBuffer() { //TODO all iterator have to be invalid after this operation //TODO make sure there are no regressions, the timeOfLastInvalidation should stop iterators from // corrupting the cache m_bufferlist.clear(); m_bufferlist.resize( m_parent->datasetCount() ); m_accumulatedDistances.clear(); m_accumulatedDistances.resize( m_parent->datasetCount() ); m_timeOfLastInvalidation = QDateTime::currentDateTime(); } PlotterDiagramCompressor::PlotterDiagramCompressor(QObject *parent) : QObject(parent) , d( new Private( this ) ) { } PlotterDiagramCompressor::~PlotterDiagramCompressor() { delete d; d = 0; } void PlotterDiagramCompressor::setForcedDataBoundaries( const QPair< qreal, qreal > &bounds, Qt::Orientation direction ) { if ( direction == Qt::Vertical ) { d->m_forcedYBoundaries = bounds; } else { d->m_forcedXBoundaries = bounds; } d->clearBuffer(); emit boundariesChanged(); } QAbstractItemModel* PlotterDiagramCompressor::model() const { Q_ASSERT( d ); return d->m_model; } void PlotterDiagramCompressor::setModel( QAbstractItemModel *model ) { Q_ASSERT( d ); if ( d->m_model ) { d->m_model->disconnect( this ); d->m_model->disconnect( d ); } d->m_model = model; if ( d->m_model) { d->m_bufferlist.resize( datasetCount() ); d->m_accumulatedDistances.resize( datasetCount() ); d->calculateDataBoundaries(); - connect( d->m_model, SIGNAL( rowsInserted ( QModelIndex, int, int ) ), d, SLOT( rowsInserted( QModelIndex, int, int ) ) ); - connect( d->m_model, SIGNAL( modelReset() ), d, SLOT( clearBuffer() ) ); - connect( d->m_model, SIGNAL( destroyed( QObject* ) ), d, SLOT( setModelToZero() ) ); + connect( d->m_model, SIGNAL(rowsInserted(QModelIndex,int,int)), d, SLOT(rowsInserted(QModelIndex,int,int)) ); + connect( d->m_model, SIGNAL(modelReset()), d, SLOT(clearBuffer()) ); + connect( d->m_model, SIGNAL(destroyed(QObject*)), d, SLOT(setModelToZero()) ); } } PlotterDiagramCompressor::DataPoint PlotterDiagramCompressor::data( const CachePosition& pos ) const { DataPoint point; QModelIndexList indexes = d->mapToModel( pos ); Q_ASSERT( indexes.count() == 2 ); QVariant yValue = d->m_model->data( indexes.last() ); QVariant xValue = d->m_model->data( indexes.first() ); Q_ASSERT( xValue.isValid() ); Q_ASSERT( yValue.isValid() ); bool ok = false; point.key = xValue.toReal( &ok ); Q_ASSERT( ok ); ok = false; point.value = yValue.toReal( &ok ); Q_ASSERT( ok ); point.index = indexes.first(); return point; } void PlotterDiagramCompressor::setMergeRadius( qreal radius ) { if ( d->m_mergeRadius != radius ) { d->m_mergeRadius = radius; if ( d->m_mode != PlotterDiagramCompressor::SLOPE ) emit rowCountChanged(); } } void PlotterDiagramCompressor::setMaxSlopeChange( qreal value ) { if ( d->m_maxSlopeRadius != value ) { d->m_maxSlopeRadius = value; emit boundariesChanged(); } } qreal PlotterDiagramCompressor::maxSlopeChange() const { return d->m_maxSlopeRadius; } void PlotterDiagramCompressor::setMergeRadiusPercentage( qreal radius ) { Boundaries bounds = dataBoundaries(); const qreal width = radius * ( bounds.second.x() - bounds.first.x() ); const qreal height = radius * ( bounds.second.y() - bounds.first.y() ); const qreal realRadius = std::sqrt( width * height ); setMergeRadius( realRadius ); } int PlotterDiagramCompressor::rowCount() const { return d->m_model ? d->m_model->rowCount() : 0; } void PlotterDiagramCompressor::cleanCache() { d->clearBuffer(); } int PlotterDiagramCompressor::datasetCount() const { if ( d->m_model && d->m_model->columnCount() == 0 ) return 0; return d->m_model ? ( d->m_model->columnCount() + 1 ) / 2 : 0; } QPair< QPointF, QPointF > PlotterDiagramCompressor::dataBoundaries() const { Boundaries bounds = d->m_boundary; if ( d->forcedBoundaries( Qt::Vertical ) ) { bounds.first.setY( d->m_forcedYBoundaries.first ); bounds.second.setY( d->m_forcedYBoundaries.second ); } if ( d->forcedBoundaries( Qt::Horizontal ) ) { bounds.first.setX( d->m_forcedXBoundaries.first ); bounds.second.setX( d->m_forcedXBoundaries.second ); } return bounds; } PlotterDiagramCompressor::Iterator PlotterDiagramCompressor::begin( int dataSet ) { Q_ASSERT( dataSet >= 0 && dataSet < d->m_bufferlist.count() ); return Iterator( dataSet, this, d->m_bufferlist[ dataSet ] ); } PlotterDiagramCompressor::Iterator PlotterDiagramCompressor::end( int dataSet ) { Iterator it( dataSet, this ); it.m_index = -1; return it; } diff --git a/src/KChart/Cartesian/KChartPlotterDiagramCompressor.h b/src/KChart/Cartesian/KChartPlotterDiagramCompressor.h index a41ee2a..d6850db 100644 --- a/src/KChart/Cartesian/KChartPlotterDiagramCompressor.h +++ b/src/KChart/Cartesian/KChartPlotterDiagramCompressor.h @@ -1,151 +1,151 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef PLOTTERDIAGRAMCOMPRESSOR_H #define PLOTTERDIAGRAMCOMPRESSOR_H -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include namespace KChart { class PlotterDiagramCompressor : public QObject { Q_OBJECT Q_ENUMS( CompressionMode ) public: enum CompressionMode{ SLOPE = 0, DISTANCE, BOTH }; class DataPoint { public: DataPoint() : key( std::numeric_limits< qreal >::quiet_NaN() ), value( std::numeric_limits< qreal >::quiet_NaN() ), hidden( false ) {} inline qreal distance( const DataPoint &other ) { const qreal dx = key - other.key; const qreal dy = value - other.value; return std::sqrt( dx * dx + dy * dy ); } inline bool operator==( const DataPoint &other ) { return key == other.key && value == other.value; } inline bool operator!=( const DataPoint &other ) { return !( *this == other ); } qreal key; qreal value; bool hidden; QModelIndex index; }; class Iterator { friend class PlotterDiagramCompressor; public: Iterator( int dataSet, PlotterDiagramCompressor *parent ); ~Iterator(); bool isValid() const; Iterator& operator++(); Iterator operator++( int ); Iterator& operator += ( int value ); Iterator& operator--(); Iterator operator--( int ); Iterator& operator-=( int value ); DataPoint operator*(); bool operator==( const Iterator &other ) const; bool operator!=( const Iterator &other ) const; void invalidate(); protected: Iterator( int dataSet, PlotterDiagramCompressor *parent, QVector< DataPoint > buffer ); private: void handleSlopeForward( const DataPoint &dp ); QPointer< PlotterDiagramCompressor > m_parent; QVector< DataPoint > m_buffer; int m_index; int m_dataset; int m_bufferIndex; int m_rebuffer; QDateTime m_timeOfCreation; }; typedef QVector DataPointVector; class CachePosition { public: CachePosition() : first( -1 ), second( -1 ) {} CachePosition( int first, int second ) : first( first ), second( second ) {} int first; int second; bool operator==( const CachePosition& rhs ) const { return first == rhs.first && second == rhs.second; } }; explicit PlotterDiagramCompressor(QObject *parent = 0); ~PlotterDiagramCompressor(); Iterator begin( int dataSet ); Iterator end( int dataSet ); void setMergeRadius( qreal radius ); void setMergeRadiusPercentage( qreal radius ); void setModel( QAbstractItemModel *model ); QAbstractItemModel* model() const; DataPoint data( const CachePosition& pos ) const; int rowCount() const; int datasetCount() const; void setCompressionModel( CompressionMode value ); void setMaxSlopeChange( qreal value ); qreal maxSlopeChange() const; void cleanCache(); QPair< QPointF, QPointF > dataBoundaries() const; void setForcedDataBoundaries( const QPair< qreal, qreal > &bounds, Qt::Orientation direction ); Q_SIGNALS: void boundariesChanged(); void rowCountChanged(); private: class Private; Private *d; }; } #endif // PLOTTERDIAGRAMCOMPRESSOR_H diff --git a/src/KChart/Cartesian/KChartPlotterDiagramCompressor_p.h b/src/KChart/Cartesian/KChartPlotterDiagramCompressor_p.h index 6dc96ce..870b5a2 100644 --- a/src/KChart/Cartesian/KChartPlotterDiagramCompressor_p.h +++ b/src/KChart/Cartesian/KChartPlotterDiagramCompressor_p.h @@ -1,63 +1,63 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef PLOTTERDIAGRAMCOMPRESSOR_P_H #define PLOTTERDIAGRAMCOMPRESSOR_P_H #include "KChartPlotterDiagramCompressor.h" -#include -#include +#include +#include typedef QPair< QPointF, QPointF > Boundaries; namespace KChart { class Q_DECL_HIDDEN PlotterDiagramCompressor::Private : public QObject { Q_OBJECT public: Private( PlotterDiagramCompressor *parent ); QModelIndexList mapToModel( const CachePosition& pos ); void calculateDataBoundaries(); void setBoundaries( const Boundaries &bound ); bool forcedBoundaries( Qt::Orientation orient ) const; bool inBoundaries( Qt::Orientation orient, const PlotterDiagramCompressor::DataPoint &dp ) const; PlotterDiagramCompressor *m_parent; QAbstractItemModel *m_model; qreal m_mergeRadius; qreal m_maxSlopeRadius; QVector< QVector< DataPoint > > m_bufferlist; Boundaries m_boundary; QPair< qreal, qreal > m_forcedXBoundaries; QPair< qreal, qreal > m_forcedYBoundaries; QDateTime m_timeOfLastInvalidation; PlotterDiagramCompressor::CompressionMode m_mode; QVector< qreal > m_accumulatedDistances; //QVector< PlotterDiagramCompressor::Iterator > exisitingIterators; public Q_SLOTS: void rowsInserted( const QModelIndex& parent, int start, int end ); void clearBuffer(); void setModelToZero(); }; } #endif // PLOTTERDIAGRAMCOMPRESSOR_P_H diff --git a/src/KChart/KChartAbstractAxis.cpp b/src/KChart/KChartAbstractAxis.cpp index c5430d4..2380d21 100644 --- a/src/KChart/KChartAbstractAxis.cpp +++ b/src/KChart/KChartAbstractAxis.cpp @@ -1,259 +1,259 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "KChartAbstractAxis.h" #include "KChartAbstractAxis_p.h" #include "KChartAbstractDiagram.h" #include "KChartAbstractCartesianDiagram.h" #include "KChartEnums.h" #include "KChartMeasure.h" #include "KChartMath_p.h" using namespace KChart; #define d d_func() AbstractAxis::Private::Private( AbstractDiagram* diagram, AbstractAxis* axis ) : observer( 0 ) , mDiagram( diagram ) , mAxis( axis ) { // Note: We do NOT call setDiagram( diagram, axis ); // but it is called in AbstractAxis::delayedInit() instead! } AbstractAxis::Private::~Private() { delete observer; observer = 0; } bool AbstractAxis::Private::setDiagram( AbstractDiagram* diagram_, bool delayedInit ) { AbstractDiagram* diagram = delayedInit ? mDiagram : diagram_; if ( delayedInit ) { mDiagram = 0; } // do not set a diagram again that was already set if ( diagram && ((diagram == mDiagram) || secondaryDiagrams.contains( diagram )) ) return false; bool bNewDiagramStored = false; if ( ! mDiagram ) { mDiagram = diagram; delete observer; if ( mDiagram ) { observer = new DiagramObserver( mDiagram, mAxis ); - const bool con = connect( observer, SIGNAL( diagramDataChanged( AbstractDiagram *) ), - mAxis, SIGNAL( coordinateSystemChanged() ) ); + const bool con = connect( observer, SIGNAL(diagramDataChanged(AbstractDiagram*)), + mAxis, SIGNAL(coordinateSystemChanged()) ); Q_UNUSED( con ) Q_ASSERT( con ); bNewDiagramStored = true; } else { observer = 0; } } else { if ( diagram ) secondaryDiagrams.enqueue( diagram ); } return bNewDiagramStored; } void AbstractAxis::Private::unsetDiagram( AbstractDiagram* diagram ) { if ( diagram == mDiagram ) { mDiagram = 0; delete observer; observer = 0; } else { secondaryDiagrams.removeAll( diagram ); } if ( !secondaryDiagrams.isEmpty() ) { AbstractDiagram *nextDiagram = secondaryDiagrams.dequeue(); setDiagram( nextDiagram ); } } bool AbstractAxis::Private::hasDiagram( AbstractDiagram* diagram ) const { return diagram == mDiagram || secondaryDiagrams.contains( diagram ); } void AbstractAxis::Private::updateLayouts() { if ( CartesianAxis* cartesianAxis = qobject_cast< CartesianAxis* >( mAxis ) ) { cartesianAxis->layoutPlanes(); } else { mAxis->update(); } } AbstractAxis::AbstractAxis ( AbstractDiagram* diagram ) : AbstractArea( new Private( diagram, this ) ) { init(); QTimer::singleShot(0, this, SLOT(delayedInit())); } AbstractAxis::~AbstractAxis() { d->mDiagram = 0; d->secondaryDiagrams.clear(); } void AbstractAxis::init() { Measure m( 14, KChartEnums::MeasureCalculationModeAuto, KChartEnums::MeasureOrientationAuto ); d->textAttributes.setFontSize( m ); m.setValue( 6 ); m.setCalculationMode( KChartEnums::MeasureCalculationModeAbsolute ); d->textAttributes.setMinimalFontSize( m ); if ( d->diagram() ) createObserver( d->diagram() ); } void AbstractAxis::delayedInit() { // We call setDiagram() here, because the c'tor of Private // only has stored the pointers, but it did not call setDiagram(). if ( d ) d->setDiagram( 0, true /* delayedInit */ ); } bool AbstractAxis::compare( const AbstractAxis* other ) const { if ( other == this ) { return true; } if ( !other ) { return false; } return ( static_cast(this)->compare( other ) ) && (textAttributes() == other->textAttributes()) && (labels() == other->labels()) && (shortLabels() == other->shortLabels()); } const QString AbstractAxis::customizedLabel( const QString& label ) const { return label; } void AbstractAxis::createObserver( AbstractDiagram* diagram ) { d->setDiagram( diagram ); } void AbstractAxis::deleteObserver( AbstractDiagram* diagram ) { d->unsetDiagram( diagram ); } void AbstractAxis::connectSignals() { if ( d->observer ) { - const bool con = connect( d->observer, SIGNAL( diagramDataChanged( AbstractDiagram *) ), - this, SIGNAL( coordinateSystemChanged() ) ); + const bool con = connect( d->observer, SIGNAL(diagramDataChanged(AbstractDiagram*)), + this, SIGNAL(coordinateSystemChanged()) ); Q_UNUSED( con ); Q_ASSERT( con ); } } void AbstractAxis::setTextAttributes( const TextAttributes &a ) { if ( d->textAttributes == a ) return; d->textAttributes = a; d->updateLayouts(); } TextAttributes AbstractAxis::textAttributes() const { return d->textAttributes; } void AbstractAxis::setRulerAttributes( const RulerAttributes &a ) { d->rulerAttributes = a; d->updateLayouts(); } RulerAttributes AbstractAxis::rulerAttributes() const { return d->rulerAttributes; } void AbstractAxis::setLabels( const QStringList& list ) { if ( d->hardLabels == list ) return; d->hardLabels = list; d->updateLayouts(); } QStringList AbstractAxis::labels() const { return d->hardLabels; } void AbstractAxis::setShortLabels( const QStringList& list ) { if ( d->hardShortLabels == list ) return; d->hardShortLabels = list; d->updateLayouts(); } QStringList AbstractAxis::shortLabels() const { return d->hardShortLabels; } const AbstractCoordinatePlane* AbstractAxis::coordinatePlane() const { if ( d->diagram() ) return d->diagram()->coordinatePlane(); return 0; } const AbstractDiagram * KChart::AbstractAxis::diagram() const { return d->diagram(); } bool KChart::AbstractAxis::observedBy( AbstractDiagram * diagram ) const { return d->hasDiagram( diagram ); } void KChart::AbstractAxis::update() { if ( d->diagram() ) d->diagram()->update(); } diff --git a/src/KChart/KChartAbstractAxis_p.h b/src/KChart/KChartAbstractAxis_p.h index cdc917e..598b4e6 100644 --- a/src/KChart/KChartAbstractAxis_p.h +++ b/src/KChart/KChartAbstractAxis_p.h @@ -1,98 +1,98 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef KCHARTAXIS_P_H -#define KCHARTAXIS_P_H +#ifndef KCHARTABSTRACTAXIS_P_H +#define KCHARTABSTRACTAXIS_P_H // // W A R N I N G // ------------- // // This file is not part of the KD Chart API. It exists purely as an // implementation detail. This header file may change from version to // version without notice, or even be removed. // // We mean it. // #include #include #include "KChartAbstractAxis.h" #include "KChartAbstractArea_p.h" #include "KChartAbstractDiagram.h" #include "KChartMath_p.h" #include #include #include namespace KChart { /** * \internal */ class Q_DECL_HIDDEN AbstractAxis::Private : public AbstractArea::Private { friend class AbstractAxis; public: Private( AbstractDiagram* diagram, AbstractAxis* axis ); ~Private(); bool setDiagram( AbstractDiagram* diagram, bool delayedInit = false ); void unsetDiagram( AbstractDiagram* diagram ); AbstractDiagram* diagram() const { return mDiagram; } bool hasDiagram( AbstractDiagram* diagram ) const; void updateLayouts(); DiagramObserver* observer; TextAttributes textAttributes; RulerAttributes rulerAttributes; QStringList hardLabels; QStringList hardShortLabels; QQueue secondaryDiagrams; protected: AbstractDiagram* mDiagram; AbstractAxis* mAxis; }; inline AbstractAxis::AbstractAxis( Private * p, AbstractDiagram* diagram ) : AbstractArea( p ) { Q_UNUSED( diagram ); init(); QTimer::singleShot(0, this, SLOT(delayedInit())); } inline AbstractAxis::Private * AbstractAxis::d_func() { return static_cast( AbstractArea::d_func() ); } inline const AbstractAxis::Private * AbstractAxis::d_func() const { return static_cast( AbstractArea::d_func() ); } } -#endif /* KCHARTAREA_P_H */ +#endif /* KCHARTABSTRACTAXIS_P_H */ diff --git a/src/KChart/KChartAbstractCoordinatePlane.cpp b/src/KChart/KChartAbstractCoordinatePlane.cpp index 1b638d6..94d556e 100644 --- a/src/KChart/KChartAbstractCoordinatePlane.cpp +++ b/src/KChart/KChartAbstractCoordinatePlane.cpp @@ -1,450 +1,450 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "KChartAbstractCoordinatePlane.h" #include "KChartAbstractCoordinatePlane_p.h" #include "KChartChart.h" #include "KChartGridAttributes.h" #include #include #include -#include +#include using namespace KChart; #define d d_func() AbstractCoordinatePlane::Private::Private() : AbstractArea::Private() , parent( 0 ) , grid( 0 ) , referenceCoordinatePlane( 0 ) , enableCornerSpacers( true ) , enableRubberBandZooming( false ) , rubberBand( 0 ) { // this bloc left empty intentionally } AbstractCoordinatePlane::AbstractCoordinatePlane ( KChart::Chart* parent ) : AbstractArea ( new Private() ) { d->parent = parent; d->init(); } AbstractCoordinatePlane::~AbstractCoordinatePlane() { emit destroyedCoordinatePlane( this ); } void AbstractCoordinatePlane::init() { d->initialize(); // virtual method to init the correct grid: cartesian, polar, ... - connect( this, SIGNAL(internal_geometryChanged( QRect, QRect )), - this, SIGNAL(geometryChanged( QRect, QRect )), + connect( this, SIGNAL(internal_geometryChanged(QRect,QRect)), + this, SIGNAL(geometryChanged(QRect,QRect)), Qt::QueuedConnection ); } void AbstractCoordinatePlane::addDiagram ( AbstractDiagram* diagram ) { // diagrams are invisible and paint through their paint() method diagram->hide(); d->diagrams.append( diagram ); diagram->setParent( d->parent ); diagram->setCoordinatePlane( this ); layoutDiagrams(); layoutPlanes(); // there might be new axes, etc - connect( diagram, SIGNAL( modelsChanged() ), this, SLOT( layoutPlanes() ) ); - connect( diagram, SIGNAL( modelDataChanged() ), this, SLOT( update()) ); - connect( diagram, SIGNAL( modelDataChanged() ), this, SLOT( relayout()) ); - connect( this, SIGNAL( boundariesChanged() ), diagram, SIGNAL( boundariesChanged() ) ); + connect( diagram, SIGNAL(modelsChanged()), this, SLOT(layoutPlanes()) ); + connect( diagram, SIGNAL(modelDataChanged()), this, SLOT(update()) ); + connect( diagram, SIGNAL(modelDataChanged()), this, SLOT(relayout()) ); + connect( this, SIGNAL(boundariesChanged()), diagram, SIGNAL(boundariesChanged()) ); update(); emit boundariesChanged(); } /*virtual*/ void AbstractCoordinatePlane::replaceDiagram ( AbstractDiagram* diagram, AbstractDiagram* oldDiagram_ ) { if ( diagram && oldDiagram_ != diagram ) { AbstractDiagram* oldDiagram = oldDiagram_; if ( d->diagrams.count() ) { if ( ! oldDiagram ) { oldDiagram = d->diagrams.first(); if ( oldDiagram == diagram ) return; } takeDiagram( oldDiagram ); } delete oldDiagram; addDiagram( diagram ); layoutDiagrams(); layoutPlanes(); // there might be new axes, etc update(); } } /*virtual*/ void AbstractCoordinatePlane::takeDiagram ( AbstractDiagram* diagram ) { const int idx = d->diagrams.indexOf( diagram ); if ( idx != -1 ) { d->diagrams.removeAt( idx ); diagram->setParent( 0 ); diagram->setCoordinatePlane( 0 ); - disconnect( diagram, SIGNAL( modelsChanged() ), this, SLOT( layoutPlanes() ) ); - disconnect( diagram, SIGNAL( modelDataChanged() ), this, SLOT( update()) ); - disconnect( diagram, SIGNAL( modelDataChanged() ), this, SLOT( relayout()) ); + disconnect( diagram, SIGNAL(modelsChanged()), this, SLOT(layoutPlanes()) ); + disconnect( diagram, SIGNAL(modelDataChanged()), this, SLOT(update()) ); + disconnect( diagram, SIGNAL(modelDataChanged()), this, SLOT(relayout()) ); layoutDiagrams(); update(); } } AbstractDiagram* AbstractCoordinatePlane::diagram() { if ( d->diagrams.isEmpty() ) { return 0; } else { return d->diagrams.first(); } } AbstractDiagramList AbstractCoordinatePlane::diagrams() { return d->diagrams; } ConstAbstractDiagramList AbstractCoordinatePlane::diagrams() const { ConstAbstractDiagramList list; #ifndef QT_NO_STL qCopy( d->diagrams.begin(), d->diagrams.end(), std::back_inserter( list ) ); #else Q_FOREACH( AbstractDiagram * a, d->diagrams ) list.push_back( a ); #endif return list; } void KChart::AbstractCoordinatePlane::setGlobalGridAttributes( const GridAttributes& a ) { d->gridAttributes = a; update(); } GridAttributes KChart::AbstractCoordinatePlane::globalGridAttributes() const { return d->gridAttributes; } KChart::DataDimensionsList KChart::AbstractCoordinatePlane::gridDimensionsList() { return d->grid->updateData( this ); } void KChart::AbstractCoordinatePlane::setGridNeedsRecalculate() { d->grid->setNeedRecalculate(); } void KChart::AbstractCoordinatePlane::setReferenceCoordinatePlane( AbstractCoordinatePlane * plane ) { d->referenceCoordinatePlane = plane; } AbstractCoordinatePlane * KChart::AbstractCoordinatePlane::referenceCoordinatePlane( ) const { return d->referenceCoordinatePlane; } void KChart::AbstractCoordinatePlane::setParent( KChart::Chart* parent ) { d->parent = parent; } const KChart::Chart* KChart::AbstractCoordinatePlane::parent() const { return d->parent; } KChart::Chart* KChart::AbstractCoordinatePlane::parent() { return d->parent; } /* pure virtual in QLayoutItem */ bool KChart::AbstractCoordinatePlane::isEmpty() const { return false; // never empty! // coordinate planes with no associated diagrams // are showing a default grid of ()1..10, 1..10) stepWidth 1 } /* pure virtual in QLayoutItem */ Qt::Orientations KChart::AbstractCoordinatePlane::expandingDirections() const { return Qt::Vertical | Qt::Horizontal; } /* pure virtual in QLayoutItem */ QSize KChart::AbstractCoordinatePlane::maximumSize() const { // No maximum size set. Especially not parent()->size(), we are not layouting // to the parent widget's size when using Chart::paint()! return QSize(QLAYOUTSIZE_MAX, QLAYOUTSIZE_MAX); } /* pure virtual in QLayoutItem */ QSize KChart::AbstractCoordinatePlane::minimumSize() const { return QSize(60, 60); // this default can be overwritten by derived classes } /* pure virtual in QLayoutItem */ QSize KChart::AbstractCoordinatePlane::sizeHint() const { // we return our maxiumu (which is the full size of the Chart) // even if we know the plane will be smaller return maximumSize(); } /* pure virtual in QLayoutItem */ void KChart::AbstractCoordinatePlane::setGeometry( const QRect& r ) { if ( d->geometry != r ) { // inform the outside word by Signal geometryChanged() // via a queued connection to internal_geometryChanged() emit internal_geometryChanged( d->geometry, r ); d->geometry = r; // Note: We do *not* call update() here // because it would invoke KChart::update() recursively. } } /* pure virtual in QLayoutItem */ QRect KChart::AbstractCoordinatePlane::geometry() const { return d->geometry; } void KChart::AbstractCoordinatePlane::update() { //qDebug("KChart::AbstractCoordinatePlane::update() called"); emit needUpdate(); } void KChart::AbstractCoordinatePlane::relayout() { //qDebug("KChart::AbstractCoordinatePlane::relayout() called"); emit needRelayout(); } void KChart::AbstractCoordinatePlane::layoutPlanes() { //qDebug("KChart::AbstractCoordinatePlane::relayout() called"); emit needLayoutPlanes(); } void KChart::AbstractCoordinatePlane::setRubberBandZoomingEnabled( bool enable ) { d->enableRubberBandZooming = enable; if ( !enable && d->rubberBand != 0 ) { delete d->rubberBand; d->rubberBand = 0; } } bool KChart::AbstractCoordinatePlane::isRubberBandZoomingEnabled() const { return d->enableRubberBandZooming; } void KChart::AbstractCoordinatePlane::setCornerSpacersEnabled( bool enable ) { if ( d->enableCornerSpacers == enable ) return; d->enableCornerSpacers = enable; emit needRelayout(); } bool KChart::AbstractCoordinatePlane::isCornerSpacersEnabled() const { return d->enableCornerSpacers; } void KChart::AbstractCoordinatePlane::mousePressEvent( QMouseEvent* event ) { if ( event->button() == Qt::LeftButton ) { if ( d->enableRubberBandZooming && d->rubberBand == 0 ) d->rubberBand = new QRubberBand( QRubberBand::Rectangle, qobject_cast< QWidget* >( parent() ) ); if ( d->rubberBand != 0 ) { d->rubberBandOrigin = event->pos(); d->rubberBand->setGeometry( QRect( event->pos(), QSize() ) ); d->rubberBand->show(); event->accept(); } } else if ( event->button() == Qt::RightButton ) { if ( d->enableRubberBandZooming && !d->rubberBandZoomConfigHistory.isEmpty() ) { // restore the last config from the stack ZoomParameters config = d->rubberBandZoomConfigHistory.pop(); setZoomFactorX( config.xFactor ); setZoomFactorY( config.yFactor ); setZoomCenter( config.center() ); QWidget* const p = qobject_cast< QWidget* >( parent() ); if ( p != 0 ) p->update(); event->accept(); } } Q_FOREACH( AbstractDiagram * a, d->diagrams ) { a->mousePressEvent( event ); } } void KChart::AbstractCoordinatePlane::mouseDoubleClickEvent( QMouseEvent* event ) { if ( event->button() == Qt::RightButton ) { // othewise the second click gets lost // which is pretty annoying when zooming out fast mousePressEvent( event ); } Q_FOREACH( AbstractDiagram * a, d->diagrams ) { a->mouseDoubleClickEvent( event ); } } void KChart::AbstractCoordinatePlane::mouseReleaseEvent( QMouseEvent* event ) { if ( d->rubberBand != 0 ) { // save the old config on the stack d->rubberBandZoomConfigHistory.push( ZoomParameters( zoomFactorX(), zoomFactorY(), zoomCenter() ) ); // this is the height/width of the rubber band in pixel space const qreal rubberWidth = static_cast< qreal >( d->rubberBand->width() ); const qreal rubberHeight = static_cast< qreal >( d->rubberBand->height() ); if ( rubberWidth > 0.0 && rubberHeight > 0.0 ) { // this is the center of the rubber band in pixel space const qreal centerX = qFloor( d->rubberBand->geometry().width() / 2.0 + d->rubberBand->geometry().x() ); const qreal centerY = qCeil( d->rubberBand->geometry().height() / 2.0 + d->rubberBand->geometry().y() ); const qreal rubberCenterX = static_cast< qreal >( centerX - geometry().x() ); const qreal rubberCenterY = static_cast< qreal >( centerY - geometry().y() ); // this is the height/width of the plane in pixel space const qreal myWidth = static_cast< qreal >( geometry().width() ); const qreal myHeight = static_cast< qreal >( geometry().height() ); // this describes the new center of zooming, relative to the plane pixel space const qreal newCenterX = rubberCenterX / myWidth / zoomFactorX() + zoomCenter().x() - 0.5 / zoomFactorX(); const qreal newCenterY = rubberCenterY / myHeight / zoomFactorY() + zoomCenter().y() - 0.5 / zoomFactorY(); // this will be the new zoom factor const qreal newZoomFactorX = zoomFactorX() * myWidth / rubberWidth; const qreal newZoomFactorY = zoomFactorY() * myHeight / rubberHeight; // and this the new center const QPointF newZoomCenter( newCenterX, newCenterY ); setZoomFactorX( newZoomFactorX ); setZoomFactorY( newZoomFactorY ); setZoomCenter( newZoomCenter ); } d->rubberBand->parentWidget()->update(); delete d->rubberBand; d->rubberBand = 0; event->accept(); } Q_FOREACH( AbstractDiagram * a, d->diagrams ) { a->mouseReleaseEvent( event ); } } void KChart::AbstractCoordinatePlane::mouseMoveEvent( QMouseEvent* event ) { if ( d->rubberBand != 0 ) { const QRect normalized = QRect( d->rubberBandOrigin, event->pos() ).normalized(); d->rubberBand->setGeometry( normalized & geometry() ); event->accept(); } Q_FOREACH( AbstractDiagram * a, d->diagrams ) { a->mouseMoveEvent( event ); } } #if defined(Q_COMPILER_MANGLES_RETURN_TYPE) const #endif bool KChart::AbstractCoordinatePlane::isVisiblePoint( const QPointF& point ) const { return d->isVisiblePoint( this, point ); } AbstractCoordinatePlane* KChart::AbstractCoordinatePlane::sharedAxisMasterPlane( QPainter* p ) { Q_UNUSED( p ); return this; } #if !defined(QT_NO_DEBUG_STREAM) #include "KChartEnums.h" QDebug KChart::operator<<( QDebug stream, const DataDimension& r ) { stream << "DataDimension(" << " start=" << r.start << " end=" << r.end << " sequence=" << KChartEnums::granularitySequenceToString( r.sequence ) << " isCalculated=" << r.isCalculated << " calcMode=" << ( r.calcMode == AbstractCoordinatePlane::Logarithmic ? "Logarithmic" : "Linear" ) << " stepWidth=" << r.stepWidth << " subStepWidth=" << r.subStepWidth << " )"; return stream; } #endif #undef d diff --git a/src/KChart/KChartAbstractCoordinatePlane_p.h b/src/KChart/KChartAbstractCoordinatePlane_p.h index 7196cc7..65bdb77 100644 --- a/src/KChart/KChartAbstractCoordinatePlane_p.h +++ b/src/KChart/KChartAbstractCoordinatePlane_p.h @@ -1,113 +1,113 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef KCHARTABSTRCOORDINATEPLANE_P_H -#define KCHARTABSTRCOORDINATEPLANE_P_H +#ifndef KCHARTABSTRACTCOORDINATEPLANE_P_H +#define KCHARTABSTRACTCOORDINATEPLANE_P_H // // W A R N I N G // ------------- // // This file is not part of the KD Chart API. It exists purely as an // implementation detail. This header file may change from version to // version without notice, or even be removed. // // We mean it. // #include "KChartAbstractArea_p.h" #include #include #include #include #include "KChartMath_p.h" #include QT_BEGIN_NAMESPACE class QRubberBand; QT_END_NAMESPACE namespace KChart { /** * \internal */ class Q_DECL_HIDDEN AbstractCoordinatePlane::Private : public AbstractArea::Private { friend class AbstractCoordinatePlane; protected: explicit Private(); virtual ~Private() { delete grid; }; virtual void initialize() { qDebug("ERROR: Calling AbstractCoordinatePlane::Private::initialize()"); // can not call the base class: grid = new AbstractGrid(); } virtual bool isVisiblePoint( const AbstractCoordinatePlane * plane, const QPointF& point ) const { Q_UNUSED( plane ); Q_UNUSED( point ); return true; } KChart::Chart* parent; AbstractGrid* grid; QRect geometry; AbstractDiagramList diagrams; GridAttributes gridAttributes; AbstractCoordinatePlane *referenceCoordinatePlane; bool enableCornerSpacers; bool enableRubberBandZooming; QRubberBand* rubberBand; QPoint rubberBandOrigin; QStack< ZoomParameters > rubberBandZoomConfigHistory; }; inline AbstractCoordinatePlane::AbstractCoordinatePlane( Private * p, KChart::Chart* parent ) : AbstractArea( p ) { if ( p ) p->parent = parent; init(); } inline AbstractCoordinatePlane::Private * AbstractCoordinatePlane::d_func() { return static_cast( AbstractArea::d_func() ); } inline const AbstractCoordinatePlane::Private * AbstractCoordinatePlane::d_func() const { return static_cast( AbstractArea::d_func() ); } } #endif /* KCHARTABSTRACTCOORDINATEPLANE_P_H*/ diff --git a/src/KChart/KChartAbstractDiagram.cpp b/src/KChart/KChartAbstractDiagram.cpp index df54cbd..693a382 100644 --- a/src/KChart/KChartAbstractDiagram.cpp +++ b/src/KChart/KChartAbstractDiagram.cpp @@ -1,1197 +1,1197 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "KChartAbstractDiagram.h" #include "KChartAbstractDiagram_p.h" #include #include #include #include #include #include "KChartAbstractCoordinatePlane.h" #include "KChartChart.h" #include "KChartDataValueAttributes.h" #include "KChartTextAttributes.h" #include "KChartMarkerAttributes.h" #include "KChartAbstractThreeDAttributes.h" #include "KChartThreeDLineAttributes.h" #include "KChartPainterSaver_p.h" #include using namespace KChart; #define d d_func() AbstractDiagram::AbstractDiagram ( QWidget* parent, AbstractCoordinatePlane* plane ) : QAbstractItemView ( parent ), _d( new Private() ) { _d->init( plane ); init(); } AbstractDiagram::~AbstractDiagram() { emit aboutToBeDestroyed(); delete _d; } void AbstractDiagram::init() { _d->diagram = this; d->reverseMapper.setDiagram( this ); } bool AbstractDiagram::compare( const AbstractDiagram* other ) const { if ( other == this ) return true; if ( !other ) { return false; } return // compare QAbstractScrollArea properties (horizontalScrollBarPolicy() == other->horizontalScrollBarPolicy()) && (verticalScrollBarPolicy() == other->verticalScrollBarPolicy()) && // compare QFrame properties (frameShadow() == other->frameShadow()) && (frameShape() == other->frameShape()) && // frameWidth is a read-only property defined by the style, it should not be in here: // (frameWidth() == other->frameWidth()) && (lineWidth() == other->lineWidth()) && (midLineWidth() == other->midLineWidth()) && // compare QAbstractItemView properties (alternatingRowColors() == other->alternatingRowColors()) && (hasAutoScroll() == other->hasAutoScroll()) && (dragDropMode() == other->dragDropMode()) && (dragDropOverwriteMode() == other->dragDropOverwriteMode()) && (horizontalScrollMode() == other->horizontalScrollMode ()) && (verticalScrollMode() == other->verticalScrollMode()) && (dragEnabled() == other->dragEnabled()) && (editTriggers() == other->editTriggers()) && (iconSize() == other->iconSize()) && (selectionBehavior() == other->selectionBehavior()) && (selectionMode() == other->selectionMode()) && (showDropIndicator() == other->showDropIndicator()) && (tabKeyNavigation() == other->tabKeyNavigation()) && (textElideMode() == other->textElideMode()) && // compare all of the properties stored in the attributes model attributesModel()->compare( other->attributesModel() ) && // compare own properties (rootIndex().column() == other->rootIndex().column()) && (rootIndex().row() == other->rootIndex().row()) && (allowOverlappingDataValueTexts() == other->allowOverlappingDataValueTexts()) && (antiAliasing() == other->antiAliasing()) && (percentMode() == other->percentMode()) && (datasetDimension() == other->datasetDimension()); } AbstractCoordinatePlane* AbstractDiagram::coordinatePlane() const { return d->plane; } const QPair AbstractDiagram::dataBoundaries () const { if ( d->databoundariesDirty ) { d->databoundaries = calculateDataBoundaries (); d->databoundariesDirty = false; } return d->databoundaries; } void AbstractDiagram::setDataBoundariesDirty() const { d->databoundariesDirty = true; update(); } void AbstractDiagram::resize(const QSizeF& size) { d->diagramSize = size; QAbstractItemView::resize( size.toSize() ); } void AbstractDiagram::setModel( QAbstractItemModel * newModel ) { if ( newModel == model() ) { return; } AttributesModel* amodel = new PrivateAttributesModel( newModel, this ); amodel->initFrom( d->attributesModel ); d->setAttributesModel(amodel); QAbstractItemView::setModel( newModel ); scheduleDelayedItemsLayout(); setDataBoundariesDirty(); emit modelsChanged(); } void AbstractDiagram::setSelectionModel( QItemSelectionModel* newSelectionModel ) { if ( selectionModel() ) { - disconnect( selectionModel(), SIGNAL( currentChanged( QModelIndex, QModelIndex ) ), this, SIGNAL( modelsChanged() ) ); - disconnect( selectionModel(), SIGNAL( selectionChanged( QItemSelection, QItemSelection ) ), this, SIGNAL( modelsChanged() ) ); + disconnect( selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SIGNAL(modelsChanged()) ); + disconnect( selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SIGNAL(modelsChanged()) ); } QAbstractItemView::setSelectionModel( newSelectionModel ); if ( selectionModel() ) { - connect( selectionModel(), SIGNAL( currentChanged( QModelIndex, QModelIndex ) ), this, SIGNAL( modelsChanged() ) ); - connect( selectionModel(), SIGNAL( selectionChanged( QItemSelection, QItemSelection ) ), this, SIGNAL( modelsChanged() ) ); + connect( selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SIGNAL(modelsChanged()) ); + connect( selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SIGNAL(modelsChanged()) ); } emit modelsChanged(); } /*! Sets an external AttributesModel on this diagram. By default, a diagram has it's own internal set of attributes, but an external one can be set. This can be used to share attributes between several diagrams. The diagram does not take ownership of the attributesmodel. @param amodel The AttributesModel to use for this diagram. */ void AbstractDiagram::setAttributesModel( AttributesModel* amodel ) { if ( amodel->sourceModel() != model() ) { qWarning("KChart::AbstractDiagram::setAttributesModel() failed: " "Trying to set an attributesmodel which works on a different " "model than the diagram."); return; } if ( qobject_cast(amodel) ) { qWarning("KChart::AbstractDiagram::setAttributesModel() failed: " "Trying to set an attributesmodel that is private to another diagram."); return; } d->setAttributesModel( amodel ); scheduleDelayedItemsLayout(); setDataBoundariesDirty(); emit modelsChanged(); } bool AbstractDiagram::usesExternalAttributesModel() const { return d->usesExternalAttributesModel(); } AttributesModel* AbstractDiagram::attributesModel() const { return d->attributesModel; } QModelIndex AbstractDiagram::conditionallyMapFromSource( const QModelIndex & index ) const { Q_ASSERT( !index.isValid() || index.model() == attributesModel() || index.model() == attributesModel()->sourceModel() ); return index.model() == attributesModel() ? index : attributesModel()->mapFromSource( index ); } /*! \reimpl */ void AbstractDiagram::setRootIndex ( const QModelIndex& idx ) { QAbstractItemView::setRootIndex( idx ); setAttributesModelRootIndex( d->attributesModel->mapFromSource( idx ) ); } /*! \internal */ void AbstractDiagram::setAttributesModelRootIndex( const QModelIndex& idx ) { d->attributesModelRootIndex = idx; setDataBoundariesDirty(); scheduleDelayedItemsLayout(); } /*! returns a QModelIndex pointing into the AttributesModel that corresponds to the root index of the diagram. */ QModelIndex AbstractDiagram::attributesModelRootIndex() const { if ( !d->attributesModelRootIndex.isValid() ) d->attributesModelRootIndex = d->attributesModel->mapFromSource( rootIndex() ); return d->attributesModelRootIndex; } void AbstractDiagram::setCoordinatePlane( AbstractCoordinatePlane* parent ) { d->plane = parent; } void AbstractDiagram::doItemsLayout() { if ( d->plane ) { d->plane->layoutDiagrams(); update(); } QAbstractItemView::doItemsLayout(); } void AbstractDiagram::dataChanged( const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector & ) { Q_UNUSED( topLeft ); Q_UNUSED( bottomRight ); // We are still too dumb to do intelligent updates... setDataBoundariesDirty(); scheduleDelayedItemsLayout(); } void AbstractDiagram::setHidden( const QModelIndex & index, bool hidden ) { d->attributesModel->setData( conditionallyMapFromSource( index ), qVariantFromValue( hidden ), DataHiddenRole ); emit dataHidden(); } void AbstractDiagram::setHidden( int dataset, bool hidden ) { d->setDatasetAttrs( dataset, qVariantFromValue( hidden ), DataHiddenRole ); emit dataHidden(); } void AbstractDiagram::setHidden( bool hidden ) { d->attributesModel->setModelData( qVariantFromValue( hidden ), DataHiddenRole ); emit dataHidden(); } bool AbstractDiagram::isHidden() const { return attributesModel()->modelData( DataHiddenRole ).value< bool >(); } bool AbstractDiagram::isHidden( int dataset ) const { const QVariant boolFlag( d->datasetAttrs( dataset, DataHiddenRole ) ); if ( boolFlag.isValid() ) return boolFlag.value< bool >(); return isHidden(); } bool AbstractDiagram::isHidden( const QModelIndex & index ) const { const QVariant boolFlag( attributesModel()->data( conditionallyMapFromSource( index ), DataHiddenRole ) ); if ( boolFlag.isValid() ) { return boolFlag.value< bool >(); } int dataset = index.column() / d->datasetDimension; return isHidden( dataset ); } void AbstractDiagram::setDataValueAttributes( const QModelIndex & index, const DataValueAttributes & a ) { d->attributesModel->setData( conditionallyMapFromSource( index ), qVariantFromValue( a ), DataValueLabelAttributesRole ); emit propertiesChanged(); } void AbstractDiagram::setDataValueAttributes( int dataset, const DataValueAttributes & a ) { d->setDatasetAttrs( dataset, qVariantFromValue( a ), DataValueLabelAttributesRole ); emit propertiesChanged(); } DataValueAttributes AbstractDiagram::dataValueAttributes() const { return attributesModel()->modelData( KChart::DataValueLabelAttributesRole ).value< DataValueAttributes >(); } DataValueAttributes AbstractDiagram::dataValueAttributes( int dataset ) const { /* The following did not work! (khz, 2008-01-25) If there was some attrs specified for the 0-th cells of a dataset, then this logic would return the cell's settings instead of the header settings: return qVariantValue( attributesModel()->data( attributesModel()->mapFromSource(columnToIndex( column )), KChart::DataValueLabelAttributesRole ) ); */ const QVariant headerAttrs( d->datasetAttrs( dataset, KChart::DataValueLabelAttributesRole ) ); if ( headerAttrs.isValid() ) return headerAttrs.value< DataValueAttributes >(); return dataValueAttributes(); } DataValueAttributes AbstractDiagram::dataValueAttributes( const QModelIndex & index ) const { return attributesModel()->data( conditionallyMapFromSource( index ), KChart::DataValueLabelAttributesRole ).value< DataValueAttributes >(); } void AbstractDiagram::setDataValueAttributes( const DataValueAttributes & a ) { d->attributesModel->setModelData( qVariantFromValue( a ), DataValueLabelAttributesRole ); emit propertiesChanged(); } void AbstractDiagram::setAllowOverlappingDataValueTexts( bool allow ) { DataValueAttributes attrs = dataValueAttributes(); attrs.setShowOverlappingDataLabels( allow ); setDataValueAttributes( attrs ); d->allowOverlappingDataValueTexts = allow; emit propertiesChanged(); } bool AbstractDiagram::allowOverlappingDataValueTexts() const { return d->allowOverlappingDataValueTexts; } void AbstractDiagram::setAntiAliasing( bool enabled ) { d->antiAliasing = enabled; emit propertiesChanged(); } bool AbstractDiagram::antiAliasing() const { return d->antiAliasing; } void AbstractDiagram::setPercentMode ( bool percent ) { d->percent = percent; emit propertiesChanged(); } bool AbstractDiagram::percentMode() const { return d->percent; } void AbstractDiagram::paintDataValueText( QPainter* painter, const QModelIndex& index, const QPointF& pos, qreal value ) { d->paintDataValueText( painter, index, pos, value ); } void AbstractDiagram::paintDataValueTexts( QPainter* painter ) { if ( !checkInvariants() ) { return; } d->forgetAlreadyPaintedDataValues(); const int rowCount = model()->rowCount( rootIndex() ); const int columnCount = model()->columnCount( rootIndex() ); for ( int column = 0; column < columnCount; column += datasetDimension() ) { for ( int row = 0; row < rowCount; ++row ) { QModelIndex index = model()->index( row, column, rootIndex() ); // checked qreal x; qreal y; if ( datasetDimension() == 1 ) { x = row; y = index.data().toReal(); } else { x = index.data().toReal(); y = model()->index( row, column + 1, rootIndex() ).data().toReal(); } paintDataValueText( painter, index, coordinatePlane()->translate( QPointF( x, y ) ), y ); } } } void AbstractDiagram::paintMarker( QPainter* painter, const DataValueAttributes& a, const QModelIndex& index, const QPointF& pos ) { if ( !checkInvariants() || !a.isVisible() ) return; const MarkerAttributes ma = a.markerAttributes(); if ( !ma.isVisible() ) return; const PainterSaver painterSaver( painter ); QSizeF maSize = ma.markerSize(); const qreal diagramWidth = d->diagramSize.width(); const qreal diagramHeight = d->diagramSize.height(); switch( ma.markerSizeMode() ) { case MarkerAttributes::AbsoluteSize: // Unscaled, i.e. without the painter's "zoom" maSize.rwidth() /= painter->matrix().m11(); maSize.rheight() /= painter->matrix().m22(); break; case MarkerAttributes::AbsoluteSizeScaled: // Keep maSize as is. It is specified directly in pixels and desired // to be effected by the painter's "zoom". break; case MarkerAttributes::RelativeToDiagramWidthHeightMin: maSize *= qMin( diagramWidth, diagramHeight ); break; } QBrush indexBrush( brush( index ) ); QPen indexPen( ma.pen() ); if ( ma.markerColor().isValid() ) indexBrush.setColor( ma.markerColor() ); paintMarker( painter, ma, indexBrush, indexPen, pos, maSize ); // workaround: BC cannot be changed, otherwise we would pass the // index down to next-lower paintMarker function. So far, we // basically save a circle of radius maSize at pos in the // reverseMapper. This means that ^^^ this version of paintMarker // needs to be called to reverse-map the marker. d->reverseMapper.addCircle( index.row(), index.column(), pos, 2 * maSize ); } void AbstractDiagram::paintMarker( QPainter* painter, const QModelIndex& index, const QPointF& pos ) { if ( !checkInvariants() ) return; paintMarker( painter, dataValueAttributes( index ), index, pos ); } void AbstractDiagram::paintMarker( QPainter* painter, const MarkerAttributes& markerAttributes, const QBrush& brush, const QPen& pen, const QPointF& pos, const QSizeF& maSize ) { const QPen oldPen( painter->pen() ); // Pen is used to paint 4Pixels - 1 Pixel - Ring and FastCross types. // make sure to use the brush color - see above in those cases. const bool isFourPixels = (markerAttributes.markerStyle() == MarkerAttributes::Marker4Pixels); if ( isFourPixels || (markerAttributes.markerStyle() == MarkerAttributes::Marker1Pixel) ) { // for high-performance point charts with tiny point markers: painter->setPen( PrintingParameters::scalePen( QPen( brush.color().light() ) ) ); if ( isFourPixels ) { const qreal x = pos.x(); const qreal y = pos.y(); painter->drawLine( QPointF(x-1.0,y-1.0), QPointF(x+1.0,y-1.0) ); painter->drawLine( QPointF(x-1.0,y), QPointF(x+1.0,y) ); painter->drawLine( QPointF(x-1.0,y+1.0), QPointF(x+1.0,y+1.0) ); } painter->drawPoint( pos ); } else { const PainterSaver painterSaver( painter ); QPen painterPen( pen ); painter->setPen( PrintingParameters::scalePen( painterPen ) ); painter->setBrush( brush ); painter->setRenderHint ( QPainter::Antialiasing ); painter->translate( pos ); switch ( markerAttributes.markerStyle() ) { case MarkerAttributes::MarkerCircle: { if ( markerAttributes.threeD() ) { QRadialGradient grad; grad.setCoordinateMode( QGradient::ObjectBoundingMode ); QColor drawColor = brush.color(); grad.setCenter( 0.5, 0.5 ); grad.setRadius( 1.0 ); grad.setFocalPoint( 0.35, 0.35 ); grad.setColorAt( 0.00, drawColor.lighter( 150 ) ); grad.setColorAt( 0.20, drawColor ); grad.setColorAt( 0.50, drawColor.darker( 150 ) ); grad.setColorAt( 0.75, drawColor.darker( 200 ) ); grad.setColorAt( 0.95, drawColor.darker( 250 ) ); grad.setColorAt( 1.00, drawColor.darker( 200 ) ); QBrush newBrush( grad ); newBrush.setMatrix( brush.matrix() ); painter->setBrush( newBrush ); } painter->drawEllipse( QRectF( 0 - maSize.height()/2, 0 - maSize.width()/2, maSize.height(), maSize.width()) ); } break; case MarkerAttributes::MarkerSquare: { QRectF rect( 0 - maSize.width()/2, 0 - maSize.height()/2, maSize.width(), maSize.height() ); painter->drawRect( rect ); break; } case MarkerAttributes::MarkerDiamond: { QVector diamondPoints; QPointF top, left, bottom, right; top = QPointF( 0, 0 - maSize.height()/2 ); left = QPointF( 0 - maSize.width()/2, 0 ); bottom = QPointF( 0, maSize.height()/2 ); right = QPointF( maSize.width()/2, 0 ); diamondPoints << top << left << bottom << right; painter->drawPolygon( diamondPoints ); break; } // both handled on top of the method: case MarkerAttributes::Marker1Pixel: case MarkerAttributes::Marker4Pixels: break; case MarkerAttributes::MarkerRing: { painter->setBrush( Qt::NoBrush ); painter->setPen( PrintingParameters::scalePen( QPen( brush.color() ) ) ); painter->drawEllipse( QRectF( 0 - maSize.height()/2, 0 - maSize.width()/2, maSize.height(), maSize.width()) ); break; } case MarkerAttributes::MarkerCross: { // Note: Markers can have outline, // so just drawing two rects is NOT the solution here! const qreal w02 = maSize.width() * 0.2; const qreal w05 = maSize.width() * 0.5; const qreal h02 = maSize.height()* 0.2; const qreal h05 = maSize.height()* 0.5; QVector crossPoints; QPointF p[12]; p[ 0] = QPointF( -w02, -h05 ); p[ 1] = QPointF( w02, -h05 ); p[ 2] = QPointF( w02, -h02 ); p[ 3] = QPointF( w05, -h02 ); p[ 4] = QPointF( w05, h02 ); p[ 5] = QPointF( w02, h02 ); p[ 6] = QPointF( w02, h05 ); p[ 7] = QPointF( -w02, h05 ); p[ 8] = QPointF( -w02, h02 ); p[ 9] = QPointF( -w05, h02 ); p[10] = QPointF( -w05, -h02 ); p[11] = QPointF( -w02, -h02 ); for ( int i=0; i<12; ++i ) crossPoints << p[i]; crossPoints << p[0]; painter->drawPolygon( crossPoints ); break; } case MarkerAttributes::MarkerFastCross: { QPointF left, right, top, bottom; left = QPointF( -maSize.width()/2, 0 ); right = QPointF( maSize.width()/2, 0 ); top = QPointF( 0, -maSize.height()/2 ); bottom= QPointF( 0, maSize.height()/2 ); painter->setPen( PrintingParameters::scalePen( QPen( brush.color() ) ) ); painter->drawLine( left, right ); painter->drawLine( top, bottom ); break; } case MarkerAttributes::MarkerArrowDown: { QVector arrowPoints; QPointF topLeft, topRight, bottom; topLeft = QPointF( 0 - maSize.width()/2, 0 - maSize.height()/2 ); topRight = QPointF( maSize.width()/2, 0 - maSize.height()/2 ); bottom = QPointF( 0, maSize.height()/2 ); arrowPoints << topLeft << bottom << topRight; painter->drawPolygon( arrowPoints ); break; } case MarkerAttributes::MarkerArrowUp: { QVector arrowPoints; QPointF top, bottomLeft, bottomRight; top = QPointF( 0, 0 - maSize.height()/2 ); bottomLeft = QPointF( 0 - maSize.width()/2, maSize.height()/2 ); bottomRight = QPointF( maSize.width()/2, maSize.height()/2 ); arrowPoints << top << bottomLeft << bottomRight; painter->drawPolygon( arrowPoints ); break; } case MarkerAttributes::MarkerArrowRight: { QVector arrowPoints; QPointF right, topLeft, bottomLeft; right = QPointF( maSize.width()/2, 0 ); topLeft = QPointF( 0 - maSize.width()/2, 0 - maSize.height()/2 ); bottomLeft = QPointF( 0 - maSize.width()/2, maSize.height()/2 ); arrowPoints << topLeft << bottomLeft << right; painter->drawPolygon( arrowPoints ); break; } case MarkerAttributes::MarkerArrowLeft: { QVector arrowPoints; QPointF left, topRight, bottomRight; left = QPointF( 0 - maSize.width()/2, 0 ); topRight = QPointF( maSize.width()/2, 0 - maSize.height()/2 ); bottomRight = QPointF( maSize.width()/2, maSize.height()/2 ); arrowPoints << left << bottomRight << topRight; painter->drawPolygon( arrowPoints ); break; } case MarkerAttributes::MarkerBowTie: case MarkerAttributes::MarkerHourGlass: { QVector points; QPointF topLeft, topRight, bottomLeft, bottomRight; topLeft = QPointF( 0 - maSize.width()/2, 0 - maSize.height()/2); topRight = QPointF( maSize.width()/2, 0 - maSize.height()/2 ); bottomLeft = QPointF( 0 - maSize.width()/2, maSize.height()/2 ); bottomRight = QPointF( maSize.width()/2, maSize.height()/2 ); if ( markerAttributes.markerStyle() == MarkerAttributes::MarkerBowTie) points << topLeft << bottomLeft << topRight << bottomRight; else points << topLeft << bottomRight << bottomLeft << topRight; painter->drawPolygon( points ); break; } case MarkerAttributes::MarkerStar: { const qreal w01 = maSize.width() * 0.1; const qreal w05 = maSize.width() * 0.5; const qreal h01 = maSize.height() * 0.1; const qreal h05 = maSize.height() * 0.5; QVector points; QPointF p1 = QPointF( 0, -h05 ); QPointF p2 = QPointF( -w01, -h01 ); QPointF p3 = QPointF( -w05, 0 ); QPointF p4 = QPointF( -w01, h01 ); QPointF p5 = QPointF( 0, h05 ); QPointF p6 = QPointF( w01, h01 ); QPointF p7 = QPointF( w05, 0 ); QPointF p8 = QPointF( w01, -h01 ); points << p1 << p2 << p3 << p4 << p5 << p6 << p7 << p8; painter->drawPolygon( points ); break; } case MarkerAttributes::MarkerX: { const qreal w01 = maSize.width() * 0.1; const qreal w04 = maSize.width() * 0.4; const qreal w05 = maSize.width() * 0.5; const qreal h01 = maSize.height() * 0.1; const qreal h04 = maSize.height() * 0.4; const qreal h05 = maSize.height() * 0.5; QVector crossPoints; QPointF p1 = QPointF( -w04, -h05 ); QPointF p2 = QPointF( -w05, -h04 ); QPointF p3 = QPointF( -w01, 0 ); QPointF p4 = QPointF( -w05, h04 ); QPointF p5 = QPointF( -w04, h05 ); QPointF p6 = QPointF( 0, h01 ); QPointF p7 = QPointF( w04, h05 ); QPointF p8 = QPointF( w05, h04 ); QPointF p9 = QPointF( w01, 0 ); QPointF p10 = QPointF( w05, -h04 ); QPointF p11 = QPointF( w04, -h05 ); QPointF p12 = QPointF( 0, -h01 ); crossPoints << p1 << p2 << p3 << p4 << p5 << p6 << p7 << p8 << p9 << p10 << p11 << p12; painter->drawPolygon( crossPoints ); break; } case MarkerAttributes::MarkerAsterisk: { // Note: Markers can have outline, // so just drawing three lines is NOT the solution here! // The idea that we use is to draw 3 lines anyway, but convert their // outlines to QPainterPaths which are then united and filled. const qreal w04 = maSize.width() * 0.4; const qreal h02 = maSize.height() * 0.2; const qreal h05 = maSize.height() * 0.5; //QVector crossPoints; QPointF p1 = QPointF( 0, -h05 ); QPointF p2 = QPointF( -w04, -h02 ); QPointF p3 = QPointF( -w04, h02 ); QPointF p4 = QPointF( 0, h05 ); QPointF p5 = QPointF( w04, h02 ); QPointF p6 = QPointF( w04, -h02 ); QPen pen = painter->pen(); QPainterPathStroker stroker; stroker.setWidth( pen.widthF() ); stroker.setCapStyle( pen.capStyle() ); QPainterPath path; QPainterPath dummyPath; dummyPath.moveTo( p1 ); dummyPath.lineTo( p4 ); path = stroker.createStroke( dummyPath ); dummyPath = QPainterPath(); dummyPath.moveTo( p2 ); dummyPath.lineTo( p5 ); path = path.united( stroker.createStroke( dummyPath ) ); dummyPath = QPainterPath(); dummyPath.moveTo( p3 ); dummyPath.lineTo( p6 ); path = path.united( stroker.createStroke( dummyPath ) ); painter->drawPath( path ); break; } case MarkerAttributes::MarkerHorizontalBar: { const qreal w05 = maSize.width() * 0.5; const qreal h02 = maSize.height()* 0.2; QVector points; QPointF p1 = QPointF( -w05, -h02 ); QPointF p2 = QPointF( -w05, h02 ); QPointF p3 = QPointF( w05, h02 ); QPointF p4 = QPointF( w05, -h02 ); points << p1 << p2 << p3 << p4; painter->drawPolygon( points ); break; } case MarkerAttributes::MarkerVerticalBar: { const qreal w02 = maSize.width() * 0.2; const qreal h05 = maSize.height()* 0.5; QVector points; QPointF p1 = QPointF( -w02, -h05 ); QPointF p2 = QPointF( -w02, h05 ); QPointF p3 = QPointF( w02, h05 ); QPointF p4 = QPointF( w02, -h05 ); points << p1 << p2 << p3 << p4; painter->drawPolygon( points ); break; } case MarkerAttributes::NoMarker: break; case MarkerAttributes::PainterPathMarker: { QPainterPath path = markerAttributes.customMarkerPath(); const QRectF pathBoundingRect = path.boundingRect(); const qreal xScaling = maSize.height() / pathBoundingRect.height(); const qreal yScaling = maSize.width() / pathBoundingRect.width(); const qreal scaling = qMin( xScaling, yScaling ); painter->scale( scaling, scaling ); painter->setPen( PrintingParameters::scalePen( QPen( brush.color() ) ) ); painter->drawPath(path); break; } default: Q_ASSERT_X ( false, "paintMarkers()", "Type item does not match a defined Marker Type." ); } } painter->setPen( oldPen ); } void AbstractDiagram::paintMarkers( QPainter* painter ) { if ( !checkInvariants() ) { return; } const int rowCount = model()->rowCount( rootIndex() ); const int columnCount = model()->columnCount( rootIndex() ); for ( int column = 0; column < columnCount; column += datasetDimension() ) { for ( int row = 0; row < rowCount; ++row ) { QModelIndex index = model()->index( row, column, rootIndex() ); // checked qreal x; qreal y; if ( datasetDimension() == 1 ) { x = row; y = index.data().toReal(); } else { x = index.data().toReal(); y = model()->index( row, column + 1, rootIndex() ).data().toReal(); } paintMarker( painter, index, coordinatePlane()->translate( QPointF( x, y ) ) ); } } } void AbstractDiagram::setPen( const QModelIndex& index, const QPen& pen ) { attributesModel()->setData( conditionallyMapFromSource( index ), qVariantFromValue( pen ), DatasetPenRole ); emit propertiesChanged(); } void AbstractDiagram::setPen( const QPen& pen ) { attributesModel()->setModelData( qVariantFromValue( pen ), DatasetPenRole ); emit propertiesChanged(); } void AbstractDiagram::setPen( int dataset, const QPen& pen ) { d->setDatasetAttrs( dataset, qVariantFromValue( pen ), DatasetPenRole ); emit propertiesChanged(); } QPen AbstractDiagram::pen() const { return attributesModel()->data( DatasetPenRole ).value< QPen >(); } QPen AbstractDiagram::pen( int dataset ) const { const QVariant penSettings( d->datasetAttrs( dataset, DatasetPenRole ) ); if ( penSettings.isValid() ) return penSettings.value< QPen >(); return pen(); } QPen AbstractDiagram::pen( const QModelIndex& index ) const { return attributesModel()->data( conditionallyMapFromSource( index ), DatasetPenRole ).value< QPen >(); } void AbstractDiagram::setBrush( const QModelIndex& index, const QBrush& brush ) { attributesModel()->setData( conditionallyMapFromSource( index ), qVariantFromValue( brush ), DatasetBrushRole ); emit propertiesChanged(); } void AbstractDiagram::setBrush( const QBrush& brush ) { attributesModel()->setModelData( qVariantFromValue( brush ), DatasetBrushRole ); emit propertiesChanged(); } void AbstractDiagram::setBrush( int dataset, const QBrush& brush ) { d->setDatasetAttrs( dataset, qVariantFromValue( brush ), DatasetBrushRole ); emit propertiesChanged(); } QBrush AbstractDiagram::brush() const { return attributesModel()->data( DatasetBrushRole ).value< QBrush >(); } QBrush AbstractDiagram::brush( int dataset ) const { const QVariant brushSettings( d->datasetAttrs( dataset, DatasetBrushRole ) ); if ( brushSettings.isValid() ) return brushSettings.value< QBrush >(); return brush(); } QBrush AbstractDiagram::brush( const QModelIndex& index ) const { return attributesModel()->data( conditionallyMapFromSource( index ), DatasetBrushRole ).value< QBrush >(); } /** * Sets the unit prefix for one value * @param prefix the prefix to be set * @param column the value using that prefix * @param orientation the orientantion of the axis to set */ void AbstractDiagram::setUnitPrefix( const QString& prefix, int column, Qt::Orientation orientation ) { d->unitPrefixMap[ column ][ orientation ]= prefix; } /** * Sets the unit prefix for all values * @param prefix the prefix to be set * @param orientation the orientantion of the axis to set */ void AbstractDiagram::setUnitPrefix( const QString& prefix, Qt::Orientation orientation ) { d->unitPrefix[ orientation ] = prefix; } /** * Sets the unit suffix for one value * @param suffix the suffix to be set * @param column the value using that suffix * @param orientation the orientantion of the axis to set */ void AbstractDiagram::setUnitSuffix( const QString& suffix, int column, Qt::Orientation orientation ) { d->unitSuffixMap[ column ][ orientation ]= suffix; } /** * Sets the unit suffix for all values * @param suffix the suffix to be set * @param orientation the orientantion of the axis to set */ void AbstractDiagram::setUnitSuffix( const QString& suffix, Qt::Orientation orientation ) { d->unitSuffix[ orientation ] = suffix; } /** * Returns the unit prefix for a special value * @param column the value which's prefix is requested * @param orientation the orientation of the axis * @param fallback if true, the global prefix is return when no specific one is set for that value * @return the unit prefix */ QString AbstractDiagram::unitPrefix( int column, Qt::Orientation orientation, bool fallback ) const { if ( !fallback || d->unitPrefixMap[ column ].contains( orientation ) ) return d->unitPrefixMap[ column ][ orientation ]; return d->unitPrefix[ orientation ]; } /** Returns the global unit prefix * @param orientation the orientation of the axis * @return the unit prefix */ QString AbstractDiagram::unitPrefix( Qt::Orientation orientation ) const { return d->unitPrefix[ orientation ]; } /** * Returns the unit suffix for a special value * @param column the value which's suffix is requested * @param orientation the orientation of the axis * @param fallback if true, the global suffix is return when no specific one is set for that value * @return the unit suffix */ QString AbstractDiagram::unitSuffix( int column, Qt::Orientation orientation, bool fallback ) const { if ( !fallback || d->unitSuffixMap[ column ].contains( orientation ) ) return d->unitSuffixMap[ column ][ orientation ]; return d->unitSuffix[ orientation ]; } /** Returns the global unit suffix * @param orientation the orientation of the axis * @return the unit siffix */ QString AbstractDiagram::unitSuffix( Qt::Orientation orientation ) const { return d->unitSuffix[ orientation ]; } // implement QAbstractItemView: QRect AbstractDiagram::visualRect( const QModelIndex &index ) const { return d->reverseMapper.boundingRect( index.row(), index.column() ).toRect(); } void AbstractDiagram::scrollTo(const QModelIndex &, ScrollHint ) {} // indexAt ... down below QModelIndex AbstractDiagram::moveCursor(CursorAction, Qt::KeyboardModifiers ) { return QModelIndex(); } int AbstractDiagram::horizontalOffset() const { return 0; } int AbstractDiagram::verticalOffset() const { return 0; } bool AbstractDiagram::isIndexHidden(const QModelIndex &) const { return true; } void AbstractDiagram::setSelection(const QRect& rect , QItemSelectionModel::SelectionFlags command ) { const QModelIndexList indexes = d->indexesIn( rect ); QItemSelection selection; Q_FOREACH( const QModelIndex& index, indexes ) { selection.append( QItemSelectionRange( index ) ); } selectionModel()->select( selection, command ); } QRegion AbstractDiagram::visualRegionForSelection(const QItemSelection &selection) const { QPolygonF polygon; Q_FOREACH( const QModelIndex& index, selection.indexes() ) { polygon << d->reverseMapper.polygon(index.row(), index.column()); } return polygon.isEmpty() ? QRegion() : QRegion( polygon.toPolygon() ); } QRegion AbstractDiagram::visualRegion(const QModelIndex &index) const { QPolygonF polygon = d->reverseMapper.polygon(index.row(), index.column()); return polygon.isEmpty() ? QRegion() : QRegion( polygon.toPolygon() ); } void KChart::AbstractDiagram::useDefaultColors( ) { d->attributesModel->setPaletteType( AttributesModel::PaletteTypeDefault ); } void KChart::AbstractDiagram::useSubduedColors( ) { d->attributesModel->setPaletteType( AttributesModel::PaletteTypeSubdued ); } void KChart::AbstractDiagram::useRainbowColors( ) { d->attributesModel->setPaletteType( AttributesModel::PaletteTypeRainbow ); } QStringList AbstractDiagram::itemRowLabels() const { QStringList ret; if ( model() ) { //qDebug() << "AbstractDiagram::itemRowLabels(): " << attributesModel()->rowCount(attributesModelRootIndex()) << "entries"; const int rowCount = attributesModel()->rowCount(attributesModelRootIndex()); for ( int i = 0; i < rowCount; ++i ) { //qDebug() << "item row label: " << attributesModel()->headerData( i, Qt::Vertical, Qt::DisplayRole ).toString(); ret << unitPrefix( i, Qt::Horizontal, true ) + attributesModel()->headerData( i, Qt::Vertical, Qt::DisplayRole ).toString() + unitSuffix( i, Qt::Horizontal, true ); } } return ret; } QStringList AbstractDiagram::datasetLabels() const { QStringList ret; if ( !model() ) { return ret; } const int datasetCount = d->datasetCount(); for ( int i = 0; i < datasetCount; ++i ) { ret << d->datasetAttrs( i, Qt::DisplayRole ).toString(); } return ret; } QList AbstractDiagram::datasetBrushes() const { QList ret; if ( !model() ) { return ret; } const int datasetCount = d->datasetCount(); for ( int i = 0; i < datasetCount; ++i ) { ret << brush( i ); } return ret; } QList AbstractDiagram::datasetPens() const { QList ret; if ( !model() ) { return ret; } const int datasetCount = d->datasetCount(); for ( int i = 0; i < datasetCount; ++i ) { ret << pen( i ); } return ret; } QList AbstractDiagram::datasetMarkers() const { QList ret; if ( !model() ) { return ret; } const int datasetCount = d->datasetCount(); for ( int i = 0; i < datasetCount; ++i ) { ret << dataValueAttributes( i ).markerAttributes(); } return ret; } bool AbstractDiagram::checkInvariants( bool justReturnTheStatus ) const { if ( ! justReturnTheStatus ) { Q_ASSERT_X ( model(), "AbstractDiagram::checkInvariants()", "There is no usable model set, for the diagram." ); Q_ASSERT_X ( coordinatePlane(), "AbstractDiagram::checkInvariants()", "There is no usable coordinate plane set, for the diagram." ); } return model() && coordinatePlane(); } int AbstractDiagram::datasetDimension( ) const { return d->datasetDimension; } void AbstractDiagram::setDatasetDimension( int dimension ) { Q_UNUSED( dimension ); qDebug() << "Setting the dataset dimension using AbstractDiagram::setDatasetDimension is " "obsolete. Use the specific diagram types instead."; } void AbstractDiagram::setDatasetDimensionInternal( int dimension ) { Q_ASSERT( dimension != 0 ); if ( d->datasetDimension == dimension ) { return; } d->datasetDimension = dimension; d->attributesModel->setDatasetDimension( dimension ); setDataBoundariesDirty(); emit layoutChanged( this ); } qreal AbstractDiagram::valueForCell( int row, int column ) const { if ( !d->attributesModel->hasIndex( row, column, attributesModelRootIndex() ) ) { qWarning() << "AbstractDiagram::valueForCell(): Requesting value for invalid index!"; return std::numeric_limits::quiet_NaN(); } return d->attributesModel->data( d->attributesModel->index( row, column, attributesModelRootIndex() ) ).toReal(); // checked } void AbstractDiagram::update() const { if ( d->plane ) { d->plane->update(); } } QModelIndex AbstractDiagram::indexAt( const QPoint& point ) const { return d->indexAt( point ); } QModelIndexList AbstractDiagram::indexesAt( const QPoint& point ) const { return d->indexesAt( point ); } QModelIndexList AbstractDiagram::indexesIn( const QRect& rect ) const { return d->indexesIn( rect ); } diff --git a/src/KChart/KChartAbstractDiagram_p.cpp b/src/KChart/KChartAbstractDiagram_p.cpp index 3bce0e7..ccef30b 100644 --- a/src/KChart/KChartAbstractDiagram_p.cpp +++ b/src/KChart/KChartAbstractDiagram_p.cpp @@ -1,678 +1,678 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ // // W A R N I N G // ------------- // // This file is not part of the KD Chart API. It exists purely as an // implementation detail. This header file may change from version to // version without notice, or even be removed. // // We mean it. // #include "KChartAbstractDiagram_p.h" #include "KChartBarDiagram.h" #include "KChartFrameAttributes.h" #include "KChartPainterSaver_p.h" #include #include #include using namespace KChart; LabelPaintInfo::LabelPaintInfo() : isValuePositive( false ) { } LabelPaintInfo::LabelPaintInfo( const QModelIndex& _index, const DataValueAttributes& _attrs, const QPainterPath& _labelArea, const QPointF& _markerPos, bool _isValuePositive, const QString& _value ) : index( _index ) , attrs( _attrs ) , labelArea( _labelArea ) , markerPos( _markerPos ) , isValuePositive( _isValuePositive ) , value( _value ) { } LabelPaintInfo::LabelPaintInfo( const LabelPaintInfo& other ) : index( other.index ) , attrs( other.attrs ) , labelArea( other.labelArea ) , markerPos( other.markerPos ) , isValuePositive( other.isValuePositive ) , value( other.value ) { } AbstractDiagram::Private::Private() : diagram( 0 ) , doDumpPaintTime( false ) , plane( 0 ) , attributesModel( new PrivateAttributesModel(0,0) ) , allowOverlappingDataValueTexts( false ) , antiAliasing( true ) , percent( false ) , datasetDimension( 1 ) , databoundariesDirty( true ) , mCachedFontMetrics( QFontMetrics( qApp->font() ) ) { } AbstractDiagram::Private::~Private() { if ( attributesModel && qobject_cast(attributesModel) ) delete attributesModel; } void AbstractDiagram::Private::init() { } void AbstractDiagram::Private::init( AbstractCoordinatePlane* newPlane ) { plane = newPlane; } bool AbstractDiagram::Private::usesExternalAttributesModel() const { return ( ! attributesModel.isNull() ) && ( ! qobject_cast(attributesModel) ); } void AbstractDiagram::Private::setAttributesModel( AttributesModel* amodel ) { if ( attributesModel == amodel ) { return; } if ( !attributesModel.isNull() ) { if ( qobject_cast< PrivateAttributesModel* >( attributesModel ) ) { delete attributesModel; } else { - disconnect( attributesModel, SIGNAL( rowsInserted( QModelIndex, int, int ) ), - diagram, SLOT( setDataBoundariesDirty() ) ); - disconnect( attributesModel, SIGNAL( columnsInserted( QModelIndex, int, int ) ), - diagram, SLOT( setDataBoundariesDirty() ) ); - disconnect( attributesModel, SIGNAL( rowsRemoved( QModelIndex, int, int ) ), - diagram, SLOT( setDataBoundariesDirty() ) ); - disconnect( attributesModel, SIGNAL( columnsRemoved( QModelIndex, int, int ) ), - diagram, SLOT( setDataBoundariesDirty() ) ); - disconnect( attributesModel, SIGNAL( modelReset() ), - diagram, SLOT( setDataBoundariesDirty() ) ); - disconnect( attributesModel, SIGNAL( layoutChanged() ), - diagram, SLOT( setDataBoundariesDirty() ) ); - disconnect( attributesModel, SIGNAL( dataChanged( QModelIndex, QModelIndex ) ), - diagram, SIGNAL( modelDataChanged() )); + disconnect( attributesModel, SIGNAL(rowsInserted(QModelIndex,int,int)), + diagram, SLOT(setDataBoundariesDirty()) ); + disconnect( attributesModel, SIGNAL(columnsInserted(QModelIndex,int,int)), + diagram, SLOT(setDataBoundariesDirty()) ); + disconnect( attributesModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), + diagram, SLOT(setDataBoundariesDirty()) ); + disconnect( attributesModel, SIGNAL(columnsRemoved(QModelIndex,int,int)), + diagram, SLOT(setDataBoundariesDirty()) ); + disconnect( attributesModel, SIGNAL(modelReset()), + diagram, SLOT(setDataBoundariesDirty()) ); + disconnect( attributesModel, SIGNAL(layoutChanged()), + diagram, SLOT(setDataBoundariesDirty()) ); + disconnect( attributesModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + diagram, SIGNAL(modelDataChanged())); } } emit diagram->attributesModelAboutToChange( amodel, attributesModel ); - connect( amodel, SIGNAL( rowsInserted( QModelIndex, int, int ) ), - diagram, SLOT( setDataBoundariesDirty() ) ); - connect( amodel, SIGNAL( columnsInserted( QModelIndex, int, int ) ), - diagram, SLOT( setDataBoundariesDirty() ) ); - connect( amodel, SIGNAL( rowsRemoved( QModelIndex, int, int ) ), - diagram, SLOT( setDataBoundariesDirty() ) ); - connect( amodel, SIGNAL( columnsRemoved( QModelIndex, int, int ) ), - diagram, SLOT( setDataBoundariesDirty() ) ); - connect( amodel, SIGNAL( modelReset() ), - diagram, SLOT( setDataBoundariesDirty() ) ); - connect( amodel, SIGNAL( layoutChanged() ), - diagram, SLOT( setDataBoundariesDirty() ) ); - connect( amodel, SIGNAL( dataChanged( QModelIndex, QModelIndex ) ), - diagram, SIGNAL( modelDataChanged() )); + connect( amodel, SIGNAL(rowsInserted(QModelIndex,int,int)), + diagram, SLOT(setDataBoundariesDirty()) ); + connect( amodel, SIGNAL(columnsInserted(QModelIndex,int,int)), + diagram, SLOT(setDataBoundariesDirty()) ); + connect( amodel, SIGNAL(rowsRemoved(QModelIndex,int,int)), + diagram, SLOT(setDataBoundariesDirty()) ); + connect( amodel, SIGNAL(columnsRemoved(QModelIndex,int,int)), + diagram, SLOT(setDataBoundariesDirty()) ); + connect( amodel, SIGNAL(modelReset()), + diagram, SLOT(setDataBoundariesDirty()) ); + connect( amodel, SIGNAL(layoutChanged()), + diagram, SLOT(setDataBoundariesDirty()) ); + connect( amodel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + diagram, SIGNAL(modelDataChanged())); attributesModel = amodel; } AbstractDiagram::Private::Private( const AbstractDiagram::Private& rhs ) : diagram( 0 ), doDumpPaintTime( rhs.doDumpPaintTime ), // Do not copy the plane plane( 0 ), attributesModelRootIndex( QModelIndex() ), attributesModel( rhs.attributesModel ), allowOverlappingDataValueTexts( rhs.allowOverlappingDataValueTexts ), antiAliasing( rhs.antiAliasing ), percent( rhs.percent ), datasetDimension( rhs.datasetDimension ), mCachedFontMetrics( rhs.cachedFontMetrics() ) { attributesModel = new PrivateAttributesModel( 0, 0); attributesModel->initFrom( rhs.attributesModel ); } // FIXME: Optimize if necessary qreal AbstractDiagram::Private::calcPercentValue( const QModelIndex & index ) const { qreal sum = 0.0; for ( int col = 0; col < attributesModel->columnCount( QModelIndex() ); col++ ) sum += attributesModel->data( attributesModel->index( index.row(), col, QModelIndex() ) ).toReal(); // checked if ( sum == 0.0 ) return 0.0; return attributesModel->data( attributesModel->mapFromSource( index ) ).toReal() / sum * 100.0; } void AbstractDiagram::Private::addLabel( LabelPaintCache* cache, const QModelIndex& index, const CartesianDiagramDataCompressor::CachePosition* position, const PositionPoints& points, const Position& autoPositionPositive, const Position& autoPositionNegative, const qreal value, qreal favoriteAngle /* = 0.0 */ ) { CartesianDiagramDataCompressor::AggregatedDataValueAttributes allAttrs( aggregatedAttrs( index, position ) ); QMap::const_iterator it; for ( it = allAttrs.constBegin(); it != allAttrs.constEnd(); ++it ) { DataValueAttributes dva = it.value(); if ( !dva.isVisible() ) { continue; } const bool isPositive = ( value >= 0.0 ); RelativePosition relPos( dva.position( isPositive ) ); relPos.setReferencePoints( points ); if ( relPos.referencePosition().isUnknown() ) { relPos.setReferencePosition( isPositive ? autoPositionPositive : autoPositionNegative ); } // Rotate the label position (not the label itself) if the diagram is rotated so that the defaults still work if ( isTransposed() ) { KChartEnums::PositionValue posValue = relPos.referencePosition().value(); if ( posValue >= KChartEnums::PositionNorthWest && posValue <= KChartEnums::PositionWest ) { // rotate 90 degrees clockwise posValue = static_cast< KChartEnums::PositionValue >( posValue + 2 ); if ( posValue > KChartEnums::PositionWest ) { // wraparound posValue = static_cast< KChartEnums::PositionValue >( posValue - ( KChartEnums::PositionWest - KChartEnums::PositionNorthWest ) ); } relPos.setReferencePosition( Position( posValue ) ); } } const QPointF referencePoint = relPos.referencePoint(); if ( !diagram->coordinatePlane()->isVisiblePoint( referencePoint ) ) { continue; } const qreal fontHeight = cachedFontMetrics( dva.textAttributes(). calculatedFont( plane, KChartEnums::MeasureOrientationMinimum ), diagram )->height(); // Note: When printing data value texts and padding's Measure is using automatic reference area // detection, the font height is used as reference size for both horizontal and vertical // padding. QSizeF relativeMeasureSize( fontHeight, fontHeight ); if ( !dva.textAttributes().hasRotation() ) { TextAttributes ta = dva.textAttributes(); ta.setRotation( favoriteAngle ); dva.setTextAttributes( ta ); } // get the size of the label text using a subset of the information going into the final layout const QString text = formatDataValueText( dva, index, value ); QTextDocument doc; doc.setDocumentMargin( 0 ); if ( Qt::mightBeRichText( text ) ) { doc.setHtml( text ); } else { doc.setPlainText( text ); } const QFont calculatedFont( dva.textAttributes() .calculatedFont( plane, KChartEnums::MeasureOrientationMinimum ) ); doc.setDefaultFont( calculatedFont ); const QRectF plainRect = doc.documentLayout()->frameBoundingRect( doc.rootFrame() ); /** * A few hints on how the positioning of the text frame is done: * * Let's assume we have a bar chart, a text for a positive value * to be drawn, and "North" as attrs.positivePosition(). * * The reference point (pos) is then set to the top center point * of a bar. The offset now depends on the alignment: * * Top: text is centered horizontally to the bar, bottom of * text frame starts at top of bar * * Bottom: text is centered horizontally to the bar, top of * text frame starts at top of bar * * Center: text is centered horizontally to the bar, center * line of text frame is same as top of bar * * TopLeft: right edge of text frame is horizontal center of * bar, bottom of text frame is top of bar. * * ... * * Positive and negative value labels are treated equally, "North" * also refers to the top of a negative bar, and *not* to the bottom. * * * "NorthEast" likewise refers to the top right edge of the bar, * "NorthWest" to the top left edge of the bar, and so on. * * In other words, attrs.positivePosition() always refers to a * position of the *bar*, and relPos.alignment() always refers * to an alignment of the text frame relative to this position. */ QTransform transform; { // move to the general area where the label should be QPointF calcPoint = relPos.calculatedPoint( relativeMeasureSize ); transform.translate( calcPoint.x(), calcPoint.y() ); // align the text rect; find out by how many half-widths / half-heights to move. int dx = -1; if ( relPos.alignment() & Qt::AlignLeft ) { dx -= 1; } else if ( relPos.alignment() & Qt::AlignRight ) { dx += 1; } int dy = -1; if ( relPos.alignment() & Qt::AlignTop ) { dy -= 1; } else if ( relPos.alignment() & Qt::AlignBottom ) { dy += 1; } transform.translate( qreal( dx ) * plainRect.width() * 0.5, qreal( dy ) * plainRect.height() * 0.5 ); // rotate the text rect around its center transform.translate( plainRect.center().x(), plainRect.center().y() ); int rotation = dva.textAttributes().rotation(); if ( !isPositive && dva.mirrorNegativeValueTextRotation() ) { rotation *= -1; } transform.rotate( rotation ); transform.translate( -plainRect.center().x(), -plainRect.center().y() ); } QPainterPath labelArea; //labelArea.addPolygon( transform.mapToPolygon( plainRect.toRect() ) ); //labelArea.closeSubpath(); // Not doing that because QTransform has a special case for 180° that gives a different than // usual ordering of the points in the polygon returned by mapToPolygon( const QRect & ). // We expect a particular ordering in paintDataValueTextsAndMarkers() by using elementAt( 0 ), // and similar things might happen elsewhere. labelArea.addPolygon( transform.map( QPolygon( plainRect.toRect(), true ) ) ); // store the label geometry and auxiliary data cache->paintReplay.append( LabelPaintInfo( it.key(), dva, labelArea, referencePoint, value >= 0.0, text ) ); } } const QFontMetrics* AbstractDiagram::Private::cachedFontMetrics( const QFont& font, const QPaintDevice* paintDevice) const { if ( ( font != mCachedFont ) || ( paintDevice != mCachedPaintDevice ) ) { mCachedFontMetrics = QFontMetrics( font, const_cast( paintDevice ) ); // TODO what about setting mCachedFont and mCachedPaintDevice? } return &mCachedFontMetrics; } const QFontMetrics AbstractDiagram::Private::cachedFontMetrics() const { return mCachedFontMetrics; } QString AbstractDiagram::Private::formatNumber( qreal value, int decimalDigits ) const { const int digits = qMax(decimalDigits, 0); const qreal roundingEpsilon = pow( 0.1, digits ) * ( value >= 0.0 ? 0.5 : -0.5 ); QString asString = QString::number( value + roundingEpsilon, 'f' ); const int decimalPos = asString.indexOf( QLatin1Char( '.' ) ); if ( decimalPos < 0 ) { return asString; } int last = qMin( decimalPos + digits, asString.length() - 1 ); // remove trailing zeros (and maybe decimal dot) while ( last > decimalPos && asString[ last ] == QLatin1Char( '0' ) ) { last--; } if ( last == decimalPos ) { last--; } asString.chop( asString.length() - last - 1 ); return asString; } void AbstractDiagram::Private::forgetAlreadyPaintedDataValues() { alreadyDrawnDataValueTexts.clear(); prevPaintedDataValueText.clear(); } void AbstractDiagram::Private::paintDataValueTextsAndMarkers( PaintContext* ctx, const LabelPaintCache &cache, bool paintMarkers, bool justCalculateRect /* = false */, QRectF* cumulatedBoundingRect /* = 0 */ ) { if ( justCalculateRect && !cumulatedBoundingRect ) { qWarning() << Q_FUNC_INFO << "Neither painting nor finding the bounding rect, what are we doing?"; } const PainterSaver painterSaver( ctx->painter() ); ctx->painter()->setClipping( false ); if ( paintMarkers && !justCalculateRect ) { Q_FOREACH ( const LabelPaintInfo& info, cache.paintReplay ) { diagram->paintMarker( ctx->painter(), info.index, info.markerPos ); } } TextAttributes ta; { Measure m( 18.0, KChartEnums::MeasureCalculationModeRelative, KChartEnums::MeasureOrientationMinimum ); m.setReferenceArea( ctx->coordinatePlane() ); ta.setFontSize( m ); m.setAbsoluteValue( 6.0 ); ta.setMinimalFontSize( m ); } forgetAlreadyPaintedDataValues(); Q_FOREACH ( const LabelPaintInfo& info, cache.paintReplay ) { const QPointF pos = info.labelArea.elementAt( 0 ); paintDataValueText( ctx->painter(), info.attrs, pos, info.isValuePositive, info.value, justCalculateRect, cumulatedBoundingRect ); const QString comment = info.index.data( KChart::CommentRole ).toString(); if ( comment.isEmpty() ) { continue; } TextBubbleLayoutItem item( comment, ta, ctx->coordinatePlane()->parent(), KChartEnums::MeasureOrientationMinimum, Qt::AlignHCenter | Qt::AlignVCenter ); const QRect rect( pos.toPoint(), item.sizeHint() ); if (cumulatedBoundingRect) { (*cumulatedBoundingRect) |= rect; } if ( !justCalculateRect ) { item.setGeometry( rect ); item.paint( ctx->painter() ); } } if ( cumulatedBoundingRect ) { *cumulatedBoundingRect = ctx->painter()->transform().inverted().mapRect( *cumulatedBoundingRect ); } } QString AbstractDiagram::Private::formatDataValueText( const DataValueAttributes &dva, const QModelIndex& index, qreal value ) const { if ( !dva.isVisible() ) { return QString(); } if ( dva.usePercentage() ) { value = calcPercentValue( index ); } QString ret; if ( dva.dataLabel().isNull() ) { ret = formatNumber( value, dva.decimalDigits() ); } else { ret = dva.dataLabel(); } ret.prepend( dva.prefix() ); ret.append( dva.suffix() ); return ret; } void AbstractDiagram::Private::paintDataValueText( QPainter* painter, const QModelIndex& index, const QPointF& pos, qreal value, bool justCalculateRect /* = false */, QRectF* cumulatedBoundingRect /* = 0 */ ) { const DataValueAttributes dva( diagram->dataValueAttributes( index ) ); const QString text = formatDataValueText( dva, index, value ); paintDataValueText( painter, dva, pos, value >= 0.0, text, justCalculateRect, cumulatedBoundingRect ); } void AbstractDiagram::Private::paintDataValueText( QPainter* painter, const DataValueAttributes& attrs, const QPointF& pos, bool valueIsPositive, const QString& text, bool justCalculateRect /* = false */, QRectF* cumulatedBoundingRect /* = 0 */ ) { if ( !attrs.isVisible() ) { return; } const TextAttributes ta( attrs.textAttributes() ); if ( !ta.isVisible() || ( !attrs.showRepetitiveDataLabels() && prevPaintedDataValueText == text ) ) { return; } prevPaintedDataValueText = text; QTextDocument doc; doc.setDocumentMargin( 0.0 ); if ( Qt::mightBeRichText( text ) ) { doc.setHtml( text ); } else { doc.setPlainText( text ); } const QFont calculatedFont( ta.calculatedFont( plane, KChartEnums::MeasureOrientationMinimum ) ); const PainterSaver painterSaver( painter ); painter->setPen( PrintingParameters::scalePen( ta.pen() ) ); doc.setDefaultFont( calculatedFont ); QAbstractTextDocumentLayout::PaintContext context; context.palette = diagram->palette(); context.palette.setColor( QPalette::Text, ta.pen().color() ); QAbstractTextDocumentLayout* const layout = doc.documentLayout(); layout->setPaintDevice( painter->device() ); painter->translate( pos.x(), pos.y() ); int rotation = ta.rotation(); if ( !valueIsPositive && attrs.mirrorNegativeValueTextRotation() ) { rotation *= -1; } painter->rotate( rotation ); // do overlap detection "as seen by the painter" QTransform transform = painter->worldTransform(); bool drawIt = true; // note: This flag can be set differently for every label text! // In theory a user could e.g. have some small red text on one of the // values that she wants to have written in any case - so we just // do not test if such texts would cover some of the others. if ( !attrs.showOverlappingDataLabels() ) { const QRectF br( layout->frameBoundingRect( doc.rootFrame() ) ); QPolygon pr = transform.mapToPolygon( br.toRect() ); // Using QPainterPath allows us to use intersects() (which has many early-exits) // instead of QPolygon::intersected (which calculates a slow and precise intersection polygon) QPainterPath path; path.addPolygon( pr ); // iterate backwards because recently added items are more likely to overlap, so we spend // less time checking irrelevant items when there is overlap for ( int i = alreadyDrawnDataValueTexts.count() - 1; i >= 0; i-- ) { if ( alreadyDrawnDataValueTexts.at( i ).intersects( path ) ) { // qDebug() << "not painting this label due to overlap"; drawIt = false; break; } } if ( drawIt ) { alreadyDrawnDataValueTexts << path; } } if ( drawIt ) { QRectF rect = layout->frameBoundingRect( doc.rootFrame() ); if ( cumulatedBoundingRect ) { (*cumulatedBoundingRect) |= transform.mapRect( rect ); } if ( !justCalculateRect ) { bool paintBack = false; BackgroundAttributes back( attrs.backgroundAttributes() ); if ( back.isVisible() ) { paintBack = true; painter->setBrush( back.brush() ); } else { painter->setBrush( QBrush() ); } qreal radius = 0.0; FrameAttributes frame( attrs.frameAttributes() ); if ( frame.isVisible() ) { paintBack = true; painter->setPen( frame.pen() ); radius = frame.cornerRadius(); } if ( paintBack ) { QRectF borderRect( QPointF( 0, 0 ), rect.size() ); painter->drawRoundedRect( borderRect, radius, radius ); } layout->draw( painter, context ); } } } QModelIndex AbstractDiagram::Private::indexAt( const QPoint& point ) const { QModelIndexList l = indexesAt( point ); qSort( l ); if ( !l.isEmpty() ) return l.first(); else return QModelIndex(); } QModelIndexList AbstractDiagram::Private::indexesAt( const QPoint& point ) const { return reverseMapper.indexesAt( point ); // which could be empty } QModelIndexList AbstractDiagram::Private::indexesIn( const QRect& rect ) const { return reverseMapper.indexesIn( rect ); } CartesianDiagramDataCompressor::AggregatedDataValueAttributes AbstractDiagram::Private::aggregatedAttrs( const QModelIndex& index, const CartesianDiagramDataCompressor::CachePosition* position ) const { Q_UNUSED( position ); // used by cartesian diagrams only CartesianDiagramDataCompressor::AggregatedDataValueAttributes allAttrs; allAttrs[index] = diagram->dataValueAttributes( index ); return allAttrs; } void AbstractDiagram::Private::setDatasetAttrs( int dataset, const QVariant& data, int role ) { // To store attributes for a dataset, we use the first column // that's associated with it. (i.e., with a dataset dimension // of two, the column of the keys). In most cases however, there's // only one data dimension, and thus also only one column per data set. int column = dataset * datasetDimension; // For DataHiddenRole, also store the flag in the other data points that belong to this data set, // otherwise it's impossible to hide data points in a plotter diagram because there will always // be one model index that belongs to this data point that is not hidden. // For more details on how hiding works, see the data compressor. // Also see KDCH-503 for which this is a workaround. int columnSpan = role == DataHiddenRole ? datasetDimension : 1; for ( int i = 0; i < columnSpan; i++ ) { attributesModel->setHeaderData( column + i, Qt::Horizontal, data, role ); } } QVariant AbstractDiagram::Private::datasetAttrs( int dataset, int role ) const { // See setDataSetAttrs for explanation of column int column = dataset * datasetDimension; return attributesModel->headerData( column, Qt::Horizontal, role ); } void AbstractDiagram::Private::resetDatasetAttrs( int dataset, int role ) { // See setDataSetAttrs for explanation of column int column = dataset * datasetDimension; attributesModel->resetHeaderData( column, Qt::Horizontal, role ); } bool AbstractDiagram::Private::isTransposed() const { // Determine the diagram that specifies the orientation. // That diagram is the reference diagram, if it exists, or otherwise the diagram itself. // Note: In KChart 2.3 or earlier, only a bar diagram can be transposed. const AbstractCartesianDiagram* refDiagram = qobject_cast< const AbstractCartesianDiagram * >( diagram ); if ( !refDiagram ) { return false; } if ( refDiagram->referenceDiagram() ) { refDiagram = refDiagram->referenceDiagram(); } const BarDiagram* barDiagram = qobject_cast< const BarDiagram* >( refDiagram ); if ( !barDiagram ) { return false; } return barDiagram->orientation() == Qt::Horizontal; } LineAttributesInfo::LineAttributesInfo() { } LineAttributesInfo::LineAttributesInfo( const QModelIndex& _index, const QPointF& _value, const QPointF& _nextValue ) : index( _index ) , value ( _value ) , nextValue ( _nextValue ) { } diff --git a/src/KChart/KChartAttributesModel.cpp b/src/KChart/KChartAttributesModel.cpp index e642fd8..09f712d 100644 --- a/src/KChart/KChartAttributesModel.cpp +++ b/src/KChart/KChartAttributesModel.cpp @@ -1,712 +1,712 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "KChartAttributesModel.h" #include "KChartPalette.h" #include "KChartGlobal.h" #include "KChartMath_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KChart; class Q_DECL_HIDDEN AttributesModel::Private { public: Private(); QMap< int, QMap< int, QMap< int, QVariant > > > dataMap; QMap< int, QMap< int, QVariant > > horizontalHeaderDataMap; QMap< int, QMap< int, QVariant > > verticalHeaderDataMap; QMap< int, QVariant > modelDataMap; QMap< int, QVariant > defaultsMap; int dataDimension; AttributesModel::PaletteType paletteType; Palette palette; }; AttributesModel::Private::Private() : dataDimension( 1 ), paletteType( AttributesModel::PaletteTypeDefault ), palette( Palette::defaultPalette() ) { } #define d d_func() AttributesModel::AttributesModel( QAbstractItemModel* model, QObject * parent/* = 0 */ ) : AbstractProxyModel( parent ), _d( new Private ) { setSourceModel( model ); setDefaultForRole( KChart::DataValueLabelAttributesRole, DataValueAttributes::defaultAttributesAsVariant() ); } AttributesModel::~AttributesModel() { delete _d; _d = 0; } void AttributesModel::initFrom( const AttributesModel* other ) { *d = *other->d; } bool AttributesModel::compareHeaderDataMaps( const QMap< int, QMap< int, QVariant > >& mapA, const QMap< int, QMap< int, QVariant > >& mapB ) const { if ( mapA.count() != mapB.count() ) { return false; } QMap< int, QMap< int, QVariant > >::const_iterator itA = mapA.constBegin(); QMap< int, QMap< int, QVariant > >::const_iterator itB = mapB.constBegin(); for ( ; itA != mapA.constEnd(); ++itA, ++itB ) { if ( itA->count() != itB->count() ) { return false; } QMap< int, QVariant >::const_iterator it2A = itA->constBegin(); QMap< int, QVariant >::const_iterator it2B = itB->constBegin(); for ( ; it2A != itA->constEnd(); ++it2A, ++it2B ) { if ( it2A.key() != it2B.key() ) { return false; } if ( !compareAttributes( it2A.key(), it2A.value(), it2B.value() ) ) { return false; } } } return true; } bool AttributesModel::compare( const AttributesModel* other ) const { if ( other == this ) { return true; } if ( !other || d->paletteType != other->d->paletteType ) { return false; } { if ( d->dataMap.count() != other->d->dataMap.count() ) { return false; } QMap< int, QMap< int, QMap > >::const_iterator itA = d->dataMap.constBegin(); QMap< int, QMap< int, QMap > >::const_iterator itB = other->d->dataMap.constBegin(); for ( ; itA != d->dataMap.constEnd(); ++itA, ++itB ) { if ( itA->count() != itB->count() ) { return false; } QMap< int, QMap< int, QVariant > >::const_iterator it2A = itA->constBegin(); QMap< int, QMap< int, QVariant > >::const_iterator it2B = itB->constBegin(); for ( ; it2A != itA->constEnd(); ++it2A, ++it2B ) { if ( it2A->count() != it2B->count() ) { return false; } QMap< int, QVariant >::const_iterator it3A = it2A->constBegin(); QMap< int, QVariant >::const_iterator it3B = it2B->constBegin(); for ( ; it3A != it2A->constEnd(); ++it3A, ++it3B ) { if ( it3A.key() != it3B.key() ) { return false; } if ( !compareAttributes( it3A.key(), it3A.value(), it3B.value() ) ) { return false; } } } } } if ( !compareHeaderDataMaps( d->horizontalHeaderDataMap, other->d->horizontalHeaderDataMap ) || !compareHeaderDataMaps( d->verticalHeaderDataMap, other->d->verticalHeaderDataMap ) ) { return false; } { if ( d->modelDataMap.count() != other->d->modelDataMap.count() ) { return false; } QMap< int, QVariant >::const_iterator itA = d->modelDataMap.constBegin(); QMap< int, QVariant >::const_iterator itB = other->d->modelDataMap.constBegin(); for ( ; itA != d->modelDataMap.constEnd(); ++itA, ++itB ) { if ( itA.key() != itB.key() ) { return false; } if ( !compareAttributes( itA.key(), itA.value(), itB.value() ) ) { return false; } } } return true; } bool AttributesModel::compareAttributes( int role, const QVariant& a, const QVariant& b ) const { if ( isKnownAttributesRole( role ) ) { switch ( role ) { case DataValueLabelAttributesRole: return (a.value() == b.value()); case DatasetBrushRole: return (a.value() == b.value()); case DatasetPenRole: return (a.value() == b.value()); case ThreeDAttributesRole: // As of yet there is no ThreeDAttributes class, // and the AbstractThreeDAttributes class is pure virtual, // so we ignore this role for now. // (khz, 04.04.2007) /* return (qVariantValue( a ) == qVariantValue( b )); */ break; case LineAttributesRole: return (a.value() == b.value()); case ThreeDLineAttributesRole: return (a.value() == b.value()); case BarAttributesRole: return (a.value() == b.value()); case StockBarAttributesRole: return (a.value() == b.value()); case ThreeDBarAttributesRole: return (a.value() == b.value()); case PieAttributesRole: return (a.value() == b.value()); case ThreeDPieAttributesRole: return (a.value() == b.value()); case ValueTrackerAttributesRole: return (a.value() == b.value()); case DataHiddenRole: return (a.value() == b.value()); default: Q_ASSERT( false ); // all of our own roles need to be handled break; } } else { return (a == b); } return true; } QVariant AttributesModel::headerData( int section, Qt::Orientation orientation, int role/* = Qt::DisplayRole */ ) const { if ( sourceModel() ) { const QVariant sourceData = sourceModel()->headerData( section, orientation, role ); if ( sourceData.isValid() ) { return sourceData; } } // the source model didn't have data set, let's use our stored values const QMap< int, QMap< int, QVariant> >& map = orientation == Qt::Horizontal ? d->horizontalHeaderDataMap : d->verticalHeaderDataMap; QMap< int, QMap< int, QVariant > >::const_iterator mapIt = map.find( section ); if ( mapIt != map.constEnd() ) { const QMap< int, QVariant >& dataMap = mapIt.value(); QMap< int, QVariant >::const_iterator dataMapIt = dataMap.find( role ); if ( dataMapIt != dataMap.constEnd() ) { return dataMapIt.value(); } } return defaultHeaderData( section, orientation, role ); } QVariant AttributesModel::defaultHeaderData( int section, Qt::Orientation orientation, int role ) const { // Default values if nothing else matches const int dataset = section / d->dataDimension; switch ( role ) { case Qt::DisplayRole: //TODO for KChart 3.0: return QString::number( dataset + 1 ); return QVariant( (orientation == Qt::Vertical ? QStringLiteral("Series ") : QStringLiteral("Item ")) + QString::number( dataset )) ; case KChart::DatasetBrushRole: return d->palette.getBrush( dataset ); case KChart::DatasetPenRole: // if no per model override was set, use the (possibly default) color set for the brush if ( !modelData( role ).isValid() ) { QBrush brush = headerData( section, orientation, DatasetBrushRole ).value< QBrush >(); return QPen( brush.color() ); } default: break; } return QVariant(); } QVariant AttributesModel::data( int role ) const { if ( isKnownAttributesRole( role ) ) { // check if there is something set at global level QVariant v = modelData( role ); // else return the default setting, if any if ( !v.isValid() ) v = defaultsForRole( role ); return v; } return QVariant(); } QVariant AttributesModel::data( int column, int role ) const { if ( isKnownAttributesRole( role ) ) { // check if there is something set for the column (dataset) QVariant v; v = headerData( column, Qt::Horizontal, role ); // check if there is something set at global level if ( !v.isValid() ) v = data( role ); // includes automatic fallback to default return v; } return QVariant(); } QVariant AttributesModel::data( const QModelIndex& index, int role ) const { if ( index.isValid() ) { Q_ASSERT( index.model() == this ); } if ( !sourceModel() ) { return QVariant(); } if ( index.isValid() ) { const QVariant sourceData = sourceModel()->data( mapToSource( index ), role ); if ( sourceData.isValid() ) { return sourceData; } } // check if we are storing a value for this role at this cell index if ( d->dataMap.contains( index.column() ) ) { const QMap< int, QMap< int, QVariant > >& colDataMap = d->dataMap[ index.column() ]; if ( colDataMap.contains( index.row() ) ) { const QMap< int, QVariant >& dataMap = colDataMap[ index.row() ]; if ( dataMap.contains( role ) ) { const QVariant v = dataMap[ role ]; if ( v.isValid() ) { return v; } } } } // check if there is something set for the column (dataset), or at global level if ( index.isValid() ) { return data( index.column(), role ); // includes automatic fallback to default } return QVariant(); } bool AttributesModel::isKnownAttributesRole( int role ) const { switch ( role ) { // fallthrough intended case DataValueLabelAttributesRole: case DatasetBrushRole: case DatasetPenRole: case ThreeDAttributesRole: case LineAttributesRole: case ThreeDLineAttributesRole: case BarAttributesRole: case StockBarAttributesRole: case ThreeDBarAttributesRole: case PieAttributesRole: case ThreeDPieAttributesRole: case ValueTrackerAttributesRole: case DataHiddenRole: return true; default: return false; } } QVariant AttributesModel::defaultsForRole( int role ) const { // returns default-constructed QVariant if not found return d->defaultsMap.value( role ); } bool AttributesModel::setData ( const QModelIndex & index, const QVariant & value, int role ) { if ( !isKnownAttributesRole( role ) ) { return sourceModel()->setData( mapToSource(index), value, role ); } else { QMap< int, QMap< int, QVariant> > &colDataMap = d->dataMap[ index.column() ]; QMap< int, QVariant > &dataMap = colDataMap[ index.row() ]; dataMap.insert( role, value ); emit attributesChanged( index, index ); return true; } } bool AttributesModel::resetData ( const QModelIndex & index, int role ) { return setData( index, QVariant(), role ); } bool AttributesModel::setHeaderData ( int section, Qt::Orientation orientation, const QVariant & value, int role ) { if ( sourceModel() && headerData( section, orientation, role ) == value ) { return true; } if ( !isKnownAttributesRole( role ) ) { return sourceModel()->setHeaderData( section, orientation, value, role ); } else { QMap< int, QMap > §ionDataMap = orientation == Qt::Horizontal ? d->horizontalHeaderDataMap : d->verticalHeaderDataMap; QMap< int, QVariant > &dataMap = sectionDataMap[ section ]; dataMap.insert( role, value ); if ( sourceModel() ) { int numRows = rowCount( QModelIndex() ); int numCols = columnCount( QModelIndex() ); if ( orientation == Qt::Horizontal && numRows > 0 ) emit attributesChanged( index( 0, section, QModelIndex() ), index( numRows - 1, section, QModelIndex() ) ); else if ( orientation == Qt::Vertical && numCols > 0 ) emit attributesChanged( index( section, 0, QModelIndex() ), index( section, numCols - 1, QModelIndex() ) ); emit headerDataChanged( orientation, section, section ); // FIXME: This only makes sense for orientation == Qt::Horizontal, // but what if orientation == Qt::Vertical? if ( section != -1 && numRows > 0 ) emit dataChanged( index( 0, section, QModelIndex() ), index( numRows - 1, section, QModelIndex() ) ); } return true; } } bool AttributesModel::resetHeaderData ( int section, Qt::Orientation orientation, int role ) { return setHeaderData ( section, orientation, QVariant(), role ); } void AttributesModel::setPaletteType( AttributesModel::PaletteType type ) { if ( d->paletteType == type ) { return; } d->paletteType = type; switch ( type ) { case PaletteTypeDefault: d->palette = Palette::defaultPalette(); break; case PaletteTypeSubdued: d->palette = Palette::subduedPalette(); break; case PaletteTypeRainbow: d->palette = Palette::rainbowPalette(); break; default: qWarning( "Unknown palette type!" ); } } AttributesModel::PaletteType AttributesModel::paletteType() const { return d->paletteType; } bool KChart::AttributesModel::setModelData( const QVariant value, int role ) { d->modelDataMap.insert( role, value ); int numRows = rowCount( QModelIndex() ); int numCols = columnCount( QModelIndex() ); if ( sourceModel() && numRows > 0 && numCols > 0 ) { emit attributesChanged( index( 0, 0, QModelIndex() ), index( numRows - 1, numCols - 1, QModelIndex() ) ); beginResetModel(); endResetModel(); } return true; } QVariant KChart::AttributesModel::modelData( int role ) const { return d->modelDataMap.value( role, QVariant() ); } int AttributesModel::rowCount( const QModelIndex& index ) const { if ( sourceModel() ) { return sourceModel()->rowCount( mapToSource(index) ); } else { return 0; } } int AttributesModel::columnCount( const QModelIndex& index ) const { if ( sourceModel() ) { return sourceModel()->columnCount( mapToSource(index) ); } else { return 0; } } void AttributesModel::setSourceModel( QAbstractItemModel* sourceModel ) { if ( this->sourceModel() != 0 ) { - disconnect( this->sourceModel(), SIGNAL( dataChanged( const QModelIndex&, const QModelIndex&)), - this, SLOT( slotDataChanged( const QModelIndex&, const QModelIndex&))); - disconnect( this->sourceModel(), SIGNAL( rowsInserted( const QModelIndex&, int, int ) ), - this, SLOT( slotRowsInserted( const QModelIndex&, int, int ) ) ); - disconnect( this->sourceModel(), SIGNAL( rowsRemoved( const QModelIndex&, int, int ) ), - this, SLOT( slotRowsRemoved( const QModelIndex&, int, int ) ) ); - disconnect( this->sourceModel(), SIGNAL( rowsAboutToBeInserted( const QModelIndex&, int, int ) ), - this, SLOT( slotRowsAboutToBeInserted( const QModelIndex&, int, int ) ) ); - disconnect( this->sourceModel(), SIGNAL( rowsAboutToBeRemoved( const QModelIndex&, int, int ) ), - this, SLOT( slotRowsAboutToBeRemoved( const QModelIndex&, int, int ) ) ); - disconnect( this->sourceModel(), SIGNAL( columnsInserted( const QModelIndex&, int, int ) ), - this, SLOT( slotColumnsInserted( const QModelIndex&, int, int ) ) ); - disconnect( this->sourceModel(), SIGNAL( columnsRemoved( const QModelIndex&, int, int ) ), - this, SLOT( slotColumnsRemoved( const QModelIndex&, int, int ) ) ); - disconnect( this->sourceModel(), SIGNAL( columnsAboutToBeInserted( const QModelIndex&, int, int ) ), - this, SLOT( slotColumnsAboutToBeInserted( const QModelIndex&, int, int ) ) ); - disconnect( this->sourceModel(), SIGNAL( columnsAboutToBeRemoved( const QModelIndex&, int, int ) ), - this, SLOT( slotColumnsAboutToBeRemoved( const QModelIndex&, int, int ) ) ); - disconnect( this->sourceModel(), SIGNAL( modelReset() ), - this, SIGNAL( modelReset() ) ); - disconnect( this->sourceModel(), SIGNAL( layoutChanged() ), - this, SIGNAL( layoutChanged() ) ); + disconnect( this->sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(slotDataChanged(QModelIndex,QModelIndex))); + disconnect( this->sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(slotRowsInserted(QModelIndex,int,int)) ); + disconnect( this->sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(slotRowsRemoved(QModelIndex,int,int)) ); + disconnect( this->sourceModel(), SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(slotRowsAboutToBeInserted(QModelIndex,int,int)) ); + disconnect( this->sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int)) ); + disconnect( this->sourceModel(), SIGNAL(columnsInserted(QModelIndex,int,int)), + this, SLOT(slotColumnsInserted(QModelIndex,int,int)) ); + disconnect( this->sourceModel(), SIGNAL(columnsRemoved(QModelIndex,int,int)), + this, SLOT(slotColumnsRemoved(QModelIndex,int,int)) ); + disconnect( this->sourceModel(), SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(slotColumnsAboutToBeInserted(QModelIndex,int,int)) ); + disconnect( this->sourceModel(), SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(slotColumnsAboutToBeRemoved(QModelIndex,int,int)) ); + disconnect( this->sourceModel(), SIGNAL(modelReset()), + this, SIGNAL(modelReset()) ); + disconnect( this->sourceModel(), SIGNAL(layoutChanged()), + this, SIGNAL(layoutChanged()) ); } QAbstractProxyModel::setSourceModel( sourceModel ); if ( this->sourceModel() != NULL ) { - connect( this->sourceModel(), SIGNAL( dataChanged( const QModelIndex&, const QModelIndex&)), - this, SLOT( slotDataChanged( const QModelIndex&, const QModelIndex&))); - connect( this->sourceModel(), SIGNAL( rowsInserted( const QModelIndex&, int, int ) ), - this, SLOT( slotRowsInserted( const QModelIndex&, int, int ) ) ); - connect( this->sourceModel(), SIGNAL( rowsRemoved( const QModelIndex&, int, int ) ), - this, SLOT( slotRowsRemoved( const QModelIndex&, int, int ) ) ); - connect( this->sourceModel(), SIGNAL( rowsAboutToBeInserted( const QModelIndex&, int, int ) ), - this, SLOT( slotRowsAboutToBeInserted( const QModelIndex&, int, int ) ) ); - connect( this->sourceModel(), SIGNAL( rowsAboutToBeRemoved( const QModelIndex&, int, int ) ), - this, SLOT( slotRowsAboutToBeRemoved( const QModelIndex&, int, int ) ) ); - connect( this->sourceModel(), SIGNAL( columnsInserted( const QModelIndex&, int, int ) ), - this, SLOT( slotColumnsInserted( const QModelIndex&, int, int ) ) ); - connect( this->sourceModel(), SIGNAL( columnsRemoved( const QModelIndex&, int, int ) ), - this, SLOT( slotColumnsRemoved( const QModelIndex&, int, int ) ) ); - connect( this->sourceModel(), SIGNAL( columnsAboutToBeInserted( const QModelIndex&, int, int ) ), - this, SLOT( slotColumnsAboutToBeInserted( const QModelIndex&, int, int ) ) ); - connect( this->sourceModel(), SIGNAL( columnsAboutToBeRemoved( const QModelIndex&, int, int ) ), - this, SLOT( slotColumnsAboutToBeRemoved( const QModelIndex&, int, int ) ) ); - connect( this->sourceModel(), SIGNAL( modelReset() ), - this, SIGNAL( modelReset() ) ); - connect( this->sourceModel(), SIGNAL( layoutChanged() ), - this, SIGNAL( layoutChanged() ) ); + connect( this->sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(slotDataChanged(QModelIndex,QModelIndex))); + connect( this->sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(slotRowsInserted(QModelIndex,int,int)) ); + connect( this->sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(slotRowsRemoved(QModelIndex,int,int)) ); + connect( this->sourceModel(), SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(slotRowsAboutToBeInserted(QModelIndex,int,int)) ); + connect( this->sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int)) ); + connect( this->sourceModel(), SIGNAL(columnsInserted(QModelIndex,int,int)), + this, SLOT(slotColumnsInserted(QModelIndex,int,int)) ); + connect( this->sourceModel(), SIGNAL(columnsRemoved(QModelIndex,int,int)), + this, SLOT(slotColumnsRemoved(QModelIndex,int,int)) ); + connect( this->sourceModel(), SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(slotColumnsAboutToBeInserted(QModelIndex,int,int)) ); + connect( this->sourceModel(), SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(slotColumnsAboutToBeRemoved(QModelIndex,int,int)) ); + connect( this->sourceModel(), SIGNAL(modelReset()), + this, SIGNAL(modelReset()) ); + connect( this->sourceModel(), SIGNAL(layoutChanged()), + this, SIGNAL(layoutChanged()) ); } } void AttributesModel::slotRowsAboutToBeInserted( const QModelIndex& parent, int start, int end ) { beginInsertRows( mapFromSource( parent ), start, end ); } void AttributesModel::slotColumnsAboutToBeInserted( const QModelIndex& parent, int start, int end ) { beginInsertColumns( mapFromSource( parent ), start, end ); } void AttributesModel::slotRowsInserted( const QModelIndex& parent, int start, int end ) { Q_UNUSED( parent ); Q_UNUSED( start ); Q_UNUSED( end ); endInsertRows(); } void AttributesModel::slotColumnsInserted( const QModelIndex& parent, int start, int end ) { Q_UNUSED( parent ); Q_UNUSED( start ); Q_UNUSED( end ); endInsertColumns(); } void AttributesModel::slotRowsAboutToBeRemoved( const QModelIndex& parent, int start, int end ) { beginRemoveRows( mapFromSource( parent ), start, end ); } void AttributesModel::slotColumnsAboutToBeRemoved( const QModelIndex& parent, int start, int end ) { beginRemoveColumns( mapFromSource( parent ), start, end ); } void AttributesModel::slotRowsRemoved( const QModelIndex& parent, int start, int end ) { Q_UNUSED( parent ); Q_UNUSED( start ); Q_UNUSED( end ); endRemoveRows(); } void AttributesModel::removeEntriesFromDataMap( int start, int end ) { QMap< int, QMap< int, QMap< int, QVariant > > >::iterator it = d->dataMap.find( end ); // check that the element was found if ( it != d->dataMap.end() ) { ++it; QVector< int > indexesToDel; for ( int i = start; i < end && it != d->dataMap.end(); ++i ) { d->dataMap[ i ] = it.value(); indexesToDel << it.key(); ++it; } if ( indexesToDel.isEmpty() ) { for ( int i = start; i < end; ++i ) { indexesToDel << i; } } for ( int i = 0; i < indexesToDel.count(); ++i ) { d->dataMap.remove( indexesToDel[ i ] ); } } } void AttributesModel::removeEntriesFromDirectionDataMaps( Qt::Orientation dir, int start, int end ) { QMap > §ionDataMap = dir == Qt::Horizontal ? d->horizontalHeaderDataMap : d->verticalHeaderDataMap; QMap >::iterator it = sectionDataMap.upperBound( end ); // check that the element was found if ( it != sectionDataMap.end() ) { QVector< int > indexesToDel; for ( int i = start; i < end && it != sectionDataMap.end(); ++i ) { sectionDataMap[ i ] = it.value(); indexesToDel << it.key(); ++it; } if ( indexesToDel.isEmpty() ) { for ( int i = start; i < end; ++i ) { indexesToDel << i; } } for ( int i = 0; i < indexesToDel.count(); ++i ) { sectionDataMap.remove( indexesToDel[ i ] ); } } } void AttributesModel::slotColumnsRemoved( const QModelIndex& parent, int start, int end ) { Q_UNUSED( parent ); Q_UNUSED( start ); Q_UNUSED( end ); Q_ASSERT_X( sourceModel(), "removeColumn", "This should only be triggered if a valid source Model exists!" ); for ( int i = start; i <= end; ++i ) { d->verticalHeaderDataMap.remove( start ); } removeEntriesFromDataMap( start, end ); removeEntriesFromDirectionDataMaps( Qt::Horizontal, start, end ); removeEntriesFromDirectionDataMaps( Qt::Vertical, start, end ); endRemoveColumns(); } void AttributesModel::slotDataChanged( const QModelIndex& topLeft, const QModelIndex& bottomRight ) { emit dataChanged( mapFromSource( topLeft ), mapFromSource( bottomRight ) ); } void AttributesModel::setDefaultForRole( int role, const QVariant& value ) { if ( value.isValid() ) { d->defaultsMap.insert( role, value ); } else { // erase the possibily existing value to not let the map grow: QMap::iterator it = d->defaultsMap.find( role ); if ( it != d->defaultsMap.end() ) { d->defaultsMap.erase( it ); } } Q_ASSERT( defaultsForRole( role ).value() == value.value() ); } void AttributesModel::setDatasetDimension( int dimension ) { //### need to "reformat" or throw away internal data? d->dataDimension = dimension; } int AttributesModel::datasetDimension() const { return d->dataDimension; } diff --git a/src/KChart/KChartChart.cpp b/src/KChart/KChartChart.cpp index 15af35a..e14a823 100644 --- a/src/KChart/KChartChart.cpp +++ b/src/KChart/KChartChart.cpp @@ -1,1805 +1,1805 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "KChartChart.h" #include "KChartChart_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "KChartCartesianCoordinatePlane.h" #include "KChartAbstractCartesianDiagram.h" #include "KChartHeaderFooter.h" #include "KChartEnums.h" #include "KChartLegend.h" #include "KChartLayoutItems.h" #include #include #include "KChartPainterSaver_p.h" #include "KChartPrintingParameters.h" #include #if defined KDAB_EVAL #include "../evaldialog/evaldialog.h" #endif #if 0 // dumpLayoutTree dumps a QLayout tree in a hopefully easy to read format to stderr - feel free to // use, improve and extend; it is very useful for looking at any layout problem. #include // this is this different from both QRect::isEmpty() and QRect::isNull() for "wrong" QRects, // i.e. those where topLeft() is actually below and / or right of bottomRight(). static bool isZeroArea(const QRect &r) { return !r.width() || !r.height(); } static QString lineProlog(int nestingDepth, int lineno) { QString numbering(QString::number(lineno).rightJustified(5).append(QChar::fromAscii(':'))); QString indent(nestingDepth * 4, QLatin1Char(' ')); return numbering + indent; } static void dumpLayoutTreeRecurse(QLayout *l, int *counter, int depth) { const QLatin1String colorOn(isZeroArea(l->geometry()) ? "\033[0m" : "\033[32m"); const QLatin1String colorOff("\033[0m"); QString prolog = lineProlog(depth, *counter); (*counter)++; qDebug() << colorOn + prolog << l->metaObject()->className() << l->geometry() << "hint" << l->sizeHint() << l->hasHeightForWidth() << "min" << l->minimumSize() << "max" << l->maximumSize() << l->expandingDirections() << l->alignment() << colorOff; for (int i = 0; i < l->count(); i++) { QLayoutItem *child = l->itemAt(i); if (QLayout *childL = child->layout()) { dumpLayoutTreeRecurse(childL, counter, depth + 1); } else { // The isZeroArea check culls usually largely useless output - you might want to remove it in // some debugging situations. Add a boolean parameter to this and dumpLayoutTree() if you do. if (!isZeroArea(child->geometry())) { prolog = lineProlog(depth + 1, *counter); (*counter)++; qDebug() << colorOn + prolog << typeid(*child).name() << child->geometry() << "hint" << child->sizeHint() << child->hasHeightForWidth() << "min" << child->minimumSize() << "max" << child->maximumSize() << child->expandingDirections() << child->alignment() << colorOff; } } } } static void dumpLayoutTree(QLayout *l) { int counter = 0; dumpLayoutTreeRecurse(l, &counter, 0); } #endif static const Qt::Alignment s_gridAlignments[ 3 ][ 3 ] = { // [ row ][ column ] { Qt::AlignTop | Qt::AlignLeft, Qt::AlignTop | Qt::AlignHCenter, Qt::AlignTop | Qt::AlignRight }, { Qt::AlignVCenter | Qt::AlignLeft, Qt::AlignVCenter | Qt::AlignHCenter, Qt::AlignVCenter | Qt::AlignRight }, { Qt::AlignBottom | Qt::AlignLeft, Qt::AlignBottom | Qt::AlignHCenter, Qt::AlignBottom | Qt::AlignRight } }; static void getRowAndColumnForPosition(KChartEnums::PositionValue pos, int* row, int* column) { switch ( pos ) { case KChartEnums::PositionNorthWest: *row = 0; *column = 0; break; case KChartEnums::PositionNorth: *row = 0; *column = 1; break; case KChartEnums::PositionNorthEast: *row = 0; *column = 2; break; case KChartEnums::PositionEast: *row = 1; *column = 2; break; case KChartEnums::PositionSouthEast: *row = 2; *column = 2; break; case KChartEnums::PositionSouth: *row = 2; *column = 1; break; case KChartEnums::PositionSouthWest: *row = 2; *column = 0; break; case KChartEnums::PositionWest: *row = 1; *column = 0; break; case KChartEnums::PositionCenter: *row = 1; *column = 1; break; default: *row = -1; *column = -1; break; } } using namespace KChart; // Layout widgets even if they are not visible (that's why isEmpty() is overridden) - at least that // was the original reason... class MyWidgetItem : public QWidgetItem { public: explicit MyWidgetItem(QWidget *w, Qt::Alignment alignment = 0) : QWidgetItem( w ) { setAlignment( alignment ); } // All of the methods are reimplemented from QWidgetItem, and work around some oddity in QLayout and / or // KD Chart - I forgot the details between writing this code as an experiment and committing it, very // sorry about that! // Feel free to comment out any of them and then try the line-breaking feature in horizontal legends in // the Legends/Advanced example. It will not work well in various ways - won't get enough space and look // very broken, will inhibit resizing the window etc. QSize sizeHint() const Q_DECL_OVERRIDE { QWidget* w = const_cast< MyWidgetItem * >( this )->widget(); return w->sizeHint(); } QSize minimumSize() const Q_DECL_OVERRIDE { QWidget* w = const_cast< MyWidgetItem * >( this )->widget(); return w->minimumSize(); } QSize maximumSize() const Q_DECL_OVERRIDE { // Not just passing on w->maximumSize() fixes that the size policy of Legend is disregarded, making // Legend take all available space, which makes both the Legend internal layout and the overall // layout of chart + legend look bad. QWidget::maximumSize() is not a virtual method, it's a // property, so "overriding" that one would be even uglier, and prevent user-set property // values from doing anything. QWidget* w = const_cast< MyWidgetItem * >( this )->widget(); QSize ret = w->maximumSize(); const QSize hint = w->sizeHint(); const QSizePolicy::Policy hPolicy = w->sizePolicy().horizontalPolicy(); if (hPolicy == QSizePolicy::Fixed || hPolicy == QSizePolicy::Maximum) { ret.rwidth() = hint.width(); } const QSizePolicy::Policy vPolicy = w->sizePolicy().verticalPolicy(); if (vPolicy == QSizePolicy::Fixed || vPolicy == QSizePolicy::Maximum) { ret.rheight() = hint.height(); } return ret; } Qt::Orientations expandingDirections() const Q_DECL_OVERRIDE { QWidget* w = const_cast< MyWidgetItem * >( this )->widget(); if ( isEmpty() ) { return Qt::Orientations(0); } Qt::Orientations e = w->sizePolicy().expandingDirections(); return e; } void setGeometry(const QRect &g) Q_DECL_OVERRIDE { QWidget* w = const_cast< MyWidgetItem * >( this )->widget(); w->setGeometry(g); } QRect geometry() const Q_DECL_OVERRIDE { QWidget* w = const_cast< MyWidgetItem * >( this )->widget(); return w->geometry(); } bool hasHeightForWidth() const Q_DECL_OVERRIDE { QWidget* w = const_cast< MyWidgetItem * >( this )->widget(); bool ret = !isEmpty() && qobject_cast< Legend* >( w )->hasHeightForWidth(); return ret; } int heightForWidth( int width ) const Q_DECL_OVERRIDE { QWidget* w = const_cast< MyWidgetItem * >( this )->widget(); int ret = w->heightForWidth( width ); return ret; } bool isEmpty() const Q_DECL_OVERRIDE { QWidget* w = const_cast< MyWidgetItem * >( this )->widget(); // legend->hide() should indeed hide the legend, // but a legend in a chart that hasn't been shown yet isn't hidden // (as can happen when using Chart::paint() without showing the chart) return w->isHidden() && w->testAttribute( Qt::WA_WState_ExplicitShowHide ); } }; // When "abusing" QLayouts to lay out items with different geometry from the backing QWidgets, // some manual work is required to correctly update all the sublayouts. // This is because all the convenient ways to deal with QLayouts assume QWidgets somewhere. // What this does is somewhat similar to QLayout::activate(), but it never refers to the parent // QWidget which has the wrong geometry. static void invalidateLayoutTree( QLayoutItem *item ) { QLayout *layout = item->layout(); if ( layout ) { const int count = layout->count(); for ( int i = 0; i < count; i++ ) { invalidateLayoutTree( layout->itemAt( i ) ); } } item->invalidate(); } void Chart::Private::slotUnregisterDestroyedLegend( Legend *l ) { chart->takeLegend( l ); } void Chart::Private::slotUnregisterDestroyedHeaderFooter( HeaderFooter* hf ) { chart->takeHeaderFooter( hf ); } void Chart::Private::slotUnregisterDestroyedPlane( AbstractCoordinatePlane* plane ) { coordinatePlanes.removeAll( plane ); Q_FOREACH ( AbstractCoordinatePlane* p, coordinatePlanes ) { if ( p->referenceCoordinatePlane() == plane) { p->setReferenceCoordinatePlane( 0 ); } } plane->layoutPlanes(); } Chart::Private::Private( Chart* chart_ ) : chart( chart_ ) , useNewLayoutSystem( false ) , layout( 0 ) , vLayout( 0 ) , planesLayout( 0 ) , headerLayout( 0 ) , footerLayout( 0 ) , dataAndLegendLayout( 0 ) , leftOuterSpacer( 0 ) , rightOuterSpacer( 0 ) , topOuterSpacer( 0 ) , bottomOuterSpacer( 0 ) , isFloatingLegendsLayoutDirty( true ) , isPlanesLayoutDirty( true ) , globalLeadingLeft( 0 ) , globalLeadingRight( 0 ) , globalLeadingTop( 0 ) , globalLeadingBottom( 0 ) { for ( int row = 0; row < 3; ++row ) { for ( int column = 0; column < 3; ++column ) { for ( int i = 0; i < 2; i++ ) { innerHdFtLayouts[ i ][ row ][ column ] = 0; } } } } Chart::Private::~Private() { } enum VisitorState{ Visited, Unknown }; struct ConnectedComponentsComparator{ bool operator()( const LayoutGraphNode *lhs, const LayoutGraphNode *rhs ) const { return lhs->priority < rhs->priority; } }; static QVector< LayoutGraphNode* > getPrioritySortedConnectedComponents( QVector< LayoutGraphNode* > &nodeList ) { QVector< LayoutGraphNode* >connectedComponents; QHash< LayoutGraphNode*, VisitorState > visitedComponents; Q_FOREACH ( LayoutGraphNode* node, nodeList ) visitedComponents[ node ] = Unknown; for ( int i = 0; i < nodeList.size(); ++i ) { LayoutGraphNode *curNode = nodeList[ i ]; LayoutGraphNode *representativeNode = curNode; if ( visitedComponents[ curNode ] != Visited ) { QStack< LayoutGraphNode* > stack; stack.push( curNode ); while ( !stack.isEmpty() ) { curNode = stack.pop(); Q_ASSERT( visitedComponents[ curNode ] != Visited ); visitedComponents[ curNode ] = Visited; if ( curNode->bottomSuccesor && visitedComponents[ curNode->bottomSuccesor ] != Visited ) stack.push( curNode->bottomSuccesor ); if ( curNode->leftSuccesor && visitedComponents[ curNode->leftSuccesor ] != Visited ) stack.push( curNode->leftSuccesor ); if ( curNode->sharedSuccesor && visitedComponents[ curNode->sharedSuccesor ] != Visited ) stack.push( curNode->sharedSuccesor ); if ( curNode->priority < representativeNode->priority ) representativeNode = curNode; } connectedComponents.append( representativeNode ); } } std::sort( connectedComponents.begin(), connectedComponents.end(), ConnectedComponentsComparator() ); return connectedComponents; } struct PriorityComparator{ public: PriorityComparator( QHash< AbstractCoordinatePlane*, LayoutGraphNode* > mapping ) : m_mapping( mapping ) {} bool operator() ( AbstractCoordinatePlane *lhs, AbstractCoordinatePlane *rhs ) const { const LayoutGraphNode *lhsNode = m_mapping[ lhs ]; Q_ASSERT( lhsNode ); const LayoutGraphNode *rhsNode = m_mapping[ rhs ]; Q_ASSERT( rhsNode ); return lhsNode->priority < rhsNode->priority; } const QHash< AbstractCoordinatePlane*, LayoutGraphNode* > m_mapping; }; void checkExistingAxes( LayoutGraphNode* node ) { if ( node && node->diagramPlane && node->diagramPlane->diagram() ) { AbstractCartesianDiagram *diag = qobject_cast< AbstractCartesianDiagram* >( node->diagramPlane->diagram() ); if ( diag ) { Q_FOREACH( const CartesianAxis* axis, diag->axes() ) { switch ( axis->position() ) { case( CartesianAxis::Top ): node->topAxesLayout = true; break; case( CartesianAxis::Bottom ): node->bottomAxesLayout = true; break; case( CartesianAxis::Left ): node->leftAxesLayout = true; break; case( CartesianAxis::Right ): node->rightAxesLayout = true; break; } } } } } static void mergeNodeAxisInformation( LayoutGraphNode* lhs, LayoutGraphNode* rhs ) { lhs->topAxesLayout |= rhs->topAxesLayout; rhs->topAxesLayout = lhs->topAxesLayout; lhs->bottomAxesLayout |= rhs->bottomAxesLayout; rhs->bottomAxesLayout = lhs->bottomAxesLayout; lhs->leftAxesLayout |= rhs->leftAxesLayout; rhs->leftAxesLayout = lhs->leftAxesLayout; lhs->rightAxesLayout |= rhs->rightAxesLayout; rhs->rightAxesLayout = lhs->rightAxesLayout; } static CoordinatePlaneList findSharingAxisDiagrams( AbstractCoordinatePlane* plane, const CoordinatePlaneList& list, Chart::Private::AxisType type, QVector< CartesianAxis* >* sharedAxes ) { if ( !plane || !plane->diagram() ) return CoordinatePlaneList(); Q_ASSERT( plane ); Q_ASSERT( plane->diagram() ); CoordinatePlaneList result; AbstractCartesianDiagram* diagram = qobject_cast< AbstractCartesianDiagram* >( plane->diagram() ); if ( !diagram ) return CoordinatePlaneList(); QList< CartesianAxis* > axes; Q_FOREACH( CartesianAxis* axis, diagram->axes() ) { if ( ( type == Chart::Private::Ordinate && ( axis->position() == CartesianAxis::Left || axis->position() == CartesianAxis::Right ) ) || ( type == Chart::Private::Abscissa && ( axis->position() == CartesianAxis::Top || axis->position() == CartesianAxis::Bottom ) ) ) { axes.append( axis ); } } Q_FOREACH( AbstractCoordinatePlane *curPlane, list ) { AbstractCartesianDiagram* diagram = qobject_cast< AbstractCartesianDiagram* > ( curPlane->diagram() ); if ( !diagram ) continue; Q_FOREACH( CartesianAxis* curSearchedAxis, axes ) { Q_FOREACH( CartesianAxis* curAxis, diagram->axes() ) { if ( curSearchedAxis == curAxis ) { result.append( curPlane ); if ( !sharedAxes->contains( curSearchedAxis ) ) sharedAxes->append( curSearchedAxis ); } } } } return result; } /** * this method determines the needed layout of the graph * taking care of the sharing problematic * its NOT allowed to have a diagram that shares * more than one axis in the same direction */ QVector< LayoutGraphNode* > Chart::Private::buildPlaneLayoutGraph() { QHash< AbstractCoordinatePlane*, LayoutGraphNode* > planeNodeMapping; QVector< LayoutGraphNode* > allNodes; // create all nodes and a mapping between plane and nodes Q_FOREACH( AbstractCoordinatePlane* curPlane, coordinatePlanes ) { if ( curPlane->diagram() ) { allNodes.append( new LayoutGraphNode ); allNodes[ allNodes.size() - 1 ]->diagramPlane = curPlane; allNodes[ allNodes.size() - 1 ]->priority = allNodes.size(); checkExistingAxes( allNodes[ allNodes.size() - 1 ] ); planeNodeMapping[ curPlane ] = allNodes[ allNodes.size() - 1 ]; } } // build the graph connections Q_FOREACH( LayoutGraphNode* curNode, allNodes ) { QVector< CartesianAxis* > sharedAxes; CoordinatePlaneList xSharedPlanes = findSharingAxisDiagrams( curNode->diagramPlane, coordinatePlanes, Abscissa, &sharedAxes ); Q_ASSERT( sharedAxes.size() < 2 ); // TODO duplicated code make a method out of it if ( sharedAxes.size() == 1 && xSharedPlanes.size() > 1 ) { //xSharedPlanes.removeAll( sharedAxes.first()->diagram()->coordinatePlane() ); //std::sort( xSharedPlanes.begin(), xSharedPlanes.end(), PriorityComparator( planeNodeMapping ) ); for ( int i = 0; i < xSharedPlanes.size() - 1; ++i ) { LayoutGraphNode *tmpNode = planeNodeMapping[ xSharedPlanes[ i ] ]; Q_ASSERT( tmpNode ); LayoutGraphNode *tmpNode2 = planeNodeMapping[ xSharedPlanes[ i + 1 ] ]; Q_ASSERT( tmpNode2 ); tmpNode->bottomSuccesor = tmpNode2; } // if ( sharedAxes.first()->diagram() && sharedAxes.first()->diagram()->coordinatePlane() ) // { // LayoutGraphNode *lastNode = planeNodeMapping[ xSharedPlanes.last() ]; // Q_ASSERT( lastNode ); // Q_ASSERT( sharedAxes.first()->diagram()->coordinatePlane() ); // LayoutGraphNode *ownerNode = planeNodeMapping[ sharedAxes.first()->diagram()->coordinatePlane() ]; // Q_ASSERT( ownerNode ); // lastNode->bottomSuccesor = ownerNode; // } //merge AxisInformation, needs a two pass run LayoutGraphNode axisInfoNode; for ( int count = 0; count < 2; ++count ) { for ( int i = 0; i < xSharedPlanes.size(); ++i ) { mergeNodeAxisInformation( &axisInfoNode, planeNodeMapping[ xSharedPlanes[ i ] ] ); } } } sharedAxes.clear(); CoordinatePlaneList ySharedPlanes = findSharingAxisDiagrams( curNode->diagramPlane, coordinatePlanes, Ordinate, &sharedAxes ); Q_ASSERT( sharedAxes.size() < 2 ); if ( sharedAxes.size() == 1 && ySharedPlanes.size() > 1 ) { //ySharedPlanes.removeAll( sharedAxes.first()->diagram()->coordinatePlane() ); //std::sort( ySharedPlanes.begin(), ySharedPlanes.end(), PriorityComparator( planeNodeMapping ) ); for ( int i = 0; i < ySharedPlanes.size() - 1; ++i ) { LayoutGraphNode *tmpNode = planeNodeMapping[ ySharedPlanes[ i ] ]; Q_ASSERT( tmpNode ); LayoutGraphNode *tmpNode2 = planeNodeMapping[ ySharedPlanes[ i + 1 ] ]; Q_ASSERT( tmpNode2 ); tmpNode->leftSuccesor = tmpNode2; } // if ( sharedAxes.first()->diagram() && sharedAxes.first()->diagram()->coordinatePlane() ) // { // LayoutGraphNode *lastNode = planeNodeMapping[ ySharedPlanes.last() ]; // Q_ASSERT( lastNode ); // Q_ASSERT( sharedAxes.first()->diagram()->coordinatePlane() ); // LayoutGraphNode *ownerNode = planeNodeMapping[ sharedAxes.first()->diagram()->coordinatePlane() ]; // Q_ASSERT( ownerNode ); // lastNode->bottomSuccesor = ownerNode; // } //merge AxisInformation, needs a two pass run LayoutGraphNode axisInfoNode; for ( int count = 0; count < 2; ++count ) { for ( int i = 0; i < ySharedPlanes.size(); ++i ) { mergeNodeAxisInformation( &axisInfoNode, planeNodeMapping[ ySharedPlanes[ i ] ] ); } } } sharedAxes.clear(); if ( curNode->diagramPlane->referenceCoordinatePlane() ) curNode->sharedSuccesor = planeNodeMapping[ curNode->diagramPlane->referenceCoordinatePlane() ]; } return allNodes; } QHash Chart::Private::buildPlaneLayoutInfos() { /* There are two ways in which planes can be caused to interact in * where they are put layouting wise: The first is the reference plane. If * such a reference plane is set, on a plane, it will use the same cell in the * layout as that one. In addition to this, planes can share an axis. In that case * they will be laid out in relation to each other as suggested by the position * of the axis. If, for example Plane1 and Plane2 share an axis at position Left, * that will result in the layout: Axis Plane1 Plane 2, vertically. If Plane1 * also happens to be Plane2's referece plane, both planes are drawn over each * other. The reference plane concept allows two planes to share the same space * even if neither has any axis, and in case there are shared axis, it is used * to decided, whether the planes should be painted on top of each other or * laid out vertically or horizontally next to each other. */ QHash axisInfos; QHash planeInfos; Q_FOREACH(AbstractCoordinatePlane* plane, coordinatePlanes ) { PlaneInfo p; // first check if we share space with another plane p.referencePlane = plane->referenceCoordinatePlane(); planeInfos.insert( plane, p ); Q_FOREACH( AbstractDiagram* abstractDiagram, plane->diagrams() ) { AbstractCartesianDiagram* diagram = qobject_cast ( abstractDiagram ); if ( !diagram ) { continue; } Q_FOREACH( CartesianAxis* axis, diagram->axes() ) { if ( !axisInfos.contains( axis ) ) { /* If this is the first time we see this axis, add it, with the * current plane. The first plane added to the chart that has * the axis associated with it thus "owns" it, and decides about * layout. */ AxisInfo i; i.plane = plane; axisInfos.insert( axis, i ); } else { AxisInfo i = axisInfos[axis]; if ( i.plane == plane ) { continue; // we don't want duplicates, only shared } /* The user expects diagrams to be added on top, and to the right * so that horizontally we need to move the new diagram, vertically * the reference one. */ PlaneInfo pi = planeInfos[plane]; // plane-to-plane linking overrides linking via axes if ( !pi.referencePlane ) { // we're not the first plane to see this axis, mark us as a slave pi.referencePlane = i.plane; if ( axis->position() == CartesianAxis::Left || axis->position() == CartesianAxis::Right ) { pi.horizontalOffset += 1; } planeInfos[plane] = pi; pi = planeInfos[i.plane]; if ( axis->position() == CartesianAxis::Top || axis->position() == CartesianAxis::Bottom ) { pi.verticalOffset += 1; } planeInfos[i.plane] = pi; } } } } // Create a new grid layout for each plane that has no reference. p = planeInfos[plane]; if ( p.referencePlane == 0 ) { p.gridLayout = new QGridLayout(); p.gridLayout->setMargin( 0 ); planeInfos[plane] = p; } } return planeInfos; } void Chart::Private::slotLayoutPlanes() { /*TODO make sure this is really needed */ const QBoxLayout::Direction oldPlanesDirection = planesLayout ? planesLayout->direction() : QBoxLayout::TopToBottom; if ( planesLayout && dataAndLegendLayout ) dataAndLegendLayout->removeItem( planesLayout ); const bool hadPlanesLayout = planesLayout != 0; int left, top, right, bottom; if ( hadPlanesLayout ) planesLayout->getContentsMargins(&left, &top, &right, &bottom); Q_FOREACH( AbstractLayoutItem* plane, planeLayoutItems ) { plane->removeFromParentLayout(); } //TODO they should get a correct parent, but for now it works Q_FOREACH( AbstractLayoutItem* plane, planeLayoutItems ) { if ( dynamic_cast< AutoSpacerLayoutItem* >( plane ) ) delete plane; } planeLayoutItems.clear(); delete planesLayout; //hint: The direction is configurable by the user now, as // we are using a QBoxLayout rather than a QVBoxLayout. (khz, 2007/04/25) planesLayout = new QBoxLayout( oldPlanesDirection ); isPlanesLayoutDirty = true; // here we create the layouts; we need to "run" them before painting if ( useNewLayoutSystem ) { gridPlaneLayout = new QGridLayout; planesLayout->addLayout( gridPlaneLayout ); if (hadPlanesLayout) planesLayout->setContentsMargins(left, top, right, bottom); planesLayout->setObjectName( QString::fromLatin1( "planesLayout" ) ); /* First go through all planes and all axes and figure out whether the planes * need to coordinate. If they do, they share a grid layout, if not, each * get their own. See buildPlaneLayoutInfos() for more details. */ QVector< LayoutGraphNode* > vals = buildPlaneLayoutGraph(); //qDebug() << Q_FUNC_INFO << "GraphNodes" << vals.size(); QVector< LayoutGraphNode* > connectedComponents = getPrioritySortedConnectedComponents( vals ); //qDebug() << Q_FUNC_INFO << "SubGraphs" << connectedComponents.size(); int row = 0; int col = 0; QSet< CartesianAxis* > laidOutAxes; for ( int i = 0; i < connectedComponents.size(); ++i ) { LayoutGraphNode *curComponent = connectedComponents[ i ]; for ( LayoutGraphNode *curRowComponent = curComponent; curRowComponent; curRowComponent = curRowComponent->bottomSuccesor ) { col = 0; for ( LayoutGraphNode *curColComponent = curRowComponent; curColComponent; curColComponent = curColComponent->leftSuccesor ) { Q_ASSERT( curColComponent->diagramPlane->diagrams().size() == 1 ); Q_FOREACH( AbstractDiagram* diagram, curColComponent->diagramPlane->diagrams() ) { const int planeRowOffset = 1;//curColComponent->topAxesLayout ? 1 : 0; const int planeColOffset = 1;//curColComponent->leftAxesLayout ? 1 : 0; //qDebug() << Q_FUNC_INFO << row << col << planeRowOffset << planeColOffset; //qDebug() << Q_FUNC_INFO << row + planeRowOffset << col + planeColOffset; planeLayoutItems << curColComponent->diagramPlane; AbstractCartesianDiagram *cartDiag = qobject_cast< AbstractCartesianDiagram* >( diagram ); if ( cartDiag ) { gridPlaneLayout->addItem( curColComponent->diagramPlane, row + planeRowOffset, col + planeColOffset, 2, 2 ); curColComponent->diagramPlane->setParentLayout( gridPlaneLayout ); QHBoxLayout *leftLayout = 0; QHBoxLayout *rightLayout = 0; QVBoxLayout *topLayout = 0; QVBoxLayout *bottomLayout = 0; if ( curComponent->sharedSuccesor ) { gridPlaneLayout->addItem( curColComponent->sharedSuccesor->diagramPlane, row + planeRowOffset, col + planeColOffset, 2, 2 ); curColComponent->sharedSuccesor->diagramPlane->setParentLayout( gridPlaneLayout ); planeLayoutItems << curColComponent->sharedSuccesor->diagramPlane; } Q_FOREACH( CartesianAxis* axis, cartDiag->axes() ) { if ( axis->isAbscissa() ) { if ( curColComponent->bottomSuccesor ) continue; } if ( laidOutAxes.contains( axis ) ) continue; // if ( axis->diagram() != diagram ) // continue; switch ( axis->position() ) { case( CartesianAxis::Top ): if ( !topLayout ) topLayout = new QVBoxLayout; topLayout->addItem( axis ); axis->setParentLayout( topLayout ); break; case( CartesianAxis::Bottom ): if ( !bottomLayout ) bottomLayout = new QVBoxLayout; bottomLayout->addItem( axis ); axis->setParentLayout( bottomLayout ); break; case( CartesianAxis::Left ): if ( !leftLayout ) leftLayout = new QHBoxLayout; leftLayout->addItem( axis ); axis->setParentLayout( leftLayout ); break; case( CartesianAxis::Right ): if ( !rightLayout ) { rightLayout = new QHBoxLayout; } rightLayout->addItem( axis ); axis->setParentLayout( rightLayout ); break; } planeLayoutItems << axis; laidOutAxes.insert( axis ); } if ( leftLayout ) gridPlaneLayout->addLayout( leftLayout, row + planeRowOffset, col, 2, 1, Qt::AlignRight | Qt::AlignVCenter ); if ( rightLayout ) gridPlaneLayout->addLayout( rightLayout, row, col + planeColOffset + 2, 2, 1, Qt::AlignLeft | Qt::AlignVCenter ); if ( topLayout ) gridPlaneLayout->addLayout( topLayout, row, col + planeColOffset, 1, 2, Qt::AlignBottom | Qt::AlignHCenter ); if ( bottomLayout ) gridPlaneLayout->addLayout( bottomLayout, row + planeRowOffset + 2, col + planeColOffset, 1, 2, Qt::AlignTop | Qt::AlignHCenter ); } else { gridPlaneLayout->addItem( curColComponent->diagramPlane, row, col, 4, 4 ); curColComponent->diagramPlane->setParentLayout( gridPlaneLayout ); } col += planeColOffset + 2 + ( 1 ); } } int axisOffset = 2;//curRowComponent->topAxesLayout ? 1 : 0; //axisOffset += curRowComponent->bottomAxesLayout ? 1 : 0; const int rowOffset = axisOffset + 2; row += rowOffset; } // if ( planesLayout->direction() == QBoxLayout::TopToBottom ) // ++row; // else // ++col; } qDeleteAll( vals ); // re-add our grid(s) to the chart's layout if ( dataAndLegendLayout ) { dataAndLegendLayout->addLayout( planesLayout, 1, 1 ); dataAndLegendLayout->setRowStretch( 1, 1000 ); dataAndLegendLayout->setColumnStretch( 1, 1000 ); } slotResizePlanes(); #ifdef NEW_LAYOUT_DEBUG for ( int i = 0; i < gridPlaneLayout->rowCount(); ++i ) { for ( int j = 0; j < gridPlaneLayout->columnCount(); ++j ) { if ( gridPlaneLayout->itemAtPosition( i, j ) ) qDebug() << Q_FUNC_INFO << "item at" << i << j << gridPlaneLayout->itemAtPosition( i, j )->geometry(); else qDebug() << Q_FUNC_INFO << "item at" << i << j << "no item present"; } } //qDebug() << Q_FUNC_INFO << "Relayout ended"; #endif } else { if ( hadPlanesLayout ) { planesLayout->setContentsMargins( left, top, right, bottom ); } planesLayout->setMargin( 0 ); planesLayout->setSpacing( 0 ); planesLayout->setObjectName( QString::fromLatin1( "planesLayout" ) ); /* First go through all planes and all axes and figure out whether the planes * need to coordinate. If they do, they share a grid layout, if not, each * gets their own. See buildPlaneLayoutInfos() for more details. */ QHash planeInfos = buildPlaneLayoutInfos(); QHash axisInfos; Q_FOREACH( AbstractCoordinatePlane* plane, coordinatePlanes ) { Q_ASSERT( planeInfos.contains(plane) ); PlaneInfo& pi = planeInfos[ plane ]; const int column = pi.horizontalOffset; const int row = pi.verticalOffset; //qDebug() << "processing plane at column" << column << "and row" << row; QGridLayout *planeLayout = pi.gridLayout; if ( !planeLayout ) { PlaneInfo& refPi = pi; // if this plane is sharing an axis with another one, recursively check for the original plane and use // the grid of that as planeLayout. while ( !planeLayout && refPi.referencePlane ) { refPi = planeInfos[refPi.referencePlane]; planeLayout = refPi.gridLayout; } Q_ASSERT_X( planeLayout, "Chart::Private::slotLayoutPlanes()", "Invalid reference plane. Please check that the reference plane has been added to the Chart." ); } else { planesLayout->addLayout( planeLayout ); } /* Put the plane in the center of the layout. If this is our own, that's * the middle of the layout, if we are sharing, it's a cell in the center * column of the shared grid. */ planeLayoutItems << plane; plane->setParentLayout( planeLayout ); planeLayout->addItem( plane, row, column, 1, 1, 0 ); //qDebug() << "Chart slotLayoutPlanes() calls planeLayout->addItem("<< row << column << ")"; planeLayout->setRowStretch( row, 2 ); planeLayout->setColumnStretch( column, 2 ); Q_FOREACH( AbstractDiagram* abstractDiagram, plane->diagrams() ) { AbstractCartesianDiagram* diagram = qobject_cast< AbstractCartesianDiagram* >( abstractDiagram ); if ( !diagram ) { continue; // FIXME what about polar ? } if ( pi.referencePlane != 0 ) { pi.topAxesLayout = planeInfos[ pi.referencePlane ].topAxesLayout; pi.bottomAxesLayout = planeInfos[ pi.referencePlane ].bottomAxesLayout; pi.leftAxesLayout = planeInfos[ pi.referencePlane ].leftAxesLayout; pi.rightAxesLayout = planeInfos[ pi.referencePlane ].rightAxesLayout; } // collect all axes of a kind into sublayouts if ( pi.topAxesLayout == 0 ) { pi.topAxesLayout = new QVBoxLayout; pi.topAxesLayout->setMargin( 0 ); pi.topAxesLayout->setObjectName( QString::fromLatin1( "topAxesLayout" ) ); } if ( pi.bottomAxesLayout == 0 ) { pi.bottomAxesLayout = new QVBoxLayout; pi.bottomAxesLayout->setMargin( 0 ); pi.bottomAxesLayout->setObjectName( QString::fromLatin1( "bottomAxesLayout" ) ); } if ( pi.leftAxesLayout == 0 ) { pi.leftAxesLayout = new QHBoxLayout; pi.leftAxesLayout->setMargin( 0 ); pi.leftAxesLayout->setObjectName( QString::fromLatin1( "leftAxesLayout" ) ); } if ( pi.rightAxesLayout == 0 ) { pi.rightAxesLayout = new QHBoxLayout; pi.rightAxesLayout->setMargin( 0 ); pi.rightAxesLayout->setObjectName( QString::fromLatin1( "rightAxesLayout" ) ); } if ( pi.referencePlane != 0 ) { planeInfos[ pi.referencePlane ].topAxesLayout = pi.topAxesLayout; planeInfos[ pi.referencePlane ].bottomAxesLayout = pi.bottomAxesLayout; planeInfos[ pi.referencePlane ].leftAxesLayout = pi.leftAxesLayout; planeInfos[ pi.referencePlane ].rightAxesLayout = pi.rightAxesLayout; } //pi.leftAxesLayout->setSizeConstraint( QLayout::SetFixedSize ); Q_FOREACH( CartesianAxis* axis, diagram->axes() ) { if ( axisInfos.contains( axis ) ) { continue; // already laid out this one } Q_ASSERT ( axis ); axis->setCachedSizeDirty(); //qDebug() << "--------------- axis added to planeLayoutItems -----------------"; planeLayoutItems << axis; switch ( axis->position() ) { case CartesianAxis::Top: axis->setParentLayout( pi.topAxesLayout ); pi.topAxesLayout->addItem( axis ); break; case CartesianAxis::Bottom: axis->setParentLayout( pi.bottomAxesLayout ); pi.bottomAxesLayout->addItem( axis ); break; case CartesianAxis::Left: axis->setParentLayout( pi.leftAxesLayout ); pi.leftAxesLayout->addItem( axis ); break; case CartesianAxis::Right: axis->setParentLayout( pi.rightAxesLayout ); pi.rightAxesLayout->addItem( axis ); break; default: Q_ASSERT_X( false, "Chart::paintEvent", "unknown axis position" ); break; }; axisInfos.insert( axis, AxisInfo() ); } /* Put each stack of axes-layouts in the cells surrounding the * associated plane. We are laying out in the oder the planes * were added, and the first one gets to lay out shared axes. * Private axes go here as well, of course. */ if ( !pi.topAxesLayout->parent() ) { planeLayout->addLayout( pi.topAxesLayout, row - 1, column ); } if ( !pi.bottomAxesLayout->parent() ) { planeLayout->addLayout( pi.bottomAxesLayout, row + 1, column ); } if ( !pi.leftAxesLayout->parent() ) { planeLayout->addLayout( pi.leftAxesLayout, row, column - 1 ); } if ( !pi.rightAxesLayout->parent() ) { planeLayout->addLayout( pi.rightAxesLayout,row, column + 1 ); } } // use up to four auto-spacer items in the corners around the diagrams: #define ADD_AUTO_SPACER_IF_NEEDED( \ spacerRow, spacerColumn, hLayoutIsAtTop, hLayout, vLayoutIsAtLeft, vLayout ) \ { \ if ( hLayout || vLayout ) { \ AutoSpacerLayoutItem * spacer \ = new AutoSpacerLayoutItem( hLayoutIsAtTop, hLayout, vLayoutIsAtLeft, vLayout ); \ planeLayout->addItem( spacer, spacerRow, spacerColumn, 1, 1 ); \ spacer->setParentLayout( planeLayout ); \ planeLayoutItems << spacer; \ } \ } if ( plane->isCornerSpacersEnabled() ) { ADD_AUTO_SPACER_IF_NEEDED( row - 1, column - 1, false, pi.leftAxesLayout, false, pi.topAxesLayout ) ADD_AUTO_SPACER_IF_NEEDED( row + 1, column - 1, true, pi.leftAxesLayout, false, pi.bottomAxesLayout ) ADD_AUTO_SPACER_IF_NEEDED( row - 1, column + 1, false, pi.rightAxesLayout, true, pi.topAxesLayout ) ADD_AUTO_SPACER_IF_NEEDED( row + 1, column + 1, true, pi.rightAxesLayout, true, pi.bottomAxesLayout ) } } // re-add our grid(s) to the chart's layout if ( dataAndLegendLayout ) { dataAndLegendLayout->addLayout( planesLayout, 1, 1 ); dataAndLegendLayout->setRowStretch( 1, 1000 ); dataAndLegendLayout->setColumnStretch( 1, 1000 ); } slotResizePlanes(); } } void Chart::Private::createLayouts() { // The toplevel layout provides the left and right global margins layout = new QHBoxLayout( chart ); layout->setMargin( 0 ); layout->setObjectName( QString::fromLatin1( "Chart::Private::layout" ) ); layout->addSpacing( globalLeadingLeft ); leftOuterSpacer = layout->itemAt( layout->count() - 1 )->spacerItem(); // The vLayout provides top and bottom global margins and lays // out headers, footers and the diagram area. vLayout = new QVBoxLayout(); vLayout->setMargin( 0 ); vLayout->setObjectName( QString::fromLatin1( "vLayout" ) ); layout->addLayout( vLayout, 1000 ); layout->addSpacing( globalLeadingRight ); rightOuterSpacer = layout->itemAt( layout->count() - 1 )->spacerItem(); // 1. the spacing above the header area vLayout->addSpacing( globalLeadingTop ); topOuterSpacer = vLayout->itemAt( vLayout->count() - 1 )->spacerItem(); // 2. the header area headerLayout = new QGridLayout(); headerLayout->setMargin( 0 ); vLayout->addLayout( headerLayout ); // 3. the area containing coordinate planes, axes, and legends dataAndLegendLayout = new QGridLayout(); dataAndLegendLayout->setMargin( 0 ); dataAndLegendLayout->setObjectName( QString::fromLatin1( "dataAndLegendLayout" ) ); vLayout->addLayout( dataAndLegendLayout, 1000 ); // 4. the footer area footerLayout = new QGridLayout(); footerLayout->setMargin( 0 ); footerLayout->setObjectName( QString::fromLatin1( "footerLayout" ) ); vLayout->addLayout( footerLayout ); // 5. Prepare the header / footer layout cells: // Each of the 9 header cells (the 9 footer cells) // contain their own QVBoxLayout // since there can be more than one header (footer) per cell. for ( int row = 0; row < 3; ++row ) { for ( int column = 0; column < 3; ++ column ) { const Qt::Alignment align = s_gridAlignments[ row ][ column ]; for ( int headOrFoot = 0; headOrFoot < 2; headOrFoot++ ) { QVBoxLayout* innerLayout = new QVBoxLayout(); innerLayout->setMargin( 0 ); innerLayout->setAlignment( align ); innerHdFtLayouts[ headOrFoot ][ row ][ column ] = innerLayout; QGridLayout* outerLayout = headOrFoot == 0 ? headerLayout : footerLayout; outerLayout->addLayout( innerLayout, row, column, align ); } } } // 6. the spacing below the footer area vLayout->addSpacing( globalLeadingBottom ); bottomOuterSpacer = vLayout->itemAt( vLayout->count() - 1 )->spacerItem(); // the data+axes area dataAndLegendLayout->addLayout( planesLayout, 1, 1 ); dataAndLegendLayout->setRowStretch( 1, 1 ); dataAndLegendLayout->setColumnStretch( 1, 1 ); } void Chart::Private::slotResizePlanes() { if ( !dataAndLegendLayout ) { return; } if ( !overrideSize.isValid() ) { // activate() takes the size from the layout's parent QWidget, which is not updated when overrideSize // is set. So don't let the layout grab the wrong size in that case. // When overrideSize *is* set, we call layout->setGeometry() in paint( QPainter*, const QRect& ), // which also "activates" the layout in the sense that it distributes space internally. layout->activate(); } // Adapt diagram drawing to the new size Q_FOREACH (AbstractCoordinatePlane* plane, coordinatePlanes ) { plane->layoutDiagrams(); } } void Chart::Private::updateDirtyLayouts() { if ( isPlanesLayoutDirty ) { Q_FOREACH ( AbstractCoordinatePlane* p, coordinatePlanes ) { p->setGridNeedsRecalculate(); p->layoutPlanes(); p->layoutDiagrams(); } } if ( isPlanesLayoutDirty || isFloatingLegendsLayoutDirty ) { chart->reLayoutFloatingLegends(); } isPlanesLayoutDirty = false; isFloatingLegendsLayoutDirty = false; } void Chart::Private::reapplyInternalLayouts() { QRect geo = layout->geometry(); invalidateLayoutTree( layout ); layout->setGeometry( geo ); slotResizePlanes(); } void Chart::Private::paintAll( QPainter* painter ) { updateDirtyLayouts(); QRect rect( QPoint( 0, 0 ), overrideSize.isValid() ? overrideSize : chart->size() ); //qDebug() << this<<"::paintAll() uses layout size" << currentLayoutSize; // Paint the background (if any) AbstractAreaBase::paintBackgroundAttributes( *painter, rect, backgroundAttributes ); // Paint the frame (if any) AbstractAreaBase::paintFrameAttributes( *painter, rect, frameAttributes ); chart->reLayoutFloatingLegends(); Q_FOREACH( AbstractLayoutItem* planeLayoutItem, planeLayoutItems ) { planeLayoutItem->paintAll( *painter ); } Q_FOREACH( TextArea* textLayoutItem, textLayoutItems ) { textLayoutItem->paintAll( *painter ); } Q_FOREACH( Legend *legend, legends ) { const bool hidden = legend->isHidden() && legend->testAttribute( Qt::WA_WState_ExplicitShowHide ); if ( !hidden ) { //qDebug() << "painting legend at " << legend->geometry(); legend->paintIntoRect( *painter, legend->geometry() ); } } } // ******** Chart interface implementation *********** #define d d_func() Chart::Chart ( QWidget* parent ) : QWidget ( parent ) , _d( new Private( this ) ) { #if defined KDAB_EVAL EvalDialog::checkEvalLicense( "KD Chart" ); #endif FrameAttributes frameAttrs; // no frame per default... // frameAttrs.setVisible( true ); frameAttrs.setPen( QPen( Qt::black ) ); frameAttrs.setPadding( 1 ); setFrameAttributes( frameAttrs ); addCoordinatePlane( new CartesianCoordinatePlane ( this ) ); d->createLayouts(); } Chart::~Chart() { delete d; } void Chart::setFrameAttributes( const FrameAttributes &a ) { d->frameAttributes = a; } FrameAttributes Chart::frameAttributes() const { return d->frameAttributes; } void Chart::setBackgroundAttributes( const BackgroundAttributes &a ) { d->backgroundAttributes = a; } BackgroundAttributes Chart::backgroundAttributes() const { return d->backgroundAttributes; } //TODO KChart 3.0; change QLayout into QBoxLayout::Direction void Chart::setCoordinatePlaneLayout( QLayout * layout ) { if (layout == d->planesLayout) return; if (d->planesLayout) { // detach all QLayoutItem's the previous planesLayout has cause // otherwise deleting the planesLayout would delete them too. for(int i = d->planesLayout->count() - 1; i >= 0; --i) { d->planesLayout->takeAt(i); } delete d->planesLayout; } d->planesLayout = qobject_cast( layout ); d->slotLayoutPlanes(); } QLayout* Chart::coordinatePlaneLayout() { return d->planesLayout; } AbstractCoordinatePlane* Chart::coordinatePlane() { if ( d->coordinatePlanes.isEmpty() ) { qWarning() << "Chart::coordinatePlane: warning: no coordinate plane defined."; return 0; } else { return d->coordinatePlanes.first(); } } CoordinatePlaneList Chart::coordinatePlanes() { return d->coordinatePlanes; } void Chart::addCoordinatePlane( AbstractCoordinatePlane* plane ) { // Append insertCoordinatePlane( d->coordinatePlanes.count(), plane ); } void Chart::insertCoordinatePlane( int index, AbstractCoordinatePlane* plane ) { if ( index < 0 || index > d->coordinatePlanes.count() ) { return; } - connect( plane, SIGNAL( destroyedCoordinatePlane( AbstractCoordinatePlane* ) ), - d, SLOT( slotUnregisterDestroyedPlane( AbstractCoordinatePlane* ) ) ); - connect( plane, SIGNAL( needUpdate() ), this, SLOT( update() ) ); - connect( plane, SIGNAL( needRelayout() ), d, SLOT( slotResizePlanes() ) ) ; - connect( plane, SIGNAL( needLayoutPlanes() ), d, SLOT( slotLayoutPlanes() ) ) ; - connect( plane, SIGNAL( propertiesChanged() ),this, SIGNAL( propertiesChanged() ) ); + connect( plane, SIGNAL(destroyedCoordinatePlane(AbstractCoordinatePlane*)), + d, SLOT(slotUnregisterDestroyedPlane(AbstractCoordinatePlane*)) ); + connect( plane, SIGNAL(needUpdate()), this, SLOT(update()) ); + connect( plane, SIGNAL(needRelayout()), d, SLOT(slotResizePlanes()) ) ; + connect( plane, SIGNAL(needLayoutPlanes()), d, SLOT(slotLayoutPlanes()) ) ; + connect( plane, SIGNAL(propertiesChanged()),this, SIGNAL(propertiesChanged()) ); d->coordinatePlanes.insert( index, plane ); plane->setParent( this ); d->slotLayoutPlanes(); } void Chart::replaceCoordinatePlane( AbstractCoordinatePlane* plane, AbstractCoordinatePlane* oldPlane_ ) { if ( plane && oldPlane_ != plane ) { AbstractCoordinatePlane* oldPlane = oldPlane_; if ( d->coordinatePlanes.count() ) { if ( ! oldPlane ) { oldPlane = d->coordinatePlanes.first(); if ( oldPlane == plane ) return; } takeCoordinatePlane( oldPlane ); } delete oldPlane; addCoordinatePlane( plane ); } } void Chart::takeCoordinatePlane( AbstractCoordinatePlane* plane ) { const int idx = d->coordinatePlanes.indexOf( plane ); if ( idx != -1 ) { d->coordinatePlanes.takeAt( idx ); disconnect( plane, 0, d, 0 ); disconnect( plane, 0, this, 0 ); plane->removeFromParentLayout(); plane->setParent( 0 ); d->mouseClickedPlanes.removeAll(plane); } d->slotLayoutPlanes(); // Need to emit the signal: In case somebody has connected the signal // to her own slot for e.g. calling update() on a widget containing the chart. emit propertiesChanged(); } void Chart::setGlobalLeading( int left, int top, int right, int bottom ) { setGlobalLeadingLeft( left ); setGlobalLeadingTop( top ); setGlobalLeadingRight( right ); setGlobalLeadingBottom( bottom ); } void Chart::setGlobalLeadingLeft( int leading ) { d->globalLeadingLeft = leading; d->leftOuterSpacer->changeSize( leading, 0, QSizePolicy::Fixed, QSizePolicy::Minimum ); d->reapplyInternalLayouts(); } int Chart::globalLeadingLeft() const { return d->globalLeadingLeft; } void Chart::setGlobalLeadingTop( int leading ) { d->globalLeadingTop = leading; d->topOuterSpacer->changeSize( 0, leading, QSizePolicy::Minimum, QSizePolicy::Fixed ); d->reapplyInternalLayouts(); } int Chart::globalLeadingTop() const { return d->globalLeadingTop; } void Chart::setGlobalLeadingRight( int leading ) { d->globalLeadingRight = leading; d->rightOuterSpacer->changeSize( leading, 0, QSizePolicy::Fixed, QSizePolicy::Minimum ); d->reapplyInternalLayouts(); } int Chart::globalLeadingRight() const { return d->globalLeadingRight; } void Chart::setGlobalLeadingBottom( int leading ) { d->globalLeadingBottom = leading; d->bottomOuterSpacer->changeSize( 0, leading, QSizePolicy::Minimum, QSizePolicy::Fixed ); d->reapplyInternalLayouts(); } int Chart::globalLeadingBottom() const { return d->globalLeadingBottom; } void Chart::paint( QPainter* painter, const QRect& target ) { if ( target.isEmpty() || !painter ) { return; } QPaintDevice* prevDevice = GlobalMeasureScaling::paintDevice(); GlobalMeasureScaling::setPaintDevice( painter->device() ); // Output on a widget if ( dynamic_cast< QWidget* >( painter->device() ) != 0 ) { GlobalMeasureScaling::setFactors( qreal( target.width() ) / qreal( geometry().size().width() ), qreal( target.height() ) / qreal( geometry().size().height() ) ); } else { // Output onto a QPixmap PrintingParameters::setScaleFactor( qreal( painter->device()->logicalDpiX() ) / qreal( logicalDpiX() ) ); const qreal resX = qreal( logicalDpiX() ) / qreal( painter->device()->logicalDpiX() ); const qreal resY = qreal( logicalDpiY() ) / qreal( painter->device()->logicalDpiY() ); GlobalMeasureScaling::setFactors( qreal( target.width() ) / qreal( geometry().size().width() ) * resX, qreal( target.height() ) / qreal( geometry().size().height() ) * resY ); } const QPoint translation = target.topLeft(); painter->translate( translation ); // the following layout logic has the disadvantage that repeatedly calling this method can // cause a relayout every time, but since this method's main use seems to be printing, the // gratuitous relayouts shouldn't be much of a performance problem. const bool differentSize = target.size() != size(); QRect oldGeometry; if ( differentSize ) { oldGeometry = geometry(); d->isPlanesLayoutDirty = true; d->isFloatingLegendsLayoutDirty = true; invalidateLayoutTree( d->dataAndLegendLayout ); d->dataAndLegendLayout->setGeometry( QRect( QPoint(), target.size() ) ); } d->overrideSize = target.size(); d->paintAll( painter ); d->overrideSize = QSize(); if ( differentSize ) { invalidateLayoutTree( d->dataAndLegendLayout ); d->dataAndLegendLayout->setGeometry( oldGeometry ); d->isPlanesLayoutDirty = true; d->isFloatingLegendsLayoutDirty = true; } // for debugging // painter->setPen( QPen( Qt::blue, 8 ) ); // painter->drawRect( target ); painter->translate( -translation.x(), -translation.y() ); GlobalMeasureScaling::instance()->resetFactors(); PrintingParameters::resetScaleFactor(); GlobalMeasureScaling::setPaintDevice( prevDevice ); } void Chart::resizeEvent ( QResizeEvent* event ) { d->isPlanesLayoutDirty = true; d->isFloatingLegendsLayoutDirty = true; QWidget::resizeEvent( event ); } void Chart::reLayoutFloatingLegends() { Q_FOREACH( Legend *legend, d->legends ) { const bool hidden = legend->isHidden() && legend->testAttribute( Qt::WA_WState_ExplicitShowHide ); if ( legend->position().isFloating() && !hidden ) { // resize the legend const QSize legendSize( legend->sizeHint() ); legend->setGeometry( QRect( legend->geometry().topLeft(), legendSize ) ); // find the legends corner point (reference point plus any paddings) const RelativePosition relPos( legend->floatingPosition() ); QPointF pt( relPos.calculatedPoint( size() ) ); //qDebug() << pt; // calculate the legend's top left point const Qt::Alignment alignTopLeft = Qt::AlignBottom | Qt::AlignLeft; if ( (relPos.alignment() & alignTopLeft) != alignTopLeft ) { if ( relPos.alignment() & Qt::AlignRight ) pt.rx() -= legendSize.width(); else if ( relPos.alignment() & Qt::AlignHCenter ) pt.rx() -= 0.5 * legendSize.width(); if ( relPos.alignment() & Qt::AlignBottom ) pt.ry() -= legendSize.height(); else if ( relPos.alignment() & Qt::AlignVCenter ) pt.ry() -= 0.5 * legendSize.height(); } //qDebug() << pt << endl; legend->move( static_cast(pt.x()), static_cast(pt.y()) ); } } } void Chart::paintEvent( QPaintEvent* ) { QPainter painter( this ); d->paintAll( &painter ); emit finishedDrawing(); } void Chart::addHeaderFooter( HeaderFooter* hf ) { Q_ASSERT( hf->type() == HeaderFooter::Header || hf->type() == HeaderFooter::Footer ); int row; int column; getRowAndColumnForPosition( hf->position().value(), &row, &column ); if ( row == -1 ) { qWarning( "Unknown header/footer position" ); return; } d->headerFooters.append( hf ); d->textLayoutItems.append( hf ); - connect( hf, SIGNAL( destroyedHeaderFooter( HeaderFooter* ) ), - d, SLOT( slotUnregisterDestroyedHeaderFooter( HeaderFooter* ) ) ); - connect( hf, SIGNAL( positionChanged( HeaderFooter* ) ), - d, SLOT( slotHeaderFooterPositionChanged( HeaderFooter* ) ) ); + connect( hf, SIGNAL(destroyedHeaderFooter(HeaderFooter*)), + d, SLOT(slotUnregisterDestroyedHeaderFooter(HeaderFooter*)) ); + connect( hf, SIGNAL(positionChanged(HeaderFooter*)), + d, SLOT(slotHeaderFooterPositionChanged(HeaderFooter*)) ); // set the text attributes (why?) TextAttributes textAttrs( hf->textAttributes() ); Measure measure( textAttrs.fontSize() ); measure.setRelativeMode( this, KChartEnums::MeasureOrientationMinimum ); measure.setValue( 20 ); textAttrs.setFontSize( measure ); hf->setTextAttributes( textAttrs ); // add it to the appropriate layout int innerLayoutIdx = hf->type() == HeaderFooter::Header ? 0 : 1; QVBoxLayout* headerFooterLayout = d->innerHdFtLayouts[ innerLayoutIdx ][ row ][ column ]; hf->setParentLayout( headerFooterLayout ); hf->setAlignment( s_gridAlignments[ row ][ column ] ); headerFooterLayout->addItem( hf ); d->slotResizePlanes(); } void Chart::replaceHeaderFooter( HeaderFooter* headerFooter, HeaderFooter* oldHeaderFooter_ ) { if ( headerFooter && oldHeaderFooter_ != headerFooter ) { HeaderFooter* oldHeaderFooter = oldHeaderFooter_; if ( d->headerFooters.count() ) { if ( ! oldHeaderFooter ) { oldHeaderFooter = d->headerFooters.first(); if ( oldHeaderFooter == headerFooter ) return; } takeHeaderFooter( oldHeaderFooter ); } delete oldHeaderFooter; addHeaderFooter( headerFooter ); } } void Chart::takeHeaderFooter( HeaderFooter* headerFooter ) { const int idx = d->headerFooters.indexOf( headerFooter ); if ( idx == -1 ) { return; } - disconnect( headerFooter, SIGNAL( destroyedHeaderFooter( HeaderFooter* ) ), - d, SLOT( slotUnregisterDestroyedHeaderFooter( HeaderFooter* ) ) ); + disconnect( headerFooter, SIGNAL(destroyedHeaderFooter(HeaderFooter*)), + d, SLOT(slotUnregisterDestroyedHeaderFooter(HeaderFooter*)) ); d->headerFooters.takeAt( idx ); headerFooter->removeFromParentLayout(); headerFooter->setParentLayout( 0 ); d->textLayoutItems.remove( d->textLayoutItems.indexOf( headerFooter ) ); d->slotResizePlanes(); } void Chart::Private::slotHeaderFooterPositionChanged( HeaderFooter* hf ) { chart->takeHeaderFooter( hf ); chart->addHeaderFooter( hf ); } HeaderFooter* Chart::headerFooter() { if ( d->headerFooters.isEmpty() ) { return 0; } else { return d->headerFooters.first(); } } HeaderFooterList Chart::headerFooters() { return d->headerFooters; } void Chart::Private::slotLegendPositionChanged( AbstractAreaWidget* aw ) { Legend* legend = qobject_cast< Legend* >( aw ); Q_ASSERT( legend ); chart->takeLegend( legend ); chart->addLegendInternal( legend, false ); } void Chart::addLegend( Legend* legend ) { legend->show(); addLegendInternal( legend, true ); emit propertiesChanged(); } void Chart::addLegendInternal( Legend* legend, bool setMeasures ) { if ( !legend ) { return; } KChartEnums::PositionValue pos = legend->position().value(); if ( pos == KChartEnums::PositionCenter ) { qWarning( "Not showing legend because PositionCenter is not supported for legends." ); } int row; int column; getRowAndColumnForPosition( pos, &row, &column ); if ( row < 0 && pos != KChartEnums::PositionFloating ) { qWarning( "Not showing legend because of unknown legend position." ); return; } d->legends.append( legend ); legend->setParent( this ); // set text attributes (why?) if ( setMeasures ) { TextAttributes textAttrs( legend->textAttributes() ); Measure measure( textAttrs.fontSize() ); measure.setRelativeMode( this, KChartEnums::MeasureOrientationMinimum ); measure.setValue( 20 ); textAttrs.setFontSize( measure ); legend->setTextAttributes( textAttrs ); textAttrs = legend->titleTextAttributes(); measure.setRelativeMode( this, KChartEnums::MeasureOrientationMinimum ); measure.setValue( 24 ); textAttrs.setFontSize( measure ); legend->setTitleTextAttributes( textAttrs ); legend->setReferenceArea( this ); } // add it to the appropriate layout if ( pos != KChartEnums::PositionFloating ) { legend->needSizeHint(); // in each edge and corner of the outer layout, there's a grid for the different alignments that we create // on demand. we don't remove it when empty. QLayoutItem* edgeItem = d->dataAndLegendLayout->itemAtPosition( row, column ); QGridLayout* alignmentsLayout = dynamic_cast< QGridLayout* >( edgeItem ); Q_ASSERT( !edgeItem || alignmentsLayout ); // if it exists, it must be a QGridLayout if ( !alignmentsLayout ) { alignmentsLayout = new QGridLayout; d->dataAndLegendLayout->addLayout( alignmentsLayout, row, column ); alignmentsLayout->setMargin( 0 ); } // in case there are several legends in the same edge or corner with the same alignment, they are stacked // vertically using a QVBoxLayout. it is created on demand as above. row = 1; column = 1; for ( int i = 0; i < 3; i++ ) { for ( int j = 0; j < 3; j++ ) { Qt::Alignment align = s_gridAlignments[ i ][ j ]; if ( align == legend->alignment() ) { row = i; column = j; break; } } } QLayoutItem* alignmentItem = alignmentsLayout->itemAtPosition( row, column ); QVBoxLayout* sameAlignmentLayout = dynamic_cast< QVBoxLayout* >( alignmentItem ); Q_ASSERT( !alignmentItem || sameAlignmentLayout ); // if it exists, it must be a QVBoxLayout if ( !sameAlignmentLayout ) { sameAlignmentLayout = new QVBoxLayout; alignmentsLayout->addLayout( sameAlignmentLayout, row, column ); sameAlignmentLayout->setMargin( 0 ); } sameAlignmentLayout->addItem( new MyWidgetItem( legend, legend->alignment() ) ); } - connect( legend, SIGNAL( destroyedLegend( Legend* ) ), - d, SLOT( slotUnregisterDestroyedLegend( Legend* ) ) ); - connect( legend, SIGNAL( positionChanged( AbstractAreaWidget* ) ), - d, SLOT( slotLegendPositionChanged( AbstractAreaWidget* ) ) ); - connect( legend, SIGNAL( propertiesChanged() ), this, SIGNAL( propertiesChanged() ) ); + connect( legend, SIGNAL(destroyedLegend(Legend*)), + d, SLOT(slotUnregisterDestroyedLegend(Legend*)) ); + connect( legend, SIGNAL(positionChanged(AbstractAreaWidget*)), + d, SLOT(slotLegendPositionChanged(AbstractAreaWidget*)) ); + connect( legend, SIGNAL(propertiesChanged()), this, SIGNAL(propertiesChanged()) ); d->slotResizePlanes(); } void Chart::replaceLegend( Legend* legend, Legend* oldLegend_ ) { if ( legend && oldLegend_ != legend ) { Legend* oldLegend = oldLegend_; if ( d->legends.count() ) { if ( ! oldLegend ) { oldLegend = d->legends.first(); if ( oldLegend == legend ) return; } takeLegend( oldLegend ); } delete oldLegend; addLegend( legend ); } } void Chart::takeLegend( Legend* legend ) { const int idx = d->legends.indexOf( legend ); if ( idx == -1 ) { return; } d->legends.takeAt( idx ); disconnect( legend, 0, d, 0 ); disconnect( legend, 0, this, 0 ); // the following removes the legend from its layout and destroys its MyWidgetItem (the link to the layout) legend->setParent( 0 ); d->slotResizePlanes(); emit propertiesChanged(); } Legend* Chart::legend() { return d->legends.isEmpty() ? 0 : d->legends.first(); } LegendList Chart::legends() { return d->legends; } void Chart::mousePressEvent( QMouseEvent* event ) { const QPoint pos = mapFromGlobal( event->globalPos() ); Q_FOREACH( AbstractCoordinatePlane* plane, d->coordinatePlanes ) { if ( plane->geometry().contains( event->pos() ) && plane->diagrams().size() > 0 ) { QMouseEvent ev( QEvent::MouseButtonPress, pos, event->globalPos(), event->button(), event->buttons(), event->modifiers() ); plane->mousePressEvent( &ev ); d->mouseClickedPlanes.append( plane ); } } } void Chart::mouseDoubleClickEvent( QMouseEvent* event ) { const QPoint pos = mapFromGlobal( event->globalPos() ); Q_FOREACH( AbstractCoordinatePlane* plane, d->coordinatePlanes ) { if ( plane->geometry().contains( event->pos() ) && plane->diagrams().size() > 0 ) { QMouseEvent ev( QEvent::MouseButtonPress, pos, event->globalPos(), event->button(), event->buttons(), event->modifiers() ); plane->mouseDoubleClickEvent( &ev ); } } } void Chart::mouseMoveEvent( QMouseEvent* event ) { QSet< AbstractCoordinatePlane* > eventReceivers = QSet< AbstractCoordinatePlane* >::fromList( d->mouseClickedPlanes ); Q_FOREACH( AbstractCoordinatePlane* plane, d->coordinatePlanes ) { if ( plane->geometry().contains( event->pos() ) && plane->diagrams().size() > 0 ) { eventReceivers.insert( plane ); } } const QPoint pos = mapFromGlobal( event->globalPos() ); Q_FOREACH( AbstractCoordinatePlane* plane, eventReceivers ) { QMouseEvent ev( QEvent::MouseMove, pos, event->globalPos(), event->button(), event->buttons(), event->modifiers() ); plane->mouseMoveEvent( &ev ); } } void Chart::mouseReleaseEvent( QMouseEvent* event ) { QSet< AbstractCoordinatePlane* > eventReceivers = QSet< AbstractCoordinatePlane* >::fromList( d->mouseClickedPlanes ); Q_FOREACH( AbstractCoordinatePlane* plane, d->coordinatePlanes ) { if ( plane->geometry().contains( event->pos() ) && plane->diagrams().size() > 0 ) { eventReceivers.insert( plane ); } } const QPoint pos = mapFromGlobal( event->globalPos() ); Q_FOREACH( AbstractCoordinatePlane* plane, eventReceivers ) { QMouseEvent ev( QEvent::MouseButtonRelease, pos, event->globalPos(), event->button(), event->buttons(), event->modifiers() ); plane->mouseReleaseEvent( &ev ); } d->mouseClickedPlanes.clear(); } bool Chart::event( QEvent* event ) { if ( event->type() == QEvent::ToolTip ) { const QHelpEvent* const helpEvent = static_cast< QHelpEvent* >( event ); Q_FOREACH( const AbstractCoordinatePlane* const plane, d->coordinatePlanes ) { // iterate diagrams in reverse, so that the top-most painted diagram is // queried first for a tooltip before the diagrams behind it const ConstAbstractDiagramList& diagrams = plane->diagrams(); for (int i = diagrams.size() - 1; i >= 0; --i) { const AbstractDiagram* diagram = diagrams[i]; if (diagram->isHidden()) { continue; } const QModelIndex index = diagram->indexAt( helpEvent->pos() ); const QVariant toolTip = index.data( Qt::ToolTipRole ); if ( toolTip.isValid() ) { QPoint pos = mapFromGlobal( helpEvent->pos() ); QRect rect( pos - QPoint( 1, 1 ), QSize( 3, 3 ) ); QToolTip::showText( QCursor::pos(), toolTip.toString(), this, rect ); return true; } } } } return QWidget::event( event ); } bool Chart::useNewLayoutSystem() const { return d_func()->useNewLayoutSystem; } void Chart::setUseNewLayoutSystem( bool value ) { if ( d_func()->useNewLayoutSystem != value ) d_func()->useNewLayoutSystem = value; } diff --git a/src/KChart/KChartDatasetProxyModel.cpp b/src/KChart/KChartDatasetProxyModel.cpp index abb6a90..d98b4fd 100644 --- a/src/KChart/KChartDatasetProxyModel.cpp +++ b/src/KChart/KChartDatasetProxyModel.cpp @@ -1,294 +1,294 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "KChartDatasetProxyModel.h" #include "KChartMath_p.h" #include using namespace KChart; DatasetProxyModel::DatasetProxyModel(QObject* parent) : QSortFilterProxyModel( parent ) { } QModelIndex DatasetProxyModel::buddy( const QModelIndex& index ) const { return index; } Qt::ItemFlags DatasetProxyModel::flags( const QModelIndex& index ) const { return sourceModel()->flags( mapToSource( index ) ); } void DatasetProxyModel::setDatasetRowDescriptionVector( const DatasetDescriptionVector& configuration ) { Q_ASSERT_X( sourceModel(), "DatasetProxyModel::setDatasetRowDescriptionVector", "A source model must be set before the selection can be configured." ); initializeDatasetDecriptors( configuration, sourceModel()->rowCount(mRootIndex), mRowSrcToProxyMap, mRowProxyToSrcMap ); clear(); // clear emits layoutChanged() } void DatasetProxyModel::setDatasetColumnDescriptionVector( const DatasetDescriptionVector& configuration ) { Q_ASSERT_X( sourceModel(), "DatasetProxyModel::setDatasetColumnDescriptionVector", "A source model must be set before the selection can be configured." ); initializeDatasetDecriptors( configuration, sourceModel()->columnCount(mRootIndex), mColSrcToProxyMap, mColProxyToSrcMap ); clear(); // clear emits layoutChanged() } void DatasetProxyModel::setDatasetDescriptionVectors( const DatasetDescriptionVector& rowConfig, const DatasetDescriptionVector& columnConfig ) { setDatasetRowDescriptionVector( rowConfig ); setDatasetColumnDescriptionVector( columnConfig ); } QModelIndex DatasetProxyModel::index( int row, int column, const QModelIndex &parent ) const { return mapFromSource( sourceModel()->index( mapProxyRowToSource(row), mapProxyColumnToSource(column), parent ) ); } QModelIndex DatasetProxyModel::parent( const QModelIndex& child ) const { // return mapFromSource( sourceModel()->parent( child ) ); return mapFromSource( sourceModel()->parent( mapToSource( child ) ) ); } QModelIndex DatasetProxyModel::mapFromSource( const QModelIndex & sourceIndex ) const { Q_ASSERT_X( sourceModel(), "DatasetProxyModel::mapFromSource", "A source " "model must be set before the selection can be configured." ); if ( !sourceIndex.isValid() ) return sourceIndex; if ( mRowSrcToProxyMap.isEmpty() && mColSrcToProxyMap.isEmpty() ) { return createIndex( sourceIndex.row(), sourceIndex.column(), sourceIndex.internalPointer() ); } else { int row = mapSourceRowToProxy( sourceIndex.row() ); int column = mapSourceColumnToProxy( sourceIndex.column() ); return createIndex( row, column, sourceIndex.internalPointer() ); } } QModelIndex DatasetProxyModel::mapToSource( const QModelIndex& proxyIndex ) const { Q_ASSERT_X( sourceModel(), "DatasetProxyModel::mapToSource", "A source " "model must be set before the selection can be configured." ); if ( !proxyIndex.isValid() ) return proxyIndex; if ( mRowSrcToProxyMap.isEmpty() && mColSrcToProxyMap.isEmpty() ) { return sourceModel()->index( proxyIndex.row(), proxyIndex.column(), mRootIndex ); } else { int row = mapProxyRowToSource( proxyIndex.row() ); int column = mapProxyColumnToSource( proxyIndex.column() ); return sourceModel()->index( row, column, mRootIndex ); } } bool DatasetProxyModel::filterAcceptsRow( int sourceRow, const QModelIndex & ) const { if ( mRowSrcToProxyMap.isEmpty() ) { // no row mapping set, all rows are passed down: return true; } else { Q_ASSERT( sourceModel() ); Q_ASSERT( mRowSrcToProxyMap.size() == sourceModel()->rowCount(mRootIndex) ); if ( mRowSrcToProxyMap[sourceRow] == -1 ) { // this row is explicitly not accepted: return false; } else { Q_ASSERT( mRowSrcToProxyMap[sourceRow] >= 0 && mRowSrcToProxyMap[sourceRow] < mRowSrcToProxyMap.size() ); return true; } } } bool DatasetProxyModel::filterAcceptsColumn( int sourceColumn, const QModelIndex & ) const { if ( mColSrcToProxyMap.isEmpty() ) { // no column mapping set up yet, all columns are passed down: return true; } else { Q_ASSERT( sourceModel() ); Q_ASSERT( mColSrcToProxyMap.size() == sourceModel()->columnCount(mRootIndex) ); if ( mColSrcToProxyMap[sourceColumn] == -1 ) { // this column is explicitly not accepted: return false; } else { Q_ASSERT( mColSrcToProxyMap[sourceColumn] >= 0 && mColSrcToProxyMap[sourceColumn] < mColSrcToProxyMap.size() ); return true; } } } int DatasetProxyModel::mapProxyRowToSource( const int& proxyRow ) const { if ( mRowProxyToSrcMap.isEmpty() ) { // if no row mapping is set, we pass down the row: return proxyRow; } else { Q_ASSERT( proxyRow >= 0 && proxyRow < mRowProxyToSrcMap.size() ); return mRowProxyToSrcMap[ proxyRow ]; } } int DatasetProxyModel::mapProxyColumnToSource( const int& proxyColumn ) const { if ( mColProxyToSrcMap.isEmpty() ) { // if no column mapping is set, we pass down the column: return proxyColumn; } else { Q_ASSERT( proxyColumn >= 0 && proxyColumn < mColProxyToSrcMap.size() ); return mColProxyToSrcMap[ proxyColumn ]; } } int DatasetProxyModel::mapSourceRowToProxy( const int& sourceRow ) const { if ( mRowSrcToProxyMap.isEmpty() ) { return sourceRow; } else { Q_ASSERT( sourceRow >= 0 && sourceRow < mRowSrcToProxyMap.size() ); return mRowSrcToProxyMap[sourceRow]; } } int DatasetProxyModel::mapSourceColumnToProxy( const int& sourceColumn ) const { if ( mColSrcToProxyMap.isEmpty() ) { return sourceColumn; } else { Q_ASSERT( sourceColumn >= 0 && sourceColumn < mColSrcToProxyMap.size() ); return mColSrcToProxyMap.at( sourceColumn ) ; } } void DatasetProxyModel::resetDatasetDescriptions() { mRowSrcToProxyMap.clear(); mRowProxyToSrcMap.clear(); mColSrcToProxyMap.clear(); mColProxyToSrcMap.clear(); clear(); } QVariant DatasetProxyModel::data(const QModelIndex &index, int role) const { return sourceModel()->data( mapToSource( index ), role ); } bool DatasetProxyModel::setData( const QModelIndex& index, const QVariant& value, int role ) { return sourceModel()->setData( mapToSource( index ), value, role ); } QVariant DatasetProxyModel::headerData( int section, Qt::Orientation orientation, int role ) const { if ( orientation == Qt::Horizontal ) { if ( mapProxyColumnToSource ( section ) == -1 ) { return QVariant(); } else { return sourceModel()->headerData( mapProxyColumnToSource( section ), orientation, role ); } } else { if ( mapProxyRowToSource ( section ) == -1 ) { return QVariant(); } else { return sourceModel()->headerData( mapProxyRowToSource ( section ), orientation, role ); } } } void DatasetProxyModel::initializeDatasetDecriptors( const DatasetDescriptionVector& inConfiguration, const int sourceCount, DatasetDescriptionVector& outSourceToProxyMap, DatasetDescriptionVector& outProxyToSourceMap ) { // in the current mapping implementation, the proxy-to-source map is // identical to the configuration vector: outProxyToSourceMap = inConfiguration; outSourceToProxyMap.fill( -1, sourceCount ); for ( int index = 0; index < inConfiguration.size(); ++index ) { // make sure the values in inConfiguration point to columns in the // source model: if ( inConfiguration[index] == -1 ) { continue; } Q_ASSERT_X( inConfiguration[ index ] >= 0 && inConfiguration[ index ] < sourceCount, "DatasetProxyModel::initializeDatasetDecriptors", "column index outside of source model" ); Q_ASSERT_X( outSourceToProxyMap[ inConfiguration[ index ] ] == -1 , "DatasetProxyModel::initializeDatasetDecriptors", "no duplicates allowed in mapping configuration, mapping has to be reversible" ); outSourceToProxyMap[ inConfiguration[ index ] ] = index; } } void DatasetProxyModel::setSourceModel(QAbstractItemModel *m) { if ( sourceModel() ) { - disconnect( sourceModel(), SIGNAL( layoutChanged() ), - this, SLOT( resetDatasetDescriptions() ) ); + disconnect( sourceModel(), SIGNAL(layoutChanged()), + this, SLOT(resetDatasetDescriptions()) ); } QSortFilterProxyModel::setSourceModel( m ); mRootIndex = QModelIndex(); if ( m ) { - connect( m, SIGNAL( layoutChanged() ), - this, SLOT( resetDatasetDescriptions() ) ); - connect( m, SIGNAL( layoutChanged() ), this, SIGNAL( layoutChanged() ) ); + connect( m, SIGNAL(layoutChanged()), + this, SLOT(resetDatasetDescriptions()) ); + connect( m, SIGNAL(layoutChanged()), this, SIGNAL(layoutChanged()) ); } resetDatasetDescriptions(); } void DatasetProxyModel::setSourceRootIndex(const QModelIndex& rootIdx) { mRootIndex = rootIdx; resetDatasetDescriptions(); } diff --git a/src/KChart/KChartDiagramObserver.cpp b/src/KChart/KChartDiagramObserver.cpp index 27e7c53..7dd169c 100644 --- a/src/KChart/KChartDiagramObserver.cpp +++ b/src/KChart/KChartDiagramObserver.cpp @@ -1,152 +1,152 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include "KChartMath_p.h" #include using namespace KChart; DiagramObserver::DiagramObserver( AbstractDiagram * diagram, QObject* parent ) : QObject( parent ), m_diagram( diagram ) { if ( m_diagram ) { connect( m_diagram, SIGNAL(destroyed(QObject*)), SLOT(slotDestroyed(QObject*))); connect( m_diagram, SIGNAL(aboutToBeDestroyed()), SLOT(slotAboutToBeDestroyed())); connect( m_diagram, SIGNAL(modelsChanged()), SLOT(slotModelsChanged())); } init(); } DiagramObserver::~DiagramObserver() { } const AbstractDiagram* DiagramObserver::diagram() const { return m_diagram; } AbstractDiagram* DiagramObserver::diagram() { return m_diagram; } void DiagramObserver::init() { if ( !m_diagram ) return; if ( m_model ) disconnect(m_model); if ( m_attributesmodel ) disconnect(m_attributesmodel); - const bool con = connect( m_diagram, SIGNAL( viewportCoordinateSystemChanged() ), this, SLOT( slotDataChanged() ) ); + const bool con = connect( m_diagram, SIGNAL(viewportCoordinateSystemChanged()), this, SLOT(slotDataChanged()) ); Q_ASSERT( con ); Q_UNUSED( con ) connect( m_diagram, SIGNAL(dataHidden()), SLOT(slotDataHidden()) ); if ( m_diagram->model() ) { connect( m_diagram->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), SLOT(slotDataChanged(QModelIndex,QModelIndex))); connect( m_diagram->model(), SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(slotDataChanged())); connect( m_diagram->model(), SIGNAL(columnsInserted(QModelIndex,int,int)), SLOT(slotDataChanged())); connect( m_diagram->model(), SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(slotDataChanged())); connect( m_diagram->model(), SIGNAL(columnsRemoved(QModelIndex,int,int)), SLOT(slotDataChanged())); connect( m_diagram->model(), SIGNAL(modelReset()), SLOT(slotDataChanged())); connect( m_diagram->model(), SIGNAL(headerDataChanged(Qt::Orientation,int,int)), SLOT(slotHeaderDataChanged(Qt::Orientation,int,int))); } if ( m_diagram->attributesModel() ) connect( m_diagram->attributesModel(), SIGNAL(attributesChanged(QModelIndex,QModelIndex)), SLOT(slotAttributesChanged(QModelIndex,QModelIndex))); m_model = m_diagram->model(); m_attributesmodel = m_diagram->attributesModel(); } void DiagramObserver::slotDestroyed(QObject*) { //qDebug() << this << "emits signal\n" // " emit diagramDestroyed(" << m_diagram << ")"; AbstractDiagram* diag = m_diagram; disconnect( m_diagram, 0, this, 0); m_diagram = 0; emit diagramDestroyed( diag ); } void DiagramObserver::slotAboutToBeDestroyed() { emit diagramAboutToBeDestroyed( m_diagram ); } void DiagramObserver::slotModelsChanged() { init(); slotDataChanged(); slotAttributesChanged(); } void DiagramObserver::slotHeaderDataChanged(Qt::Orientation,int,int) { //qDebug() << "DiagramObserver::slotHeaderDataChanged()"; emit diagramDataChanged( m_diagram ); } void DiagramObserver::slotDataChanged(QModelIndex,QModelIndex) { slotDataChanged(); } void DiagramObserver::slotDataChanged() { //qDebug() << "DiagramObserver::slotDataChanged()"; emit diagramDataChanged( m_diagram ); } void DiagramObserver::slotDataHidden() { //qDebug() << "DiagramObserver::slotDataHidden()"; emit diagramDataHidden( m_diagram ); } void DiagramObserver::slotAttributesChanged(QModelIndex,QModelIndex) { slotAttributesChanged(); } void DiagramObserver::slotAttributesChanged() { //qDebug() << "DiagramObserver::slotAttributesChanged()"; emit diagramAttributesChanged( m_diagram ); } diff --git a/src/KChart/KChartLegend.cpp b/src/KChart/KChartLegend.cpp index 1f13864..a964f47 100644 --- a/src/KChart/KChartLegend.cpp +++ b/src/KChart/KChartLegend.cpp @@ -1,1259 +1,1259 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "KChartLegend.h" #include "KChartLegend_p.h" #include #include #include #include #include "KTextDocument.h" #include #include "KChartLayoutItems.h" #include #include #include #include #include #include #include #include #include #include #include using namespace KChart; Legend::Private::Private() : referenceArea( 0 ), position( Position::East ), alignment( Qt::AlignCenter ), textAlignment( Qt::AlignCenter ), relativePosition( RelativePosition() ), orientation( Qt::Vertical ), order( Qt::AscendingOrder ), showLines( false ), titleText( QObject::tr( "Legend" ) ), spacing( 1 ), useAutomaticMarkerSize( true ), legendStyle( MarkersOnly ) { // By default we specify a simple, hard point as the 'relative' position's ref. point, // since we can not be sure that there will be any parent specified for the legend. relativePosition.setReferencePoints( PositionPoints( QPointF( 0.0, 0.0 ) ) ); relativePosition.setReferencePosition( Position::NorthWest ); relativePosition.setAlignment( Qt::AlignTop | Qt::AlignLeft ); relativePosition.setHorizontalPadding( Measure( 4.0, KChartEnums::MeasureCalculationModeAbsolute ) ); relativePosition.setVerticalPadding( Measure( 4.0, KChartEnums::MeasureCalculationModeAbsolute ) ); } Legend::Private::~Private() { // this bloc left empty intentionally } #define d d_func() Legend::Legend( QWidget* parent ) : AbstractAreaWidget( new Private(), parent ) { d->referenceArea = parent; init(); } Legend::Legend( AbstractDiagram* diagram, QWidget* parent ) : AbstractAreaWidget( new Private(), parent ) { d->referenceArea = parent; init(); setDiagram( diagram ); } Legend::~Legend() { emit destroyedLegend( this ); } void Legend::init() { setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); d->layout = new QGridLayout( this ); d->layout->setMargin( 2 ); d->layout->setSpacing( d->spacing ); const Measure normalFontSizeTitle( 12, KChartEnums::MeasureCalculationModeAbsolute ); const Measure normalFontSizeLabels( 10, KChartEnums::MeasureCalculationModeAbsolute ); const Measure minimalFontSize( 4, KChartEnums::MeasureCalculationModeAbsolute ); TextAttributes textAttrs; textAttrs.setPen( QPen( Qt::black ) ); textAttrs.setFont( QFont( QLatin1String( "helvetica" ), 10, QFont::Normal, false ) ); textAttrs.setFontSize( normalFontSizeLabels ); textAttrs.setMinimalFontSize( minimalFontSize ); setTextAttributes( textAttrs ); TextAttributes titleTextAttrs; titleTextAttrs.setPen( QPen( Qt::black ) ); titleTextAttrs.setFont( QFont( QLatin1String( "helvetica" ), 12, QFont::Bold, false ) ); titleTextAttrs.setFontSize( normalFontSizeTitle ); titleTextAttrs.setMinimalFontSize( minimalFontSize ); setTitleTextAttributes( titleTextAttrs ); FrameAttributes frameAttrs; frameAttrs.setVisible( true ); frameAttrs.setPen( QPen( Qt::black ) ); frameAttrs.setPadding( 1 ); setFrameAttributes( frameAttrs ); d->position = Position::NorthEast; d->alignment = Qt::AlignCenter; } QSize Legend::minimumSizeHint() const { return sizeHint(); } //#define DEBUG_LEGEND_PAINT QSize Legend::sizeHint() const { #ifdef DEBUG_LEGEND_PAINT qDebug() << "Legend::sizeHint() started"; #endif Q_FOREACH( AbstractLayoutItem* paintItem, d->paintItems ) { paintItem->sizeHint(); } return AbstractAreaWidget::sizeHint(); } void Legend::needSizeHint() { buildLegend(); } void Legend::resizeLayout( const QSize& size ) { #ifdef DEBUG_LEGEND_PAINT qDebug() << "Legend::resizeLayout started"; #endif if ( d->layout ) { d->reflowHDatasetItems( this ); d->layout->setGeometry( QRect(QPoint( 0,0 ), size) ); activateTheLayout(); } #ifdef DEBUG_LEGEND_PAINT qDebug() << "Legend::resizeLayout done"; #endif } void Legend::activateTheLayout() { if ( d->layout && d->layout->parent() ) { d->layout->activate(); } } void Legend::setLegendStyle( LegendStyle style ) { if ( d->legendStyle == style ) { return; } d->legendStyle = style; setNeedRebuild(); } Legend::LegendStyle Legend::legendStyle() const { return d->legendStyle; } /** * Creates an exact copy of this legend. */ Legend* Legend::clone() const { Legend* legend = new Legend( new Private( *d ), 0 ); legend->setTextAttributes( textAttributes() ); legend->setTitleTextAttributes( titleTextAttributes() ); legend->setFrameAttributes( frameAttributes() ); legend->setUseAutomaticMarkerSize( useAutomaticMarkerSize() ); legend->setPosition( position() ); legend->setAlignment( alignment() ); legend->setTextAlignment( textAlignment() ); legend->setLegendStyle( legendStyle() ); return legend; } bool Legend::compare( const Legend* other ) const { if ( other == this ) { return true; } if ( !other ) { return false; } return ( AbstractAreaBase::compare( other ) ) && (isVisible() == other->isVisible()) && (position() == other->position()) && (alignment() == other->alignment())&& (textAlignment() == other->textAlignment())&& (floatingPosition() == other->floatingPosition()) && (orientation() == other->orientation())&& (showLines() == other->showLines())&& (texts() == other->texts())&& (brushes() == other->brushes())&& (pens() == other->pens())&& (markerAttributes() == other->markerAttributes())&& (useAutomaticMarkerSize() == other->useAutomaticMarkerSize()) && (textAttributes() == other->textAttributes()) && (titleText() == other->titleText())&& (titleTextAttributes() == other->titleTextAttributes()) && (spacing() == other->spacing()) && (legendStyle() == other->legendStyle()); } void Legend::paint( QPainter* painter ) { #ifdef DEBUG_LEGEND_PAINT qDebug() << "entering Legend::paint( QPainter* painter )"; #endif if ( !diagram() ) { return; } activateTheLayout(); Q_FOREACH( AbstractLayoutItem* paintItem, d->paintItems ) { paintItem->paint( painter ); } #ifdef DEBUG_LEGEND_PAINT qDebug() << "leaving Legend::paint( QPainter* painter )"; #endif } uint Legend::datasetCount() const { int modelLabelsCount = 0; Q_FOREACH ( DiagramObserver* observer, d->observers ) { AbstractDiagram* diagram = observer->diagram(); Q_ASSERT( diagram->datasetLabels().count() == diagram->datasetBrushes().count() ); modelLabelsCount += diagram->datasetLabels().count(); } return modelLabelsCount; } void Legend::setReferenceArea( const QWidget* area ) { if ( area == d->referenceArea ) { return; } d->referenceArea = area; setNeedRebuild(); } const QWidget* Legend::referenceArea() const { return d->referenceArea ? d->referenceArea : qobject_cast< const QWidget* >( parent() ); } AbstractDiagram* Legend::diagram() const { if ( d->observers.isEmpty() ) { return 0; } return d->observers.first()->diagram(); } DiagramList Legend::diagrams() const { DiagramList list; for ( int i = 0; i < d->observers.size(); ++i ) { list << d->observers.at(i)->diagram(); } return list; } ConstDiagramList Legend::constDiagrams() const { ConstDiagramList list; for ( int i = 0; i < d->observers.size(); ++i ) { list << d->observers.at(i)->diagram(); } return list; } void Legend::addDiagram( AbstractDiagram* newDiagram ) { if ( newDiagram ) { DiagramObserver* observer = new DiagramObserver( newDiagram, this ); DiagramObserver* oldObs = d->findObserverForDiagram( newDiagram ); if ( oldObs ) { delete oldObs; d->observers[ d->observers.indexOf( oldObs ) ] = observer; } else { d->observers.append( observer ); } - connect( observer, SIGNAL( diagramAboutToBeDestroyed(AbstractDiagram*) ), - SLOT( resetDiagram(AbstractDiagram*) )); - connect( observer, SIGNAL( diagramDataChanged(AbstractDiagram*) ), - SLOT( setNeedRebuild() )); - connect( observer, SIGNAL( diagramDataHidden(AbstractDiagram*) ), - SLOT( setNeedRebuild() )); - connect( observer, SIGNAL( diagramAttributesChanged(AbstractDiagram*) ), - SLOT( setNeedRebuild() )); + connect( observer, SIGNAL(diagramAboutToBeDestroyed(AbstractDiagram*)), + SLOT(resetDiagram(AbstractDiagram*))); + connect( observer, SIGNAL(diagramDataChanged(AbstractDiagram*)), + SLOT(setNeedRebuild())); + connect( observer, SIGNAL(diagramDataHidden(AbstractDiagram*)), + SLOT(setNeedRebuild())); + connect( observer, SIGNAL(diagramAttributesChanged(AbstractDiagram*)), + SLOT(setNeedRebuild())); setNeedRebuild(); } } void Legend::removeDiagram( AbstractDiagram* oldDiagram ) { int datasetBrushOffset = 0; QList< AbstractDiagram * > diagrams = this->diagrams(); for ( int i = 0; i datasetBrushes().count(); i++ ) { d->brushes.remove(datasetBrushOffset + i); d->texts.remove(datasetBrushOffset + i); } for ( int i = 0; i < oldDiagram->datasetPens().count(); i++ ) { d->pens.remove(datasetBrushOffset + i); } break; } datasetBrushOffset += diagrams.at(i)->datasetBrushes().count(); } if ( oldDiagram ) { DiagramObserver *oldObs = d->findObserverForDiagram( oldDiagram ); if ( oldObs ) { delete oldObs; d->observers.removeAt( d->observers.indexOf( oldObs ) ); } setNeedRebuild(); } } void Legend::removeDiagrams() { // removeDiagram() may change the d->observers list. So, build up the list of // diagrams to remove first and then remove them one by one. QList< AbstractDiagram * > diagrams; for ( int i = 0; i < d->observers.size(); ++i ) { diagrams.append( d->observers.at( i )->diagram() ); } for ( int i = 0; i < diagrams.count(); ++i ) { removeDiagram( diagrams[ i ] ); } } void Legend::replaceDiagram( AbstractDiagram* newDiagram, AbstractDiagram* oldDiagram ) { AbstractDiagram* old = oldDiagram; if ( !d->observers.isEmpty() && !old ) { old = d->observers.first()->diagram(); if ( !old ) { d->observers.removeFirst(); // first entry had a 0 diagram } } if ( old ) { removeDiagram( old ); } if ( newDiagram ) { addDiagram( newDiagram ); } } uint Legend::dataSetOffset( AbstractDiagram* diagram ) { uint offset = 0; for ( int i = 0; i < d->observers.count(); ++i ) { if ( d->observers.at(i)->diagram() == diagram ) { return offset; } AbstractDiagram* diagram = d->observers.at(i)->diagram(); if ( !diagram->model() ) { continue; } offset = offset + diagram->model()->columnCount(); } return offset; } void Legend::setDiagram( AbstractDiagram* newDiagram ) { replaceDiagram( newDiagram ); } void Legend::resetDiagram( AbstractDiagram* oldDiagram ) { removeDiagram( oldDiagram ); } void Legend::setVisible( bool visible ) { // do NOT bail out if visible == isVisible(), because the return value of isVisible() also depends // on the visibility of the parent. QWidget::setVisible( visible ); emitPositionChanged(); } void Legend::setNeedRebuild() { buildLegend(); sizeHint(); } void Legend::setPosition( Position position ) { if ( d->position == position ) { return; } d->position = position; emitPositionChanged(); } void Legend::emitPositionChanged() { emit positionChanged( this ); emit propertiesChanged(); } Position Legend::position() const { return d->position; } void Legend::setAlignment( Qt::Alignment alignment ) { if ( d->alignment == alignment ) { return; } d->alignment = alignment; emitPositionChanged(); } Qt::Alignment Legend::alignment() const { return d->alignment; } void Legend::setTextAlignment( Qt::Alignment alignment ) { if ( d->textAlignment == alignment ) { return; } d->textAlignment = alignment; emitPositionChanged(); } Qt::Alignment Legend::textAlignment() const { return d->textAlignment; } void Legend::setLegendSymbolAlignment( Qt::Alignment alignment ) { if ( d->legendLineSymbolAlignment == alignment ) { return; } d->legendLineSymbolAlignment = alignment; emitPositionChanged(); } Qt::Alignment Legend::legendSymbolAlignment() const { return d->legendLineSymbolAlignment ; } void Legend::setFloatingPosition( const RelativePosition& relativePosition ) { d->position = Position::Floating; if ( d->relativePosition != relativePosition ) { d->relativePosition = relativePosition; emitPositionChanged(); } } const RelativePosition Legend::floatingPosition() const { return d->relativePosition; } void Legend::setOrientation( Qt::Orientation orientation ) { if ( d->orientation == orientation ) { return; } d->orientation = orientation; setNeedRebuild(); emitPositionChanged(); } Qt::Orientation Legend::orientation() const { return d->orientation; } void Legend::setSortOrder( Qt::SortOrder order ) { if ( d->order == order ) { return; } d->order = order; setNeedRebuild(); emitPositionChanged(); } Qt::SortOrder Legend::sortOrder() const { return d->order; } void Legend::setShowLines( bool legendShowLines ) { if ( d->showLines == legendShowLines ) { return; } d->showLines = legendShowLines; setNeedRebuild(); emitPositionChanged(); } bool Legend::showLines() const { return d->showLines; } void Legend::setUseAutomaticMarkerSize( bool useAutomaticMarkerSize ) { d->useAutomaticMarkerSize = useAutomaticMarkerSize; setNeedRebuild(); emitPositionChanged(); } bool Legend::useAutomaticMarkerSize() const { return d->useAutomaticMarkerSize; } /** \brief Removes all legend texts that might have been set by setText. This resets the Legend to default behaviour: Texts are created automatically. */ void Legend::resetTexts() { if ( !d->texts.count() ) { return; } d->texts.clear(); setNeedRebuild(); } void Legend::setText( uint dataset, const QString& text ) { if ( d->texts[ dataset ] == text ) { return; } d->texts[ dataset ] = text; setNeedRebuild(); } QString Legend::text( uint dataset ) const { if ( d->texts.find( dataset ) != d->texts.end() ) { return d->texts[ dataset ]; } else { return d->modelLabels[ dataset ]; } } const QMap Legend::texts() const { return d->texts; } void Legend::setColor( uint dataset, const QColor& color ) { if ( d->brushes[ dataset ] != color ) { d->brushes[ dataset ] = color; setNeedRebuild(); update(); } } void Legend::setBrush( uint dataset, const QBrush& brush ) { if ( d->brushes[ dataset ] != brush ) { d->brushes[ dataset ] = brush; setNeedRebuild(); update(); } } QBrush Legend::brush( uint dataset ) const { if ( d->brushes.contains( dataset ) ) { return d->brushes[ dataset ]; } else { return d->modelBrushes[ dataset ]; } } const QMap Legend::brushes() const { return d->brushes; } void Legend::setBrushesFromDiagram( AbstractDiagram* diagram ) { bool changed = false; QList datasetBrushes = diagram->datasetBrushes(); for ( int i = 0; i < datasetBrushes.count(); i++ ) { if ( d->brushes[ i ] != datasetBrushes[ i ] ) { d->brushes[ i ] = datasetBrushes[ i ]; changed = true; } } if ( changed ) { setNeedRebuild(); update(); } } void Legend::setPen( uint dataset, const QPen& pen ) { if ( d->pens[dataset] == pen ) { return; } d->pens[dataset] = pen; setNeedRebuild(); update(); } QPen Legend::pen( uint dataset ) const { if ( d->pens.find( dataset ) != d->pens.end() ) { return d->pens[ dataset ]; } else { return d->modelPens[ dataset ]; } } const QMap Legend::pens() const { return d->pens; } void Legend::setMarkerAttributes( uint dataset, const MarkerAttributes& markerAttributes ) { if ( d->markerAttributes[dataset] == markerAttributes ) { return; } d->markerAttributes[ dataset ] = markerAttributes; setNeedRebuild(); update(); } MarkerAttributes Legend::markerAttributes( uint dataset ) const { if ( d->markerAttributes.find( dataset ) != d->markerAttributes.end() ) { return d->markerAttributes[ dataset ]; } else if ( static_cast( d->modelMarkers.count() ) > dataset ) { return d->modelMarkers[ dataset ]; } else { return MarkerAttributes(); } } const QMap Legend::markerAttributes() const { return d->markerAttributes; } void Legend::setTextAttributes( const TextAttributes &a ) { if ( d->textAttributes == a ) { return; } d->textAttributes = a; setNeedRebuild(); } TextAttributes Legend::textAttributes() const { return d->textAttributes; } void Legend::setTitleText( const QString& text ) { if ( d->titleText == text ) { return; } d->titleText = text; setNeedRebuild(); } QString Legend::titleText() const { return d->titleText; } void Legend::setTitleTextAttributes( const TextAttributes &a ) { if ( d->titleTextAttributes == a ) { return; } d->titleTextAttributes = a; setNeedRebuild(); } TextAttributes Legend::titleTextAttributes() const { return d->titleTextAttributes; } void Legend::forceRebuild() { #ifdef DEBUG_LEGEND_PAINT qDebug() << "entering Legend::forceRebuild()"; #endif buildLegend(); #ifdef DEBUG_LEGEND_PAINT qDebug() << "leaving Legend::forceRebuild()"; #endif } void Legend::setSpacing( uint space ) { if ( d->spacing == space && d->layout->spacing() == int( space ) ) { return; } d->spacing = space; d->layout->setSpacing( space ); setNeedRebuild(); } uint Legend::spacing() const { return d->spacing; } void Legend::setDefaultColors() { Palette pal = Palette::defaultPalette(); for ( int i = 0; i < pal.size(); i++ ) { setBrush( i, pal.getBrush( i ) ); } } void Legend::setRainbowColors() { Palette pal = Palette::rainbowPalette(); for ( int i = 0; i < pal.size(); i++ ) { setBrush( i, pal.getBrush( i ) ); } } void Legend::setSubduedColors( bool ordered ) { Palette pal = Palette::subduedPalette(); if ( ordered ) { for ( int i = 0; i < pal.size(); i++ ) { setBrush( i, pal.getBrush( i ) ); } } else { static const int s_subduedColorsCount = 18; Q_ASSERT( pal.size() >= s_subduedColorsCount ); static const int order[ s_subduedColorsCount ] = { 0, 5, 10, 15, 2, 7, 12, 17, 4, 9, 14, 1, 6, 11, 16, 3, 8, 13 }; for ( int i = 0; i < s_subduedColorsCount; i++ ) { setBrush( i, pal.getBrush( order[i] ) ); } } } void Legend::resizeEvent( QResizeEvent * event ) { Q_UNUSED( event ); #ifdef DEBUG_LEGEND_PAINT qDebug() << "Legend::resizeEvent() called"; #endif forceRebuild(); sizeHint(); QTimer::singleShot( 0, this, SLOT(emitPositionChanged()) ); } void Legend::Private::fetchPaintOptions( Legend *q ) { modelLabels.clear(); modelBrushes.clear(); modelPens.clear(); modelMarkers.clear(); // retrieve the diagrams' settings for all non-hidden datasets for ( int i = 0; i < observers.size(); ++i ) { const AbstractDiagram* diagram = observers.at( i )->diagram(); if ( !diagram ) { continue; } const QStringList diagramLabels = diagram->datasetLabels(); const QList diagramBrushes = diagram->datasetBrushes(); const QList diagramPens = diagram->datasetPens(); const QList diagramMarkers = diagram->datasetMarkers(); const bool ascend = q->sortOrder() == Qt::AscendingOrder; int dataset = ascend ? 0 : diagramLabels.count() - 1; const int end = ascend ? diagramLabels.count() : -1; for ( ; dataset != end; dataset += ascend ? 1 : -1 ) { if ( diagram->isHidden( dataset ) || q->datasetIsHidden( dataset ) ) { continue; } modelLabels += diagramLabels[ dataset ]; modelBrushes += diagramBrushes[ dataset ]; modelPens += diagramPens[ dataset ]; modelMarkers += diagramMarkers[ dataset ]; } } Q_ASSERT( modelLabels.count() == modelBrushes.count() ); } QSizeF Legend::Private::markerSize( Legend *q, int dataset, qreal fontHeight ) const { QSizeF suppliedSize = q->markerAttributes( dataset ).markerSize(); if ( q->useAutomaticMarkerSize() || !suppliedSize.isValid() ) { return QSizeF( fontHeight, fontHeight ); } else { return suppliedSize; } } QSizeF Legend::Private::maxMarkerSize( Legend *q, qreal fontHeight ) const { QSizeF ret( 1.0, 1.0 ); if ( q->legendStyle() != LinesOnly ) { for ( int dataset = 0; dataset < modelLabels.count(); ++dataset ) { ret = ret.expandedTo( markerSize( q, dataset, fontHeight ) ); } } return ret; } HDatasetItem::HDatasetItem() : markerLine(0), label(0), separatorLine(0), spacer(0) {} static void updateToplevelLayout(QWidget *w) { while ( w ) { if ( w->isTopLevel() ) { // The null check has proved necessary during destruction of the Legend / Chart if ( w->layout() ) { w->layout()->update(); } break; } else { w = qobject_cast< QWidget * >( w->parent() ); Q_ASSERT( w ); } } } void Legend::buildLegend() { /* Grid layout partitioning (horizontal orientation): row zero is the title, row one the divider line between title and dataset items, row two for each item: line, marker, text label and separator line in that order. In a vertically oriented legend, row pairs (2, 3), ... contain a possible separator line (first row) and (second row) line, marker, text label each. */ d->destroyOldLayout(); if ( orientation() == Qt::Vertical ) { d->layout->setColumnStretch( 6, 1 ); } else { d->layout->setColumnStretch( 6, 0 ); } d->fetchPaintOptions( this ); const KChartEnums::MeasureOrientation measureOrientation = orientation() == Qt::Vertical ? KChartEnums::MeasureOrientationMinimum : KChartEnums::MeasureOrientationHorizontal; // legend caption if ( !titleText().isEmpty() && titleTextAttributes().isVisible() ) { TextLayoutItem* titleItem = new TextLayoutItem( titleText(), titleTextAttributes(), referenceArea(), measureOrientation, d->textAlignment ); titleItem->setParentWidget( this ); d->paintItems << titleItem; d->layout->addItem( titleItem, 0, 0, 1, 5, Qt::AlignCenter ); // The line between the title and the legend items, if any. if ( showLines() && d->modelLabels.count() ) { HorizontalLineLayoutItem* lineItem = new HorizontalLineLayoutItem; d->paintItems << lineItem; d->layout->addItem( lineItem, 1, 0, 1, 5, Qt::AlignCenter ); } } qreal fontHeight = textAttributes().calculatedFontSize( referenceArea(), measureOrientation ); { QFont tmpFont = textAttributes().font(); tmpFont.setPointSizeF( fontHeight ); if ( GlobalMeasureScaling::paintDevice() ) { fontHeight = QFontMetricsF( tmpFont, GlobalMeasureScaling::paintDevice() ).height(); } else { fontHeight = QFontMetricsF( tmpFont ).height(); } } const QSizeF maxMarkerSize = d->maxMarkerSize( this, fontHeight ); // If we show a marker on a line, we paint it after 8 pixels // of the line have been painted. This allows to see the line style // at the right side of the marker without the line needing to // be too long. // (having the marker in the middle of the line would require longer lines) const int lineLengthLeftOfMarker = 8; int maxLineLength = 18; { bool hasComplexPenStyle = false; for ( int dataset = 0; dataset < d->modelLabels.count(); ++dataset ) { const QPen pn = pen( dataset ); const Qt::PenStyle ps = pn.style(); if ( ps != Qt::NoPen ) { maxLineLength = qMin( pn.width() * 18, maxLineLength ); if ( ps != Qt::SolidLine ) { hasComplexPenStyle = true; } } } if ( legendStyle() != LinesOnly ) { if ( hasComplexPenStyle ) maxLineLength += lineLengthLeftOfMarker; maxLineLength += int( maxMarkerSize.width() ); } } // for all datasets: add (line)marker items and text items to the layout; // actual layout happens in flowHDatasetItems() for horizontal layout, here for vertical for ( int dataset = 0; dataset < d->modelLabels.count(); ++dataset ) { const int vLayoutRow = 2 + dataset * 2; HDatasetItem dsItem; // It is possible to set the marker brush through markerAttributes as well as // the dataset brush set in the diagram - the markerAttributes have higher precedence. MarkerAttributes markerAttrs = markerAttributes( dataset ); markerAttrs.setMarkerSize( d->markerSize( this, dataset, fontHeight ) ); const QBrush markerBrush = markerAttrs.markerColor().isValid() ? QBrush( markerAttrs.markerColor() ) : brush( dataset ); switch ( legendStyle() ) { case MarkersOnly: dsItem.markerLine = new MarkerLayoutItem( diagram(), markerAttrs, markerBrush, markerAttrs.pen(), Qt::AlignLeft | Qt::AlignVCenter ); break; case LinesOnly: dsItem.markerLine = new LineLayoutItem( diagram(), maxLineLength, pen( dataset ), d->legendLineSymbolAlignment, Qt::AlignCenter ); break; case MarkersAndLines: dsItem.markerLine = new LineWithMarkerLayoutItem( diagram(), maxLineLength, pen( dataset ), lineLengthLeftOfMarker, markerAttrs, markerBrush, markerAttrs.pen(), Qt::AlignCenter ); break; default: Q_ASSERT( false ); } dsItem.label = new TextLayoutItem( text( dataset ), textAttributes(), referenceArea(), measureOrientation, d->textAlignment ); dsItem.label->setParentWidget( this ); // horizontal layout is deferred to flowDatasetItems() if ( orientation() == Qt::Horizontal ) { d->hLayoutDatasets << dsItem; continue; } // (actual) vertical layout here if ( dsItem.markerLine ) { d->layout->addItem( dsItem.markerLine, vLayoutRow, 1, 1, 1, Qt::AlignCenter ); d->paintItems << dsItem.markerLine; } d->layout->addItem( dsItem.label, vLayoutRow, 3, 1, 1, Qt::AlignLeft | Qt::AlignVCenter ); d->paintItems << dsItem.label; // horizontal separator line, only between items if ( showLines() && dataset != d->modelLabels.count() - 1 ) { HorizontalLineLayoutItem* lineItem = new HorizontalLineLayoutItem; d->layout->addItem( lineItem, vLayoutRow + 1, 0, 1, 5, Qt::AlignCenter ); d->paintItems << lineItem; } } if ( orientation() == Qt::Horizontal ) { d->flowHDatasetItems( this ); } // vertical line (only in vertical mode) if ( orientation() == Qt::Vertical && showLines() && d->modelLabels.count() ) { VerticalLineLayoutItem* lineItem = new VerticalLineLayoutItem; d->paintItems << lineItem; d->layout->addItem( lineItem, 2, 2, d->modelLabels.count() * 2, 1 ); } updateToplevelLayout( this ); emit propertiesChanged(); #ifdef DEBUG_LEGEND_PAINT qDebug() << "leaving Legend::buildLegend()"; #endif } int HDatasetItem::height() const { return qMax( markerLine->sizeHint().height(), label->sizeHint().height() ); } void Legend::Private::reflowHDatasetItems( Legend *q ) { if (hLayoutDatasets.isEmpty()) { return; } paintItems.clear(); // Dissolve exactly the QHBoxLayout(s) created as "currentLine" in flowHDatasetItems - don't remove the // caption and line under the caption! Those are easily identified because they aren't QLayouts. for ( int i = layout->count() - 1; i >= 0; i-- ) { QLayoutItem *const item = layout->itemAt( i ); QLayout *const hbox = item->layout(); if ( !hbox ) { AbstractLayoutItem *alItem = dynamic_cast< AbstractLayoutItem * >( item ); Q_ASSERT( alItem ); paintItems << alItem; continue; } Q_ASSERT( dynamic_cast< QHBoxLayout * >( hbox ) ); layout->takeAt( i ); // detach children so they aren't deleted with the parent for ( int j = hbox->count() - 1; j >= 0; j-- ) { hbox->takeAt( j ); } delete hbox; } flowHDatasetItems( q ); } // this works pretty much like flow layout for text, and it is only applicable to dataset items // laid out horizontally void Legend::Private::flowHDatasetItems( Legend *q ) { const int separatorLineWidth = 3; // hardcoded in VerticalLineLayoutItem::sizeHint() const int allowedWidth = q->areaGeometry().width(); QHBoxLayout *currentLine = new QHBoxLayout; int columnSpan = 5; int mainLayoutColumn = 0; int row = 0; if ( !titleText.isEmpty() && titleTextAttributes.isVisible() ) { ++row; if (q->showLines()){ ++row; } } layout->addItem( currentLine, row, mainLayoutColumn, /*rowSpan*/1 , columnSpan, Qt::AlignLeft | Qt::AlignVCenter ); mainLayoutColumn += columnSpan; for ( int dataset = 0; dataset < hLayoutDatasets.size(); dataset++ ) { HDatasetItem *hdsItem = &hLayoutDatasets[ dataset ]; bool spacerUsed = false; bool separatorUsed = false; if ( !currentLine->isEmpty() ) { const int separatorWidth = ( q->showLines() ? separatorLineWidth : 0 ) + q->spacing(); const int payloadWidth = hdsItem->markerLine->sizeHint().width() + hdsItem->label->sizeHint().width(); if ( currentLine->sizeHint().width() + separatorWidth + payloadWidth > allowedWidth ) { // too wide, "line break" #ifdef DEBUG_LEGEND_PAINT qDebug() << Q_FUNC_INFO << "break" << mainLayoutColumn << currentLine->sizeHint().width() << currentLine->sizeHint().width() + separatorWidth + payloadWidth << allowedWidth; #endif currentLine = new QHBoxLayout; layout->addItem( currentLine, row, mainLayoutColumn, /*rowSpan*/1 , columnSpan, Qt::AlignLeft | Qt::AlignVCenter ); mainLayoutColumn += columnSpan; } else { // > 1 dataset item in line, put spacing and maybe a separator between them if ( !hdsItem->spacer ) { hdsItem->spacer = new QSpacerItem( q->spacing(), 1 ); } currentLine->addItem( hdsItem->spacer ); spacerUsed = true; if ( q->showLines() ) { if ( !hdsItem->separatorLine ) { hdsItem->separatorLine = new VerticalLineLayoutItem; } paintItems << hdsItem->separatorLine; currentLine->addItem( hdsItem->separatorLine ); separatorUsed = true; } } } // those have no parents in the current layout, so they wouldn't get cleaned up otherwise if ( !spacerUsed ) { delete hdsItem->spacer; hdsItem->spacer = 0; } if ( !separatorUsed ) { delete hdsItem->separatorLine; hdsItem->separatorLine = 0; } currentLine->addItem( hdsItem->markerLine ); paintItems << hdsItem->markerLine; currentLine->addItem( hdsItem->label ); paintItems << hdsItem->label; } } bool Legend::hasHeightForWidth() const { // this is better than using orientation() because, for layout purposes, we're not height-for-width // *yet* before buildLegend() has been called, and the layout logic might get upset if we say // something that will only be true in the future return !d->hLayoutDatasets.isEmpty(); } int Legend::heightForWidth( int width ) const { if ( d->hLayoutDatasets.isEmpty() ) { return -1; } int ret = 0; // space for caption and line under caption (if any) for (int i = 0; i < 2; i++) { if ( QLayoutItem *item = d->layout->itemAtPosition( i, 0 ) ) { ret += item->sizeHint().height(); } } const int separatorLineWidth = 3; // ### hardcoded in VerticalLineLayoutItem::sizeHint() int currentLineWidth = 0; int currentLineHeight = 0; Q_FOREACH( const HDatasetItem &hdsItem, d->hLayoutDatasets ) { const int payloadWidth = hdsItem.markerLine->sizeHint().width() + hdsItem.label->sizeHint().width(); if ( !currentLineWidth ) { // first iteration currentLineWidth = payloadWidth; } else { const int separatorWidth = ( showLines() ? separatorLineWidth : 0 ) + spacing(); currentLineWidth += separatorWidth + payloadWidth; if ( currentLineWidth > width ) { // too wide, "line break" #ifdef DEBUG_LEGEND_PAINT qDebug() << Q_FUNC_INFO << "heightForWidth break" << currentLineWidth << currentLineWidth + separatorWidth + payloadWidth << width; #endif ret += currentLineHeight + spacing(); currentLineWidth = payloadWidth; currentLineHeight = 0; } } currentLineHeight = qMax( currentLineHeight, hdsItem.height() ); } ret += currentLineHeight; // one less spacings than lines return ret; } void Legend::Private::destroyOldLayout() { // in the horizontal layout case, the QHBoxLayout destructor also deletes child layout items // (it isn't documented that QLayoutItems delete their children) for ( int i = layout->count() - 1; i >= 0; i-- ) { delete layout->takeAt( i ); } Q_ASSERT( !layout->count() ); hLayoutDatasets.clear(); paintItems.clear(); } void Legend::setHiddenDatasets( const QList hiddenDatasets ) { d->hiddenDatasets = hiddenDatasets; } const QList Legend::hiddenDatasets() const { return d->hiddenDatasets; } void Legend::setDatasetHidden( uint dataset, bool hidden ) { if ( hidden && !d->hiddenDatasets.contains( dataset ) ) { d->hiddenDatasets.append( dataset ); } else if ( !hidden && d->hiddenDatasets.contains( dataset ) ) { d->hiddenDatasets.removeAll( dataset ); } } bool Legend::datasetIsHidden( uint dataset ) const { return d->hiddenDatasets.contains( dataset ); } diff --git a/src/KChart/KChartMath_p.h b/src/KChart/KChartMath_p.h index 9e75479..9801d01 100644 --- a/src/KChart/KChartMath_p.h +++ b/src/KChart/KChartMath_p.h @@ -1,67 +1,67 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef KDAB_LIB_FAKES_H -#define KDAB_LIB_FAKES_H +#ifndef KCHARTMATH_P_H +#define KCHARTMATH_P_H #if defined Q_OS_DARWIN #include #endif #include #ifdef Q_OS_SOLARIS #include #include #endif #include #include #define NaN std::numeric_limits< qreal >::quiet_NaN() #define signalingNaN std::numeric_limits< qreal >::signaling_NaN() #ifndef M_PI #define M_PI 3.14159265358979323846 #endif #define DEGTORAD(d) (d)*M_PI/180 // We use our own ISNAN / ISINF in the code to detect // that we defined them. // reason: Windows does not have isnan() / isinf() #if defined (Q_OS_WIN) #include #define ISNAN(x ) _isnan(x ) #define ISINF(x ) (!(_finite(x ) + _isnan(x ) ) ) #elif defined (Q_OS_DARWIN) // OS X does have isnan() & isinf() in math.h, but it appears to be // required to cast the argument to a double explicitly. #define ISNAN(x) isnan(double(x)) #define ISINF(x) isinf(double(x)) #elif defined (Q_OS_CYGWIN) || __cplusplus >= 201103L #define ISNAN(x) std::isnan(x) #define ISINF(x) std::isinf(x) #else #define ISNAN(x) isnan(x) #define ISINF(x) isinf(x) #endif #endif diff --git a/src/KChart/KChartModelDataCache_p.cpp b/src/KChart/KChartModelDataCache_p.cpp index 2de3e1e..9779d91 100644 --- a/src/KChart/KChartModelDataCache_p.cpp +++ b/src/KChart/KChartModelDataCache_p.cpp @@ -1,98 +1,98 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "KChartModelDataCache_p.h" #include using namespace KChart::ModelDataCachePrivate; ModelSignalMapperConnector::ModelSignalMapperConnector( ModelSignalMapper& mapper ) : QObject( 0 ), m_mapper( mapper ) { } ModelSignalMapperConnector::~ModelSignalMapperConnector() { } void ModelSignalMapperConnector::connectSignals( QAbstractItemModel* model ) { - connect( model, SIGNAL( destroyed() ), this, SLOT( resetModel() ) ); - connect( model, SIGNAL( columnsInserted( QModelIndex, int, int ) ), this, SLOT( columnsInserted( QModelIndex, int, int ) ) ); - connect( model, SIGNAL( columnsRemoved( QModelIndex, int, int ) ), this, SLOT( columnsRemoved( QModelIndex, int, int ) ) ); - connect( model, SIGNAL( dataChanged( QModelIndex, QModelIndex ) ), this, SLOT( dataChanged( QModelIndex, QModelIndex ) ) ); - connect( model, SIGNAL( layoutChanged() ), this, SLOT( layoutChanged() ) ); - connect( model, SIGNAL( modelReset() ), this, SLOT( modelReset() ) ); - connect( model, SIGNAL( rowsInserted( QModelIndex, int, int ) ), this, SLOT( rowsInserted( QModelIndex, int, int )) ); - connect( model, SIGNAL( rowsRemoved( QModelIndex, int, int ) ), this, SLOT( rowsRemoved( QModelIndex, int, int ) ) ); + connect( model, SIGNAL(destroyed()), this, SLOT(resetModel()) ); + connect( model, SIGNAL(columnsInserted(QModelIndex,int,int)), this, SLOT(columnsInserted(QModelIndex,int,int)) ); + connect( model, SIGNAL(columnsRemoved(QModelIndex,int,int)), this, SLOT(columnsRemoved(QModelIndex,int,int)) ); + connect( model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(dataChanged(QModelIndex,QModelIndex)) ); + connect( model, SIGNAL(layoutChanged()), this, SLOT(layoutChanged()) ); + connect( model, SIGNAL(modelReset()), this, SLOT(modelReset()) ); + connect( model, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(rowsInserted(QModelIndex,int,int)) ); + connect( model, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(rowsRemoved(QModelIndex,int,int)) ); } void ModelSignalMapperConnector::disconnectSignals( QAbstractItemModel* model ) { - disconnect( model, SIGNAL( destroyed() ), this, SLOT( resetModel() ) ); - disconnect( model, SIGNAL( columnsInserted( QModelIndex, int, int ) ), this, SLOT( columnsInserted( QModelIndex, int, int ) ) ); - disconnect( model, SIGNAL( columnsRemoved( QModelIndex, int, int ) ), this, SLOT( columnsRemoved( QModelIndex, int, int ) ) ); - disconnect( model, SIGNAL( dataChanged( QModelIndex, QModelIndex ) ), this, SLOT( dataChanged( QModelIndex, QModelIndex ) ) ); - disconnect( model, SIGNAL( layoutChanged() ), this, SLOT( layoutChanged() ) ); - disconnect( model, SIGNAL( modelReset() ), this, SLOT( modelReset() ) ); - disconnect( model, SIGNAL( rowsInserted( QModelIndex, int, int ) ), this, SLOT( rowsInserted( QModelIndex, int, int )) ); - disconnect( model, SIGNAL( rowsRemoved( QModelIndex, int, int ) ), this, SLOT( rowsRemoved( QModelIndex, int, int ) ) ); + disconnect( model, SIGNAL(destroyed()), this, SLOT(resetModel()) ); + disconnect( model, SIGNAL(columnsInserted(QModelIndex,int,int)), this, SLOT(columnsInserted(QModelIndex,int,int)) ); + disconnect( model, SIGNAL(columnsRemoved(QModelIndex,int,int)), this, SLOT(columnsRemoved(QModelIndex,int,int)) ); + disconnect( model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(dataChanged(QModelIndex,QModelIndex)) ); + disconnect( model, SIGNAL(layoutChanged()), this, SLOT(layoutChanged()) ); + disconnect( model, SIGNAL(modelReset()), this, SLOT(modelReset()) ); + disconnect( model, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(rowsInserted(QModelIndex,int,int)) ); + disconnect( model, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(rowsRemoved(QModelIndex,int,int)) ); } void ModelSignalMapperConnector::resetModel() { m_mapper.resetModel(); } void ModelSignalMapperConnector::columnsInserted( const QModelIndex& parent, int start, int end ) { m_mapper.columnsInserted( parent, start, end ); } void ModelSignalMapperConnector::columnsRemoved( const QModelIndex& parent, int start, int end ) { m_mapper.columnsRemoved( parent, start, end ); } void ModelSignalMapperConnector::dataChanged( const QModelIndex& topLeft, const QModelIndex& bottomRight ) { m_mapper.dataChanged( topLeft, bottomRight ); } void ModelSignalMapperConnector::layoutChanged() { m_mapper.layoutChanged(); } void ModelSignalMapperConnector::modelReset() { m_mapper.modelReset(); } void ModelSignalMapperConnector::rowsInserted( const QModelIndex& parent, int start, int end ) { m_mapper.rowsInserted( parent, start, end ); } void ModelSignalMapperConnector::rowsRemoved( const QModelIndex& parent, int start, int end ) { m_mapper.rowsRemoved( parent, start, end ); } diff --git a/src/KChart/KChartThreeDLineAttributes_p.h b/src/KChart/KChartThreeDLineAttributes_p.h index d335f78..0541839 100644 --- a/src/KChart/KChartThreeDLineAttributes_p.h +++ b/src/KChart/KChartThreeDLineAttributes_p.h @@ -1,61 +1,61 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef KCHARTTHREEDLINESATTRIBUTES_P_H -#define KCHARTTHREEDLINESATTRIBUTES_P_H +#ifndef KCHARTTHREEDLINEATTRIBUTES_P_H +#define KCHARTTHREEDLINEATTRIBUTES_P_H // // W A R N I N G // ------------- // // This file is not part of the KD Chart API. It exists purely as an // implementation detail. This header file may change from version to // version without notice, or even be removed. // // We mean it. // #include #include #include "KChartMath_p.h" namespace KChart { /** * \internal */ class Q_DECL_HIDDEN ThreeDLineAttributes::Private : public AbstractThreeDAttributes::Private { friend class ThreeDLineAttributes; public: Private(); private: //ThreeDLines uint lineXRotation; uint lineYRotation; }; KCHART_DERIVED_PRIVATE_FOOTER(ThreeDLineAttributes, AbstractThreeDAttributes) } -#endif // KCHARTTHREEDLINESATTRIBUTES_P_H +#endif // KCHARTTHREEDLINEATTRIBUTES_P_H diff --git a/src/KChart/KTextDocument.h b/src/KChart/KTextDocument.h index 67e9971..0c483ec 100644 --- a/src/KChart/KTextDocument.h +++ b/src/KChart/KTextDocument.h @@ -1,64 +1,64 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef KDTEXTDOCUMENT_H -#define KDTEXTDOCUMENT_H +#ifndef KTEXTDOCUMENT_H +#define KTEXTDOCUMENT_H // // W A R N I N G // ------------- // // This file is not part of the KD Chart API. It exists purely as an // implementation detail. This header file may change from version to // version without notice, or even be removed. // // We mean it. // #include #include /** * KTextDocument is an internally used enhanced QTextDocument * \internal */ class KTextDocument : public QTextDocument { Q_OBJECT public: explicit KTextDocument( QObject* parent = 0 ); explicit KTextDocument( const QString& text, QObject* parent = 0 ); ~KTextDocument(); QSize sizeHint(); QSize minimumSizeHint(); private: QSize sizeForWidth( int width ); private: bool mHintValid; QSize mSizeHint; QSize mMinimumSizeHint; }; -#endif /* KDTEXTDOCUMENT_H */ +#endif /* KTEXTDOCUMENT_H */ diff --git a/src/KChart/Polar/KChartPolarCoordinatePlane.cpp b/src/KChart/Polar/KChartPolarCoordinatePlane.cpp index 948bada..ded8aca 100644 --- a/src/KChart/Polar/KChartPolarCoordinatePlane.cpp +++ b/src/KChart/Polar/KChartPolarCoordinatePlane.cpp @@ -1,376 +1,376 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KD Chart library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "KChartPolarCoordinatePlane.h" #include "KChartPolarCoordinatePlane_p.h" #include "KChartPainterSaver_p.h" #include "KChartChart.h" #include "KChartPaintContext.h" #include "KChartAbstractDiagram.h" #include "KChartAbstractPolarDiagram.h" #include "KChartPolarDiagram.h" #include "KChartMath_p.h" #include #include #include #include #include using namespace KChart; #define d d_func() PolarCoordinatePlane::PolarCoordinatePlane ( Chart* parent ) : AbstractCoordinatePlane ( new Private(), parent ) { // this bloc left empty intentionally } PolarCoordinatePlane::~PolarCoordinatePlane() { // this bloc left empty intentionally } void PolarCoordinatePlane::init() { // this bloc left empty intentionally } void PolarCoordinatePlane::addDiagram ( AbstractDiagram* diagram ) { Q_ASSERT_X ( dynamic_cast ( diagram ), "PolarCoordinatePlane::addDiagram", "Only polar" "diagrams can be added to a polar coordinate plane!" ); AbstractCoordinatePlane::addDiagram ( diagram ); - connect ( diagram, SIGNAL ( layoutChanged ( AbstractDiagram* ) ), - SLOT ( slotLayoutChanged ( AbstractDiagram* ) ) ); + connect ( diagram, SIGNAL (layoutChanged(AbstractDiagram*)), + SLOT (slotLayoutChanged(AbstractDiagram*)) ); } void PolarCoordinatePlane::paint ( QPainter* painter ) { AbstractDiagramList diags = diagrams(); if ( d->coordinateTransformations.size() != diags.size() ) { // diagrams have not been set up yet return; } // need at least one so d->currentTransformation can be a valid pointer Q_ASSERT( !d->coordinateTransformations.isEmpty() ); PaintContext ctx; ctx.setPainter ( painter ); ctx.setCoordinatePlane ( this ); ctx.setRectangle ( geometry() /*d->contentRect*/ ); // 1. ask (only!) PolarDiagrams if they need additional space for data labels / data comments const qreal oldZoomX = zoomFactorX(); const qreal oldZoomY = zoomFactorY(); d->newZoomX = oldZoomX; d->newZoomY = oldZoomY; for ( int i = 0; i < diags.size(); i++ ) { d->currentTransformation = & ( d->coordinateTransformations[i] ); qreal zoomX; qreal zoomY; PolarDiagram* polarDia = dynamic_cast ( diags[i] ); if ( polarDia ) { polarDia->paint( &ctx, true, zoomX, zoomY ); d->newZoomX = qMin( d->newZoomX, zoomX ); d->newZoomY = qMin( d->newZoomY, zoomY ); } } if ( d->newZoomX != oldZoomX || d->newZoomY != oldZoomY ) { //qDebug() << "new zoom:" << d->newZoomY << " old zoom:" << oldZoomY; d->currentTransformation = 0; // not painting anymore until we get called again QMetaObject::invokeMethod( this, "adjustZoomAndRepaint", Qt::QueuedConnection ); return; } // 2. there was room enough for the labels, so start drawing // paint the coordinate system rulers: d->currentTransformation = &d->coordinateTransformations.first(); d->grid->drawGrid( &ctx ); // paint the diagrams which will re-use their DataValueTextInfoList(s) filled in step 1: for ( int i = 0; i < diags.size(); i++ ) { d->currentTransformation = & ( d->coordinateTransformations[i] ); PainterSaver painterSaver( painter ); PolarDiagram* polarDia = dynamic_cast( diags[i] ); if ( polarDia ) { qreal dummy1, dummy2; polarDia->paint( &ctx, false, dummy1, dummy2 ); } else { diags[i]->paint( &ctx ); } } d->currentTransformation = 0; } void PolarCoordinatePlane::adjustZoomAndRepaint() { const qreal newZoom = qMin(d->newZoomX, d->newZoomY); setZoomFactors(newZoom, newZoom); update(); } void PolarCoordinatePlane::resizeEvent ( QResizeEvent* ) { d->initialResizeEventReceived = true; layoutDiagrams(); } void PolarCoordinatePlane::layoutDiagrams() { // the rectangle the diagrams cover in the *plane*: // (Why -3? We save 1px on each side for the antialiased drawing, and // respect the way QPainter calculates the width of a painted rect (the // size is the rectangle size plus the pen width). This way, most clipping // for regular pens should be avoided. When pens with a penWidth or larger // than 1 are used, this may not b sufficient. const QRect rect( areaGeometry() ); d->contentRect = QRectF ( 1, 1, rect.width() - 3, rect.height() - 3 ); const ZoomParameters zoom = d->coordinateTransformations.isEmpty() ? ZoomParameters() : d->coordinateTransformations.front().zoom; // FIXME distribute space according to options: const qreal oldStartPosition = startPosition(); d->coordinateTransformations.clear(); Q_FOREACH( AbstractDiagram* diagram, diagrams() ) { AbstractPolarDiagram *polarDiagram = dynamic_cast( diagram ); Q_ASSERT( polarDiagram ); QPair dataBoundariesPair = polarDiagram->dataBoundaries(); const qreal angleUnit = 360 / polarDiagram->valueTotals(); //qDebug() << "--------------------------------------------------------"; const qreal radius = qAbs( dataBoundariesPair.first.y() ) + dataBoundariesPair.second.y(); //qDebug() << radius <<"="<contentRect.width(); const qreal planeHeight = d->contentRect.height(); const qreal radiusUnit = qMin( planeWidth, planeHeight ) / diagramWidth; //qDebug() << radiusUnit <<"=" << "qMin( "<contentRect.topLeft(); CoordinateTransformation diagramTransposition; diagramTransposition.originTranslation = coordinateOrigin; diagramTransposition.radiusUnit = radiusUnit; diagramTransposition.angleUnit = angleUnit; diagramTransposition.startPosition = oldStartPosition; diagramTransposition.zoom = zoom; diagramTransposition.minValue = dataBoundariesPair.first.y() < 0 ? dataBoundariesPair.first.y() : 0.0; d->coordinateTransformations.append( diagramTransposition ); } update(); } const QPointF PolarCoordinatePlane::translate( const QPointF& diagramPoint ) const { Q_ASSERT_X ( d->currentTransformation != 0, "PolarCoordinatePlane::translate", "Only call translate() from within paint()." ); return d->currentTransformation->translate ( diagramPoint ); } const QPointF PolarCoordinatePlane::translatePolar( const QPointF& diagramPoint ) const { Q_ASSERT_X ( d->currentTransformation != 0, "PolarCoordinatePlane::translate", "Only call translate() from within paint()." ); return d->currentTransformation->translatePolar ( diagramPoint ); } qreal PolarCoordinatePlane::angleUnit() const { Q_ASSERT_X ( d->currentTransformation != 0, "PolarCoordinatePlane::angleUnit", "Only call angleUnit() from within paint()." ); return d->currentTransformation->angleUnit; } qreal PolarCoordinatePlane::radiusUnit() const { Q_ASSERT_X ( d->currentTransformation != 0, "PolarCoordinatePlane::radiusUnit", "Only call radiusUnit() from within paint()." ); return d->currentTransformation->radiusUnit; } void PolarCoordinatePlane::slotLayoutChanged ( AbstractDiagram* ) { if ( d->initialResizeEventReceived ) layoutDiagrams(); } void PolarCoordinatePlane::setStartPosition( qreal degrees ) { Q_ASSERT_X ( diagram(), "PolarCoordinatePlane::setStartPosition", "setStartPosition() needs a diagram to be associated to the plane." ); for ( CoordinateTransformationList::iterator it = d->coordinateTransformations.begin(); it != d->coordinateTransformations.end(); ++it ) { CoordinateTransformation& trans = *it; trans.startPosition = degrees; } } qreal PolarCoordinatePlane::startPosition() const { return d->coordinateTransformations.isEmpty() ? 0.0 : d->coordinateTransformations.first().startPosition; } qreal PolarCoordinatePlane::zoomFactorX() const { return d->coordinateTransformations.isEmpty() ? 1.0 : d->coordinateTransformations.first().zoom.xFactor; } qreal PolarCoordinatePlane::zoomFactorY() const { return d->coordinateTransformations.isEmpty() ? 1.0 : d->coordinateTransformations.first().zoom.yFactor; } void PolarCoordinatePlane::setZoomFactors( qreal factorX, qreal factorY ) { setZoomFactorX( factorX ); setZoomFactorY( factorY ); } void PolarCoordinatePlane::setZoomFactorX( qreal factor ) { for ( CoordinateTransformationList::iterator it = d->coordinateTransformations.begin(); it != d->coordinateTransformations.end(); ++it ) { CoordinateTransformation& trans = *it; trans.zoom.xFactor = factor; } } void PolarCoordinatePlane::setZoomFactorY( qreal factor ) { for ( CoordinateTransformationList::iterator it = d->coordinateTransformations.begin(); it != d->coordinateTransformations.end(); ++it ) { CoordinateTransformation& trans = *it; trans.zoom.yFactor = factor; } } QPointF PolarCoordinatePlane::zoomCenter() const { return d->coordinateTransformations.isEmpty() ? QPointF( 0.5, 0.5 ) : QPointF( d->coordinateTransformations.first().zoom.xCenter, d->coordinateTransformations.first().zoom.yCenter ); } void PolarCoordinatePlane::setZoomCenter( const QPointF& center ) { for ( CoordinateTransformationList::iterator it = d->coordinateTransformations.begin(); it != d->coordinateTransformations.end(); ++it ) { CoordinateTransformation& trans = *it; trans.zoom.xCenter = center.x(); trans.zoom.yCenter = center.y(); } } DataDimensionsList PolarCoordinatePlane::getDataDimensionsList() const { DataDimensionsList l; //FIXME(khz): do the real calculation return l; } void KChart::PolarCoordinatePlane::setGridAttributes( bool circular, const GridAttributes& a ) { if ( circular ) d->gridAttributesCircular = a; else d->gridAttributesSagittal = a; setHasOwnGridAttributes( circular, true ); update(); emit propertiesChanged(); } void KChart::PolarCoordinatePlane::resetGridAttributes( bool circular ) { setHasOwnGridAttributes( circular, false ); update(); } const GridAttributes KChart::PolarCoordinatePlane::gridAttributes( bool circular ) const { if ( hasOwnGridAttributes( circular ) ) { if ( circular ) return d->gridAttributesCircular; else return d->gridAttributesSagittal; } else { return globalGridAttributes(); } } QRectF KChart::PolarCoordinatePlane::Private::contentsRect( const KChart::PolarCoordinatePlane* plane ) { QRectF contentsRect; QPointF referencePointAtTop = plane->translate( QPointF( 1, 0 ) ); QPointF temp = plane->translate( QPointF( 0, 0 ) ) - referencePointAtTop; const qreal offset = temp.y(); referencePointAtTop.setX( referencePointAtTop.x() - offset ); contentsRect.setTopLeft( referencePointAtTop ); contentsRect.setBottomRight( referencePointAtTop + QPointF( 2.0 * offset, 2.0 * offset) ); return contentsRect; } void KChart::PolarCoordinatePlane::setHasOwnGridAttributes( bool circular, bool on ) { if ( circular ) d->hasOwnGridAttributesCircular = on; else d->hasOwnGridAttributesSagittal = on; emit propertiesChanged(); } bool KChart::PolarCoordinatePlane::hasOwnGridAttributes( bool circular ) const { return ( circular ) ? d->hasOwnGridAttributesCircular : d->hasOwnGridAttributesSagittal; } diff --git a/src/KGantt/kganttconstraintmodel.h b/src/KGantt/kganttconstraintmodel.h index a9e23e3..75c8368 100644 --- a/src/KGantt/kganttconstraintmodel.h +++ b/src/KGantt/kganttconstraintmodel.h @@ -1,83 +1,82 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KGantt library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef KGANTTCONSTRAINTMODEL_H #define KGANTTCONSTRAINTMODEL_H #include #include #include "kganttglobal.h" #include "kganttconstraint.h" namespace KGantt { class KGANTT_EXPORT ConstraintModel : public QObject { Q_OBJECT KGANTT_DECLARE_PRIVATE_DERIVED_PARENT( ConstraintModel, QObject* ) public: explicit ConstraintModel( QObject* parent=0 ); virtual ~ConstraintModel(); /** * Subclassing ConstraintModel and overriding addConstraint() and removeConstraint() can provide * re-entrancy issues in the ConstraintModel<->ConstraintProxy interaction. Therefore it is recommed * to better subclass GraphicsView and override addConstraint() there. */ virtual void addConstraint( const Constraint& c ); virtual bool removeConstraint( const Constraint& c ); void clear(); void cleanup(); QList constraints() const; bool hasConstraint( const Constraint& c ) const; inline bool hasConstraint( const QModelIndex& s, const QModelIndex& e ) const; QList constraintsForIndex( const QModelIndex& ) const; Q_SIGNALS: void constraintAdded(const KGantt::Constraint&); void constraintRemoved(const KGantt::Constraint&); private: Private* _d; }; inline const ConstraintModel::Private* ConstraintModel::d_func() const { return _d; } inline ConstraintModel::Private* ConstraintModel::d_func() { return _d; } inline bool ConstraintModel::hasConstraint( const QModelIndex& s, const QModelIndex& e ) const { return hasConstraint( Constraint( s, e ) ); } } #ifndef QT_NO_DEBUG_STREAM -#include QDebug KGANTT_EXPORT operator<<( QDebug dbg, const KGantt::ConstraintModel& model ); inline QDebug operator<<( QDebug dbg, KGantt::ConstraintModel* model ) { return operator<<(dbg,*model); } #endif /* QT_NO_DEBUG_STREAM */ #endif /* KGANTTCONSTRAINTMODEL_H */ diff --git a/src/KGantt/kganttconstraintproxy.cpp b/src/KGantt/kganttconstraintproxy.cpp index a8779eb..b4d9532 100644 --- a/src/KGantt/kganttconstraintproxy.cpp +++ b/src/KGantt/kganttconstraintproxy.cpp @@ -1,142 +1,142 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KGantt library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "kganttconstraintproxy.h" #include "kganttconstraintmodel.h" #include using namespace KGantt; /*!\class KGantt::ConstraintProxy * \internal */ ConstraintProxy::ConstraintProxy( QObject* parent ) : QObject( parent ) { } ConstraintProxy::~ConstraintProxy() { } void ConstraintProxy::setSourceModel( ConstraintModel* src ) { if ( m_source ) m_source->disconnect( this ); m_source = src; copyFromSource(); - connect( m_source, SIGNAL( constraintAdded( const KGantt::Constraint& ) ), - this, SLOT( slotSourceConstraintAdded( const KGantt::Constraint& ) ) ); - connect( m_source, SIGNAL( constraintRemoved( const KGantt::Constraint& ) ), - this, SLOT( slotSourceConstraintRemoved( const KGantt::Constraint& ) ) ); + connect( m_source, SIGNAL(constraintAdded(KGantt::Constraint)), + this, SLOT(slotSourceConstraintAdded(KGantt::Constraint)) ); + connect( m_source, SIGNAL(constraintRemoved(KGantt::Constraint)), + this, SLOT(slotSourceConstraintRemoved(KGantt::Constraint)) ); } void ConstraintProxy::setDestinationModel( ConstraintModel* dest ) { if ( m_destination ) m_destination->disconnect( this ); m_destination = dest; copyFromSource(); - connect( m_destination, SIGNAL( constraintAdded( const KGantt::Constraint& ) ), - this, SLOT( slotDestinationConstraintAdded( const KGantt::Constraint& ) ) ); - connect( m_destination, SIGNAL( constraintRemoved( const KGantt::Constraint& ) ), - this, SLOT( slotDestinationConstraintRemoved( const KGantt::Constraint& ) ) ); + connect( m_destination, SIGNAL(constraintAdded(KGantt::Constraint)), + this, SLOT(slotDestinationConstraintAdded(KGantt::Constraint)) ); + connect( m_destination, SIGNAL(constraintRemoved(KGantt::Constraint)), + this, SLOT(slotDestinationConstraintRemoved(KGantt::Constraint)) ); } void ConstraintProxy::setProxyModel( QAbstractProxyModel* proxy ) { if ( m_proxy == proxy ) return; if ( m_proxy ) m_proxy->disconnect( this ); m_proxy = proxy; if ( m_proxy ) { - connect( m_proxy, SIGNAL( layoutChanged() ), this, SLOT( slotLayoutChanged() ) ); - connect( m_proxy, SIGNAL( modelReset() ), this, SLOT( slotLayoutChanged() ) ); + connect( m_proxy, SIGNAL(layoutChanged()), this, SLOT(slotLayoutChanged()) ); + connect( m_proxy, SIGNAL(modelReset()), this, SLOT(slotLayoutChanged()) ); } } ConstraintModel* ConstraintProxy::sourceModel() const { return m_source; } ConstraintModel* ConstraintProxy::destinationModel() const { return m_destination; } QAbstractProxyModel* ConstraintProxy::proxyModel() const { return m_proxy; } void ConstraintProxy::copyFromSource() { if ( m_destination ) { m_destination->clear(); if ( !m_source ) return; const QList lst = m_source->constraints(); Q_FOREACH( const Constraint& c, lst ) { Constraint temp( m_proxy->mapFromSource( c.startIndex() ), m_proxy->mapFromSource( c.endIndex() ), c.type(), c.relationType(), c.dataMap() ); m_destination->addConstraint( temp ); } } } void ConstraintProxy::slotSourceConstraintAdded( const KGantt::Constraint& c ) { if ( m_destination ) { Constraint temp( m_proxy->mapFromSource( c.startIndex() ), m_proxy->mapFromSource( c.endIndex() ), c.type(), c.relationType(), c.dataMap() ); m_destination->addConstraint( temp ); } } void ConstraintProxy::slotSourceConstraintRemoved( const KGantt::Constraint& c ) { if ( m_destination ) { Constraint temp( m_proxy->mapFromSource( c.startIndex() ), m_proxy->mapFromSource( c.endIndex() ), c.type(), c.relationType(), c.dataMap() ); m_destination->removeConstraint( temp ); } } void ConstraintProxy::slotDestinationConstraintAdded( const KGantt::Constraint& c ) { if ( m_source ) { Constraint temp( m_proxy->mapToSource( c.startIndex() ), m_proxy->mapToSource( c.endIndex() ), c.type(), c.relationType(), c.dataMap() ); m_source->addConstraint( temp ); } } void ConstraintProxy::slotDestinationConstraintRemoved( const KGantt::Constraint& c ) { if ( m_source ) { Constraint temp( m_proxy->mapToSource( c.startIndex() ), m_proxy->mapToSource( c.endIndex() ), c.type(), c.relationType(), c.dataMap() ); m_source->removeConstraint( temp ); } } void ConstraintProxy::slotLayoutChanged() { copyFromSource(); } #include "moc_kganttconstraintproxy.cpp" diff --git a/src/KGantt/kganttforwardingproxymodel.cpp b/src/KGantt/kganttforwardingproxymodel.cpp index e9865f5..605432c 100644 --- a/src/KGantt/kganttforwardingproxymodel.cpp +++ b/src/KGantt/kganttforwardingproxymodel.cpp @@ -1,322 +1,322 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KGantt library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "kganttforwardingproxymodel.h" #include #include using namespace KGantt; typedef QAbstractProxyModel BASE; /*! Constructor. Creates a new ForwardingProxyModel with * parent \a parent */ ForwardingProxyModel::ForwardingProxyModel( QObject* parent ) : BASE( parent ) { } ForwardingProxyModel::~ForwardingProxyModel() { } /*! Converts indexes in the source model to indexes in the proxy model */ QModelIndex ForwardingProxyModel::mapFromSource ( const QModelIndex & sourceIndex ) const { if ( !sourceIndex.isValid() ) return QModelIndex(); assert( sourceIndex.model() == sourceModel() ); // Create an index that preserves the internal pointer from the source; // this way KDDataConverterProxyModel preserves the structure of the source model return createIndex( sourceIndex.row(), sourceIndex.column(), sourceIndex.internalPointer() ); } #ifdef __GNUC__ #if __GNUC__ > 3 #define ATTRIBUTE __attribute__((__may_alias__)) #endif #else #define ATTRIBUTE #endif namespace { // Think this is ugly? Well, it's not from me, it comes from QProxyModel struct ATTRIBUTE KDPrivateModelIndex { int r, c; void *p; const QAbstractItemModel *m; }; } /*! Converts indexes in the proxy model to indexes in the source model */ QModelIndex ForwardingProxyModel::mapToSource ( const QModelIndex & proxyIndex ) const { if ( !proxyIndex.isValid() ) return QModelIndex(); assert( proxyIndex.model() == this ); // So here we need to create a source index which holds that internal pointer. // No way to pass it to sourceModel()->index... so we have to do the ugly way: QModelIndex sourceIndex; KDPrivateModelIndex* hack = reinterpret_cast(&sourceIndex); hack->r = proxyIndex.row(); hack->c = proxyIndex.column(); hack->p = proxyIndex.internalPointer(); hack->m = sourceModel(); assert( sourceIndex.isValid() ); return sourceIndex; } /*! Sets the model to be used as the source model for this proxy. * The proxy does not take ownership of the model. * \see QAbstractProxyModel::setSourceModel */ void ForwardingProxyModel::setSourceModel( QAbstractItemModel* model ) { if ( sourceModel() ) sourceModel()->disconnect( this ); BASE::setSourceModel( model ); if (!model) return; connect( model, SIGNAL(modelAboutToBeReset()), this, SLOT(sourceModelAboutToBeReset()) ); connect( model, SIGNAL(modelReset()), this, SLOT(sourceModelReset()) ); connect( model, SIGNAL(layoutAboutToBeChanged()), this, SLOT(sourceLayoutAboutToBeChanged()) ); connect( model, SIGNAL(layoutChanged()), this, SLOT(sourceLayoutChanged()) ); - connect( model, SIGNAL(dataChanged(const QModelIndex&,const QModelIndex&)), - this, SLOT(sourceDataChanged(const QModelIndex&,const QModelIndex&)) ); - - - connect( model, SIGNAL(columnsAboutToBeInserted(const QModelIndex&, int,int)), - this, SLOT(sourceColumnsAboutToBeInserted(const QModelIndex&,int,int)) ); - connect( model, SIGNAL(columnsInserted(const QModelIndex&, int,int)), - this, SLOT(sourceColumnsInserted(const QModelIndex&,int,int)) ); - connect( model, SIGNAL(columnsAboutToBeRemoved(const QModelIndex&, int,int)), - this, SLOT(sourceColumnsAboutToBeRemoved(const QModelIndex&,int,int)) ); - connect( model, SIGNAL(columnsRemoved(const QModelIndex&, int,int)), - this, SLOT(sourceColumnsRemoved(const QModelIndex&,int,int)) ); - - connect( model, SIGNAL(rowsAboutToBeInserted(const QModelIndex&, int,int)), - this, SLOT(sourceRowsAboutToBeInserted(const QModelIndex&,int,int)) ); - connect( model, SIGNAL(rowsInserted(const QModelIndex&, int,int)), - this, SLOT(sourceRowsInserted(const QModelIndex&,int,int)) ); - connect( model, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int,int)), - this, SLOT(sourceRowsAboutToBeRemoved(const QModelIndex&,int,int)) ); - connect( model, SIGNAL(rowsRemoved(const QModelIndex&, int,int)), - this, SLOT(sourceRowsRemoved(const QModelIndex&,int,int)) ); + connect( model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(sourceDataChanged(QModelIndex,QModelIndex)) ); + + + connect( model, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(sourceColumnsAboutToBeInserted(QModelIndex,int,int)) ); + connect( model, SIGNAL(columnsInserted(QModelIndex,int,int)), + this, SLOT(sourceColumnsInserted(QModelIndex,int,int)) ); + connect( model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(sourceColumnsAboutToBeRemoved(QModelIndex,int,int)) ); + connect( model, SIGNAL(columnsRemoved(QModelIndex,int,int)), + this, SLOT(sourceColumnsRemoved(QModelIndex,int,int)) ); + + connect( model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(sourceRowsAboutToBeInserted(QModelIndex,int,int)) ); + connect( model, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(sourceRowsInserted(QModelIndex,int,int)) ); + connect( model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(sourceRowsAboutToBeRemoved(QModelIndex,int,int)) ); + connect( model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(sourceRowsRemoved(QModelIndex,int,int)) ); } /*! Called when the source model is about to be reset. * \sa QAbstractItemModel::modelAboutToBeReset() */ void ForwardingProxyModel::sourceModelAboutToBeReset() { // The matching signal is emitted be reset() } /*! Called when the source model is reset * \sa QAbstractItemModel::modelReset() */ void ForwardingProxyModel::sourceModelReset() { //qDebug() << "ForwardingProxyModel::sourceModelReset()"; beginResetModel(); endResetModel(); } /*! Called just before the layout of the source model is changed. * \sa QAbstractItemModel::layoutAboutToBeChanged() */ void ForwardingProxyModel::sourceLayoutAboutToBeChanged() { //qDebug() << "ForwardingProxyModel::sourceLayoutAboutToBeChanged()"; emit layoutAboutToBeChanged(); } /*! Called when the layout of the source model has changed. * \sa QAbstractItemModel::layoutChanged() */ void ForwardingProxyModel::sourceLayoutChanged() { //qDebug() << "ForwardingProxyModel::sourceLayoutChanged()"; beginResetModel(); endResetModel(); } /*! Called when the data in an existing item in the source model changes. * \sa QAbstractItemModel::dataChanged() */ void ForwardingProxyModel::sourceDataChanged( const QModelIndex& from, const QModelIndex& to ) { //qDebug() << "ForwardingProxyModel::sourceDataChanged("<rowCount( mapToSource( idx ) ); } /*! \see QAbstractItemModel::columnCount */ int ForwardingProxyModel::columnCount( const QModelIndex& idx ) const { return sourceModel()->columnCount( mapToSource( idx ) ); } /*! \see QAbstractItemModel::index */ QModelIndex ForwardingProxyModel::index( int row, int column, const QModelIndex& parent ) const { return mapFromSource( sourceModel()->index( row, column, mapToSource( parent ) ) ); } /*! \see QAbstractItemModel::parent */ QModelIndex ForwardingProxyModel::parent( const QModelIndex& idx ) const { return mapFromSource( sourceModel()->parent( mapToSource( idx ) ) ); } /*! \see QAbstractItemModel::setData */ bool ForwardingProxyModel::setData( const QModelIndex& index, const QVariant& value, int role ) { //qDebug() << "ForwardingProxyModel::setData( " << index<setData( mapToSource( index ), value, role ); } QMimeData *ForwardingProxyModel::mimeData(const QModelIndexList &indexes) const { QModelIndexList source_indexes; for (int i = 0; i < indexes.count(); ++i) source_indexes << mapToSource(indexes.at(i)); return sourceModel()->mimeData(source_indexes); } bool ForwardingProxyModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { if ((row == -1) && (column == -1)) return sourceModel()->dropMimeData(data, action, -1, -1, mapToSource(parent)); int source_destination_row = -1; int source_destination_column = -1; QModelIndex source_parent; if (row == rowCount(parent)) { source_parent = mapToSource(parent); source_destination_row = sourceModel()->rowCount(source_parent); } else { QModelIndex proxy_index = index(row, column, parent); QModelIndex source_index = mapToSource(proxy_index); source_destination_row = source_index.row(); source_destination_column = source_index.column(); source_parent = source_index.parent(); } return sourceModel()->dropMimeData(data, action, source_destination_row, source_destination_column, source_parent); } QStringList ForwardingProxyModel::mimeTypes() const { return sourceModel()->mimeTypes(); } Qt::DropActions ForwardingProxyModel::supportedDropActions() const { return sourceModel()->supportedDropActions(); } #include "moc_kganttforwardingproxymodel.cpp" diff --git a/src/KGantt/kganttgraphicsscene.cpp b/src/KGantt/kganttgraphicsscene.cpp index 8e639ef..f231422 100644 --- a/src/KGantt/kganttgraphicsscene.cpp +++ b/src/KGantt/kganttgraphicsscene.cpp @@ -1,954 +1,954 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KGantt library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "kganttgraphicsscene.h" #include "kganttgraphicsscene_p.h" #include "kganttgraphicsitem.h" #include "kganttconstraint.h" #include "kganttconstraintgraphicsitem.h" #include "kganttitemdelegate.h" #include "kganttabstractrowcontroller.h" #include "kganttdatetimegrid.h" #include "kganttsummaryhandlingproxymodel.h" #include #include #include #include #include #include #include #include #include #include #include // defines HAVE_PRINTER if support for printing should be included #ifdef _WIN32_WCE // There is no printer support under wince even if QT_NO_PRINTER is not set #else #ifndef QT_NO_PRINTER #define HAVE_PRINTER #endif #endif /*!\class KGantt::GraphicsScene * \internal */ using namespace KGantt; GraphicsScene::Private::Private( GraphicsScene* _q ) : q( _q ), dragSource( 0 ), itemDelegate( new ItemDelegate( _q ) ), rowController( 0 ), grid( &default_grid ), readOnly( false ), isPrinting( false ), drawColumnLabels( true ), labelsWidth( 0.0 ), summaryHandlingModel( new SummaryHandlingProxyModel( _q ) ), selectionModel( 0 ) { default_grid.setStartDateTime( QDateTime::currentDateTime().addDays( -1 ) ); } void GraphicsScene::Private::clearConstraintItems() { for(ConstraintGraphicsItem *citem : constraintItems) { // remove constraint from items first for(GraphicsItem *item : items) { item->removeStartConstraint(citem); item->removeEndConstraint(citem); } q->removeItem(citem); delete citem; } constraintItems.clear(); } void GraphicsScene::Private::resetConstraintItems() { clearConstraintItems(); if ( constraintModel.isNull() ) return; QList clst = constraintModel->constraints(); Q_FOREACH( const Constraint& c, clst ) { createConstraintItem( c ); } q->updateItems(); } void GraphicsScene::Private::createConstraintItem( const Constraint& c ) { GraphicsItem* sitem = q->findItem( summaryHandlingModel->mapFromSource( c.startIndex() ) ); GraphicsItem* eitem = q->findItem( summaryHandlingModel->mapFromSource( c.endIndex() ) ); if ( sitem && eitem ) { ConstraintGraphicsItem* citem = new ConstraintGraphicsItem( c ); sitem->addStartConstraint( citem ); eitem->addEndConstraint( citem ); constraintItems.append( citem ); q->addItem( citem ); } //q->insertConstraintItem( c, citem ); } // Delete the constraint item, and clean up pointers in the start- and end item void GraphicsScene::Private::deleteConstraintItem( ConstraintGraphicsItem *citem ) { //qDebug()<<"GraphicsScene::Private::deleteConstraintItem citem="<constraint(); GraphicsItem* item = items.value( summaryHandlingModel->mapFromSource( c.startIndex() ), 0 ); if ( item ) { item->removeStartConstraint( citem ); } item = items.value( summaryHandlingModel->mapFromSource( c.endIndex() ), 0 ); if ( item ) { item->removeEndConstraint( citem ); } constraintItems.removeAt(constraintItems.indexOf(citem)); delete citem; } void GraphicsScene::Private::deleteConstraintItem( const Constraint& c ) { deleteConstraintItem( findConstraintItem( c ) ); } ConstraintGraphicsItem* GraphicsScene::Private::findConstraintItem( const Constraint& c ) const { GraphicsItem* item = items.value( summaryHandlingModel->mapFromSource( c.startIndex() ), 0 ); if ( item ) { const QList clst = item->startConstraints(); QList::const_iterator it = clst.begin(); for ( ; it != clst.end() ; ++it ) { if ( c.compareIndexes((*it)->constraint()) ) break; } if ( it != clst.end() ) { return *it; } } item = items.value( summaryHandlingModel->mapFromSource( c.endIndex() ), 0 ); if ( item ) { const QList clst = item->endConstraints(); QList::const_iterator it = clst.begin(); for ( ; it != clst.end() ; ++it ) { if ( c.compareIndexes( (*it)->constraint() ) ) break; } if ( it != clst.end() ) { return *it; } } return 0; } // NOTE: we might get here after indexes are invalidated, so cannot do any controlled cleanup void GraphicsScene::Private::clearItems() { for(GraphicsItem *item : items) { q->removeItem(item); delete item; } items.clear(); // do last to avoid cleaning up items clearConstraintItems(); } GraphicsScene::GraphicsScene( QObject* parent ) : QGraphicsScene( parent ), _d( new Private( this ) ) { init(); } GraphicsScene::~GraphicsScene() { qDeleteAll( items() ); delete _d; } #define d d_func() void GraphicsScene::init() { setItemIndexMethod( QGraphicsScene::NoIndex ); setConstraintModel( new ConstraintModel( this ) ); - connect( d->grid, SIGNAL( gridChanged() ), this, SLOT( slotGridChanged() ) ); + connect( d->grid, SIGNAL(gridChanged()), this, SLOT(slotGridChanged()) ); } /* NOTE: The delegate should really be a property * of the view, but that doesn't really fit at * this time */ void GraphicsScene::setItemDelegate( ItemDelegate* delegate ) { if ( !d->itemDelegate.isNull() && d->itemDelegate->parent()==this ) delete d->itemDelegate; d->itemDelegate = delegate; update(); } ItemDelegate* GraphicsScene::itemDelegate() const { return d->itemDelegate; } QAbstractItemModel* GraphicsScene::model() const { assert(!d->summaryHandlingModel.isNull()); return d->summaryHandlingModel->sourceModel(); } void GraphicsScene::setModel( QAbstractItemModel* model ) { assert(!d->summaryHandlingModel.isNull()); d->summaryHandlingModel->setSourceModel(model); d->grid->setModel( d->summaryHandlingModel ); setSelectionModel( new QItemSelectionModel( model, this ) ); } QAbstractProxyModel* GraphicsScene::summaryHandlingModel() const { return d->summaryHandlingModel; } void GraphicsScene::setSummaryHandlingModel( QAbstractProxyModel* proxyModel ) { proxyModel->setSourceModel( model() ); d->summaryHandlingModel = proxyModel; } void GraphicsScene::setRootIndex( const QModelIndex& idx ) { d->grid->setRootIndex( idx ); } QModelIndex GraphicsScene::rootIndex() const { return d->grid->rootIndex(); } ConstraintModel* GraphicsScene::constraintModel() const { return d->constraintModel; } void GraphicsScene::setConstraintModel( ConstraintModel* cm ) { if ( !d->constraintModel.isNull() ) { d->constraintModel->disconnect( this ); d->clearConstraintItems(); } d->constraintModel = cm; - connect( cm, SIGNAL( constraintAdded( const KGantt::Constraint& ) ), - this, SLOT( slotConstraintAdded( const KGantt::Constraint& ) ) ); - connect( cm, SIGNAL( constraintRemoved( const KGantt::Constraint& ) ), - this, SLOT( slotConstraintRemoved( const KGantt::Constraint& ) ) ); + connect( cm, SIGNAL(constraintAdded(KGantt::Constraint)), + this, SLOT(slotConstraintAdded(KGantt::Constraint)) ); + connect( cm, SIGNAL(constraintRemoved(KGantt::Constraint)), + this, SLOT(slotConstraintRemoved(KGantt::Constraint)) ); d->resetConstraintItems(); } void GraphicsScene::setSelectionModel( QItemSelectionModel* smodel ) { d->selectionModel = smodel; // TODO: update selection from model and connect signals } QItemSelectionModel* GraphicsScene::selectionModel() const { return d->selectionModel; } void GraphicsScene::setRowController( AbstractRowController* rc ) { d->rowController = rc; } AbstractRowController* GraphicsScene::rowController() const { return d->rowController; } void GraphicsScene::setGrid( AbstractGrid* grid ) { QAbstractItemModel* model = 0; if ( grid == 0 ) grid = &d->default_grid; if ( d->grid ) { d->grid->disconnect( this ); model = d->grid->model(); } d->grid = grid; - connect( d->grid, SIGNAL( gridChanged() ), this, SLOT( slotGridChanged() ) ); + connect( d->grid, SIGNAL(gridChanged()), this, SLOT(slotGridChanged()) ); d->grid->setModel( model ); slotGridChanged(); } AbstractGrid* GraphicsScene::grid() const { return d->grid; } void GraphicsScene::setReadOnly( bool ro ) { d->readOnly = ro; } bool GraphicsScene::isReadOnly() const { return d->readOnly; } /* Returns the index with column=0 fromt the * same row as idx and with the same parent. * This is used to traverse the tree-structure * of the model */ QModelIndex GraphicsScene::mainIndex( const QModelIndex& idx ) { #if 0 if ( idx.isValid() ) { return idx.model()->index( idx.row(), 0,idx.parent() ); } else { return QModelIndex(); } #else return idx; #endif } /*! Returns the index pointing to the last column * in the same row as idx. This can be thought of * as in "inverse" of mainIndex() */ QModelIndex GraphicsScene::dataIndex( const QModelIndex& idx ) { #if 0 if ( idx.isValid() ) { const QAbstractItemModel* model = idx.model(); return model->index( idx.row(), model->columnCount( idx.parent() )-1,idx.parent() ); } else { return QModelIndex(); } #else return idx; #endif } /*! Creates a new item of type type. * TODO: If the user should be allowed to override * this in any way, it needs to be in View! */ GraphicsItem* GraphicsScene::createItem( ItemType type ) const { #if 0 switch ( type ) { case TypeEvent: return 0; case TypeTask: return new TaskItem; case TypeSummary: return new SummaryItem; default: return 0; } #endif //qDebug() << "GraphicsScene::createItem("<findItem( idx ); const int itemtype = summaryHandlingModel->data( idx, ItemTypeRole ).toInt(); if (!item) { item = q->createItem( static_cast( itemtype ) ); item->setIndex( idx ); q->insertItem( idx, item); } item->updateItem( span, idx ); QModelIndex child; int cr = 0; while ( ( child = idx.child( cr, 0 ) ).isValid() ) { recursiveUpdateMultiItem( span, child ); ++cr; } } void GraphicsScene::updateRow( const QModelIndex& rowidx ) { //qDebug() << "GraphicsScene::updateRow("<mapToSource( rowidx ); Span rg = rowController()->rowGeometry( sidx ); for ( QModelIndex treewalkidx = sidx; treewalkidx.isValid(); treewalkidx = treewalkidx.parent() ) { if ( treewalkidx.data( ItemTypeRole ).toInt() == TypeMulti && !rowController()->isRowExpanded( treewalkidx )) { rg = rowController()->rowGeometry( treewalkidx ); } } bool blocked = blockSignals( true ); for ( int col = 0; col < summaryHandlingModel()->columnCount( rowidx.parent() ); ++col ) { const QModelIndex idx = summaryHandlingModel()->index( rowidx.row(), col, rowidx.parent() ); const QModelIndex sidx = summaryHandlingModel()->mapToSource( idx ); const int itemtype = summaryHandlingModel()->data( idx, ItemTypeRole ).toInt(); const bool isExpanded = rowController()->isRowExpanded( sidx ); if ( itemtype == TypeNone ) { removeItem( idx ); continue; } if ( itemtype == TypeMulti && !isExpanded ) { d->recursiveUpdateMultiItem( rg, idx ); } else { if ( summaryHandlingModel()->data( rowidx.parent(), ItemTypeRole ).toInt() == TypeMulti && !isExpanded ) { //continue; } GraphicsItem* item = findItem( idx ); if (!item) { item = createItem( static_cast( itemtype ) ); item->setIndex( idx ); insertItem(idx, item); } const Span span = rowController()->rowGeometry( sidx ); item->updateItem( span, idx ); } } blockSignals( blocked ); } void GraphicsScene::insertItem( const QPersistentModelIndex& idx, GraphicsItem* item ) { if ( !d->constraintModel.isNull() ) { // Create items for constraints const QModelIndex sidx = summaryHandlingModel()->mapToSource( idx ); const QList clst = d->constraintModel->constraintsForIndex( sidx ); Q_FOREACH( const Constraint& c, clst ) { QModelIndex other_idx; if ( c.startIndex() == sidx ) { other_idx = c.endIndex(); GraphicsItem* other_item = d->items.value(summaryHandlingModel()->mapFromSource( other_idx ),0); if ( !other_item ) continue; ConstraintGraphicsItem* citem = new ConstraintGraphicsItem( c ); item->addStartConstraint( citem ); other_item->addEndConstraint( citem ); d->constraintItems.append( citem ); addItem( citem ); } else if ( c.endIndex() == sidx ) { other_idx = c.startIndex(); GraphicsItem* other_item = d->items.value(summaryHandlingModel()->mapFromSource( other_idx ),0); if ( !other_item ) continue; ConstraintGraphicsItem* citem = new ConstraintGraphicsItem( c ); other_item->addStartConstraint( citem ); item->addEndConstraint( citem ); d->constraintItems.append( citem ); addItem( citem ); } else { assert( 0 ); // Impossible } } } d->items.insert( idx, item ); addItem( item ); } void GraphicsScene::removeItem( const QModelIndex& idx ) { //qDebug() << "GraphicsScene::removeItem("<::iterator it = d->items.find( idx ); if ( it != d->items.end() ) { GraphicsItem* item = *it; assert( item ); // We have to remove the item from the list first because // there is a good chance there will be reentrant calls d->items.erase( it ); { // Remove any constraintitems attached const QSet clst = QSet::fromList( item->startConstraints() ) + QSet::fromList( item->endConstraints() ); Q_FOREACH( ConstraintGraphicsItem* citem, clst ) { d->deleteConstraintItem( citem ); } } // Get rid of the item delete item; } } GraphicsItem* GraphicsScene::findItem( const QModelIndex& idx ) const { if ( !idx.isValid() ) return 0; assert( idx.model() == summaryHandlingModel() ); QHash::const_iterator it = d->items.find( idx ); return ( it != d->items.end() )?*it:0; } GraphicsItem* GraphicsScene::findItem( const QPersistentModelIndex& idx ) const { if ( !idx.isValid() ) return 0; assert( idx.model() == summaryHandlingModel() ); QHash::const_iterator it = d->items.find( idx ); return ( it != d->items.end() )?*it:0; } void GraphicsScene::clearItems() { d->clearItems(); } void GraphicsScene::updateItems() { for ( QHash::iterator it = d->items.begin(); it != d->items.end(); ++it ) { GraphicsItem* const item = it.value(); const QPersistentModelIndex& idx = it.key(); item->updateItem( Span( item->pos().y(), item->rect().height() ), idx ); } invalidate( QRectF(), QGraphicsScene::BackgroundLayer ); } void GraphicsScene::deleteSubtree( const QModelIndex& _idx ) { QModelIndex idx = dataIndex( _idx ); if ( !idx.model() ) return; const QModelIndex parent( idx.parent() ); const int colcount = idx.model()->columnCount( parent ); {for ( int i = 0; i < colcount; ++i ) { removeItem( parent.child( idx.row(), i ) ); }} const int rowcount = summaryHandlingModel()->rowCount( _idx ); {for ( int i = 0; i < rowcount; ++i ) { deleteSubtree( summaryHandlingModel()->index( i, summaryHandlingModel()->columnCount(_idx)-1, _idx ) ); }} } ConstraintGraphicsItem* GraphicsScene::findConstraintItem( const Constraint& c ) const { return d->findConstraintItem( c ); } void GraphicsScene::slotConstraintAdded( const KGantt::Constraint& c ) { d->createConstraintItem( c ); } void GraphicsScene::slotConstraintRemoved( const KGantt::Constraint& c ) { d->deleteConstraintItem( c ); } void GraphicsScene::slotGridChanged() { updateItems(); update(); emit gridChanged(); } void GraphicsScene::helpEvent( QGraphicsSceneHelpEvent *helpEvent ) { #ifndef QT_NO_TOOLTIP QGraphicsItem *item = itemAt( helpEvent->scenePos(), QTransform() ); if ( GraphicsItem* gitem = qgraphicsitem_cast( item ) ) { QToolTip::showText(helpEvent->screenPos(), gitem->ganttToolTip()); } else if ( ConstraintGraphicsItem* citem = qgraphicsitem_cast( item ) ) { QToolTip::showText(helpEvent->screenPos(), citem->ganttToolTip()); } else { QGraphicsScene::helpEvent( helpEvent ); } #endif /* QT_NO_TOOLTIP */ } void GraphicsScene::drawBackground( QPainter* painter, const QRectF& _rect ) { QRectF scn( sceneRect() ); QRectF rect( _rect ); if ( d->isPrinting && d->drawColumnLabels ) { QRectF headerRect( scn.topLeft()+QPointF( d->labelsWidth, 0 ), QSizeF( scn.width()-d->labelsWidth, d->rowController->headerHeight() )); d->grid->paintHeader( painter, headerRect, rect, 0, 0 ); #if 0 /* We have to blank out the part of the header that is invisible during * normal rendering when we are printing. */ QRectF labelsTabRect( scn.topLeft(), QSizeF( d->labelsWidth, headerRect.height() ) ); QStyleOptionHeader opt; opt.rect = labelsTabRect.toRect(); opt.text = QLatin1String(""); opt.textAlignment = Qt::AlignCenter; style()->drawControl(QStyle::CE_Header, &opt, painter, 0); #endif scn.setTop( headerRect.bottom() ); scn.setLeft( headerRect.left() ); rect = rect.intersected( scn ); } d->grid->paintGrid( painter, scn, rect, d->rowController ); d->grid->drawBackground(painter, rect); } void GraphicsScene::drawForeground( QPainter* painter, const QRectF& rect ) { d->grid->drawForeground(painter, rect); } void GraphicsScene::itemEntered( const QModelIndex& idx ) { emit entered( idx ); } void GraphicsScene::itemPressed( const QModelIndex& idx ) { emit pressed( idx ); } void GraphicsScene::itemClicked( const QModelIndex& idx ) { emit clicked( idx ); } void GraphicsScene::itemDoubleClicked( const QModelIndex& idx ) { emit qrealClicked( idx ); } void GraphicsScene::setDragSource( GraphicsItem* item ) { d->dragSource = item; } GraphicsItem* GraphicsScene::dragSource() const { return d->dragSource; } /*! Print the Gantt chart using \a printer. If \a drawRowLabels * is true (the default), each row will have it's label printed * on the left side. If \a drawColumnLabels is true (the * default), each column will have it's label printed at the * top side. * * This version of print() will print multiple pages. */ void GraphicsScene::print( QPrinter* printer, bool drawRowLabels, bool drawColumnLabels ) { #ifndef HAVE_PRINTER Q_UNUSED( printer ); Q_UNUSED( drawRowLabels ); Q_UNUSED( drawColumnLabels ); #else QPainter painter( printer ); doPrint( &painter, printer->pageRect(), sceneRect().left(), sceneRect().right(), printer, drawRowLabels, drawColumnLabels ); #endif } /*! Print part of the Gantt chart from \a start to \a end using \a printer. * If \a drawRowLabels is true (the default), each row will have it's * label printed on the left side. If \a drawColumnLabels is true (the * default), each column will have it's label printed at the * top side. * * This version of print() will print multiple pages. * * To print a certain range of a chart with a DateTimeGrid, use * qreal DateTimeGrid::mapFromDateTime( const QDateTime& dt) const * to figure out the values for \a start and \a end. */ void GraphicsScene::print( QPrinter* printer, qreal start, qreal end, bool drawRowLabels, bool drawColumnLabels ) { #ifndef HAVE_PRINTER Q_UNUSED( printer ); Q_UNUSED( start ); Q_UNUSED( end ); Q_UNUSED( drawRowLabels ); Q_UNUSED( drawColumnLabels ); #else QPainter painter( printer ); doPrint( &painter, printer->pageRect(), start, end, printer, drawRowLabels, drawColumnLabels ); #endif } /*! Render the GanttView inside the rectangle \a target using the painter \a painter. * If \a drawRowLabels is true (the default), each row will have it's * label printed on the left side. If \a drawColumnLabels is true (the * default), each column will have it's label printed at the * top side. */ void GraphicsScene::print( QPainter* painter, const QRectF& _targetRect, bool drawRowLabels, bool drawColumnLabels ) { QRectF targetRect( _targetRect ); if ( targetRect.isNull() ) { targetRect = sceneRect(); } doPrint( painter, targetRect, sceneRect().left(), sceneRect().right(), 0, drawRowLabels, drawColumnLabels ); } /*! Render the GanttView inside the rectangle \a target using the painter \a painter. * If \a drawRowLabels is true (the default), each row will have it's * label printed on the left side. If \a drawColumnLabels is true (the * default), each column will have it's label printed at the * top side. * * To print a certain range of a chart with a DateTimeGrid, use * qreal DateTimeGrid::mapFromDateTime( const QDateTime& dt) const * to figure out the values for \a start and \a end. */ void GraphicsScene::print( QPainter* painter, qreal start, qreal end, const QRectF& _targetRect, bool drawRowLabels, bool drawColumnLabels ) { QRectF targetRect( _targetRect ); if ( targetRect.isNull() ) { targetRect = sceneRect(); } doPrint( painter, targetRect, start, end, 0, drawRowLabels, drawColumnLabels ); } /*!\internal */ void GraphicsScene::doPrint( QPainter* painter, const QRectF& targetRect, qreal start, qreal end, QPrinter* printer, bool drawRowLabels, bool drawColumnLabels ) { assert( painter ); d->isPrinting = true; d->drawColumnLabels = drawColumnLabels; d->labelsWidth = 0.0; QFont sceneFont( font() ); #ifdef HAVE_PRINTER if ( printer ) { sceneFont = QFont( font(), printer ); if ( font().pointSizeF() >= 0.0 ) sceneFont.setPointSizeF( font().pointSizeF() ); else if ( font().pointSize() >= 0 ) sceneFont.setPointSize( font().pointSize() ); else sceneFont.setPixelSize( font().pixelSize() ); } #endif QGraphicsTextItem dummyTextItem( QLatin1String("X") ); dummyTextItem.adjustSize(); QFontMetrics fm(dummyTextItem.font()); sceneFont.setPixelSize( fm.height() ); const QRectF oldScnRect( sceneRect() ); QRectF scnRect( oldScnRect ); scnRect.setLeft( start ); scnRect.setRight( end ); bool b = blockSignals( true ); /* column labels */ if ( d->drawColumnLabels ) { QRectF headerRect( scnRect ); headerRect.setHeight( - d->rowController->headerHeight() ); scnRect.setTop(scnRect.top() - d->rowController->headerHeight()); } /* row labels */ QVector textLabels; if ( drawRowLabels ) { qreal textWidth = 0.; qreal charWidth = QFontMetricsF(sceneFont).width( QString::fromLatin1( "X" ) ); QModelIndex sidx = summaryHandlingModel()->mapToSource( summaryHandlingModel()->index( 0, 0, rootIndex()) ); do { QModelIndex idx = summaryHandlingModel()->mapFromSource( sidx ); const Span rg=rowController()->rowGeometry( sidx ); const QString txt = idx.data( Qt::DisplayRole ).toString(); QGraphicsTextItem* item = new QGraphicsTextItem( txt ); addItem( item ); textLabels << item; item->setTextWidth( QFontMetricsF(sceneFont).width( txt ) + charWidth ); textWidth = qMax( item->textWidth(), textWidth ); item->setPos( 0, rg.start() ); } while ( ( sidx = rowController()->indexBelow( sidx ) ).isValid() ); Q_FOREACH( QGraphicsTextItem* item, textLabels ) { item->setPos( scnRect.left()-textWidth, item->y() ); item->show(); } scnRect.setLeft( scnRect.left()-textWidth ); d->labelsWidth = textWidth; } setSceneRect( scnRect ); painter->save(); painter->setClipRect( targetRect ); qreal yratio = targetRect.height()/scnRect.height(); /* If we're not printing multiple pages, * check if the span fits and adjust: */ if ( !printer && targetRect.width()/scnRect.width() < yratio ) { yratio = targetRect.width()/scnRect.width(); } qreal offset = scnRect.left(); int pagecount = 0; while ( offset < scnRect.right() ) { painter->setFont( sceneFont ); render( painter, targetRect, QRectF( QPointF( offset, scnRect.top()), QSizeF( targetRect.width()/yratio, scnRect.height() ) ) ); offset += targetRect.width()/yratio; ++pagecount; if ( printer && offset < scnRect.right() ) { #ifdef HAVE_PRINTER printer->newPage(); #endif } else { break; } } d->isPrinting = false; d->drawColumnLabels = true; d->labelsWidth = 0.0; qDeleteAll( textLabels ); blockSignals( b ); setSceneRect( oldScnRect ); painter->restore(); } #include "moc_kganttgraphicsscene.cpp" #ifndef KDAB_NO_UNIT_TESTS #include "unittest/test.h" #include #include #include #include "kganttgraphicsview.h" class SceneTestRowController : public KGantt::AbstractRowController { private: static const int ROW_HEIGHT; QPointer m_model; public: SceneTestRowController() { } void setModel( QAbstractItemModel* model ) { m_model = model; } /*reimp*/int headerHeight() const Q_DECL_OVERRIDE { return 40; } /*reimp*/ bool isRowVisible( const QModelIndex& ) const Q_DECL_OVERRIDE { return true;} /*reimp*/ bool isRowExpanded( const QModelIndex& ) const Q_DECL_OVERRIDE { return false; } /*reimp*/ KGantt::Span rowGeometry( const QModelIndex& idx ) const Q_DECL_OVERRIDE { return KGantt::Span( idx.row() * ROW_HEIGHT, ROW_HEIGHT ); } /*reimp*/ int maximumItemHeight() const Q_DECL_OVERRIDE { return ROW_HEIGHT/2; } /*reimp*/int totalHeight() const Q_DECL_OVERRIDE { return m_model->rowCount()* ROW_HEIGHT; } /*reimp*/ QModelIndex indexAt( int height ) const Q_DECL_OVERRIDE { return m_model->index( height/ROW_HEIGHT, 0 ); } /*reimp*/ QModelIndex indexBelow( const QModelIndex& idx ) const Q_DECL_OVERRIDE { if ( !idx.isValid() )return QModelIndex(); return idx.model()->index( idx.row()+1, idx.column(), idx.parent() ); } /*reimp*/ QModelIndex indexAbove( const QModelIndex& idx ) const Q_DECL_OVERRIDE { if ( !idx.isValid() )return QModelIndex(); return idx.model()->index( idx.row()-1, idx.column(), idx.parent() ); } }; class TestLineItem : public QGraphicsLineItem { public: TestLineItem( bool *destroyedFlag ) : QGraphicsLineItem( 0, 0, 10, 10 ), // geometry doesn't matter m_destroyedFlag( destroyedFlag ) {} ~TestLineItem() { *m_destroyedFlag = true; } private: bool *m_destroyedFlag; }; const int SceneTestRowController::ROW_HEIGHT = 30; KDAB_SCOPED_UNITTEST_SIMPLE( KGantt, GraphicsView, "test" ) { QStandardItemModel model; QStandardItem* item = new QStandardItem(); item->setData( KGantt::TypeTask, KGantt::ItemTypeRole ); item->setData( QString::fromLatin1( "Decide on new product" ) ); item->setData( QDateTime( QDate( 2007, 3, 1 ) ), KGantt::StartTimeRole ); item->setData( QDateTime( QDate( 2007, 3, 3 ) ), KGantt::EndTimeRole ); QStandardItem* item2 = new QStandardItem(); item2->setData( KGantt::TypeTask, KGantt::ItemTypeRole ); item2->setData( QString::fromLatin1( "Educate personnel" ) ); item2->setData( QDateTime( QDate( 2007, 3, 3 ) ), KGantt::StartTimeRole ); item2->setData( QDateTime( QDate( 2007, 3, 6 ) ), KGantt::EndTimeRole ); model.appendRow( item ); model.appendRow( item2 ); SceneTestRowController rowController; rowController.setModel( &model ); KGantt::GraphicsView graphicsView; graphicsView.setRowController( &rowController ); graphicsView.setModel( &model ); // Now the interesting stuff - the items above are just for a "realistic environment" bool foreignItemDestroyed = false; TestLineItem *foreignItem = new TestLineItem( &foreignItemDestroyed ); graphicsView.scene()->addItem( foreignItem ); assertFalse( foreignItemDestroyed ); graphicsView.updateScene(); assertFalse( foreignItemDestroyed ); } #endif /* KDAB_NO_UNIT_TESTS */ diff --git a/src/KGantt/kganttgraphicsview.cpp b/src/KGantt/kganttgraphicsview.cpp index 69f614c..5d920e5 100644 --- a/src/KGantt/kganttgraphicsview.cpp +++ b/src/KGantt/kganttgraphicsview.cpp @@ -1,795 +1,795 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KGantt library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "kganttgraphicsview.h" #include "kganttgraphicsview_p.h" #include "kganttabstractrowcontroller.h" #include "kganttgraphicsitem.h" #include "kganttconstraintmodel.h" #include #include #include #include #include #include #include #include #if defined KDAB_EVAL #include "../evaldialog/evaldialog.h" #endif /*!\class KGantt::HeaderWidget * \internal */ using namespace KGantt; HeaderWidget::HeaderWidget( GraphicsView* parent ) : QWidget( parent ), m_offset( 0. ) { assert( parent ); // Parent must be set } HeaderWidget::~HeaderWidget() { } void HeaderWidget::scrollTo( int v ) { m_offset = v; // QWidget::scroll() wont work properly for me on Mac //scroll( static_cast( old-v ), 0 ); update(); } void HeaderWidget::paintEvent( QPaintEvent* ev ) { QPainter p( this ); view()->grid()->paintHeader( &p, rect(), ev->rect(), m_offset, this ); } bool HeaderWidget::event( QEvent* event ) { if ( event->type() == QEvent::ToolTip ) { DateTimeGrid* const grid = qobject_cast< DateTimeGrid* >( view()->grid() ); if ( grid ) { QHelpEvent *e = static_cast( event ); QDateTime dt = grid->mapFromChart( view()->mapToScene( e->x(), 0 ).x() ).toDateTime(); setToolTip( dt.toString() ); } } return QWidget::event( event ); } void HeaderWidget::contextMenuEvent( QContextMenuEvent* event ) { QMenu contextMenu; DateTimeGrid* const grid = qobject_cast< DateTimeGrid* >( view()->grid() ); QAction* actionScaleAuto = 0; QAction* actionScaleMonth = 0; QAction* actionScaleWeek = 0; QAction* actionScaleDay = 0; QAction* actionScaleHour = 0; QAction* actionZoomIn = 0; QAction* actionZoomOut = 0; if ( grid != 0 ) { QMenu* menuScale = new QMenu( tr( "Scale", "@title:menu" ), &contextMenu ); QActionGroup* scaleGroup = new QActionGroup( &contextMenu ); scaleGroup->setExclusive( true ); actionScaleAuto = new QAction( tr( "Auto", "@item:inmenu Automatic scale" ), menuScale ); actionScaleAuto->setCheckable( true ); actionScaleAuto->setChecked( grid->scale() == DateTimeGrid::ScaleAuto ); actionScaleMonth = new QAction( tr( "Month", "@item:inmenu" ), menuScale ); actionScaleMonth->setCheckable( true ); actionScaleMonth->setChecked( grid->scale() == DateTimeGrid::ScaleMonth ); actionScaleWeek = new QAction( tr( "Week", "@item:inmenu" ), menuScale ); actionScaleWeek->setCheckable( true ); actionScaleWeek->setChecked( grid->scale() == DateTimeGrid::ScaleWeek ); actionScaleDay = new QAction( tr( "Day", "@item:inmenu" ), menuScale ); actionScaleDay->setCheckable( true ); actionScaleDay->setChecked( grid->scale() == DateTimeGrid::ScaleDay ); actionScaleHour = new QAction( tr( "Hour", "@item:inmenu" ), menuScale ); actionScaleHour->setCheckable( true ); actionScaleHour->setChecked( grid->scale() == DateTimeGrid::ScaleHour ); scaleGroup->addAction( actionScaleAuto ); menuScale->addAction( actionScaleAuto ); scaleGroup->addAction( actionScaleMonth ); menuScale->addAction( actionScaleMonth ); scaleGroup->addAction( actionScaleWeek ); menuScale->addAction( actionScaleWeek ); scaleGroup->addAction( actionScaleDay ); menuScale->addAction( actionScaleDay ); scaleGroup->addAction( actionScaleHour ); menuScale->addAction( actionScaleHour ); contextMenu.addMenu( menuScale ); contextMenu.addSeparator(); actionZoomIn = new QAction( tr( "Zoom In", "@action:inmenu" ), &contextMenu ); contextMenu.addAction( actionZoomIn ); actionZoomOut = new QAction( tr( "Zoom Out", "@action:inmenu" ), &contextMenu ); contextMenu.addAction( actionZoomOut ); } if ( contextMenu.isEmpty() ) { event->ignore(); return; } const QAction* const action = contextMenu.exec( event->globalPos() ); if ( action == 0 ) {} else if ( action == actionScaleAuto ) { assert( grid != 0 ); grid->setScale( DateTimeGrid::ScaleAuto ); } else if ( action == actionScaleMonth ) { assert( grid != 0 ); grid->setScale( DateTimeGrid::ScaleMonth ); } else if ( action == actionScaleWeek ) { assert( grid != 0 ); grid->setScale( DateTimeGrid::ScaleWeek ); } else if ( action == actionScaleDay ) { assert( grid != 0 ); grid->setScale( DateTimeGrid::ScaleDay ); } else if ( action == actionScaleHour ) { assert( grid != 0 ); grid->setScale( DateTimeGrid::ScaleHour ); } else if ( action == actionZoomIn ) { assert( grid != 0 ); grid->setDayWidth( grid->dayWidth() * 1.25 ); } else if ( action == actionZoomOut ) { assert( grid != 0 ); // daywidth *MUST NOT* go below 1.0, it is used as an integer later on grid->setDayWidth( qMax( 1.0, grid->dayWidth() * 0.8 ) ); } event->accept(); } GraphicsView::Private::Private( GraphicsView* _q ) : q( _q ), rowcontroller(0), headerwidget( _q ) { } void GraphicsView::Private::updateHeaderGeometry() { q->setViewportMargins(0,rowcontroller->headerHeight(),0,0); headerwidget.setGeometry( q->viewport()->x(), q->viewport()->y() - rowcontroller->headerHeight(), q->viewport()->width(), rowcontroller->headerHeight() ); } void GraphicsView::Private::slotGridChanged() { updateHeaderGeometry(); headerwidget.update(); q->updateSceneRect(); q->update(); } void GraphicsView::Private::slotHorizontalScrollValueChanged( int val ) { const QRectF viewRect = q->transform().mapRect( q->sceneRect() ); headerwidget.scrollTo( val-q->horizontalScrollBar()->minimum()+static_cast( viewRect.left() ) ); } void GraphicsView::Private::slotColumnsInserted( const QModelIndex& parent, int start, int end ) { Q_UNUSED( start ); Q_UNUSED( end ); QModelIndex idx = scene.model()->index( 0, 0, scene.summaryHandlingModel()->mapToSource( parent ) ); do { scene.updateRow( scene.summaryHandlingModel()->mapFromSource( idx ) ); } while ( ( idx = rowcontroller->indexBelow( idx ) ) != QModelIndex() && rowcontroller->isRowVisible( idx ) ); //} while ( ( idx = d->treeview.indexBelow( idx ) ) != QModelIndex() && d->treeview.visualRect(idx).isValid() ); q->updateSceneRect(); } void GraphicsView::Private::slotColumnsRemoved( const QModelIndex& parent, int start, int end ) { // TODO Q_UNUSED( start ); Q_UNUSED( end ); Q_UNUSED( parent ); q->updateScene(); } void GraphicsView::Private::slotDataChanged( const QModelIndex& topLeft, const QModelIndex& bottomRight ) { //qDebug() << "GraphicsView::slotDataChanged("<index( row, 0, parent ) ); } } void GraphicsView::Private::slotLayoutChanged() { //qDebug() << "slotLayoutChanged()"; q->updateScene(); } void GraphicsView::Private::slotModelReset() { //qDebug() << "slotModelReset()"; q->updateScene(); } void GraphicsView::Private::slotRowsInserted( const QModelIndex& parent, int start, int end ) { Q_UNUSED( parent ); Q_UNUSED( start ); Q_UNUSED( end ); q->updateScene(); // TODO: This might be optimised } void GraphicsView::Private::removeConstraintsRecursive( QAbstractProxyModel *summaryModel, const QModelIndex& index ) { if ( summaryModel->hasChildren( index ) ) { //qDebug() << "removing constraints from children of"<rowCount( index ); ++row ) { const QModelIndex child = summaryModel->index( row, index.column(), index ); removeConstraintsRecursive( summaryModel, child ); } } //qDebug() << "removing constraints from"<sourceModel() QList clst = scene.constraintModel()->constraintsForIndex( summaryModel->mapToSource( index ) ); Q_FOREACH( Constraint c, clst ) { scene.constraintModel()->removeConstraint( c ); } } void GraphicsView::Private::slotRowsAboutToBeRemoved( const QModelIndex& parent, int start, int end ) { //qDebug() << "GraphicsView::Private::slotRowsAboutToBeRemoved("<columnCount( parent ); ++col ) { const QModelIndex idx = summaryModel->index( row, col, parent ); removeConstraintsRecursive( summaryModel, idx ); scene.removeItem( idx ); } } } void GraphicsView::Private::slotRowsRemoved( const QModelIndex& parent, int start, int end ) { //qDebug() << "GraphicsView::Private::slotRowsRemoved("<updateScene(); } void GraphicsView::Private::slotItemClicked( const QModelIndex& idx ) { QModelIndex sidx = idx;//scene.summaryHandlingModel()->mapToSource( idx ); emit q->clicked( sidx ); if (q->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, 0, q)) emit q->activated( sidx ); } void GraphicsView::Private::slotItemDoubleClicked( const QModelIndex& idx ) { QModelIndex sidx = idx;//scene.summaryHandlingModel()->mapToSource( idx ); emit q->qrealClicked( sidx ); if (!q->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, 0, q)) emit q->activated( sidx ); } void GraphicsView::Private::slotHeaderContextMenuRequested( const QPoint& pt ) { emit q->headerContextMenuRequested( headerwidget.mapToGlobal( pt ) ); } /*!\class KGantt::GraphicsView kganttgraphicsview.h KGanttGraphicsView * \ingroup KGantt * \brief The GraphicsView class provides a model/view implementation of a gantt chart. * * */ /*! \fn void GraphicsView::activated( const QModelIndex & index ) */ /*! \fn void GraphicsView::clicked( const QModelIndex & index ); */ /*! \fn void GraphicsView::qrealClicked( const QModelIndex & index ); */ /*! \fn void GraphicsView::entered( const QModelIndex & index ); */ /*! \fn void GraphicsView::pressed( const QModelIndex & index ); */ /*! \fn void GraphicsView::headerContextMenuRequested( const QPoint& pt ) * This signal is emitted when the header has contextMenuPolicy Qt::CustomContextMenu * and the widget wants to show a context menu for the header. Unlike in * QWidget::customContextMenuRequested() signal, \a pt is here in global coordinates. */ /*! Constructor. Creates a new KGantt::GraphicsView with parent * \a parent. */ GraphicsView::GraphicsView( QWidget* parent ) : QGraphicsView( parent ), _d( new Private( this ) ) { #if defined KDAB_EVAL EvalDialog::checkEvalLicense( "KD Gantt" ); #endif - connect( horizontalScrollBar(), SIGNAL( valueChanged( int ) ), - this, SLOT( slotHorizontalScrollValueChanged( int ) ) ); - connect( &_d->scene, SIGNAL( gridChanged() ), - this, SLOT( slotGridChanged() ) ); - connect( &_d->scene, SIGNAL( entered( const QModelIndex& ) ), - this, SIGNAL( entered( const QModelIndex& ) ) ); - connect( &_d->scene, SIGNAL( pressed( const QModelIndex& ) ), - this, SIGNAL( pressed( const QModelIndex& ) ) ); - connect( &_d->scene, SIGNAL( clicked( const QModelIndex& ) ), - this, SLOT( slotItemClicked( const QModelIndex& ) ) ); - connect( &_d->scene, SIGNAL( qrealClicked( const QModelIndex& ) ), - this, SLOT( slotItemDoubleClicked( const QModelIndex& ) ) ); - connect( &_d->scene, SIGNAL( sceneRectChanged( const QRectF& ) ), - this, SLOT( updateSceneRect() ) ); - connect( &_d->headerwidget, SIGNAL( customContextMenuRequested( const QPoint& ) ), - this, SLOT( slotHeaderContextMenuRequested( const QPoint& ) ) ); + connect( horizontalScrollBar(), SIGNAL(valueChanged(int)), + this, SLOT(slotHorizontalScrollValueChanged(int)) ); + connect( &_d->scene, SIGNAL(gridChanged()), + this, SLOT(slotGridChanged()) ); + connect( &_d->scene, SIGNAL(entered(QModelIndex)), + this, SIGNAL(entered(QModelIndex)) ); + connect( &_d->scene, SIGNAL(pressed(QModelIndex)), + this, SIGNAL(pressed(QModelIndex)) ); + connect( &_d->scene, SIGNAL(clicked(QModelIndex)), + this, SLOT(slotItemClicked(QModelIndex)) ); + connect( &_d->scene, SIGNAL(qrealClicked(QModelIndex)), + this, SLOT(slotItemDoubleClicked(QModelIndex)) ); + connect( &_d->scene, SIGNAL(sceneRectChanged(QRectF)), + this, SLOT(updateSceneRect()) ); + connect( &_d->headerwidget, SIGNAL(customContextMenuRequested(QPoint)), + this, SLOT(slotHeaderContextMenuRequested(QPoint)) ); setScene( &_d->scene ); // HACK! setSummaryHandlingModel( _d->scene.summaryHandlingModel() ); // So that AbstractGrid::drawBackground() and AbstractGrid::drawForeground() // works properly setViewportUpdateMode(QGraphicsView::FullViewportUpdate); //setCacheMode( CacheBackground ); } /*! Destroys this view. */ GraphicsView::~GraphicsView() { delete _d; } #define d d_func() /*! Sets the model to be displayed in this view to * \a model. The view does not take ownership of the model. * * To make a model work well with GraphicsView it must * have a certain layout. Whether the model is flat or has a * treestrucure is not important, as long as an * AbstractRowController is provided that can navigate the * model. * * GraphicsView operates per row in the model. The data is always * taken from the _last_ item in the row. The ItemRoles used are * Qt::DisplayRole and the roles defined in KGantt::ItemDataRole. * * Note: This model is not returned by \a model() * * \see GraphicsView::model */ void GraphicsView::setModel( QAbstractItemModel* model ) { if ( d->scene.model() ) { disconnect( d->scene.model() ); } d->scene.setModel( model ); - connect( model, SIGNAL( dataChanged( const QModelIndex&, const QModelIndex& ) ), - this, SLOT( updateSceneRect() ) ); + connect( model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(updateSceneRect()) ); updateScene(); } /*! \returns the current model displayed by this view * * Note: The returned model is not the model set with \a setModel() * * \see GraphicsView::setModel */ QAbstractItemModel* GraphicsView::model() const { return d->scene.model(); } void GraphicsView::setSummaryHandlingModel( QAbstractProxyModel* proxyModel ) { disconnect( d->scene.summaryHandlingModel() ); d->scene.setSummaryHandlingModel( proxyModel ); /* Connections. We have to rely on the treeview * to receive the signals before we do(!) */ - connect( proxyModel, SIGNAL( columnsInserted( const QModelIndex&, int, int ) ), - this, SLOT( slotColumnsInserted( const QModelIndex&, int, int ) ) ); - connect( proxyModel, SIGNAL( columnsRemoved( const QModelIndex&, int, int ) ), - this, SLOT( slotColumnsRemoved( const QModelIndex&, int, int ) ) ); - connect( proxyModel, SIGNAL( dataChanged( const QModelIndex&, const QModelIndex& ) ), - this, SLOT( slotDataChanged( const QModelIndex&, const QModelIndex& ) ) ); - connect( proxyModel, SIGNAL( layoutChanged() ), - this, SLOT( slotLayoutChanged() ) ); - connect( proxyModel, SIGNAL( modelReset() ), - this, SLOT( slotModelReset() ) ); - connect( proxyModel, SIGNAL( rowsInserted( const QModelIndex&, int, int ) ), - this, SLOT( slotRowsInserted( const QModelIndex&, int, int ) ) ); - connect( proxyModel, SIGNAL( rowsAboutToBeRemoved( const QModelIndex&, int, int ) ), - this, SLOT( slotRowsAboutToBeRemoved( const QModelIndex&, int, int ) ) ); - connect( proxyModel, SIGNAL( rowsRemoved( const QModelIndex&, int, int ) ), - this, SLOT( slotRowsRemoved( const QModelIndex&, int, int ) ) ); + connect( proxyModel, SIGNAL(columnsInserted(QModelIndex,int,int)), + this, SLOT(slotColumnsInserted(QModelIndex,int,int)) ); + connect( proxyModel, SIGNAL(columnsRemoved(QModelIndex,int,int)), + this, SLOT(slotColumnsRemoved(QModelIndex,int,int)) ); + connect( proxyModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(slotDataChanged(QModelIndex,QModelIndex)) ); + connect( proxyModel, SIGNAL(layoutChanged()), + this, SLOT(slotLayoutChanged()) ); + connect( proxyModel, SIGNAL(modelReset()), + this, SLOT(slotModelReset()) ); + connect( proxyModel, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(slotRowsInserted(QModelIndex,int,int)) ); + connect( proxyModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int)) ); + connect( proxyModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(slotRowsRemoved(QModelIndex,int,int)) ); updateScene(); } /*! Sets the constraintmodel displayed by this view. * \see KGantt::ConstraintModel. */ void GraphicsView::setConstraintModel( ConstraintModel* cmodel ) { d->scene.setConstraintModel( cmodel ); } /*! \returns the KGantt::ConstraintModel displayed by this view. */ ConstraintModel* GraphicsView::constraintModel() const { return d->scene.constraintModel(); } /*! \returns the KGantt::SummaryHandlingProxyModel used by this view. */ QAbstractProxyModel* GraphicsView::summaryHandlingModel() const { return d->scene.summaryHandlingModel(); } /*! Sets the root index of the model displayed by this view. * Similar to QAbstractItemView::setRootIndex, default is QModelIndex(). */ void GraphicsView::setRootIndex( const QModelIndex& idx ) { d->scene.setRootIndex( idx ); } /*! \returns the rootindex for this view. */ QModelIndex GraphicsView::rootIndex() const { return d->scene.rootIndex(); } /*! Sets the QItemSelectionModel used by this view to manage * selections. Similar to QAbstractItemView::setSelectionModel */ void GraphicsView::setSelectionModel( QItemSelectionModel* model ) { d->scene.setSelectionModel( model ); } /*! \returns the QItemSelectionModel used by this view */ QItemSelectionModel* GraphicsView::selectionModel() const { return d->scene.selectionModel(); } /*! Sets the KGantt::ItemDelegate used for rendering items on this * view. \see ItemDelegate and QAbstractItemDelegate. */ void GraphicsView::setItemDelegate( ItemDelegate* delegate ) { d->scene.setItemDelegate( delegate ); } /*! \returns the ItemDelegate used by this view to render items */ ItemDelegate* GraphicsView::itemDelegate() const { return d->scene.itemDelegate(); } /*! Sets the AbstractRowController used by this view. The * AbstractRowController deals with the height and position * of each row and with which parts of the model are * displayed. \see AbstractRowController */ void GraphicsView::setRowController( AbstractRowController* rowcontroller ) { d->rowcontroller = rowcontroller; d->scene.setRowController( rowcontroller ); updateScene(); } /*! \returns the AbstractRowController * for this view. \see setRowController */ AbstractRowController* GraphicsView::rowController() const { return d->rowcontroller; } /*! Sets the AbstractGrid for this view. The grid is an * object that controls how QModelIndexes are mapped * to and from the view and how the background and header * is rendered. \see AbstractGrid and DateTimeGrid. */ void GraphicsView::setGrid( AbstractGrid* grid ) { d->scene.setGrid( grid ); d->slotGridChanged(); } /*! \returns the AbstractGrid used by this view. */ AbstractGrid* GraphicsView::grid() const { return d->scene.grid(); } /*! Sets the view to read-only mode if \a to is true. The default is * read/write if the model permits it. */ void GraphicsView::setReadOnly( bool ro ) { d->scene.setReadOnly( ro ); } /*!\returns true iff the view is in read-only mode */ bool GraphicsView::isReadOnly() const { return d->scene.isReadOnly(); } /*! Sets the context menu policy for the header. The default value * Qt::DefaultContextMenu results in a standard context menu on the header * that allows the user to set the scale and zoom. * * Setting this to Qt::CustomContextMenu will cause the signal * headerContextMenuRequested(const QPoint& pt) to be emitted instead. * * \see QWidget::setContextMenuPolicy( Qt::ContextMenuPolicy ) */ void GraphicsView::setHeaderContextMenuPolicy( Qt::ContextMenuPolicy p ) { d->headerwidget.setContextMenuPolicy( p ); } /*! \returns the context menu policy for the header */ Qt::ContextMenuPolicy GraphicsView::headerContextMenuPolicy() const { return d->headerwidget.contextMenuPolicy(); } /*! Adds a constraint from \a from to \a to. \a modifiers are the * keyboard modifiers pressed by the user when the action is invoked. * * Override this to control how contraints are added. The default * implementation adds a soft constraint unless the Shift key is pressed, * in that case it adds a hard constraint. If a constraint is already * present, it is removed and nothing is added. */ void GraphicsView::addConstraint( const QModelIndex& from, const QModelIndex& to, Qt::KeyboardModifiers modifiers ) { if ( isReadOnly() ) return; ConstraintModel* cmodel = constraintModel(); assert( cmodel ); Constraint c( from, to, ( modifiers&Qt::ShiftModifier )?Constraint::TypeHard:Constraint::TypeSoft ); if ( cmodel->hasConstraint( c ) ) cmodel->removeConstraint( c ); else cmodel->addConstraint( c ); } void GraphicsView::resizeEvent( QResizeEvent* ev ) { d->updateHeaderGeometry(); QRectF r = scene()->itemsBoundingRect(); // To scroll more to the left than the actual item start, bug #4516 r.setLeft( qMin( 0.0, r.left() ) ); // TODO: take scrollbars into account (if not always on) // The scene should be at least the size of the viewport QSizeF size = viewport()->size(); //TODO: why -2 below? size should be ex. frames etc? if ( size.width() > r.width() ) { r.setWidth( size.width() - 2 ); } if ( size.height() > r.height() ) { r.setHeight( size.height() - 2 ); } const int totalh = rowController()->totalHeight(); if ( r.height() < totalh ) { r.setHeight( totalh ); } scene()->setSceneRect( r ); QGraphicsView::resizeEvent( ev ); } /*!\returns The QModelIndex for the item located at * position \a pos in the view or an invalid index * if no item was present at that position. * * This is useful for for example contextmenus. */ QModelIndex GraphicsView::indexAt( const QPoint& pos ) const { QGraphicsItem* item = itemAt( pos ); if ( GraphicsItem* gitem = qgraphicsitem_cast( item ) ) { return d->scene.summaryHandlingModel()->mapToSource( gitem->index() ); } else { return QModelIndex(); } } /*! \internal */ void GraphicsView::clearItems() { d->scene.clearItems(); } /*! \internal */ void GraphicsView::updateRow( const QModelIndex& idx ) { d->scene.updateRow( d->scene.summaryHandlingModel()->mapFromSource( idx ) ); } /*! \internal * Adjusts the bounding rectangle of the scene. */ void GraphicsView::updateSceneRect() { /* What to do with this? We need to shrink the view to * make collapsing items work */ qreal range = horizontalScrollBar()->maximum()-horizontalScrollBar()->minimum(); const qreal hscroll = horizontalScrollBar()->value()/( range>0?range:1 ); QRectF r = d->scene.itemsBoundingRect(); // To scroll more to the left than the actual item start, bug #4516 r.setTop( 0. ); r.setLeft( qMin( 0.0, r.left() ) ); r.setSize( r.size().expandedTo( viewport()->size() ) ); const int totalh = rowController()->totalHeight(); if ( r.height() < totalh ) r.setHeight( totalh ); d->scene.setSceneRect( r ); /* set scrollbar to keep the same time in view */ range = horizontalScrollBar()->maximum()-horizontalScrollBar()->minimum(); if ( range>0 ) horizontalScrollBar()->setValue( qRound( hscroll*range ) ); /* We have to update here to adjust for any rows with no * information because they are painted with a different * background brush */ d->scene.invalidate( QRectF(), QGraphicsScene::BackgroundLayer ); } /*! \internal * Resets the state of the view. */ void GraphicsView::updateScene() { clearItems(); if ( !model()) return; if ( !rowController()) return; QModelIndex idx = model()->index( 0, 0, rootIndex() ); do { updateRow( idx ); } while ( ( idx = rowController()->indexBelow( idx ) ) != QModelIndex() && rowController()->isRowVisible(idx) ); //constraintModel()->cleanup(); //qDebug() << constraintModel(); updateSceneRect(); if ( scene() ) scene()->invalidate( QRectF(), QGraphicsScene::BackgroundLayer ); } /*! \internal */ GraphicsItem* GraphicsView::createItem( ItemType type ) const { return d->scene.createItem( type ); } /*! \internal */ void GraphicsView::deleteSubtree( const QModelIndex& idx ) { d->scene.deleteSubtree( d->scene.summaryHandlingModel()->mapFromSource( idx ) ); } /*! Print the Gantt chart using \a printer. If \a drawRowLabels * is true (the default), each row will have it's label printed * on the left side. If \a drawColumnLabels is true (the * default), each column will have it's label printed at the * top side. * * This version of print() will print multiple pages. */ void GraphicsView::print( QPrinter* printer, bool drawRowLabels, bool drawColumnLabels ) { d->scene.print( printer, drawRowLabels, drawColumnLabels ); } /*! Print part of the Gantt chart from \a start to \a end using \a printer. * If \a drawRowLabels is true (the default), each row will have it's * label printed on the left side. If \a drawColumnLabels is true (the * default), each column will have it's label printed at the * top side. * * This version of print() will print multiple pages. * * To print a certain range of a chart with a DateTimeGrid, use * qreal DateTimeGrid::mapFromDateTime( const QDateTime& dt) const * to figure out the values for \a start and \a end. */ void GraphicsView::print( QPrinter* printer, qreal start, qreal end, bool drawRowLabels, bool drawColumnLabels ) { d->scene.print( printer, start, end, drawRowLabels, drawColumnLabels ); } /*! Render the GanttView inside the rectangle \a target using the painter \a painter. * If \a drawRowLabels is true (the default), each row will have it's * label printed on the left side. If \a drawColumnLabels is true (the * default), each column will have it's label printed at the * top side. */ void GraphicsView::print( QPainter* painter, const QRectF& targetRect, bool drawRowLabels, bool drawColumnLabels ) { d->scene.print(painter, targetRect, drawRowLabels, drawColumnLabels); } /*! Render the GanttView inside the rectangle \a target using the painter \a painter. * If \a drawRowLabels is true (the default), each row will have it's * label printed on the left side. If \a drawColumnLabels is true (the * default), each column will have it's label printed at the * top side. * * To print a certain range of a chart with a DateTimeGrid, use * qreal DateTimeGrid::mapFromDateTime( const QDateTime& dt) const * to figure out the values for \a start and \a end. */ void GraphicsView::print( QPainter* painter, qreal start, qreal end, const QRectF& targetRect, bool drawRowLabels, bool drawColumnLabels ) { d->scene.print(painter, start, end, targetRect, drawRowLabels, drawColumnLabels); } #include "moc_kganttgraphicsview.cpp" diff --git a/src/KGantt/kganttlegend.cpp b/src/KGantt/kganttlegend.cpp index 87d178c..127c9d7 100644 --- a/src/KGantt/kganttlegend.cpp +++ b/src/KGantt/kganttlegend.cpp @@ -1,208 +1,208 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KGantt library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "kganttlegend.h" #include "kganttlegend_p.h" #include "kganttitemdelegate.h" #include #include #include using namespace KGantt; /*!\class KGantt::Legend kganttlegend.h KGanttLegend * \ingroup KGantt * \brief Legend showing an image and a description for Gantt items * * This is an item view class showing a small Gantt item and it's * text defined by LegendRole. */ /*! Constructor. Creates a Legend with parent \a parent. * The QObject parent is not used for anything internally. */ Legend::Legend( QWidget* parent ) : QAbstractItemView( parent ), _d( new Private ) { setItemDelegate( new ItemDelegate( this ) ); setFrameStyle( QFrame::NoFrame ); } /*! Destructor. Does nothing */ Legend::~Legend() { delete _d; } #define d d_func() QModelIndex Legend::indexAt( const QPoint& point ) const { Q_UNUSED( point ); return QModelIndex(); } QRect Legend::visualRect( const QModelIndex& index ) const { Q_UNUSED( index ); return QRect(); } QSize Legend::sizeHint() const { return measureItem( rootIndex() ); } QSize Legend::minimumSizeHint() const { return measureItem( rootIndex() ); } void Legend::setModel( QAbstractItemModel* model ) { if ( this->model() != 0 ) { - disconnect( this->model(), SIGNAL( dataChanged( QModelIndex, QModelIndex ) ), this, SLOT( modelDataChanged() ) ); - disconnect( this->model(), SIGNAL( rowsRemoved( QModelIndex, int, int ) ), this, SLOT( modelDataChanged() ) ); - disconnect( this->model(), SIGNAL( columnsRemoved( QModelIndex, int, int ) ), this, SLOT( modelDataChanged() ) ); + disconnect( this->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(modelDataChanged()) ); + disconnect( this->model(), SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(modelDataChanged()) ); + disconnect( this->model(), SIGNAL(columnsRemoved(QModelIndex,int,int)), this, SLOT(modelDataChanged()) ); } QAbstractItemView::setModel( model ); d->proxyModel.setSourceModel( model ); if ( this->model() != 0 ) { - connect( this->model(), SIGNAL( dataChanged( QModelIndex, QModelIndex ) ), this, SLOT( modelDataChanged() ) ); - connect( this->model(), SIGNAL( rowsRemoved( QModelIndex, int, int ) ), this, SLOT( modelDataChanged() ) ); - connect( this->model(), SIGNAL( columnsRemoved( QModelIndex, int, int ) ), this, SLOT( modelDataChanged() ) ); + connect( this->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(modelDataChanged()) ); + connect( this->model(), SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(modelDataChanged()) ); + connect( this->model(), SIGNAL(columnsRemoved(QModelIndex,int,int)), this, SLOT(modelDataChanged()) ); } } /*! Triggers repainting of the legend. */ void Legend::modelDataChanged() { updateGeometry(); viewport()->update(); } void Legend::paintEvent( QPaintEvent* event ) { Q_UNUSED( event ); // no model, no legend... if ( model() == 0 ) return; QPainter p( viewport() ); p.fillRect( viewport()->rect(), palette().color( QPalette::Window ) ); drawItem( &p, rootIndex() ); } /*! Creates a StyleOptionGanttItem with all style options filled in * except the target rectangles. */ StyleOptionGanttItem Legend::getStyleOption( const QModelIndex& index ) const { StyleOptionGanttItem opt; opt.displayPosition = StyleOptionGanttItem::Right; opt.displayAlignment = Qt::Alignment( d->proxyModel.data( index, Qt::TextAlignmentRole ).toInt() ); opt.text = index.model()->data( index, LegendRole ).toString(); opt.font = ( index.model()->data( index, Qt::FontRole ) ).value< QFont >(); return opt; } /*! Draws the legend item at \a index and all of it's children recursively * at \a pos onto \a painter. * Reimplement this if you want to draw items in an user defined way. * \returns the rectangle drawn. */ QRect Legend::drawItem( QPainter* painter, const QModelIndex& index, const QPoint& pos ) const { int xPos = pos.x(); int yPos = pos.y(); if ( index.isValid() && index.model() == &d->proxyModel ) { ItemDelegate* const delegate = qobject_cast< ItemDelegate* >( itemDelegate( index ) ); assert( delegate != 0 ); const QRect r( pos, measureItem( index, false ) ); StyleOptionGanttItem opt = getStyleOption( index ); opt.rect = r; opt.rect.setWidth( r.height() ); const ItemType typ = static_cast( index.model()->data( index, ItemTypeRole ).toInt() ); const int dx = (typ == TypeEvent) ? (r.height() / 2) : 0; opt.itemRect = opt.rect.adjusted(dx,0,dx,0); opt.boundingRect = r; opt.boundingRect.setWidth( r.width() + r.height() ); if ( !opt.text.isNull() ) delegate->paintGanttItem( painter, opt, index ); xPos = r.right(); yPos = r.bottom(); } const int rowCount = d->proxyModel.rowCount( index ); for ( int row = 0; row < rowCount; ++row ) { const QRect r = drawItem( painter, d->proxyModel.index( row, 0, index ), QPoint( pos.x(), yPos ) ); xPos = qMax( xPos, r.right() ); yPos = qMax( yPos, r.bottom() ); } return QRect( pos, QPoint( xPos, yPos ) ); } /*! Calculates the needed space for the legend item at \a index and, if \a recursive is true, * all child items. */ QSize Legend::measureItem( const QModelIndex& index, bool recursive ) const { if ( model() == 0 ) return QSize(); QSize baseSize; if ( index.model() != 0 ) { QFontMetrics fm( ( index.model()->data( index, Qt::FontRole ) ).value< QFont >() ); const QString text = index.model()->data( index, LegendRole ).toString(); if ( !text.isEmpty() ) baseSize += QSize( fm.width( text ) + fm.height() + 2, fm.height() + 2 ); } if ( !recursive ) return baseSize; QSize childrenSize; const int rowCount = d->proxyModel.rowCount( index ); for ( int row = 0; row < rowCount; ++row ) { const QSize childSize = measureItem( d->proxyModel.index( row, 0, index ) ); childrenSize.setWidth( qMax( childrenSize.width(), childSize.width() ) ); childrenSize.rheight() += childSize.height(); } return baseSize + childrenSize; } diff --git a/src/KGantt/kganttview.cpp b/src/KGantt/kganttview.cpp index c7d843f..c9edab4 100644 --- a/src/KGantt/kganttview.cpp +++ b/src/KGantt/kganttview.cpp @@ -1,665 +1,665 @@ /* * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KGantt library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "kganttview.h" #include "kganttview_p.h" #include "kganttitemdelegate.h" #include "kganttgraphicsitem.h" #include "kganttsummaryhandlingproxymodel.h" #include #include #include #include #include #include #include #include #include #if defined KDAB_EVAL #include "../evaldialog/evaldialog.h" #endif using namespace KGantt; namespace { class HeaderView : public QHeaderView { public: explicit HeaderView( QWidget* parent=0 ) : QHeaderView( Qt::Horizontal, parent ) { } QSize sizeHint() const Q_DECL_OVERRIDE { QSize s = QHeaderView::sizeHint(); s.rheight() *= 2; return s; } }; } KGanttTreeView::KGanttTreeView( QAbstractProxyModel* proxy, QWidget* parent ) : QTreeView( parent ), m_controller( this, proxy ) { setHeader( new HeaderView ); } KGanttTreeView::~KGanttTreeView() { } void KGanttTreeView::expandAll(QModelIndex index) { for (int i = 0; i < model()->rowCount(index); i++) { QModelIndex indexAt = model()->index(i, 0, index); if (model()->hasChildren(indexAt)) expandAll(indexAt); if (isExpanded(indexAt)) continue; expand(indexAt); } } void KGanttTreeView::collapseAll(QModelIndex index) { for (int i = 0; i < model()->rowCount(index); i++) { QModelIndex indexAt = model()->index(i, 0, index); if (model()->hasChildren(indexAt)) collapseAll(indexAt); if (!isExpanded(indexAt)) continue; collapse(indexAt); } } View::Private::Private(View* v) : q(v), splitter(v), rowController(0), gfxview( new GraphicsView( &splitter ) ), model(0) { //init(); } View::Private::~Private() { delete gfxview; } void View::Private::init() { KGanttTreeView* tw = new KGanttTreeView( &ganttProxyModel, &splitter ); tw->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); tw->setVerticalScrollMode( QAbstractItemView::ScrollPerPixel ); q->setLeftView( tw ); q->setRowController( tw->rowController() ); //gfxview.setRenderHints( QPainter::Antialiasing ); tw->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); QVBoxLayout* layout = new QVBoxLayout(q); layout->setMargin(0); layout->addWidget(&splitter); q->setLayout(layout); constraintProxy.setProxyModel( &ganttProxyModel ); constraintProxy.setDestinationModel( &mappedConstraintModel ); setupGraphicsView(); } void View::Private::setupGraphicsView() { gfxview->setParent( &splitter ); gfxview->setAlignment(Qt::AlignTop|Qt::AlignLeft); gfxview->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); gfxview->setSelectionModel( leftWidget->selectionModel() ); gfxview->setConstraintModel( &mappedConstraintModel ); q->setLeftView( leftWidget ); q->setRowController( rowController ); updateScene(); } void View::Private::updateScene() { gfxview->clearItems(); if ( !model) return; if ( QTreeView* tw = qobject_cast(leftWidget)) { QModelIndex idx = ganttProxyModel.mapFromSource( model->index( 0, 0, leftWidget->rootIndex() ) ); do { gfxview->updateRow( idx ); } while ( ( idx = tw->indexBelow( idx ) ) != QModelIndex() && gfxview->rowController()->isRowVisible(idx) ); gfxview->updateSceneRect(); } else { const QModelIndex rootidx = ganttProxyModel.mapFromSource( leftWidget->rootIndex() ); for ( int r = 0; r < ganttProxyModel.rowCount(rootidx); ++r ) { gfxview->updateRow( ganttProxyModel.index( r, 0, rootidx ) ); } } } void View::Private::slotCollapsed(const QModelIndex& _idx) { QTreeView* tw = qobject_cast(leftWidget); if (!tw) return; bool blocked = gfxview->blockSignals( true ); QModelIndex idx( _idx ); const QAbstractItemModel* model = leftWidget->model(); const QModelIndex pidx = ganttProxyModel.mapFromSource(idx); bool isMulti = false; for ( QModelIndex treewalkidx = pidx; treewalkidx.isValid(); treewalkidx = treewalkidx.parent() ) { if ( treewalkidx.data( ItemTypeRole ).toInt() == TypeMulti && !gfxview->rowController()->isRowExpanded( treewalkidx ) ) { isMulti = true; break; } } if ( !isMulti ) { for ( int i = 0; i < model->rowCount( idx ); ++i ) { gfxview->deleteSubtree( ganttProxyModel.index( i, 0, pidx ) ); } } else { gfxview->updateRow(pidx); } //qDebug() << "Looking to update from " << idx; while ( ( idx=tw->indexBelow( idx ) ) != QModelIndex() && gfxview->rowController()->isRowVisible( ganttProxyModel.mapFromSource(idx) ) ) { const QModelIndex proxyidx( ganttProxyModel.mapFromSource( idx ) ); gfxview->updateRow(proxyidx); } gfxview->blockSignals( blocked ); gfxview->updateSceneRect(); } void View::Private::slotExpanded(const QModelIndex& _idx) { QModelIndex idx( ganttProxyModel.mapFromSource( _idx ) ); do { //qDebug() << "Updating row" << idx << idx.data( Qt::DisplayRole ).toString(); gfxview->updateRow(idx); } while ( ( idx=gfxview->rowController()->indexBelow( idx ) ) != QModelIndex() && gfxview->rowController()->isRowVisible( idx ) ); gfxview->updateSceneRect(); } void View::Private::slotVerticalScrollValueChanged( int val ) { #if 0 qDebug() << "View::Private::slotVerticalScrollValueChanged("<verticalScrollBar()->singleStep(); #endif leftWidget->verticalScrollBar()->setValue( val/gfxview->verticalScrollBar()->singleStep() ); } void View::Private::slotLeftWidgetVerticalRangeChanged(int min, int max ) { //qDebug() << "View::Private::slotLeftWidgetVerticalRangeChanged("<verticalScrollBar()->setRange( min, max ); gfxview->updateSceneRect(); } } void View::Private::slotGfxViewVerticalRangeChanged( int min, int max ) { //qDebug() << "View::Private::slotGfxViewVerticalRangeChanged("<verticalScrollBar()->minimum(); int leftMax = leftWidget->verticalScrollBar()->maximum(); bool blocked = gfxview->verticalScrollBar()->blockSignals( true ); gfxview->verticalScrollBar()->setRange( qMax( min, leftMin ), qMax( max, leftMax ) ); gfxview->verticalScrollBar()->blockSignals( blocked ); } } /*!\class KGantt::View kganttview.h KGanttView * \ingroup KGantt * \brief This widget that consists of a QTreeView and a GraphicsView * * This is the easy to use, complete gantt chart widget. It * consists of a QTreeView on the left and a KGantt::GraphicsView * on the right separated by a QSplitter. The two views share the same * model. */ /*! Constructor. Creates a View with parent \a parent, * a DateTimeGrid as default grid implementaion and no model etc. */ View::View(QWidget* parent) : QWidget(parent), _d(new Private(this)) { #if defined KDAB_EVAL EvalDialog::checkEvalLicense( "KD Gantt" ); #endif _d->init(); } View::~View() { delete _d; } #define d d_func() /*! Replaces the left widget with a custom QAbstractItemView. * * \param aiv The view to be used to the left, instead of the default tree view * \sa setRowController() */ void View::setLeftView( QAbstractItemView* aiv ) { assert( aiv ); if ( aiv==d->leftWidget ) return; if ( !d->leftWidget.isNull() ) { d->leftWidget->disconnect( this ); d->leftWidget->hide(); d->leftWidget->verticalScrollBar()->disconnect( d->gfxview->verticalScrollBar() ); d->gfxview->verticalScrollBar()->disconnect( d->leftWidget->verticalScrollBar() ); } d->leftWidget = aiv; d->splitter.insertWidget( 0, d->leftWidget ); if ( qobject_cast(d->leftWidget) ) { - connect( d->leftWidget, SIGNAL( collapsed( const QModelIndex& ) ), - this, SLOT( slotCollapsed( const QModelIndex& ) ) ); - connect( d->leftWidget, SIGNAL( expanded( const QModelIndex& ) ), - this, SLOT( slotExpanded( const QModelIndex& ) ) ); + connect( d->leftWidget, SIGNAL(collapsed(QModelIndex)), + this, SLOT(slotCollapsed(QModelIndex)) ); + connect( d->leftWidget, SIGNAL(expanded(QModelIndex)), + this, SLOT(slotExpanded(QModelIndex)) ); } - connect( d->gfxview->verticalScrollBar(), SIGNAL( valueChanged( int ) ), - d->leftWidget->verticalScrollBar(), SLOT( setValue( int ) ) ); - connect( d->leftWidget->verticalScrollBar(), SIGNAL( valueChanged( int ) ), - d->gfxview->verticalScrollBar(), SLOT( setValue( int ) ) ); - connect( d->leftWidget->verticalScrollBar(), SIGNAL( rangeChanged( int, int ) ), - this, SLOT( slotLeftWidgetVerticalRangeChanged( int, int ) ) ); - connect( d->gfxview->verticalScrollBar(), SIGNAL( rangeChanged( int, int ) ), - this, SLOT( slotGfxViewVerticalRangeChanged( int, int ) ) ); + connect( d->gfxview->verticalScrollBar(), SIGNAL(valueChanged(int)), + d->leftWidget->verticalScrollBar(), SLOT(setValue(int)) ); + connect( d->leftWidget->verticalScrollBar(), SIGNAL(valueChanged(int)), + d->gfxview->verticalScrollBar(), SLOT(setValue(int)) ); + connect( d->leftWidget->verticalScrollBar(), SIGNAL(rangeChanged(int,int)), + this, SLOT(slotLeftWidgetVerticalRangeChanged(int,int)) ); + connect( d->gfxview->verticalScrollBar(), SIGNAL(rangeChanged(int,int)), + this, SLOT(slotGfxViewVerticalRangeChanged(int,int)) ); } /*! Sets \a ctrl to be the rowcontroller used by this View. * The default rowcontroller is owned by KGantt::View and is * suitable for the default treeview in the left part of the view. * You probably only want to change this if you replace the treeview. */ void View::setRowController( AbstractRowController* ctrl ) { if ( ctrl == d->rowController && d->gfxview->rowController() == ctrl ) return; d->rowController = ctrl; d->gfxview->setRowController( d->rowController ); } /*! \returns a pointer to the current rowcontroller. * \see AbstractRowController */ AbstractRowController* View::rowController() { return d->rowController; } /*! \overload AbstractRowController* KGantt::View::rowController() */ const AbstractRowController* View::rowController() const { return d->rowController; } /*! * \returns a pointer to the QAbstractItemView in the left * part of the widget. * */ const QAbstractItemView* View::leftView() const { return d->leftWidget; } /*! * \overload const QAbstractItemView* KGantt::View::leftView() const */ QAbstractItemView* View::leftView() { return d->leftWidget; } /*! Set the GraphicsView to be used for this View. It only makes sense to call this * if you need to subclass GraphicsView. * * NOTE: _Only_ call this right after creating the View, before setting a model or any other * attributes. */ void View::setGraphicsView( GraphicsView* gv ) { if ( gv != d->gfxview ) { GraphicsView* old = d->gfxview; d->gfxview = gv; d->gfxview->setModel(old->model()); // use the old ForwardingProxyModel d->setupGraphicsView(); d->gfxview->setGrid( old->grid() ); delete old; } } /*! * \returns a pointer to the GraphicsView */ const GraphicsView* View::graphicsView() const { return d->gfxview; } /*! * \overload const GraphicsView* KGantt::View::graphicsView() const */ GraphicsView* View::graphicsView() { return d->gfxview; } /*! * \returns a pointer to the QSplitter that manages the left view and graphicsView */ const QSplitter* View::splitter() const { return &d->splitter; } /*! * \overload const QSplitter* KGantt::View::splitter() const */ QSplitter* View::splitter() { return &d->splitter; } /*! \returns the current model displayed by this view */ QAbstractItemModel* View::model() const { return leftView()->model(); } /*! Sets the QAbstractItemModel to be displayed in this view * to \a model. * * \see GraphicsView::setModel */ void View::setModel( QAbstractItemModel* model ) { leftView()->setModel( model ); d->ganttProxyModel.setSourceModel( model ); d->gfxview->setModel( &d->ganttProxyModel ); } /*! \returns the QItemSelectionModel used by this view */ QItemSelectionModel* View::selectionModel() const { return leftView()->selectionModel(); } /*! Sets the QItemSelectionModel used by this view to manage * selections. Similar to QAbstractItemView::setSelectionModel */ void View::setSelectionModel( QItemSelectionModel* smodel ) { leftView()->setSelectionModel( smodel ); d->gfxview->setSelectionModel( new QItemSelectionModel( &( d->ganttProxyModel ),this ) ); } /*! Sets the AbstractGrid for this view. The grid is an * object that controls how QModelIndexes are mapped * to and from the view and how the background and header * is rendered. \see AbstractGrid and DateTimeGrid. */ void View::setGrid( AbstractGrid* grid ) { d->gfxview->setGrid( grid ); } void View::expandAll( QModelIndex index ) { // FIXME: // It is legal to call setLeftView() with any QAbstractItemView, // so expandAll should be reimplemented to work with that. KGanttTreeView* tw = qobject_cast(leftView()); if (tw) { tw->expandAll(index); } } void View::collapseAll( QModelIndex index ) { // FIXME: // It is legal to call setLeftView() with any QAbstractItemView, // so expandAll should be reimplemented to work with that. KGanttTreeView* tw = qobject_cast(leftView()); if (tw) { tw->collapseAll(index); } } /*! \returns the AbstractGrid used by this view. */ AbstractGrid* View::grid() const { return d->gfxview->grid(); } /*! \returns the rootindex for this view. */ QModelIndex View::rootIndex() const { return leftView()->rootIndex(); } /*! Sets the root index of the model displayed by this view. * Similar to QAbstractItemView::setRootIndex, default is QModelIndex(). */ void View::setRootIndex( const QModelIndex& idx ) { leftView()->setRootIndex( idx ); d->gfxview->setRootIndex( idx ); } /*! \returns the ItemDelegate used by this view to render items */ ItemDelegate* View::itemDelegate() const { return d->gfxview->itemDelegate(); } /*! Sets the KGantt::ItemDelegate used for rendering items on this * view. \see ItemDelegate and QAbstractItemDelegate. */ void View::setItemDelegate( ItemDelegate* delegate ) { leftView()->setItemDelegate( delegate ); d->gfxview->setItemDelegate( delegate ); } /*! Sets the constraintmodel displayed by this view. * \see KGantt::ConstraintModel. */ void View::setConstraintModel( ConstraintModel* cm ) { d->constraintProxy.setSourceModel( cm ); d->gfxview->setConstraintModel( &d->mappedConstraintModel ); } /*! \returns the KGantt::ConstraintModel displayed by this view. */ ConstraintModel* View::constraintModel() const { return d->constraintProxy.sourceModel(); } const QAbstractProxyModel* View::ganttProxyModel() const { return &( d->ganttProxyModel ); } QAbstractProxyModel* View::ganttProxyModel() { return &( d->ganttProxyModel ); } void View::ensureVisible(const QModelIndex& index) { QGraphicsView* view = graphicsView(); KGantt::GraphicsScene* scene = static_cast(view->scene()); if (!scene) return; KGantt::SummaryHandlingProxyModel* model = static_cast(scene->summaryHandlingModel()); const QModelIndex pidx = d->ganttProxyModel.mapFromSource(index); const QModelIndex idx = model->mapFromSource( pidx ); QGraphicsItem* item = scene->findItem(idx); view->ensureVisible(item); } void View::resizeEvent(QResizeEvent*ev) { QWidget::resizeEvent(ev); } /*!\returns The QModelIndex for the item located at * position \a pos in the view or an invalid index * if no item was present at that position. * * \see GraphicsView::indexAt */ QModelIndex View::indexAt( const QPoint& pos ) const { return d->gfxview->indexAt( pos ); } /*! Print the Gantt chart using \a printer. If \a drawRowLabels * is true (the default), each row will have it's label printed * on the left side. If \a drawColumnLabels is true (the * default), each column will have it's label printed at the * top side. * * This version of print() will print multiple pages. */ void View::print( QPrinter* printer, bool drawRowLabels, bool drawColumnLabels ) { graphicsView()->print( printer, drawRowLabels, drawColumnLabels ); } /*! Print part of the Gantt chart from \a start to \a end using \a printer. * If \a drawRowLabels is true (the default), each row will have it's * label printed on the left side. If \a drawColumnLabels is true (the * default), each column will have it's label printed at the * top side. * * This version of print() will print multiple pages. * * To print a certain range of a chart with a DateTimeGrid, use * qreal DateTimeGrid::mapFromDateTime( const QDateTime& dt) const * to figure out the values for \a start and \a end. */ void View::print( QPrinter* printer, qreal start, qreal end, bool drawRowLabels, bool drawColumnLabels ) { graphicsView()->print( printer, start, end, drawRowLabels, drawColumnLabels ); } /*! Render the GanttView inside the rectangle \a target using the painter \a painter. * If \a drawRowLabels is true (the default), each row will have it's * label printed on the left side. If \a drawColumnLabels is true (the * default), each column will have it's label printed at the * top side. */ void View::print( QPainter* painter, const QRectF& target, bool drawRowLabels, bool drawColumnLabels) { d->gfxview->print( painter, target, drawRowLabels, drawColumnLabels); } /*! Render the GanttView inside the rectangle \a target using the painter \a painter. * If \a drawRowLabels is true (the default), each row will have it's * label printed on the left side. If \a drawColumnLabels is true (the * default), each column will have it's label printed at the * top side. * * To print a certain range of a chart with a DateTimeGrid, use * qreal DateTimeGrid::mapFromDateTime( const QDateTime& dt) const * to figure out the values for \a start and \a end. */ void View::print( QPainter* painter, qreal start, qreal end, const QRectF& target, bool drawRowLabels, bool drawColumnLabels) { d->gfxview->print( painter, start, end, target, drawRowLabels, drawColumnLabels); } #include "moc_kganttview.cpp" #ifndef KDAB_NO_UNIT_TESTS #include "unittest/test.h" #include "kganttlistviewrowcontroller.h" #include #include #include #include KDAB_SCOPED_UNITTEST_SIMPLE( KGantt, View, "test" ) { View view( 0 ); #if 0 // GUI tests do not work well on the server - QTimer::singleShot( 1000, qApp, SLOT( quit() ) ); + QTimer::singleShot( 1000, qApp, SLOT(quit()) ); view.show(); qApp->exec(); QPixmap screenshot1 = QPixmap::grabWidget( &view ); QTreeView* tv = new QTreeView; view.setLeftView( tv ); view.setRowController( new TreeViewRowController(tv,view.ganttProxyModel()) ); - QTimer::singleShot( 1000, qApp, SLOT( quit() ) ); + QTimer::singleShot( 1000, qApp, SLOT(quit()) ); qApp->exec(); QPixmap screenshot2 = QPixmap::grabWidget( &view ); assertEqual( screenshot1.toImage(), screenshot2.toImage() ); QListView* lv = new QListView; view.setLeftView(lv); view.setRowController( new ListViewRowController(lv,view.ganttProxyModel())); view.show(); - QTimer::singleShot( 1000, qApp, SLOT( quit() ) ); + QTimer::singleShot( 1000, qApp, SLOT(quit()) ); qApp->exec(); #endif } #endif /* KDAB_NO_UNIT_TESTS */ diff --git a/src/KGantt/test/TestKGanttConstraint.h b/src/KGantt/test/TestKGanttConstraint.h index 29bcc1e..5f1b6c9 100644 --- a/src/KGantt/test/TestKGanttConstraint.h +++ b/src/KGantt/test/TestKGanttConstraint.h @@ -1,33 +1,36 @@ /* * Copyright (C) 2018 Dag Andersen * * This file is part of the KGantt library. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ +#ifndef TESTGANTTCONSTRAINT_H +#define TESTGANTTCONSTRAINT_H #include class TestKGanttConstraint : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void cleanupTestCase(); void test(); }; +#endif diff --git a/src/KGantt/test/TestKGanttConstraintModel.h b/src/KGantt/test/TestKGanttConstraintModel.h index c39269e..c288d81 100644 --- a/src/KGantt/test/TestKGanttConstraintModel.h +++ b/src/KGantt/test/TestKGanttConstraintModel.h @@ -1,36 +1,39 @@ /* * Copyright (C) 2018 Dag Andersen * * This file is part of the KGantt library. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ +#ifndef TESTKGANTTCONSTRAINTMODEL_H +#define TESTKGANTTCONSTRAINTMODEL_H #include #include class TestKGanttConstraintModel : public QObject { Q_OBJECT private: QStandardItemModel *itemModel; private Q_SLOTS: void initTestCase(); void cleanupTestCase(); void testModel(); }; +#endif diff --git a/src/KGantt/test/TestKGanttView.h b/src/KGantt/test/TestKGanttView.h index a9103e0..531f4ac 100644 --- a/src/KGantt/test/TestKGanttView.h +++ b/src/KGantt/test/TestKGanttView.h @@ -1,56 +1,59 @@ /* * Copyright (C) 2018 Dag Andersen * * This file is part of the KGantt library. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ +#ifndef TESTKGANTTVIEW_H +#define TESTKGANTTVIEW_H #include #include #include #include "kganttview.h" namespace KGantt { class GraphicsScene; } class TestKGanttView : public QObject { Q_OBJECT private: QStandardItemModel *itemModel; KGantt::View *view; void initListModel(); void initTreeModel(); private Q_SLOTS: void init(); void cleanup(); void testApi(); // default view is a treeview void testDefaultView(); void testTreeView(); void testListView(); void testConstraints(); }; +#endif diff --git a/tests/Gantt/apireview/entrydialog.cpp b/tests/Gantt/apireview/entrydialog.cpp index 9860e24..ecc2d41 100644 --- a/tests/Gantt/apireview/entrydialog.cpp +++ b/tests/Gantt/apireview/entrydialog.cpp @@ -1,162 +1,162 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KGantt library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "entrydialog.h" #include "ui_entrydialog.h" #include #include #include #include #include Q_DECLARE_METATYPE( QPersistentModelIndex ) EntryDialog::EntryDialog( const QAbstractItemModel* model, QWidget* parent, Qt::WindowFlags f ) : QDialog( parent, f ), indexList( QList() ), ui( new Ui::EntryDialog ) { ui->setupUi( this ); this->model = model; init(); } void EntryDialog::init() { ui->type->addItem( tr( "Event" ), KGantt::TypeEvent ); ui->type->addItem( tr( "Task" ), KGantt::TypeTask ); ui->type->addItem( tr( "Summary" ), KGantt::TypeSummary ); ui->type->addItem( tr( "Multi" ), KGantt::TypeMulti ); for (int row = 0; row < model->rowCount(); ++row ) addDependItem( model, model->index( row, 0 ) ); - connect( ui->startDate, SIGNAL( dateTimeChanged( const QDateTime& ) ), this, SLOT( updateEndDate( const QDateTime& ) ) ); - connect( ui->readOnly, SIGNAL( toggled( bool ) ), this, SLOT( disableEditing( bool ) ) ); + connect( ui->startDate, SIGNAL(dateTimeChanged(QDateTime)), this, SLOT(updateEndDate(QDateTime)) ); + connect( ui->readOnly, SIGNAL(toggled(bool)), this, SLOT(disableEditing(bool)) ); ui->startDate->setDateTime( QDateTime::currentDateTime() ); } void EntryDialog::initFrom( const QModelIndex & index, const KGantt::ConstraintModel* constraintModel ) { int row = index.row(); const QModelIndex parent = index.parent(); ui->name->setText( model->data( model->index( row, 0, parent ) ).toString() ); ui->legend->setText( model->data( model->index( row, 5, parent ) ).toString() ); int idx = ui->type->findData( model->data( model->index( row, 1, parent ) ).toInt() ); ui->type->setCurrentIndex( idx ); ui->startDate->setDateTime( model->data( model->index( row, 2, parent ) ).toDateTime() ); ui->endDate->setDateTime( model->data( model->index( row, 3, parent ) ).toDateTime() ); ui->completion->setValue( model->data( model->index( row, 4, parent ) ).toInt() ); ui->readOnly->setChecked( !(model->flags( model->index( row, 0, parent ) ) & Qt::ItemIsEditable) ); QList constraints = constraintModel->constraintsForIndex( model->index( row, 0, parent ) ); if ( constraints.isEmpty() ) return; QModelIndex constraintIndex; for ( int i = 0; i < constraints.size(); ++i ) { KGantt::Constraint constraint = constraints[i]; if ( constraint.endIndex() == index ) { constraintIndex = constraint.startIndex(); break; } } if ( !constraintIndex.isValid() ) return; ui->depends->setCurrentIndex( indexList.indexOf( constraintIndex ) + 1 ); } void EntryDialog::addDependItem( const QAbstractItemModel* model, const QModelIndex & index, int indent) { indexList << QPersistentModelIndex( index ); QString str = QString( "%1%2" ).arg( QString().fill( ' ', indent ) ).arg( model->data( index ).toString() ); ui->depends->addItem( str ); for (int row = 0; row < model->rowCount( index ); ++row ) addDependItem( model, model->index( row, 0, index ), indent + 2 ); } QString EntryDialog::name() const { return ui->name->text(); } int EntryDialog::type() const { return ui->type->itemData( ui->type->currentIndex() ).toInt(); } QDateTime EntryDialog::startDate() const { return ui->startDate->dateTime(); } QDateTime EntryDialog::endDate() const { return ui->endDate->dateTime(); } int EntryDialog::completion() const { return ui->completion->value(); } void EntryDialog::updateEndDate(const QDateTime & startDate) { ui->endDate->setMinimumDate( startDate.date() ); ui->endDate->setMinimumTime( startDate.time() ); } bool EntryDialog::readOnly() const { return ui->readOnly->isChecked(); } QModelIndex EntryDialog::depends() const { if ( ui->depends->currentIndex() == 0 ) return QModelIndex(); QPersistentModelIndex index = indexList[ ui->depends->currentIndex() - 1 ]; if ( index.isValid() ) return index; return QModelIndex(); } QString EntryDialog::legend() const { return ui->legend->text(); } void EntryDialog::disableEditing(bool disable) { ui->name->setEnabled( !disable ); ui->type->setEnabled( !disable ); ui->completion->setEnabled( !disable ); ui->startDate->setEnabled( !disable ); ui->endDate->setEnabled( !disable ); ui->depends->setEnabled( !disable ); } diff --git a/tests/Gantt/apireview/mainwindow.cpp b/tests/Gantt/apireview/mainwindow.cpp index 0222698..eaf9401 100644 --- a/tests/Gantt/apireview/mainwindow.cpp +++ b/tests/Gantt/apireview/mainwindow.cpp @@ -1,366 +1,366 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KGantt library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "mainwindow.h" #include "ui_mainwindow.h" #include "entrydelegate.h" #include "entrydialog.h" #include #include #include #include #include #include #include #include #include #include #include #include MainWindow::MainWindow( QWidget* parent, Qt::WindowFlags flags ) : QMainWindow( parent, flags ), dayWidth( 70 ), ui( new Ui::MainWindow ) { ui->setupUi( this ); initModel(); initActions(); initItemDelegate(); initGrid(); QTreeView* leftView = qobject_cast( ui->ganttView->leftView() ); Q_ASSERT( leftView ); leftView->setColumnHidden( 1, true ); leftView->setColumnHidden( 2, true ); leftView->setColumnHidden( 3, true ); leftView->setColumnHidden( 4, true ); leftView->setColumnHidden( 5, true ); leftView->header()->setStretchLastSection( true ); - connect( ui->ganttView->leftView(), SIGNAL( customContextMenuRequested( const QPoint& ) ), - this, SLOT( showContextMenu( const QPoint& ) ) ); - connect( ui->ganttView->selectionModel(), SIGNAL( selectionChanged( const QItemSelection&, const QItemSelection& ) ), - this, SLOT( enableActions( const QItemSelection& ) ) ); - - connect( ui->ganttView->graphicsView(), SIGNAL( clicked( const QModelIndex& ) ), - this, SLOT( slotClicked( const QModelIndex& ) ) ); - connect( ui->ganttView->graphicsView(), SIGNAL( qrealClicked( const QModelIndex& ) ), - this, SLOT( slotDoubleClicked( const QModelIndex& ) ) ); + connect( ui->ganttView->leftView(), SIGNAL(customContextMenuRequested(QPoint)), + this, SLOT(showContextMenu(QPoint)) ); + connect( ui->ganttView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + this, SLOT(enableActions(QItemSelection)) ); + + connect( ui->ganttView->graphicsView(), SIGNAL(clicked(QModelIndex)), + this, SLOT(slotClicked(QModelIndex)) ); + connect( ui->ganttView->graphicsView(), SIGNAL(qrealClicked(QModelIndex)), + this, SLOT(slotDoubleClicked(QModelIndex)) ); } void MainWindow::initModel() { model = new QStandardItemModel( 0, 6, this ); model->setHeaderData( 0, Qt::Horizontal, tr( "Task" ) ); ui->ganttView->setModel( model ); KGantt::Legend* l = new KGantt::Legend; l->setWindowTitle( tr( "Legend" ) ); l->show(); l->setModel( model ); constraintModel = new KGantt::ConstraintModel( this ); ui->ganttView->setConstraintModel( constraintModel ); } void MainWindow::initActions() { newEntryAction = new QAction( tr( "New entry" ), this ); newEntryAction->setShortcut( QKeySequence::New ); - connect( newEntryAction, SIGNAL( triggered() ), this, SLOT( addNewEntry() ) ); + connect( newEntryAction, SIGNAL(triggered()), this, SLOT(addNewEntry()) ); removeEntryAction = new QAction( tr( "Remove entry" ), this ); removeEntryAction->setShortcut( QKeySequence::Delete ); - connect( removeEntryAction, SIGNAL( triggered() ), this, SLOT( removeEntry() ) ); + connect( removeEntryAction, SIGNAL(triggered()), this, SLOT(removeEntry()) ); demoAction = new QAction( tr( "Demo entry" ), this ); - connect( demoAction, SIGNAL( triggered() ), this, SLOT( addDemoEntry() ) ); + connect( demoAction, SIGNAL(triggered()), this, SLOT(addDemoEntry()) ); printAction = new QAction( tr( "Print Preview..." ), this ); - connect( printAction, SIGNAL( triggered() ), this, SLOT( printPreview() ) ); + connect( printAction, SIGNAL(triggered()), this, SLOT(printPreview()) ); zoomInAction = new QAction( tr( "Zoom In" ), this ); zoomInAction->setShortcut( QKeySequence::ZoomIn ); - connect( zoomInAction, SIGNAL( triggered() ), this, SLOT( zoomIn() ) ); + connect( zoomInAction, SIGNAL(triggered()), this, SLOT(zoomIn()) ); zoomOutAction = new QAction( tr( "Zoom Out" ), this ); zoomOutAction->setShortcut( QKeySequence::ZoomOut ); - connect( zoomOutAction, SIGNAL( triggered() ), this, SLOT( zoomOut() ) ); + connect( zoomOutAction, SIGNAL(triggered()), this, SLOT(zoomOut()) ); ui->ganttView->leftView()->setContextMenuPolicy( Qt::CustomContextMenu ); ui->ganttView->leftView()->addAction( newEntryAction ); ui->ganttView->leftView()->addAction( removeEntryAction ); QMenu* entryMenu = menuBar()->addMenu( tr( "Entry" ) ); entryMenu->addAction( newEntryAction ); entryMenu->addAction( removeEntryAction ); entryMenu->addSeparator(); entryMenu->addAction( demoAction ); entryMenu->addSeparator(); entryMenu->addAction( printAction ); QMenu* zoomMenu = menuBar()->addMenu( tr( "Zoom" ) ); zoomMenu->addAction( zoomInAction ); zoomMenu->addAction( zoomOutAction ); enableActions( QItemSelection() ); } void MainWindow::initItemDelegate() { EntryDelegate* delegate = new EntryDelegate( constraintModel, this ); ui->ganttView->leftView()->setItemDelegate( delegate ); } void MainWindow::initGrid() { grid = new KGantt::DateTimeGrid(); grid->setDayWidth( dayWidth ); ui->ganttView->setGrid( grid ); } void MainWindow::showContextMenu( const QPoint& pos ) { if ( !ui->ganttView->leftView()->indexAt( pos ).isValid() ) ui->ganttView->selectionModel()->clearSelection(); QMenu menu( ui->ganttView->leftView() ); menu.addAction( newEntryAction ); menu.addAction( removeEntryAction ); menu.exec( ui->ganttView->leftView()->viewport()->mapToGlobal( pos ) ); } void MainWindow::enableActions(const QItemSelection & selected) { if ( selected.indexes().isEmpty() ) { newEntryAction->setEnabled( true ); removeEntryAction->setEnabled( false ); return; } QModelIndex selectedIndex = selected.indexes()[0]; if ( model->data( model->index( selectedIndex.row(), 1 ) ) == KGantt::TypeEvent || model->data( model->index( selectedIndex.row(), 1 ) ) == KGantt::TypeTask ) { newEntryAction->setEnabled( false ); removeEntryAction->setEnabled( true ); return; } newEntryAction->setEnabled( true ); removeEntryAction->setEnabled( true ); } void MainWindow::addNewEntry() { QPointer dialog = new EntryDialog( model ); dialog->setWindowTitle( tr( "New Entry") ); if ( dialog->exec() == QDialog::Rejected || !dialog ) { delete dialog; return; } QModelIndexList selectedIndexes = ui->ganttView->selectionModel()->selectedIndexes(); const QModelIndex parent = selectedIndexes.value( 0 ); if ( !model->insertRow( model->rowCount( parent ), parent ) ) return; int row = model->rowCount( parent ) - 1; if (model->columnCount( parent ) < model->columnCount()) { model->insertColumns( model->columnCount( parent ), model->columnCount() - model->columnCount( parent ), parent ); } model->setData( model->index( row, 0, parent ), dialog->name() ); model->setData( model->index( row, 1, parent ), dialog->type() ); if ( dialog->type() != KGantt::TypeSummary ) { model->setData( model->index( row, 2, parent ), dialog->startDate(), KGantt::StartTimeRole ); model->setData( model->index( row, 3, parent ), dialog->endDate(), KGantt::EndTimeRole ); } model->setData( model->index( row, 4, parent ), dialog->completion() ); model->setData( model->index( row, 5, parent ), dialog->legend() ); addConstraint( dialog->depends(), model->index( row, 0, parent ) ); setReadOnly( model->index( row, 0, parent ), dialog->readOnly() ); delete dialog; } void MainWindow::setReadOnly(const QModelIndex & index, bool readOnly) { int row = index.row(); const QModelIndex parent = index.parent(); QStandardItem* item; item = model->itemFromIndex( model->index( row, 0, parent ) ); item->setFlags( readOnly ? item->flags() & ~Qt::ItemIsEditable : item->flags() | Qt::ItemIsEditable ); item = model->itemFromIndex( model->index( row, 1, parent ) ); item->setFlags( readOnly ? item->flags() & ~Qt::ItemIsEditable : item->flags() | Qt::ItemIsEditable ); item = model->itemFromIndex( model->index( row, 2, parent ) ); item->setFlags( readOnly ? item->flags() & ~Qt::ItemIsEditable : item->flags() | Qt::ItemIsEditable ); item = model->itemFromIndex( model->index( row, 3, parent ) ); item->setFlags( readOnly ? item->flags() & ~Qt::ItemIsEditable : item->flags() | Qt::ItemIsEditable ); item = model->itemFromIndex( model->index( row, 4, parent ) ); item->setFlags( readOnly ? item->flags() & ~Qt::ItemIsEditable : item->flags() | Qt::ItemIsEditable ); } void MainWindow::addConstraint(const QModelIndex & index1, const QModelIndex & index2) { if ( !index1.isValid() || !index2.isValid() ) return; KGantt::Constraint c( index1, index2 ); ui->ganttView->constraintModel()->addConstraint( c ); } void MainWindow::addConstraint( const QStandardItem* item1, const QStandardItem* item2 ) { addConstraint( model->indexFromItem( item1 ), model->indexFromItem( item2 ) ); } void MainWindow::removeEntry() { QModelIndexList selectedIndexes = ui->ganttView->selectionModel()->selectedIndexes(); QModelIndex index = selectedIndexes.value( 0 ); if ( !index.isValid() ) return; model->removeRow( index.row(), index.parent() ); } class MyStandardItem : public QStandardItem { public: MyStandardItem( const QVariant& v ) : QStandardItem() { setData( v, Qt::DisplayRole ); } MyStandardItem( const QString& v ) : QStandardItem() { setData( v, Qt::DisplayRole ); } MyStandardItem( const QDateTime& dt, int role ) : QStandardItem() { setData( QVariant::fromValue( dt ), role ); } }; void MainWindow::addDemoEntry() { QStandardItem* softwareRelease = new MyStandardItem( tr("Software Release" ) ); QStandardItem* codeFreeze = new MyStandardItem( tr( "Code Freeze" ) ); codeFreeze->setData( KGantt::TextPositionRole, KGantt::StyleOptionGanttItem::Right ); QStandardItem* packaging = new MyStandardItem( tr( "Packaging" ) ); QStandardItem* upload = new MyStandardItem( tr( "Upload" ) ); QStandardItem* testing = new MyStandardItem( tr( "Testing" ) ); QStandardItem* updateDocumentation = new MyStandardItem( tr( "Update Documentation" ) ); QDateTime now = QDateTime::currentDateTime(); softwareRelease->appendRow( QList() << codeFreeze << new MyStandardItem( KGantt::TypeEvent ) << new MyStandardItem( now, KGantt::StartTimeRole ) ); softwareRelease->appendRow( QList() << packaging << new MyStandardItem( KGantt::TypeTask ) << new MyStandardItem( now.addDays( 5 ), KGantt::StartTimeRole ) << new MyStandardItem( now.addDays( 10 ), KGantt::EndTimeRole ) ); softwareRelease->appendRow( QList() << upload << new MyStandardItem( KGantt::TypeTask ) << new MyStandardItem( now.addDays( 10 ).addSecs( 2*60*60 ), KGantt::StartTimeRole ) << new MyStandardItem( now.addDays( 11 ), KGantt::EndTimeRole ) ); softwareRelease->appendRow( QList() << testing << new MyStandardItem( KGantt::TypeTask ) << new MyStandardItem( now.addSecs( 3*60*60 ), KGantt::StartTimeRole ) << new MyStandardItem( now.addDays( 5 ), KGantt::EndTimeRole ) ); softwareRelease->appendRow( QList() << updateDocumentation << new MyStandardItem( KGantt::TypeTask ) << new MyStandardItem( now.addSecs( 3*60*60 ), KGantt::StartTimeRole ) << new MyStandardItem( now.addDays( 3 ), KGantt::EndTimeRole ) ); model->appendRow( QList() << softwareRelease << new MyStandardItem( KGantt::TypeSummary ) ); addConstraint( codeFreeze, packaging ); addConstraint( codeFreeze, testing ); addConstraint( codeFreeze, updateDocumentation ); addConstraint( packaging, upload ); addConstraint( testing, packaging ); addConstraint( updateDocumentation, packaging ); } void MainWindow::zoomIn() { dayWidth += 10; if ( dayWidth > 400 ) grid->setScale( KGantt::DateTimeGrid::ScaleHour ); grid->setDayWidth( dayWidth ); } void MainWindow::zoomOut() { dayWidth -= 10; if ( dayWidth < 10 ) dayWidth = 10; if ( dayWidth <= 400 ) grid->setScale( KGantt::DateTimeGrid::ScaleDay ); grid->setDayWidth( dayWidth ); } void MainWindow::printPreview() { QLabel* preview = new QLabel( this, Qt::Window ); preview->setAttribute( Qt::WA_DeleteOnClose ); preview->setScaledContents( true ); preview->setWindowTitle( tr( "Print Preview" ) ); QPixmap pix( 1000, 300 ); pix.fill( Qt::white ); { QPainter p( &pix ); p.setRenderHints( QPainter::Antialiasing ); ui->ganttView->print( &p, pix.rect() ); } preview->setPixmap( pix ); preview->show(); } void MainWindow::slotClicked( const QModelIndex& index ) { statusBar()->showMessage( tr( "(%1,%2,_,%4) clicked" ) .arg( index.row() ) .arg( index.column() ) .arg( ( quint64 )index.model() ) ); } void MainWindow::slotDoubleClicked( const QModelIndex& index ) { statusBar()->showMessage( tr( "(%1,%2,_,%4) qreal clicked" ) .arg( index.row() ) .arg( index.column() ) .arg( ( quint64 )index.model() ) ); } diff --git a/tests/Gantt/customconstraints/main.cpp b/tests/Gantt/customconstraints/main.cpp index a37005e..ce3ad2f 100644 --- a/tests/Gantt/customconstraints/main.cpp +++ b/tests/Gantt/customconstraints/main.cpp @@ -1,136 +1,136 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KGantt library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class MyGraphicsView : public KGantt::GraphicsView { Q_OBJECT public: MyGraphicsView( QWidget* parent=0 ) : KGantt::GraphicsView( parent ) { } /*reimp*/void addConstraint( const QModelIndex& from, const QModelIndex& to, Qt::KeyboardModifiers modifiers ) Q_DECL_OVERRIDE { qDebug() << "MyGraphicsView::addConstraint()"; if ( isReadOnly() ) return; KGantt::ConstraintModel* cmodel = constraintModel(); KGantt::Constraint c( from, to, ( modifiers&Qt::ShiftModifier )?KGantt::Constraint::TypeHard:KGantt::Constraint::TypeSoft ); c.setData( KGantt::Constraint::ValidConstraintPen, QPen( QColor( Qt::green ), 3, Qt::DashLine ) ); c.setData( KGantt::Constraint::InvalidConstraintPen, QPen( QColor( Qt::blue ), 3, Qt::DashLine ) ); if ( cmodel->hasConstraint( c ) ) cmodel->removeConstraint( c ); else cmodel->addConstraint( c ); } }; class MyStandardItem : public QStandardItem { public: MyStandardItem( const QVariant& v ) : QStandardItem() { setData( v, Qt::DisplayRole ); } MyStandardItem( const QString& v ) : QStandardItem() { setData( v, Qt::DisplayRole ); } MyStandardItem( const QDateTime& dt, int role ) : QStandardItem() { setData( QVariant::fromValue( dt ), role ); } }; class MyWidget : public QWidget { Q_OBJECT public: MyWidget() : QWidget( 0 ) { view.setGraphicsView( new MyGraphicsView ); qDebug() << "Building data"; //proxyModel.setSourceModel( &model ); for ( int h = 0; h < 2; ++h ) { QList items; items << new MyStandardItem( QString::fromLatin1( "Item %1" ).arg( h ) ) << new MyStandardItem( KGantt::TypeTask ) << new MyStandardItem( QDateTime::currentDateTime().addDays( h ), KGantt::StartTimeRole ) << new MyStandardItem( QDateTime::currentDateTime().addDays( h+1 ), KGantt::EndTimeRole ) << new MyStandardItem( 50 ); model.appendRow( items ); } qDebug() << "Creating view"; slider.setOrientation( Qt::Horizontal ); slider.setRange( 1, 10000 ); slider.setValue( 100 ); QVBoxLayout* l = new QVBoxLayout( this ); l->addWidget( &view ); l->addWidget( &slider ); grid.setStartDateTime( QDateTime::currentDateTime().addDays( -3 ) ); grid.setDayWidth( 100 ); //grid.setNoInformationBrush( Qt::NoBrush ); view.setGrid( &grid ); view.setModel(&model); - connect( &slider, SIGNAL( valueChanged( int ) ), - this, SLOT( slotZoom( int ) ) ); + connect( &slider, SIGNAL(valueChanged(int)), + this, SLOT(slotZoom(int)) ); } public slots: void slotZoom( int z ) { grid.setDayWidth( z ); } private: KGantt::View view; KGantt::DateTimeGrid grid; QSlider slider; QStandardItemModel model; }; int main( int argc, char** argv ) { QApplication app( argc, argv ); //QPixmapCache::setCacheLimit( 30*1024 ); MyWidget w; w.show(); return app.exec(); } #include "main.moc" diff --git a/tests/Gantt/gfxview/main.cpp b/tests/Gantt/gfxview/main.cpp index 3a49cfc..184b4e8 100644 --- a/tests/Gantt/gfxview/main.cpp +++ b/tests/Gantt/gfxview/main.cpp @@ -1,151 +1,151 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KGantt library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include class MyRowController : public KGantt::AbstractRowController { private: static const int ROW_HEIGHT; QPointer m_model; public: MyRowController() { } void setModel( QAbstractItemModel* model ) { m_model = model; } /*reimp*/int headerHeight() const Q_DECL_OVERRIDE { return 40; } /*reimp*/ bool isRowVisible( const QModelIndex& ) const Q_DECL_OVERRIDE { return true;} /*reimp*/ bool isRowExpanded( const QModelIndex& ) const Q_DECL_OVERRIDE { return false; } /*reimp*/ KGantt::Span rowGeometry( const QModelIndex& idx ) const Q_DECL_OVERRIDE { qDebug() << "rowGeometry(" << idx.row()<<")"; return KGantt::Span( idx.row()*ROW_HEIGHT, ROW_HEIGHT ); } /*reimp*/ int maximumItemHeight() const Q_DECL_OVERRIDE { return ROW_HEIGHT/2; } /*reimp*/ int totalHeight() const Q_DECL_OVERRIDE { return m_model.isNull()?0:m_model->rowCount()*ROW_HEIGHT; } /*reimp*/ QModelIndex indexAt( int height ) const Q_DECL_OVERRIDE { return m_model->index( height/ROW_HEIGHT, 0 ); } /*reimp*/ QModelIndex indexBelow( const QModelIndex& idx ) const Q_DECL_OVERRIDE { if ( !idx.isValid() )return QModelIndex(); return idx.model()->index( idx.row()+1, idx.column(), idx.parent() ); } /*reimp*/ QModelIndex indexAbove( const QModelIndex& idx ) const Q_DECL_OVERRIDE { if ( !idx.isValid() )return QModelIndex(); return idx.model()->index( idx.row()-1, idx.column(), idx.parent() ); } }; const int MyRowController::ROW_HEIGHT = 30; class MyStandardItemModel : public QStandardItemModel { public: /*reimp*/bool setData( const QModelIndex& idx, const QVariant& value, int role ) Q_DECL_OVERRIDE { //qDebug() << "MyStandardItemModel::setData("<setReadOnly(true); view->setRowController( rowController ); view->show(); MyStandardItemModel model; for ( int i = 0; i < 10; ++i ) { QList items; for ( int j = 0; j < 10; ++j ) { QStandardItem* item = new QStandardItem; //for ( int col = 0; col < 5; ++col ) { // QStandardItem* colitem = new QStandardItem; item->setText( QString::fromLatin1( "Item %1" ).arg( i ) ); item->setData( KGantt::TypeTask, KGantt::ItemTypeRole ); item->setData( QDateTime::currentDateTime().addDays( 2*j ).addMSecs( 100000*i ), KGantt::StartTimeRole ); item->setData( QDateTime::currentDateTime().addDays( 2*j+1 ).addMSecs( 100000*i ), KGantt::EndTimeRole ); item->setData( qVariantFromValue( random_alignment() ), Qt::TextAlignmentRole ); item->setData( qVariantFromValue( random_position()), KGantt::TextPositionRole ); item->setFlags( item->flags() & ~Qt::ItemIsSelectable ); // item->appendColumn( QList() << colitem ); //} items.push_back( item ); } model.appendRow( items ); } qDebug() << &model; rowController->setModel( &model ); view->setModel( &model ); #if 0 QPushButton* pb = new QPushButton( QObject::tr( "Reset" ) ); - QObject::connect( pb, SIGNAL( clicked() ), - &model, SIGNAL( modelReset() ) ); + QObject::connect( pb, SIGNAL(clicked()), + &model, SIGNAL(modelReset()) ); pb->show(); #endif return app.exec(); //return 0; } diff --git a/tests/Gantt/listview/main.cpp b/tests/Gantt/listview/main.cpp index c415160..c5118b6 100644 --- a/tests/Gantt/listview/main.cpp +++ b/tests/Gantt/listview/main.cpp @@ -1,141 +1,141 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KGantt library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include class MyStandardItem : public QStandardItem { public: MyStandardItem( const QVariant& v, int role = Qt::DisplayRole ) : QStandardItem() { setData( v, role ); } MyStandardItem( const QString& v ) : QStandardItem() { setData( v, Qt::DisplayRole ); } }; class MyListView : public QListView { public: MyListView(QWidget* parent=0) : QListView(parent) { // Make some space for the header setViewportMargins(0, fontMetrics().height()*2+4*frameWidth(), 0, 0); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); } }; class MyWidget : public QWidget { Q_OBJECT public: MyWidget() : QWidget( 0 ) { qDebug() << "Building data"; //proxyModel.setSourceModel( &model ); for ( int i = 0; i < 10; ++i ) { QStandardItem* item = new MyStandardItem( QString::fromLatin1("Multi Item %1").arg(i) ); for ( int j=0; j < 20; j+=3 ) { item->appendRow( QList() << new MyStandardItem( QString::fromLatin1( "Item %1" ).arg( j ) ) << new MyStandardItem( KGantt::TypeTask ) << new MyStandardItem( QDateTime::currentDateTime().addDays( j ), KGantt::StartTimeRole ) << new MyStandardItem( QDateTime::currentDateTime().addDays( j+1+i/7 ), KGantt::EndTimeRole ) << new MyStandardItem( 50 ) ); } item->appendRow( QList() << new MyStandardItem( QString::fromLatin1( "Event" ) ) << new MyStandardItem( KGantt::TypeEvent ) << new MyStandardItem( QDateTime::currentDateTime(), KGantt::StartTimeRole ) << new MyStandardItem( QDateTime(), KGantt::EndTimeRole ) << new MyStandardItem( QString() ) ); model.appendRow( QList() << item << new MyStandardItem( KGantt::TypeMulti ) << new MyStandardItem( QString() ) << new MyStandardItem( QString() ) << new MyStandardItem( QString() ) ); } //cmodel.addConstraint( KGantt::Constraint( proxyModel.index( 0, 3 ), proxyModel.index( 10, 3 ) ) ); //cmodel.addConstraint( KGantt::Constraint( proxyModel.index( 10, 3 ), proxyModel.index( 5, 3 ) ) ); QModelIndex pidx = model.index( 0, 0 ); pidx = model.index( 0, 0, pidx ); cmodel.addConstraint( KGantt::Constraint( model.index( 0, 0, pidx), model.index( 1, 0, pidx ) ) ); cmodel.addConstraint( KGantt::Constraint( model.index( 1, 0, pidx), model.index( 0, 0, pidx ) ) ); cmodel.addConstraint( KGantt::Constraint( model.index( 1, 0, pidx), model.index( 10, 0, pidx ) ) ); cmodel.addConstraint( KGantt::Constraint( model.index( 3, 0, pidx), model.index( 5, 0, pidx ) ) ); cmodel.addConstraint( KGantt::Constraint( model.index( 7, 0, pidx), model.index( 4, 0, pidx ) ) ); qDebug() << "Creating view"; slider.setOrientation( Qt::Horizontal ); slider.setRange( 1, 1000 ); slider.setValue( 100 ); QVBoxLayout* l = new QVBoxLayout( this ); l->addWidget( &view ); l->addWidget( &slider ); grid.setStartDateTime( QDateTime::currentDateTime().addDays( -3 ) ); grid.setDayWidth( 100 ); grid.setFreeDays( QSet() << Qt::Saturday << Qt::Sunday ); grid.setFreeDaysBrush( QBrush( Qt::red ) ); QListView* lv = new MyListView; view.setLeftView(lv); view.setRowController( new KGantt::ListViewRowController(lv,view.ganttProxyModel())); view.setGrid( &grid ); view.setModel(&model); //view.setConstraintModel( &cmodel ); - connect( &slider, SIGNAL( valueChanged( int ) ), - this, SLOT( slotZoom( int ) ) ); + connect( &slider, SIGNAL(valueChanged(int)), + this, SLOT(slotZoom(int)) ); } public slots: void slotZoom( int z ) { grid.setDayWidth( z ); } private: KGantt::View view; KGantt::DateTimeGrid grid; QSlider slider; QStandardItemModel model; KGantt::ConstraintModel cmodel; }; int main( int argc, char** argv ) { QApplication app( argc, argv ); MyWidget w; w.show(); return app.exec(); } #include "main.moc" diff --git a/tests/Gantt/reorder/main.cpp b/tests/Gantt/reorder/main.cpp index 4d4ed10..27fa7dd 100644 --- a/tests/Gantt/reorder/main.cpp +++ b/tests/Gantt/reorder/main.cpp @@ -1,173 +1,173 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KGantt library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include /* Test model that uses beginMoveRows() */ class MyTaskModel : public QAbstractTableModel { struct Task { QString title; QDateTime start, end; }; QList m_tasks; public: MyTaskModel( QObject* parent=0 ) : QAbstractTableModel( parent ) { Task task; task.title = tr( "Item 1" ); task.start = QDateTime::currentDateTime(); task.end = task.start.addDays( 1 ); m_tasks.push_back( task ); task.title = tr( "Item 2" ); task.start = QDateTime::currentDateTime().addDays( 2 ); task.end = task.start.addDays( 1 ); m_tasks.push_back( task ); task.title = tr( "Item 3" ); task.start = QDateTime::currentDateTime().addDays( 4 ); task.end = task.start.addDays( 1 ); m_tasks.push_back( task ); task.title = tr( "Item 4" ); task.start = QDateTime::currentDateTime().addDays( 6 ); task.end = task.start.addDays( 1 ); m_tasks.push_back( task ); } /*reimp*/int rowCount( const QModelIndex& index = QModelIndex() ) const Q_DECL_OVERRIDE { return index.isValid()?0:m_tasks.size(); } /*reimp*/int columnCount( const QModelIndex& index = QModelIndex() ) const Q_DECL_OVERRIDE { return index.isValid()?0:4; } /*reimp*/QVariant data( const QModelIndex& index, int role = Qt::DisplayRole ) const Q_DECL_OVERRIDE { if ( index.isValid() && index.row() < rowCount() && index.column() < columnCount() ) { switch ( index.column() ) { case 0: return ( role == Qt::DisplayRole ) ?QVariant::fromValue( m_tasks[index.row()].title ) :QVariant(); case 1: return ( role == Qt::DisplayRole ) ?QVariant::fromValue( ( int )KGantt::TypeTask ) :QVariant(); case 2: return ( role == KGantt::StartTimeRole || role == Qt::DisplayRole ) ?QVariant::fromValue( m_tasks[index.row()].start ) :QVariant(); case 3: return ( role == KGantt::EndTimeRole || role == Qt::DisplayRole) ?QVariant::fromValue( m_tasks[index.row()].end ) :QVariant(); } } return QVariant(); } void moveRow( int from, int to ) { if ( from == to ) return; if ( from >= m_tasks.size() || to >= m_tasks.size()+1 ) return; if ( beginMoveRows( QModelIndex(), from, from, QModelIndex(), to ) ) { m_tasks.move( from, to ); endMoveRows(); } else { assert( 0 ); } } }; /* Test class to see the effect of moving rows */ class MoveHelper : public QObject { public: MoveHelper( MyTaskModel* model, KGantt::ConstraintModel*, KGantt::View*, #if 0 KGantt::ConstraintModel* constraints, KGantt::View* view, #endif int row1, int row2 ) : QObject( model ), m_model( model ), #if 0 m_constraints( constraints ), m_view( view ), #endif m_row1( row1 ), m_row2( row2 ) { } void showContraints( const QString& /*pfx*/ ) { #if 0 qDebug() << pfx << *m_constraints; qDebug() << "0:" << m_constraints->constraintsForIndex( m_model->index( 0, 0 ) ); qDebug() << "1:"<< m_constraints->constraintsForIndex( m_model->index( 1, 0 ) ); qDebug() << "2:"<< m_constraints->constraintsForIndex( m_model->index( 2, 0 ) ); qDebug() << "3:"<< m_constraints->constraintsForIndex( m_model->index( 3, 0 ) ); #endif } ~MoveHelper() { qDebug() << "Moving row" << m_row1 << "to" << m_row2; showContraints( "Before:" ); m_model->moveRow( m_row1, m_row2 ); showContraints( "After:" ); // Hack until KGantt supports this: //m_view->setConstraintModel( m_constraints ); } private: MyTaskModel* m_model; #if 0 KGantt::ConstraintModel* m_constraints; KGantt::View* m_view; #endif int m_row1, m_row2; }; int main( int argc, char** argv ) { QApplication app( argc, argv ); KGantt::View* view = new KGantt::View; MyTaskModel model; KGantt::ConstraintModel constraints; constraints.addConstraint( KGantt::Constraint( model.index( 0, 0 ), model.index( 1, 0 ) ) ); constraints.addConstraint( KGantt::Constraint( model.index( 0, 0 ), model.index( 2, 0 ) ) ); constraints.addConstraint( KGantt::Constraint( model.index( 1, 0 ), model.index( 3, 0 ) ) ); view->setModel( &model ); view->setConstraintModel( &constraints ); view->show(); /* After 5 seconds, move row 1 to pos 0: */ - QTimer::singleShot( 5000, new MoveHelper( &model, &constraints, view, 1, 0 ), SLOT( deleteLater() ) ); + QTimer::singleShot( 5000, new MoveHelper( &model, &constraints, view, 1, 0 ), SLOT(deleteLater()) ); return app.exec(); } diff --git a/tests/Gantt/view/main.cpp b/tests/Gantt/view/main.cpp index 7729d4d..ccad291 100644 --- a/tests/Gantt/view/main.cpp +++ b/tests/Gantt/view/main.cpp @@ -1,203 +1,203 @@ /** * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. * * This file is part of the KGantt library. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class MyStandardItem : public QStandardItem { public: MyStandardItem( const QVariant& v ) : QStandardItem() { setData( v, Qt::DisplayRole ); //setFlags( flags() & ~Qt::ItemIsEditable ); } MyStandardItem( const QString& v ) : QStandardItem() { setData( v, Qt::DisplayRole ); //setFlags( flags() & ~Qt::ItemIsEditable ); } MyStandardItem( const QDateTime& dt, int role ) : QStandardItem() { setData( QVariant::fromValue( dt ), role ); } }; class MyWidget : public QWidget { Q_OBJECT public: MyWidget() : QWidget( 0 ) { qDebug() << "Building data"; //proxyModel.setSourceModel( &model ); for ( int h = 0; h < 20; ++h ) { QStandardItem* topitem = new MyStandardItem( QString::fromLatin1("Top Item %1").arg( h )); for ( int i = 0; i < 20; ++i ) { QStandardItem* item = new MyStandardItem( QString::fromLatin1("Multi Item %1").arg( i )); for ( int j=0; j < 30; j+=3 ) { item->appendRow( QList() << new MyStandardItem( QString::fromLatin1( "Item %1" ).arg( j ) ) << new MyStandardItem( KGantt::TypeTask ) << new MyStandardItem( QDateTime::currentDateTime().addDays( j ), KGantt::StartTimeRole ) << new MyStandardItem( QDateTime::currentDateTime().addDays( j+1+i/7 ), KGantt::EndTimeRole ) << new MyStandardItem( 50 ) ); } item->appendRow( QList() << new MyStandardItem( QString::fromLatin1( "Event" ) ) << new MyStandardItem( KGantt::TypeEvent ) << new MyStandardItem( QDateTime::currentDateTime(), KGantt::StartTimeRole ) << new MyStandardItem( QDateTime(), KGantt::EndTimeRole ) << new MyStandardItem( QString() ) ); topitem->appendRow( QList() << item << new MyStandardItem( KGantt::TypeMulti ) << new MyStandardItem( QString() ) << new MyStandardItem( QString() ) << new MyStandardItem( QString() ) ); } model.appendRow( QList() << topitem << new MyStandardItem(KGantt::TypeMulti/*Summary*/ ) << new MyStandardItem(QString()) << new MyStandardItem(QString()) << new MyStandardItem(QString()) ); } model.appendRow( QList() << new MyStandardItem( QString::fromLatin1( "No data" ) ) ); //cmodel.addConstraint( KGantt::Constraint( proxyModel.index( 0, 3 ), proxyModel.index( 10, 3 ) ) ); //cmodel.addConstraint( KGantt::Constraint( proxyModel.index( 10, 3 ), proxyModel.index( 5, 3 ) ) ); QModelIndex pidx = model.index( 0, 0 ); pidx = model.index( 0, 0, pidx ); cmodel.addConstraint( KGantt::Constraint( model.index( 0, 0, pidx), model.index( 1, 0, pidx ) ) ); cmodel.addConstraint( KGantt::Constraint( model.index( 1, 0, pidx), model.index( 0, 0, pidx ) ) ); cmodel.addConstraint( KGantt::Constraint( model.index( 1, 0, pidx), model.index( 10, 0, pidx ) ) ); cmodel.addConstraint( KGantt::Constraint( model.index( 3, 0, pidx), model.index( 5, 0, pidx ) ) ); cmodel.addConstraint( KGantt::Constraint( model.index( 7, 0, pidx), model.index( 4, 0, pidx ) ) ); qDebug() << "Creating view"; slider.setOrientation( Qt::Horizontal ); slider.setRange( 1, 10000 ); slider.setValue( 100 ); QVBoxLayout* l = new QVBoxLayout( this ); l->addWidget( &view ); l->addWidget( &slider ); grid.setStartDateTime( QDateTime::currentDateTime().addDays( -3 ) ); grid.setDayWidth( 100 ); //grid.setNoInformationBrush( Qt::NoBrush ); view.setGrid( &grid ); view.setModel(&model); //view.setConstraintModel( &cmodel ); - connect( &slider, SIGNAL( valueChanged( int ) ), - this, SLOT( slotZoom( int ) ) ); + connect( &slider, SIGNAL(valueChanged(int)), + this, SLOT(slotZoom(int)) ); QPushButton* pb1 = new QPushButton( tr("Print Preview...")); QPushButton* pb2 = new QPushButton( tr("Print...")); l->addWidget(pb1); l->addWidget(pb2); connect(pb1,SIGNAL(clicked()),this,SLOT(slotPrintPreview())); connect(pb2,SIGNAL(clicked()),this,SLOT(slotPrint())); view.graphicsView()->setHeaderContextMenuPolicy( Qt::CustomContextMenu ); - connect( view.graphicsView(), SIGNAL( headerContextMenuRequested( const QPoint& ) ), - this, SLOT( slotHeaderMenu( const QPoint& ) ) ); + connect( view.graphicsView(), SIGNAL(headerContextMenuRequested(QPoint)), + this, SLOT(slotHeaderMenu(QPoint)) ); } public slots: void slotZoom( int z ) { grid.setDayWidth( z ); } void slotPrintPreview() { QPixmap pix(1000,200); pix.fill(Qt::white); QPainter painter(&pix); view.print(&painter, pix.rect()); painter.end(); QLabel* label = new QLabel; label->setPixmap(pix); label->show(); } void slotPrint() { QPrinter printer( QPrinter::HighResolution ); QPrintDialog pd( &printer, this ); if ( pd.exec() == QDialog::Accepted ) { /* printer.setResolution( 600 ); QPainter p( &printer ); QFont f; f.setPixelSize( 9 ); p.setFont( f ); view.print( &p ); */ QRectF r = view.graphicsView()->mapToScene( view.graphicsView()->viewport()->rect() ).boundingRect(); view.print( &printer, r.left(), r.right() ); } } void slotHeaderMenu( const QPoint& pt ) { QMenu menu; menu.addAction( tr( "This" ) ); menu.addAction( tr( "is" ) ); menu.addAction( tr( "just" ) ); menu.addAction( tr( "a" ) ); menu.addAction( tr( "test" ) ); menu.exec( pt ); } private: KGantt::View view; KGantt::DateTimeGrid grid; QSlider slider; QStandardItemModel model; KGantt::ConstraintModel cmodel; }; int main( int argc, char** argv ) { QApplication app( argc, argv ); QPixmapCache::setCacheLimit( 30*1024 ); MyWidget w; w.show(); return app.exec(); } #include "main.moc"