diff --git a/src/KGantt/kganttgraphicsscene.cpp b/src/KGantt/kganttgraphicsscene.cpp index 7ec8d38..be4c30a 100644 --- a/src/KGantt/kganttgraphicsscene.cpp +++ b/src/KGantt/kganttgraphicsscene.cpp @@ -1,1007 +1,1007 @@ /* * 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 "kganttabstractgrid.h" #include "kganttdatetimegrid.h" #include "kganttsummaryhandlingproxymodel.h" #include "kganttgraphicsview.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( nullptr ), itemDelegate( new ItemDelegate( _q ) ), rowController( nullptr ), readOnly( false ), isPrinting( false ), drawColumnLabels( true ), labelsWidth( 0.0 ), summaryHandlingModel( new SummaryHandlingProxyModel( _q ) ), selectionModel( nullptr ) { 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() ), nullptr ); if ( item ) { item->removeStartConstraint( citem ); } item = items.value( summaryHandlingModel->mapFromSource( c.endIndex() ), nullptr ); 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() ), nullptr ); 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() ), nullptr ); 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 nullptr; } // 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(); } AbstractGrid *GraphicsScene::Private::getGrid() { if (grid.isNull()) { return static_cast(&default_grid); } return grid.data(); } const AbstractGrid *GraphicsScene::Private::getGrid() const { if (grid.isNull()) { return static_cast(&default_grid); } return grid.data(); } 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->getGrid(), 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->getGrid()->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->getGrid()->setRootIndex( idx ); } QModelIndex GraphicsScene::rootIndex() const { return d->getGrid()->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(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 ) { if (d->selectionModel) { d->selectionModel->disconnect( this ); } d->selectionModel = smodel; if (smodel) { connect(d->selectionModel, SIGNAL(modelChanged(QAbstractItemModel*)), this, SLOT(selectionModelChanged(QAbstractItemModel*))); connect( smodel, SIGNAL(selectionChanged(const QItemSelection&,const QItemSelection&)), this, SLOT(slotSelectionChanged(const QItemSelection&,const QItemSelection&)) ); } } 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 = nullptr; if ( d->getGrid() ) { d->getGrid()->disconnect( this ); model = d->getGrid()->model(); } d->grid = grid; connect( d->getGrid(), SIGNAL(gridChanged()), this, SLOT(slotGridChanged()) ); d->getGrid()->setModel( model ); slotGridChanged(); } // Returns the explicitly set grid AbstractGrid* GraphicsScene::grid() const { return d->grid; } // May also return the default_grid if a grid has not been set const AbstractGrid *GraphicsScene::getGrid() const { return d->getGrid(); } 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. */ GraphicsItem* GraphicsScene::createItem( ItemType type ) const { assert(views().count() == 1); GraphicsView *v = qobject_cast(views().first()); assert(v); return v->createItem(type); } void GraphicsScene::Private::recursiveUpdateMultiItem( const Span& span, const QModelIndex& idx ) { //qDebug() << "recursiveUpdateMultiItem("<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 = summaryHandlingModel->index( cr, 0, idx ) ).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 ),nullptr); 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 ),nullptr); 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 QList lst = item->startConstraints() + item->endConstraints(); - const QSet clst(lst.cbegin(), lst.cend()); + 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 nullptr; assert( idx.model() == summaryHandlingModel() ); QHash::const_iterator it = d->items.find( idx ); return ( it != d->items.end() )?*it:nullptr; } GraphicsItem* GraphicsScene::findItem( const QPersistentModelIndex& idx ) const { if ( !idx.isValid() ) return nullptr; assert( idx.model() == summaryHandlingModel() ); QHash::const_iterator it = d->items.find( idx ); return ( it != d->items.end() )?*it:nullptr; } 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( summaryHandlingModel()->index(idx.row(), i, parent ) ); }} 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::selectionModelChanged(QAbstractItemModel *model) { Q_UNUSED(model) } void GraphicsScene::slotSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { for (const QModelIndex &idx : deselected.indexes()) { GraphicsItem *item = findItem(idx.model() == d->summaryHandlingModel ? idx : d->summaryHandlingModel->mapFromSource(idx)); if (item) { item->setSelected(false); } } for (const QModelIndex &idx : selected.indexes()) { GraphicsItem *item = findItem(idx.model() == d->summaryHandlingModel ? idx : d->summaryHandlingModel->mapFromSource(idx)); if (item) { item->setSelected(true); } } update(); } 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->getGrid()->paintHeader( painter, headerRect, rect, 0, nullptr ); #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->getGrid()->paintGrid( painter, scn, rect, d->rowController ); d->getGrid()->drawBackground(painter, rect); } void GraphicsScene::drawForeground( QPainter* painter, const QRectF& rect ) { d->getGrid()->drawForeground(painter, rect); } void GraphicsScene::itemEntered( const QModelIndex& idx ) { emit entered( idx ); } void GraphicsScene::itemPressed( const QModelIndex& idx, QGraphicsSceneMouseEvent *event ) { if (event->button() == Qt::LeftButton) { QItemSelectionModel::SelectionFlags flags; if (event->modifiers() & Qt::ControlModifier) { flags |= QItemSelectionModel::Toggle; } else { flags |= QItemSelectionModel::ClearAndSelect; } d->selectionModel->select(idx, flags); } 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(), nullptr, 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, nullptr, 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).boundingRect( QString::fromLatin1( "X" ) ).width(); 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).boundingRect( txt ).width() + 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 override { return 40; } /*reimp*/ bool isRowVisible( const QModelIndex& ) const override { return true;} /*reimp*/ bool isRowExpanded( const QModelIndex& ) const override { return false; } /*reimp*/ KGantt::Span rowGeometry( const QModelIndex& idx ) const override { return KGantt::Span( idx.row() * ROW_HEIGHT, ROW_HEIGHT ); } /*reimp*/ int maximumItemHeight() const override { return ROW_HEIGHT/2; } /*reimp*/int totalHeight() const override { return m_model->rowCount()* ROW_HEIGHT; } /*reimp*/ QModelIndex indexAt( int height ) const override { return m_model->index( height/ROW_HEIGHT, 0 ); } /*reimp*/ QModelIndex indexBelow( const QModelIndex& idx ) const override { if ( !idx.isValid() )return QModelIndex(); return idx.model()->index( idx.row()+1, idx.column(), idx.parent() ); } /*reimp*/ QModelIndex indexAbove( const QModelIndex& idx ) const 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 */