File indexing completed on 2024-05-12 04:20:42

0001 /*
0002  * SPDX-FileCopyrightText: 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved.
0003  *
0004  * This file is part of the KGantt library.
0005  *
0006  * SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 
0009 #include "kganttgraphicsscene.h"
0010 #include "kganttgraphicsscene_p.h"
0011 #include "kganttgraphicsitem.h"
0012 #include "kganttconstraint.h"
0013 #include "kganttconstraintgraphicsitem.h"
0014 #include "kganttitemdelegate.h"
0015 #include "kganttabstractrowcontroller.h"
0016 #include "kganttabstractgrid.h"
0017 #include "kganttdatetimegrid.h"
0018 #include "kganttsummaryhandlingproxymodel.h"
0019 #include "kganttgraphicsview.h"
0020 #include "kganttprintingcontext.h"
0021 
0022 #include <QApplication>
0023 #include <QGraphicsSceneHelpEvent>
0024 #include <QPainter>
0025 #include <QPrinter>
0026 #include <QTextDocument>
0027 #include <QToolTip>
0028 #include <QSet>
0029 
0030 #include <QDebug>
0031 
0032 #include <functional>
0033 #include <algorithm>
0034 #include <cassert>
0035 
0036 // defines HAVE_PRINTER if support for printing should be included
0037 #ifdef _WIN32_WCE
0038     // There is no printer support under wince even if QT_NO_PRINTER is not set
0039 #else
0040 #ifndef QT_NO_PRINTER
0041     #define HAVE_PRINTER
0042 #endif
0043 #endif
0044 
0045 
0046 
0047 using namespace KGantt;
0048 
0049 GraphicsScene::Private::Private( GraphicsScene* _q )
0050     : q( _q ),
0051       dragSource( nullptr ),
0052       itemDelegate( new ItemDelegate( _q ) ),
0053       rowController( nullptr ),
0054       readOnly( false ),
0055       isPrinting( false ),
0056       drawColumnLabels( true ),
0057       labelsWidth( 0.0 ),
0058       summaryHandlingModel( new SummaryHandlingProxyModel( _q ) ),
0059       selectionModel( nullptr )
0060 {
0061     default_grid.setStartDateTime( QDateTime::currentDateTime().addDays( -1 ) );
0062 }
0063 
0064 GraphicsScene::Private::~Private()
0065 {
0066     delete grid;
0067 }
0068 
0069 void GraphicsScene::Private::clearConstraintItems()
0070 {
0071     for(ConstraintGraphicsItem *citem : qAsConst(constraintItems)) {
0072         // remove constraint from items first
0073         for(GraphicsItem *item : qAsConst(items)) {
0074             item->removeStartConstraint(citem);
0075             item->removeEndConstraint(citem);
0076         }
0077         q->removeItem(citem);
0078         delete citem;
0079     }
0080     constraintItems.clear();
0081 }
0082 
0083 void GraphicsScene::Private::resetConstraintItems()
0084 {
0085     clearConstraintItems();
0086     if ( constraintModel.isNull() ) return;
0087     const QList<Constraint> clst = constraintModel->constraints();
0088     for ( const Constraint& c : clst ) {
0089         createConstraintItem( c );
0090     }
0091     q->updateItems();
0092 }
0093 
0094 void GraphicsScene::Private::createConstraintItem( const Constraint& c )
0095 {
0096     GraphicsItem* sitem = q->findItem( summaryHandlingModel->mapFromSource( c.startIndex() ) );
0097     GraphicsItem* eitem = q->findItem( summaryHandlingModel->mapFromSource( c.endIndex() ) );
0098 
0099     if ( sitem && eitem ) {
0100         ConstraintGraphicsItem* citem = new ConstraintGraphicsItem( c );
0101         sitem->addStartConstraint( citem );
0102         eitem->addEndConstraint( citem );
0103         constraintItems.append( citem );
0104         q->addItem( citem );
0105     }
0106 
0107     //q->insertConstraintItem( c, citem );
0108 }
0109 
0110 // Delete the constraint item, and clean up pointers in the start- and end item
0111 void GraphicsScene::Private::deleteConstraintItem( ConstraintGraphicsItem *citem )
0112 {
0113     //qDebug()<<"GraphicsScene::Private::deleteConstraintItem citem="<<citem;
0114     if ( citem == nullptr ) {
0115         return;
0116     }
0117     Constraint c = citem->constraint();
0118     GraphicsItem* item = items.value( summaryHandlingModel->mapFromSource( c.startIndex() ), nullptr );
0119     if ( item ) {
0120         item->removeStartConstraint( citem );
0121     }
0122     item = items.value( summaryHandlingModel->mapFromSource( c.endIndex() ), nullptr );
0123     if ( item ) {
0124         item->removeEndConstraint( citem );
0125     }
0126     constraintItems.removeAt(constraintItems.indexOf(citem));
0127     delete citem;
0128 }
0129 
0130 void GraphicsScene::Private::deleteConstraintItem( const Constraint& c )
0131 {
0132     deleteConstraintItem( findConstraintItem( c ) );
0133 }
0134 
0135 ConstraintGraphicsItem* GraphicsScene::Private::findConstraintItem( const Constraint& c ) const
0136 {
0137     GraphicsItem* item = items.value( summaryHandlingModel->mapFromSource( c.startIndex() ), nullptr );
0138     if ( item ) {
0139         const QList<ConstraintGraphicsItem*> clst = item->startConstraints();
0140         QList<ConstraintGraphicsItem*>::const_iterator it = clst.begin();
0141         for ( ; it != clst.end() ; ++it ) {
0142             if ( c.compareIndexes((*it)->constraint()) )
0143                 break;
0144         }
0145         if ( it != clst.end() ) {
0146             return *it;
0147         }
0148     }
0149     item = items.value( summaryHandlingModel->mapFromSource( c.endIndex() ), nullptr );
0150     if ( item ) {
0151         const QList<ConstraintGraphicsItem*> clst = item->endConstraints();
0152         QList<ConstraintGraphicsItem*>::const_iterator it = clst.begin();
0153         for ( ; it != clst.end() ; ++it ) {
0154             if ( c.compareIndexes( (*it)->constraint() ) )
0155                 break;
0156         }
0157         if ( it != clst.end() ) {
0158             return *it;
0159         }
0160     }
0161     return nullptr;
0162 }
0163 
0164 // NOTE: we might get here after indexes are invalidated, so cannot do any controlled cleanup
0165 void GraphicsScene::Private::clearItems()
0166 {
0167     for(GraphicsItem *item : qAsConst(items)) {
0168         q->removeItem(item);
0169         delete item;
0170     }
0171     items.clear();
0172     // do last to avoid cleaning up items
0173     clearConstraintItems();
0174 }
0175 
0176 AbstractGrid *GraphicsScene::Private::getGrid()
0177 {
0178     if (grid.isNull()) {
0179         return static_cast<AbstractGrid*>(&default_grid);
0180     }
0181     return grid.data();
0182 }
0183 
0184 const AbstractGrid *GraphicsScene::Private::getGrid() const
0185 {
0186     if (grid.isNull()) {
0187         return static_cast<const AbstractGrid*>(&default_grid);
0188     }
0189     return grid.data();
0190 }
0191 
0192 GraphicsScene::GraphicsScene( QObject* parent )
0193     : QGraphicsScene( parent ), _d( new Private( this ) )
0194 {
0195     init();
0196 }
0197 
0198 GraphicsScene::~GraphicsScene()
0199 {
0200     qDeleteAll( items() );
0201     delete _d;
0202 }
0203 
0204 #define d d_func()
0205 
0206 void GraphicsScene::init()
0207 {
0208     setItemIndexMethod( QGraphicsScene::NoIndex );
0209     setConstraintModel( new ConstraintModel( this ) );
0210     connect( d->getGrid(), SIGNAL(gridChanged()), this, SLOT(slotGridChanged()) );
0211 }
0212 
0213 /* NOTE: The delegate should really be a property
0214  * of the view, but that doesn't really fit at
0215  * this time
0216  */
0217 void GraphicsScene::setItemDelegate( ItemDelegate* delegate )
0218 {
0219     if ( !d->itemDelegate.isNull() && d->itemDelegate->parent()==this ) delete d->itemDelegate;
0220     d->itemDelegate = delegate;
0221     update();
0222 }
0223 
0224 ItemDelegate* GraphicsScene::itemDelegate() const
0225 {
0226     return d->itemDelegate;
0227 }
0228 
0229 QAbstractItemModel* GraphicsScene::model() const
0230 {
0231     assert(!d->summaryHandlingModel.isNull());
0232     return d->summaryHandlingModel->sourceModel();
0233 }
0234 
0235 void GraphicsScene::setModel( QAbstractItemModel* model )
0236 {
0237     assert(!d->summaryHandlingModel.isNull());
0238     d->summaryHandlingModel->setSourceModel(model);
0239     d->getGrid()->setModel( d->summaryHandlingModel );
0240     setSelectionModel( new QItemSelectionModel( model, this ) );
0241 }
0242 
0243 QAbstractProxyModel* GraphicsScene::summaryHandlingModel() const
0244 {
0245     return d->summaryHandlingModel;
0246 }
0247 
0248 void GraphicsScene::setSummaryHandlingModel( QAbstractProxyModel* proxyModel )
0249 {
0250     proxyModel->setSourceModel( model() );
0251     d->summaryHandlingModel = proxyModel;
0252 }
0253 
0254 void GraphicsScene::setRootIndex( const QModelIndex& idx )
0255 {
0256     d->getGrid()->setRootIndex( idx );
0257 }
0258 
0259 QModelIndex GraphicsScene::rootIndex() const
0260 {
0261     return d->getGrid()->rootIndex();
0262 }
0263 
0264 ConstraintModel* GraphicsScene::constraintModel() const
0265 {
0266     return d->constraintModel;
0267 }
0268 
0269 void GraphicsScene::setConstraintModel( ConstraintModel* cm )
0270 {
0271     if ( !d->constraintModel.isNull() ) {
0272         d->constraintModel->disconnect( this );
0273         d->clearConstraintItems();
0274     }
0275     d->constraintModel = cm;
0276 
0277     connect( cm, SIGNAL(constraintAdded(KGantt::Constraint)),
0278              this, SLOT(slotConstraintAdded(KGantt::Constraint)) );
0279     connect( cm, SIGNAL(constraintRemoved(KGantt::Constraint)),
0280              this, SLOT(slotConstraintRemoved(KGantt::Constraint)) );
0281     d->resetConstraintItems();
0282 }
0283 
0284 void GraphicsScene::setSelectionModel( QItemSelectionModel* smodel )
0285 {
0286     if (d->selectionModel) {
0287         d->selectionModel->disconnect( this );
0288     }
0289     d->selectionModel = smodel;
0290     if (smodel) {
0291         connect(d->selectionModel, SIGNAL(modelChanged(QAbstractItemModel*)),
0292                 this, SLOT(selectionModelChanged(QAbstractItemModel*)));
0293         connect( smodel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
0294                  this, SLOT(slotSelectionChanged(QItemSelection,QItemSelection)) );
0295     }
0296 }
0297 
0298 QItemSelectionModel* GraphicsScene::selectionModel() const
0299 {
0300     return d->selectionModel;
0301 }
0302 
0303 void GraphicsScene::setRowController( AbstractRowController* rc )
0304 {
0305     d->rowController = rc;
0306 }
0307 
0308 AbstractRowController* GraphicsScene::rowController() const
0309 {
0310     return d->rowController;
0311 }
0312 
0313 AbstractGrid *GraphicsScene::takeGrid()
0314 {
0315     AbstractGrid *grid = d->grid;
0316     grid->disconnect( this );
0317     d->grid = nullptr;
0318     if (grid) {
0319         // revert to the default_grid
0320         connect( &d->default_grid, SIGNAL(gridChanged()), this, SLOT(slotGridChanged()) );
0321     }
0322     return grid;
0323 }
0324 
0325 void GraphicsScene::setGrid( AbstractGrid* grid )
0326 {
0327     QAbstractItemModel* model = nullptr;
0328     if ( d->getGrid() ) {
0329         d->getGrid()->disconnect( this );
0330         model = d->getGrid()->model();
0331     }
0332     delete d->grid;
0333     d->grid = grid;
0334     connect( d->getGrid(), SIGNAL(gridChanged()), this, SLOT(slotGridChanged()) );
0335     d->getGrid()->setModel( model );
0336     slotGridChanged();
0337 }
0338 
0339 // Returns the explicitly set grid
0340 AbstractGrid* GraphicsScene::grid() const
0341 {
0342     return d->grid;
0343 }
0344 
0345 // May also return the default_grid if a grid has not been set
0346 const AbstractGrid *GraphicsScene::getGrid() const
0347 {
0348     return d->getGrid();
0349 }
0350 
0351 void GraphicsScene::setReadOnly( bool ro )
0352 {
0353     d->readOnly = ro;
0354 }
0355 
0356 bool GraphicsScene::isReadOnly() const
0357 {
0358     return d->readOnly;
0359 }
0360 
0361 /* Returns the index with column=0 fromt the
0362  * same row as idx and with the same parent.
0363  * This is used to traverse the tree-structure
0364  * of the model
0365  */
0366 QModelIndex GraphicsScene::mainIndex( const QModelIndex& idx )
0367 {
0368 #if 0
0369     if ( idx.isValid() ) {
0370         return idx.model()->index( idx.row(), 0,idx.parent() );
0371     } else {
0372         return QModelIndex();
0373     }
0374 #else
0375     return idx;
0376 #endif
0377 }
0378 
0379 
0380 QModelIndex GraphicsScene::dataIndex( const QModelIndex& idx )
0381 {
0382 #if 0
0383     if ( idx.isValid() ) {
0384         const QAbstractItemModel* model = idx.model();
0385         return model->index( idx.row(), model->columnCount( idx.parent() )-1,idx.parent() );
0386     } else {
0387         return QModelIndex();
0388     }
0389 #else
0390     return idx;
0391 #endif
0392 }
0393 
0394 
0395 GraphicsItem* GraphicsScene::createItem( ItemType type ) const
0396 {
0397 #if 0
0398     TODO For 3.0
0399     assert(views().count() == 1);
0400     GraphicsView *v = qobject_cast<GraphicsView*>(views().first());
0401     assert(v);
0402     return v->createItem(type);
0403 #else
0404     Q_UNUSED(type)
0405     return new GraphicsItem;
0406 #endif
0407 }
0408 
0409 void GraphicsScene::Private::recursiveUpdateMultiItem( const Span& span, const QModelIndex& idx )
0410 {
0411     //qDebug() << "recursiveUpdateMultiItem("<<span<<idx<<")";
0412     GraphicsItem* item = q->findItem( idx );
0413     const int itemtype = summaryHandlingModel->data( idx, ItemTypeRole ).toInt();
0414     if (!item) {
0415         item = q->createItem( static_cast<ItemType>( itemtype ) );
0416         item->setIndex( idx );
0417         q->insertItem( idx, item);
0418     }
0419     item->updateItem( span, idx );
0420     QModelIndex child;
0421     int cr = 0;
0422     while ( ( child = summaryHandlingModel->index( cr, 0, idx ) ).isValid() ) {
0423         recursiveUpdateMultiItem( span, child );
0424         ++cr;
0425     }
0426 }
0427 
0428 void GraphicsScene::updateRow( const QModelIndex& rowidx )
0429 {
0430     //qDebug() << "GraphicsScene::updateRow("<<rowidx<<")" << rowidx.data( Qt::DisplayRole );
0431     if ( !rowidx.isValid() ) return;
0432 #if !defined(NDEBUG)
0433     const QAbstractItemModel* model = rowidx.model(); // why const?
0434 #endif
0435     assert( model );
0436     assert( rowController() );
0437     assert( model == summaryHandlingModel() );
0438 
0439     const QModelIndex sidx = summaryHandlingModel()->mapToSource( rowidx );
0440     Span rg = rowController()->rowGeometry( sidx );
0441     for ( QModelIndex treewalkidx = sidx; treewalkidx.isValid(); treewalkidx = treewalkidx.parent() ) {
0442         if ( treewalkidx.data( ItemTypeRole ).toInt() == TypeMulti
0443              && !rowController()->isRowExpanded( treewalkidx )) {
0444             rg = rowController()->rowGeometry( treewalkidx );
0445         }
0446     }
0447 
0448     bool blocked = blockSignals( true );
0449     for ( int col = 0; col < summaryHandlingModel()->columnCount( rowidx.parent() ); ++col ) {
0450         const QModelIndex idx = summaryHandlingModel()->index( rowidx.row(), col, rowidx.parent() );
0451         const QModelIndex sidx = summaryHandlingModel()->mapToSource( idx );
0452         const int itemtype = summaryHandlingModel()->data( idx, ItemTypeRole ).toInt();
0453         const bool isExpanded = rowController()->isRowExpanded( sidx );
0454         if ( itemtype == TypeNone ) {
0455             removeItem( idx );
0456             continue;
0457         }
0458         if ( itemtype == TypeMulti && !isExpanded ) {
0459             d->recursiveUpdateMultiItem( rg, idx );
0460         } else {
0461             if ( summaryHandlingModel()->data( rowidx.parent(), ItemTypeRole ).toInt() == TypeMulti && !isExpanded ) {
0462                 //continue;
0463             }
0464 
0465             GraphicsItem* item = findItem( idx );
0466             if (!item) {
0467                 item = createItem( static_cast<ItemType>( itemtype ) );
0468                 item->setIndex( idx );
0469                 insertItem(idx, item);
0470             }
0471             const Span span = rowController()->rowGeometry( sidx );
0472             item->updateItem( span, idx );
0473         }
0474     }
0475     blockSignals( blocked );
0476 }
0477 
0478 void GraphicsScene::insertItem( const QPersistentModelIndex& idx, GraphicsItem* item )
0479 {
0480     if ( !d->constraintModel.isNull() ) {
0481         // Create items for constraints
0482         const QModelIndex sidx = summaryHandlingModel()->mapToSource( idx );
0483         const QList<Constraint> clst = d->constraintModel->constraintsForIndex( sidx );
0484         for ( const Constraint& c :  clst ) {
0485             QModelIndex other_idx;
0486             if ( c.startIndex() == sidx ) {
0487                 other_idx = c.endIndex();
0488                 GraphicsItem* other_item = d->items.value(summaryHandlingModel()->mapFromSource( other_idx ),nullptr);
0489                 if ( !other_item ) continue;
0490                 ConstraintGraphicsItem* citem = new ConstraintGraphicsItem( c );
0491                 item->addStartConstraint( citem );
0492                 other_item->addEndConstraint( citem );
0493                 d->constraintItems.append( citem );
0494                 addItem( citem );
0495             } else if ( c.endIndex() == sidx ) {
0496                 other_idx = c.startIndex();
0497                 GraphicsItem* other_item = d->items.value(summaryHandlingModel()->mapFromSource( other_idx ),nullptr);
0498                 if ( !other_item ) continue;
0499                 ConstraintGraphicsItem* citem = new ConstraintGraphicsItem( c );
0500                 other_item->addStartConstraint( citem );
0501                 item->addEndConstraint( citem );
0502                 d->constraintItems.append( citem );
0503                 addItem( citem );
0504             } else {
0505                 assert( 0 ); // Impossible
0506             }
0507         }
0508     }
0509     d->items.insert( idx, item );
0510     addItem( item );
0511 }
0512 
0513 void GraphicsScene::removeItem( const QModelIndex& idx )
0514 {
0515     //qDebug() << "GraphicsScene::removeItem("<<idx<<")";
0516     QHash<QPersistentModelIndex,GraphicsItem*>::iterator it = d->items.find( idx );
0517     if ( it != d->items.end() ) {
0518         GraphicsItem* item = *it;
0519         assert( item );
0520         // We have to remove the item from the list first because
0521         // there is a good chance there will be reentrant calls
0522         d->items.erase( it );
0523         {
0524             // Remove any constraintitems attached
0525             const QList<ConstraintGraphicsItem*> lst1 = item->startConstraints();
0526             const QList<ConstraintGraphicsItem*> lst2 = item->endConstraints();
0527             const QSet<ConstraintGraphicsItem*> clst = QSet<ConstraintGraphicsItem*>( lst1.begin(), lst1.end() ) +
0528                                                        QSet<ConstraintGraphicsItem*>( lst2.begin(), lst2.end() );
0529             for ( ConstraintGraphicsItem* citem : clst ) {
0530                 d->deleteConstraintItem( citem );
0531             }
0532         }
0533         // Get rid of the item
0534         delete item;
0535     }
0536 }
0537 
0538 GraphicsItem* GraphicsScene::findItem( const QModelIndex& idx ) const
0539 {
0540     if ( !idx.isValid() ) return nullptr;
0541     assert( idx.model() == summaryHandlingModel() );
0542     QHash<QPersistentModelIndex,GraphicsItem*>::const_iterator it = d->items.find( idx );
0543     return ( it != d->items.end() )?*it:nullptr;
0544 }
0545 
0546 GraphicsItem* GraphicsScene::findItem( const QPersistentModelIndex& idx ) const
0547 {
0548     if ( !idx.isValid() ) return nullptr;
0549     assert( idx.model() == summaryHandlingModel() );
0550     QHash<QPersistentModelIndex,GraphicsItem*>::const_iterator it = d->items.find( idx );
0551     return ( it != d->items.end() )?*it:nullptr;
0552 }
0553 
0554 void GraphicsScene::clearItems()
0555 {
0556     d->clearItems();
0557 }
0558 
0559 void GraphicsScene::updateItems()
0560 {
0561     for ( QHash<QPersistentModelIndex,GraphicsItem*>::iterator it = d->items.begin();
0562           it != d->items.end(); ++it ) {
0563         GraphicsItem* const item = it.value();
0564         const QPersistentModelIndex& idx = it.key();
0565         item->updateItem( Span( item->pos().y(), item->rect().height() ), idx );
0566     }
0567     invalidate( QRectF(), QGraphicsScene::BackgroundLayer );
0568 }
0569 
0570 void GraphicsScene::deleteSubtree( const QModelIndex& _idx )
0571 {
0572     QModelIndex idx = dataIndex( _idx );
0573     if ( !idx.model() ) return;
0574     const QModelIndex parent( idx.parent() );
0575     const int colcount = idx.model()->columnCount( parent );
0576     {for ( int i = 0; i < colcount; ++i ) {
0577         removeItem( summaryHandlingModel()->index(idx.row(), i, parent ) );
0578     }}
0579     const int rowcount = summaryHandlingModel()->rowCount( _idx );
0580     {for ( int i = 0; i < rowcount; ++i ) {
0581         deleteSubtree( summaryHandlingModel()->index( i, summaryHandlingModel()->columnCount(_idx)-1, _idx ) );
0582     }}
0583 }
0584 
0585 
0586 ConstraintGraphicsItem* GraphicsScene::findConstraintItem( const Constraint& c ) const
0587 {
0588     return d->findConstraintItem( c );
0589 }
0590 
0591 void GraphicsScene::slotConstraintAdded( const KGantt::Constraint& c )
0592 {
0593     d->createConstraintItem( c );
0594 }
0595 
0596 void GraphicsScene::slotConstraintRemoved( const KGantt::Constraint& c )
0597 {
0598     d->deleteConstraintItem( c );
0599 }
0600 
0601 void GraphicsScene::slotGridChanged()
0602 {
0603     updateItems();
0604     update();
0605     Q_EMIT gridChanged();
0606 }
0607 
0608 void GraphicsScene::selectionModelChanged(QAbstractItemModel *model)
0609 {
0610     Q_UNUSED(model)
0611 }
0612 
0613 void GraphicsScene::slotSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
0614 {
0615     const auto desel = deselected.indexes();
0616     for (const QModelIndex &idx : desel) {
0617         GraphicsItem *item = findItem(idx.model() == d->summaryHandlingModel ? idx : d->summaryHandlingModel->mapFromSource(idx));
0618         if (item) {
0619             item->setSelected(false);
0620         }
0621     }
0622     const auto sel = selected.indexes();
0623     for (const QModelIndex &idx : sel) {
0624         GraphicsItem *item = findItem(idx.model() == d->summaryHandlingModel ? idx : d->summaryHandlingModel->mapFromSource(idx));
0625         if (item) {
0626             item->setSelected(true);
0627         }
0628     }
0629     update();
0630 }
0631 
0632 void GraphicsScene::helpEvent( QGraphicsSceneHelpEvent *helpEvent )
0633 {
0634 #ifndef QT_NO_TOOLTIP
0635     QGraphicsItem *item = itemAt( helpEvent->scenePos(), QTransform() );
0636     if ( GraphicsItem* gitem = qgraphicsitem_cast<GraphicsItem*>( item ) ) {
0637         QToolTip::showText(helpEvent->screenPos(), gitem->ganttToolTip());
0638     } else if ( ConstraintGraphicsItem* citem = qgraphicsitem_cast<ConstraintGraphicsItem*>( item ) ) {
0639         QToolTip::showText(helpEvent->screenPos(), citem->ganttToolTip());
0640     } else {
0641         QGraphicsScene::helpEvent( helpEvent );
0642     }
0643 #endif /* QT_NO_TOOLTIP */
0644 }
0645 
0646 void GraphicsScene::drawBackground( QPainter* painter, const QRectF& _rect )
0647 {
0648     QRectF scn( sceneRect() );
0649     QRectF rect( _rect );
0650     if ( d->isPrinting && d->drawColumnLabels ) {
0651         QRectF headerRect( scn.topLeft()+QPointF( d->labelsWidth, 0 ),
0652                            QSizeF( scn.width()-d->labelsWidth, d->rowController->headerHeight() ));
0653 
0654         d->getGrid()->paintHeader( painter, headerRect, rect, 0, nullptr );
0655 
0656 #if 0
0657         /* We have to blank out the part of the header that is invisible during
0658         * normal rendering when we are printing.
0659         */
0660         QRectF labelsTabRect( scn.topLeft(), QSizeF( d->labelsWidth, headerRect.height() ) );
0661 
0662         QStyleOptionHeader opt;
0663         opt.rect = labelsTabRect.toRect();
0664         opt.text = QLatin1String("");
0665         opt.textAlignment = Qt::AlignCenter;
0666         style()->drawControl(QStyle::CE_Header, &opt, painter, 0);
0667 #endif
0668 
0669         scn.setTop( headerRect.bottom() );
0670         scn.setLeft( headerRect.left() );
0671         rect = rect.intersected( scn );
0672     }
0673     d->getGrid()->paintGrid( painter, scn, rect, d->rowController );
0674 
0675     d->getGrid()->drawBackground(painter, rect);
0676 }
0677 
0678 void GraphicsScene::drawForeground( QPainter* painter, const QRectF& rect )
0679 {
0680     d->getGrid()->drawForeground(painter, rect);
0681 }
0682 
0683 void GraphicsScene::itemEntered( const QModelIndex& idx )
0684 {
0685     Q_EMIT entered( idx );
0686 }
0687 
0688 void GraphicsScene::itemPressed( const QModelIndex& idx, QGraphicsSceneMouseEvent *event )
0689 {
0690     if (event->button() == Qt::LeftButton) {
0691         QItemSelectionModel::SelectionFlags flags;
0692         if (event->modifiers() & Qt::ControlModifier) {
0693             flags |= QItemSelectionModel::Toggle;
0694         } else {
0695             flags |= QItemSelectionModel::ClearAndSelect;
0696         }
0697         d->selectionModel->select(d->summaryHandlingModel->mapToSource(idx), flags);
0698     }
0699     Q_EMIT pressed( idx );
0700 }
0701 
0702 void GraphicsScene::itemClicked( const QModelIndex& idx )
0703 {
0704     Q_EMIT clicked( idx );
0705 }
0706 
0707 void GraphicsScene::itemDoubleClicked( const QModelIndex& idx )
0708 {
0709     Q_EMIT qrealClicked( idx );
0710 }
0711 
0712 void GraphicsScene::setDragSource( GraphicsItem* item )
0713 {
0714     d->dragSource = item;
0715 }
0716 
0717 GraphicsItem* GraphicsScene::dragSource() const
0718 {
0719     return d->dragSource;
0720 }
0721 
0722 
0723 void GraphicsScene::print( QPrinter* printer, bool drawRowLabels, bool drawColumnLabels )
0724 {
0725 #ifndef HAVE_PRINTER
0726     Q_UNUSED( printer );
0727     Q_UNUSED( drawRowLabels );
0728     Q_UNUSED( drawColumnLabels );
0729 #else
0730     QPainter painter( printer );
0731     const auto resolution = printer->resolution();
0732     const auto pageRect = printer->pageLayout().paintRectPixels(resolution);
0733     doPrint( &painter, pageRect, sceneRect().left(), sceneRect().right(), printer, drawRowLabels, drawColumnLabels );
0734 #endif
0735 }
0736 
0737 
0738 void GraphicsScene::print( QPrinter* printer, qreal start, qreal end, bool drawRowLabels, bool drawColumnLabels )
0739 {
0740 #ifndef HAVE_PRINTER
0741     Q_UNUSED( printer );
0742     Q_UNUSED( start );
0743     Q_UNUSED( end );
0744     Q_UNUSED( drawRowLabels );
0745     Q_UNUSED( drawColumnLabels );
0746 #else
0747     QPainter painter( printer );
0748     const auto resolution = printer->resolution();
0749     const auto pageRect = printer->pageLayout().paintRectPixels(resolution);
0750     doPrint( &painter, pageRect, start, end, printer, drawRowLabels, drawColumnLabels );
0751 #endif
0752 }
0753 
0754 
0755 void GraphicsScene::print( QPainter* painter, const QRectF& _targetRect, bool drawRowLabels, bool drawColumnLabels )
0756 {
0757     QRectF targetRect( _targetRect );
0758     if ( targetRect.isNull() ) {
0759         targetRect = sceneRect();
0760     }
0761 
0762     doPrint( painter, targetRect, sceneRect().left(), sceneRect().right(), nullptr, drawRowLabels, drawColumnLabels );
0763 }
0764 
0765 
0766 void GraphicsScene::print( QPainter* painter, qreal start, qreal end,
0767                            const QRectF& _targetRect, bool drawRowLabels, bool drawColumnLabels )
0768 {
0769     QRectF targetRect( _targetRect );
0770     if ( targetRect.isNull() ) {
0771         targetRect = sceneRect();
0772     }
0773 
0774     doPrint( painter, targetRect, start, end, nullptr, drawRowLabels, drawColumnLabels );
0775 }
0776 
0777 void GraphicsScene::printDiagram( QPrinter *printer, const PrintingContext &context )
0778 {
0779 #ifndef HAVE_PRINTER
0780     Q_UNUSED( printer );
0781     Q_UNUSED( context );
0782 #else
0783     PrintingContext ctx( context );
0784     if (ctx.sceneRect().isNull()) {
0785         ctx.setSceneRect(sceneRect());
0786     }
0787     QRectF targetRect = printer->pageRect( QPrinter::DevicePixel );
0788     if ( printer->fullPage() ) {
0789         // Handle margins
0790         QPageLayout pl = printer->pageLayout();
0791         targetRect = targetRect.marginsRemoved( pl.marginsPixels( printer->resolution() ) );
0792     }
0793     QPainter painter( printer );
0794     doPrintScene( printer, &painter, targetRect, ctx );
0795 #endif
0796 }
0797 
0798 void GraphicsScene::doPrint( QPainter* painter, const QRectF& targetRect,
0799                              qreal start, qreal end,
0800                              QPrinter* printer, bool drawRowLabels, bool drawColumnLabels )
0801 {
0802     assert( painter );
0803     PrintingContext ctx;
0804     ctx.setFitting(PrintingContext::FitPageHeight); // keep old behavior (?)
0805     ctx.setDrawRowLabels( drawRowLabels );
0806     ctx.setDrawColumnLabels( drawColumnLabels );
0807     ctx.setSceneRect( sceneRect() );
0808     ctx.setLeft( start );
0809     ctx.setRight( end );
0810     doPrintScene( printer, painter, targetRect, ctx );
0811 }
0812 
0813 void GraphicsScene::doPrintScene( QPrinter *printer, QPainter *painter, const QRectF &targetRect, const PrintingContext &context )
0814 {
0815     assert( painter );
0816 
0817     bool b = blockSignals( true );
0818 
0819     d->isPrinting = true;
0820     d->drawColumnLabels = context.drawColumnLabels();
0821     d->labelsWidth = 0.0;
0822 
0823     QFont sceneFont( font() );
0824 #ifdef HAVE_PRINTER
0825     if ( printer ) {
0826         sceneFont = QFont( font(), printer );
0827         if ( font().pointSizeF() >= 0.0 )
0828             sceneFont.setPointSizeF( font().pointSizeF() );
0829         else if ( font().pointSize() >= 0 )
0830             sceneFont.setPointSize( font().pointSize() );
0831         else
0832             sceneFont.setPixelSize( font().pixelSize() );
0833     }
0834 #endif
0835 
0836     QGraphicsTextItem dummyTextItem( QLatin1String("X") );
0837     dummyTextItem.adjustSize();
0838     QFontMetrics fm(dummyTextItem.font());
0839     sceneFont.setPixelSize( fm.height() );
0840 
0841     const QRectF oldScnRect( sceneRect() );
0842     QRectF scnRect( oldScnRect );
0843     QRectF sceneHeaderRect;
0844     QRectF labelsHeaderRect;
0845     QRectF labelsRect;
0846 
0847     /* column labels */
0848     qreal headerHeight = 0.0;
0849     if ( context.drawColumnLabels() ) {
0850         headerHeight = d->rowController->headerHeight();
0851         sceneHeaderRect = context.sceneRect();
0852         sceneHeaderRect.setLeft( context.left() );
0853         sceneHeaderRect.setTop( -headerHeight );
0854         sceneHeaderRect.setHeight( headerHeight );
0855         scnRect.setTop(scnRect.top() - headerHeight);
0856     }
0857 
0858     /* row labels */
0859     QVector<QGraphicsTextItem*> textLabels;
0860     if ( context.drawRowLabels() ) {
0861         qreal textWidth = 0.;
0862         qreal charWidth = QFontMetricsF(sceneFont).boundingRect( QString::fromLatin1( "X" ) ).width();
0863         QModelIndex sidx = summaryHandlingModel()->mapToSource( summaryHandlingModel()->index( 0, 0, rootIndex()) );
0864         do {
0865             QModelIndex idx = summaryHandlingModel()->mapFromSource( sidx );
0866             const Span rg=rowController()->rowGeometry( sidx );
0867             const QString txt = idx.data( Qt::DisplayRole ).toString();
0868             QGraphicsTextItem* item = new QGraphicsTextItem( txt );
0869             addItem( item );
0870             textLabels << item;
0871             item->setTextWidth( QFontMetricsF(sceneFont).boundingRect( txt ).width() + charWidth );
0872             textWidth = qMax( item->textWidth(), textWidth );
0873             item->setPos( 0, rg.start() );
0874         } while ( ( sidx = rowController()->indexBelow( sidx ) ).isValid() );
0875         d->labelsWidth = textWidth;
0876         scnRect.setLeft( scnRect.left() - textWidth );
0877         for( QGraphicsTextItem* item : qAsConst(textLabels) ) {
0878             item->setPos( scnRect.left(), item->y() );
0879             item->show();
0880         }
0881         if ( context.drawColumnLabels() ) {
0882             labelsHeaderRect = sceneHeaderRect;
0883             labelsHeaderRect.translate( -textWidth, 0.0 );
0884             labelsHeaderRect.setWidth( textWidth );
0885         }
0886         labelsRect = QRectF( scnRect.left(), context.top(), textWidth, context.sceneRect().height() );
0887     }
0888     setSceneRect( scnRect );
0889     scnRect.setRight( context.right() );
0890 
0891     // The scene looks like this:
0892     //  Labels   Do not print    Print        Behind end
0893     // 1       2               3            4
0894     // !-------!---------------!------------!-----------
0895     // sceneWidth is 1 to 2 + 3 to 4
0896     qreal sceneWidth = d->labelsWidth + context.right() - context.left();
0897     qreal sceneHeight = context.sceneRect().height() + sceneHeaderRect.height();
0898     // qInfo()<<Q_FUNC_INFO<<targetRect<<scnRect<<sceneWidth;
0899 
0900     int horPages = 1;
0901     int vertPages = 1;
0902     qreal scaleFactor = targetRect.height() / scnRect.height(); // FitPageHeight (default)
0903     if ( printer ) {
0904         if ( context.fitting() & PrintingContext::NoFitting ) {
0905             scaleFactor = printer->logicalDpiX() / views().at(0)->logicalDpiX(); // always have only one view
0906             vertPages = qRound( ( sceneHeight * scaleFactor / targetRect.height() ) + 0.5 );
0907             horPages = qRound( ( sceneWidth * scaleFactor / targetRect.width() ) + 0.5 );
0908         } else if ( context.fitting() & PrintingContext::FitSinglePage ) {
0909             scaleFactor = std::min( scaleFactor, targetRect.width() / sceneWidth );
0910         } else /*FitPageHeight (default)*/ {
0911             horPages = qRound( ( sceneWidth * scaleFactor / targetRect.width() ) + 0.5 );
0912         }
0913     } else {
0914         // paint device has no pages so just fit inside the target
0915         scaleFactor = std::min( scaleFactor, targetRect.width() / sceneWidth );
0916     }
0917     // qInfo()<<Q_FUNC_INFO<<"labels header:"<<labelsHeaderRect<<"labels:"<<labelsRect<<"scene header:"<<sceneHeaderRect<<"scene:"<<scnRect<<"scaleFactor:"<<scaleFactor;
0918     painter->save();
0919     painter->setFont( sceneFont );
0920 
0921     // qInfo()<<Q_FUNC_INFO<<'s'<<scaleFactor<<"pages="<<((sceneWidth * scaleFactor)/targetRect.width())<<'h'<<horPages<<'v'<<vertPages<<'s'<<scnRect<<'t'<<(targetRect.size()/scaleFactor);
0922     qreal yPos = labelsRect.top();
0923     for ( int vpage = 0; vpage < vertPages && yPos < context.bottom(); ++vpage ) {
0924         // qInfo()<<Q_FUNC_INFO<<"print vertical page"<<vpage;
0925         // Disable painting of noInformation during labels printing
0926         // or else labels might be painted over
0927         QBrush noInfoBrush;
0928         DateTimeGrid *dateTimeGrid = qobject_cast<DateTimeGrid*>(grid());
0929         if (dateTimeGrid) {
0930             noInfoBrush = dateTimeGrid->noInformationBrush();
0931             dateTimeGrid->setNoInformationBrush(QBrush());
0932         }
0933         int hpage = 0;
0934         qreal targetLabelsOffset = 0.0;
0935         qreal labelsOffsetX = 0.0;
0936         while ( labelsOffsetX < labelsHeaderRect.width() ) {
0937             // qInfo()<<Q_FUNC_INFO<<"print labels"<<"vert page:"<<vpage<<','<<hpage<<"yPos"<<yPos<<"label x:"<<labelsOffsetX;
0938             // print labels, they might span multiple pages
0939             QRectF target = targetRect;
0940             target.setWidth(std::min(target.width(), (labelsHeaderRect.width() - labelsOffsetX) * scaleFactor) );
0941             if ( vpage == 0 && headerHeight > 0.0 ) {
0942                 QRectF sourceHeader = labelsHeaderRect;
0943                 sourceHeader.translate( labelsOffsetX, 0.0 );
0944                 QRectF targetHeader = target;
0945                 targetHeader.setSize( sourceHeader.size() * scaleFactor );
0946                 drawLabelsHeader( painter, sourceHeader, targetHeader );
0947                 target.adjust( 0.0, targetHeader.height(), 0.0, 0.0 );
0948             }
0949             QRectF rect = labelsRect;
0950             rect.setLeft( rect.left() + labelsOffsetX );
0951             rect.setTop( yPos );
0952             rect.setHeight( std::min(rect.height(), target.height() / scaleFactor ) );
0953             painter->setClipRect(target);
0954             // disable header, it has been drawn above
0955             bool drawColumnLabels = d->drawColumnLabels;
0956             d->drawColumnLabels = false;
0957             // qInfo()<<Q_FUNC_INFO<<"print labels"<<"vert page:"<<vpage<<','<<hpage<<"scene rect:"<<rect<<"target:"<<target;
0958             render( painter, target, rect );
0959             d->drawColumnLabels = drawColumnLabels;
0960             labelsOffsetX += rect.width();
0961             if ( targetRect.right() <= target.right() ) {
0962                 // we have used the whole page
0963                 ++hpage;
0964 #ifdef HAVE_PRINTER
0965                 if ( printer ) {
0966                     printer->newPage();
0967                 }
0968 #endif
0969             } else {
0970                 // labels might take part of the page
0971                 targetLabelsOffset = target.width();
0972                 // qInfo()<<Q_FUNC_INFO<<"print labels finished"<<"vert page:"<<vpage<<"hor page:"<<hpage<<"target offset:"<<targetLabelsOffset;
0973                 break;
0974             }
0975         }
0976         if (dateTimeGrid) {
0977             dateTimeGrid->setNoInformationBrush(noInfoBrush);
0978         }
0979         qreal xPos = context.left();
0980         // qInfo()<<Q_FUNC_INFO<<"print diagram"<<"page:"<<vpage<<','<<hpage<<"xPos"<<xPos<<"yPos:"<<yPos;
0981         for ( ; hpage < horPages && xPos < context.right(); ++hpage ) {
0982             // Adjust for row labels (first time only)
0983             QRectF target = targetRect.adjusted(targetLabelsOffset, 0., 0., 0.);
0984             targetLabelsOffset = 0.0;
0985             if (!sceneHeaderRect.isNull() && vpage == 0) {
0986                 // draw header
0987                 QRectF rect = sceneHeaderRect;
0988                 rect.setLeft( xPos );
0989                 QRectF targetHeader = target;
0990                 targetHeader.setHeight( rect.height() * scaleFactor );
0991                 rect.setWidth( std::min( rect.width(), target.width() / scaleFactor) );
0992                 // qInfo()<<Q_FUNC_INFO<<"scene header:"<<"page:"<<vpage<<','<<hpage<<"source:"<<rect<<"target:"<<targetHeader;
0993                 render( painter, targetHeader, rect );
0994                 target.adjust( 0.0, targetHeader.height(), 0.0, 0.0 );
0995             }
0996             QRectF rect = context.sceneRect();
0997             rect.setLeft( xPos );
0998             rect.setTop( yPos );
0999             rect.setWidth( std::min( rect.width(), target.width() / scaleFactor) );
1000             rect.setHeight( std::min( rect.height(), target.height() / scaleFactor ) );
1001             target.setWidth( rect.width() * scaleFactor );
1002             painter->setClipRect( target );
1003             // disable header, it has been drawn above
1004             bool drawColumnLabels = d->drawColumnLabels;
1005             d->drawColumnLabels = false;
1006             // qInfo()<<Q_FUNC_INFO<<"scene:"<<"page:"<<vpage<<','<<hpage<<"source:"<<rect<<"target:"<<target;
1007             render( painter, target, rect );
1008             d->drawColumnLabels = drawColumnLabels;
1009 
1010             xPos += rect.width();
1011             // qInfo()<<Q_FUNC_INFO<<context<<"xPos:"<<xPos;
1012             if ( printer && xPos < context.right() ) {
1013 #ifdef HAVE_PRINTER
1014                 printer->newPage();
1015 #endif
1016             } else {
1017                 // qInfo()<<Q_FUNC_INFO<<"print horizontal finished if"<<xPos<<">="<<scnRect.right();
1018                 break;
1019             }
1020         }
1021         yPos += targetRect.height() / scaleFactor;
1022         if ( vpage == 0 ) {
1023             yPos -= headerHeight;
1024         }
1025         // qInfo()<<Q_FUNC_INFO<<"yPos:"<<yPos<<"bottom:"<<context.bottom();
1026         if ( printer && yPos < context.bottom() ) {
1027 #ifdef HAVE_PRINTER
1028             // next vertical page
1029             printer->newPage();
1030 #endif
1031         }
1032         // qInfo()<<Q_FUNC_INFO<<"next vertical page if"<<yPos<<'<'<<scnRect.bottom();
1033     }
1034 
1035     d->isPrinting = false;
1036     d->drawColumnLabels = true;
1037     d->labelsWidth = 0.0;
1038     qDeleteAll( textLabels );
1039     blockSignals( b );
1040     setSceneRect( oldScnRect );
1041     painter->restore();
1042 }
1043 
1044 void GraphicsScene::drawLabelsHeader( QPainter *painter, const QRectF &sourceRect, const QRectF &targetRect )
1045 {
1046     // qInfo()<<Q_FUNC_INFO<<"header:"<<sourceRect<<targetRect;
1047     // TODO This should paint itemview header
1048     painter->setClipRect( targetRect );
1049     render( painter, targetRect, sourceRect );
1050 }
1051 
1052 #include "moc_kganttgraphicsscene.cpp"
1053 
1054 
1055 #ifndef KDAB_NO_UNIT_TESTS
1056 #include "unittest/test.h"
1057 
1058 #include <QGraphicsLineItem>
1059 #include <QPointer>
1060 #include <QStandardItemModel>
1061 
1062 #include "kganttgraphicsview.h"
1063 
1064 class SceneTestRowController : public KGantt::AbstractRowController {
1065 private:
1066     static const int ROW_HEIGHT;
1067     QPointer<QAbstractItemModel> m_model;
1068 
1069 public:
1070     SceneTestRowController()
1071     {
1072     }
1073 
1074     void setModel( QAbstractItemModel* model )
1075     {
1076         m_model = model;
1077     }
1078 
1079     /*reimp*/int headerHeight() const override { return 40; }
1080 
1081     /*reimp*/ bool isRowVisible( const QModelIndex& ) const override { return true;}
1082     /*reimp*/ bool isRowExpanded( const QModelIndex& ) const override { return false; }
1083     /*reimp*/ KGantt::Span rowGeometry( const QModelIndex& idx ) const override
1084     {
1085         return KGantt::Span( idx.row() * ROW_HEIGHT, ROW_HEIGHT );
1086     }
1087     /*reimp*/ int maximumItemHeight() const override {
1088         return ROW_HEIGHT/2;
1089     }
1090     /*reimp*/int totalHeight() const override {
1091         return m_model->rowCount()* ROW_HEIGHT;
1092     }
1093 
1094     /*reimp*/ QModelIndex indexAt( int height ) const override {
1095         return m_model->index( height/ROW_HEIGHT, 0 );
1096     }
1097 
1098     /*reimp*/ QModelIndex indexBelow( const QModelIndex& idx ) const override {
1099         if ( !idx.isValid() )return QModelIndex();
1100         return idx.model()->index( idx.row()+1, idx.column(), idx.parent() );
1101     }
1102     /*reimp*/ QModelIndex indexAbove( const QModelIndex& idx ) const override {
1103         if ( !idx.isValid() )return QModelIndex();
1104         return idx.model()->index( idx.row()-1, idx.column(), idx.parent() );
1105     }
1106 
1107 };
1108 
1109 class TestLineItem : public QGraphicsLineItem
1110 {
1111 public:
1112     TestLineItem( bool *destroyedFlag )
1113          : QGraphicsLineItem( 0, 0, 10, 10 ), // geometry doesn't matter
1114            m_destroyedFlag( destroyedFlag )
1115     {}
1116 
1117     ~TestLineItem() override
1118     { *m_destroyedFlag = true; }
1119 
1120 private:
1121     bool *m_destroyedFlag;
1122 };
1123 
1124 const int SceneTestRowController::ROW_HEIGHT = 30;
1125 
1126 KDAB_SCOPED_UNITTEST_SIMPLE( KGantt, GraphicsView, "test" ) {
1127     QStandardItemModel model;
1128 
1129     QStandardItem* item = new QStandardItem();
1130     item->setData( KGantt::TypeTask, KGantt::ItemTypeRole );
1131     item->setData( QString::fromLatin1( "Decide on new product" ) );
1132     item->setData( QDateTime( QDate( 2007, 3, 1 ), QTime() ), KGantt::StartTimeRole );
1133     item->setData( QDateTime( QDate( 2007, 3, 3 ), QTime() ), KGantt::EndTimeRole );
1134 
1135     QStandardItem* item2 = new QStandardItem();
1136     item2->setData( KGantt::TypeTask, KGantt::ItemTypeRole );
1137     item2->setData( QString::fromLatin1( "Educate personnel" ) );
1138     item2->setData( QDateTime( QDate( 2007, 3, 3 ), QTime() ), KGantt::StartTimeRole );
1139     item2->setData( QDateTime( QDate( 2007, 3, 6 ), QTime() ), KGantt::EndTimeRole );
1140 
1141     model.appendRow( item );
1142     model.appendRow( item2 );
1143 
1144     SceneTestRowController rowController;
1145     rowController.setModel( &model );
1146 
1147     KGantt::GraphicsView graphicsView;
1148     graphicsView.setRowController( &rowController );
1149     graphicsView.setModel( &model );
1150 
1151     // Now the interesting stuff - the items above are just for a "realistic environment"
1152 
1153     bool foreignItemDestroyed = false;
1154     TestLineItem *foreignItem = new TestLineItem( &foreignItemDestroyed );
1155     graphicsView.scene()->addItem( foreignItem );
1156 
1157     assertFalse( foreignItemDestroyed );
1158     graphicsView.updateScene();
1159     assertFalse( foreignItemDestroyed );
1160 }
1161 #endif /* KDAB_NO_UNIT_TESTS */