diff --git a/src/KGantt/kganttgraphicsitem.cpp b/src/KGantt/kganttgraphicsitem.cpp index e11472b..3ced619 100644 --- a/src/KGantt/kganttgraphicsitem.cpp +++ b/src/KGantt/kganttgraphicsitem.cpp @@ -1,573 +1,573 @@ /* * 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 "kganttgraphicsitem.h" #include "kganttgraphicsscene.h" #include "kganttgraphicsview.h" #include "kganttitemdelegate.h" #include "kganttconstraintgraphicsitem.h" #include "kganttconstraintmodel.h" #include "kganttconstraint.h" #include "kganttabstractgrid.h" #include "kganttabstractrowcontroller.h" #include #include #include #include #include #include #include #include #include #include /*!\class KGantt::GraphicsItem * \internal */ using namespace KGantt; typedef QGraphicsItem BASE; namespace { class Updater { bool *u_ptr; bool oldval; public: Updater( bool* u ) : u_ptr( u ), oldval( *u ) { *u=true; } ~Updater() { *u_ptr = oldval; } }; } GraphicsItem::GraphicsItem( QGraphicsItem* parent, GraphicsScene* scene ) : BASE( parent ), m_isupdating( false ) { if ( scene ) scene->addItem( this ); init(); } GraphicsItem::GraphicsItem( const QModelIndex& idx, QGraphicsItem* parent, GraphicsScene* scene ) : BASE( parent ), m_index( idx ), m_isupdating( false ) { init(); if ( scene ) scene->addItem( this ); } GraphicsItem::~GraphicsItem() { } void GraphicsItem::init() { setCacheMode( QGraphicsItem::DeviceCoordinateCache ); setFlags( ItemIsMovable|ItemIsSelectable|ItemIsFocusable ); setAcceptHoverEvents( true ); setHandlesChildEvents( true ); setZValue( 100. ); m_dragline = nullptr; } int GraphicsItem::type() const { return Type; } StyleOptionGanttItem GraphicsItem::getStyleOption() const { StyleOptionGanttItem opt; if (!m_index.isValid()) { // TODO: find out why we get invalid indexes //qDebug()<<"GraphicsItem::getStyleOption: Invalid index"; return opt; } opt.itemRect = rect(); opt.boundingRect = boundingRect(); QVariant tp = m_index.model()->data( m_index, TextPositionRole ); if (tp.isValid()) { opt.displayPosition = static_cast(tp.toInt()); } else { #if 0 qDebug() << "Item" << m_index.model()->data( m_index, Qt::DisplayRole ).toString() << ", ends="<data( m_index, Qt::TextAlignmentRole ); if ( da.isValid() ) { opt.displayAlignment = static_cast< Qt::Alignment >( da.toInt() ); } else { switch ( opt.displayPosition ) { case StyleOptionGanttItem::Left: opt.displayAlignment = Qt::AlignLeft|Qt::AlignVCenter; break; case StyleOptionGanttItem::Right: opt.displayAlignment = Qt::AlignRight|Qt::AlignVCenter; break; case StyleOptionGanttItem::Hidden: // fall through case StyleOptionGanttItem::Center: opt.displayAlignment = Qt::AlignCenter; break; } } - opt.grid = scene()->grid(); + opt.grid = const_cast(scene()->getGrid()); opt.text = m_index.model()->data( m_index, Qt::DisplayRole ).toString(); if ( isEnabled() ) opt.state |= QStyle::State_Enabled; if ( isSelected() ) opt.state |= QStyle::State_Selected; if ( hasFocus() ) opt.state |= QStyle::State_HasFocus; return opt; } GraphicsScene* GraphicsItem::scene() const { return qobject_cast( QGraphicsItem::scene() ); } void GraphicsItem::setRect( const QRectF& r ) { #if 0 qDebug() << "GraphicsItem::setRect("<isReadOnly() && m_index.isValid() && m_index.model()->flags( m_index ) & Qt::ItemIsEditable; } void GraphicsItem::paint( QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget ) { Q_UNUSED( widget ); if ( boundingRect().isValid() && scene() ) { StyleOptionGanttItem opt = getStyleOption(); *static_cast(&opt) = *static_cast( option ); //opt.fontMetrics = painter->fontMetrics(); scene()->itemDelegate()->paintGanttItem( painter, opt, index() ); } } void GraphicsItem::setIndex( const QPersistentModelIndex& idx ) { m_index=idx; update(); } QString GraphicsItem::ganttToolTip() const { return scene()->itemDelegate()->toolTip( index() ); } QRectF GraphicsItem::boundingRect() const { return m_boundingrect; } QPointF GraphicsItem::startConnector( int relationType ) const { switch ( relationType ) { case Constraint::StartStart: case Constraint::StartFinish: return mapToScene( m_rect.left(), m_rect.top()+m_rect.height()/2. ); default: break; } return mapToScene( m_rect.right(), m_rect.top()+m_rect.height()/2. ); } QPointF GraphicsItem::endConnector( int relationType ) const { switch ( relationType ) { case Constraint::FinishFinish: case Constraint::StartFinish: return mapToScene( m_rect.right(), m_rect.top()+m_rect.height()/2. ); default: break; } return mapToScene( m_rect.left(), m_rect.top()+m_rect.height()/2. ); } void GraphicsItem::constraintsChanged() { if ( !scene() || !scene()->itemDelegate() ) return; const Span bs = scene()->itemDelegate()->itemBoundingSpan( getStyleOption(), index() ); const QRectF br = boundingRect(); setBoundingRect( QRectF( bs.start(), 0., bs.length(), br.height() ) ); } void GraphicsItem::addStartConstraint( ConstraintGraphicsItem* item ) { assert( item ); m_startConstraints << item; item->setStart( startConnector( item->constraint().relationType() ) ); constraintsChanged(); } void GraphicsItem::addEndConstraint( ConstraintGraphicsItem* item ) { assert( item ); m_endConstraints << item; item->setEnd( endConnector( item->constraint().relationType() ) ); constraintsChanged(); } void GraphicsItem::removeStartConstraint( ConstraintGraphicsItem* item ) { assert( item ); m_startConstraints.removeAll( item ); constraintsChanged(); } void GraphicsItem::removeEndConstraint( ConstraintGraphicsItem* item ) { assert( item ); m_endConstraints.removeAll( item ); constraintsChanged(); } void GraphicsItem::updateConstraintItems() { { // Workaround for multiple definition error with MSVC6 Q_FOREACH( ConstraintGraphicsItem* item, m_startConstraints ) { QPointF s = startConnector( item->constraint().relationType() ); item->setStart( s ); }} {// Workaround for multiple definition error with MSVC6 Q_FOREACH( ConstraintGraphicsItem* item, m_endConstraints ) { QPointF e = endConnector( item->constraint().relationType() ); item->setEnd( e ); }} } void GraphicsItem::updateItem( const Span& rowGeometry, const QPersistentModelIndex& idx ) { //qDebug() << "GraphicsItem::updateItem("<grid()->mapToChart( static_cast(idx) ); + const Span s = scene()->getGrid()->mapToChart( static_cast(idx) ); setPos( QPointF( s.start(), rowGeometry.start() ) ); setRect( QRectF( 0., 0., s.length(), rowGeometry.length() ) ); setIndex( idx ); const Span bs = scene()->itemDelegate()->itemBoundingSpan( getStyleOption(), index() ); //qDebug() << "boundingSpan for" << getStyleOption().text << rect() << "is" << bs; setBoundingRect( QRectF( bs.start(), 0., bs.length(), rowGeometry.length() ) ); const int maxh = scene()->rowController()->maximumItemHeight(); if ( maxh < rowGeometry.length() ) { QRectF r = rect(); const Qt::Alignment align = getStyleOption().displayAlignment; if ( align & Qt::AlignTop ) { // Do nothing } else if ( align & Qt::AlignBottom ) { r.setY( rowGeometry.length()-maxh ); } else { // Center r.setY( ( rowGeometry.length()-maxh ) / 2. ); } r.setHeight( maxh ); setRect( r ); } //scene()->setSceneRect( scene()->sceneRect().united( mapToScene( boundingRect() ).boundingRect() ) ); //updateConstraintItems(); } QVariant GraphicsItem::itemChange( GraphicsItemChange change, const QVariant& value ) { if ( !isUpdating() && change==ItemPositionChange && scene() ) { QPointF newPos=value.toPointF(); if ( isEditable() ) { newPos.setY( pos().y() ); return newPos; } else { return pos(); } } else if ( change==QGraphicsItem::ItemSelectedChange ) { if ( index().isValid() && !( index().model()->flags( index() ) & Qt::ItemIsSelectable ) ) { // Reject selection attempt return QVariant::fromValue( false ); } } return QGraphicsItem::itemChange( change, value ); } void GraphicsItem::focusInEvent( QFocusEvent* event ) { Q_UNUSED( event ); } void GraphicsItem::updateModel() { //qDebug() << "GraphicsItem::updateModel()"; if ( isEditable() ) { QAbstractItemModel* model = const_cast( index().model() ); #if !defined(NDEBUG) ConstraintModel* cmodel = scene()->constraintModel(); #endif assert( model ); assert( cmodel ); if ( model ) { //ItemType typ = static_cast( model->data( index(), // ItemTypeRole ).toInt() ); QList constraints; for ( QList::iterator it1 = m_startConstraints.begin() ; it1 != m_startConstraints.end() ; ++it1 ) constraints.push_back((*it1)->proxyConstraint()); for ( QList::iterator it2 = m_endConstraints.begin() ; it2 != m_endConstraints.end() ; ++it2 ) constraints.push_back((*it2)->proxyConstraint()); - if ( scene()->grid()->mapFromChart( Span( scenePos().x(), rect().width() ), + if ( scene()->getGrid()->mapFromChart( Span( scenePos().x(), rect().width() ), index(), constraints ) ) { scene()->updateRow( index().parent() ); } } } } void GraphicsItem::hoverMoveEvent( QGraphicsSceneHoverEvent* event ) { if ( !isEditable() ) return; StyleOptionGanttItem opt = getStyleOption(); ItemDelegate::InteractionState istate = scene()->itemDelegate()->interactionStateFor( event->pos(), opt, index() ); switch ( istate ) { case ItemDelegate::State_ExtendLeft: #ifndef QT_NO_CURSOR setCursor( Qt::SizeHorCursor ); #endif scene()->itemEntered( index() ); break; case ItemDelegate::State_ExtendRight: #ifndef QT_NO_CURSOR setCursor( Qt::SizeHorCursor ); #endif scene()->itemEntered( index() ); break; case ItemDelegate::State_Move: #ifndef QT_NO_CURSOR setCursor( Qt::SplitHCursor ); #endif scene()->itemEntered( index() ); break; default: #ifndef QT_NO_CURSOR unsetCursor(); #endif break; }; } void GraphicsItem::hoverLeaveEvent( QGraphicsSceneHoverEvent* ) { #ifndef QT_NO_CURSOR unsetCursor(); #endif } void GraphicsItem::mousePressEvent( QGraphicsSceneMouseEvent* event ) { //qDebug() << "GraphicsItem::mousePressEvent("<itemDelegate()->interactionStateFor( event->pos(), opt, index() ); // If State_None is returned by interactionStateFor(), we ignore this event so that // it can get forwarded to another item that's below this one. Needed, for example, // to allow items to be moved that are placed below the label of another item. if ( istate != ItemDelegate::State_None ) { m_istate = istate; m_presspos = event->pos(); m_pressscenepos = event->scenePos(); scene()->itemPressed( index(), event ); switch ( m_istate ) { case ItemDelegate::State_ExtendLeft: case ItemDelegate::State_ExtendRight: default: /* State_Move */ if (!(flags() & ItemIsMovable)) { event->ignore(); } break; } } else { event->ignore(); } } void GraphicsItem::mouseReleaseEvent( QGraphicsSceneMouseEvent* event ) { //qDebug() << "GraphicsItem::mouseReleaseEvent("<itemClicked( index() ); } delete m_dragline; m_dragline = nullptr; if ( scene()->dragSource() ) { // Create a new constraint GraphicsItem* other = qgraphicsitem_cast( scene()->itemAt( event->scenePos(), QTransform() ) ); if ( other && scene()->dragSource()!=other && other->index().data(KGantt::ItemTypeRole) == KGantt::TypeEvent ) { // The code below fixes bug KDCH-696. // Modified the code to add constraint even if the user drags and drops // constraint on left part of the TypeEvent symbol(i.e diamond symbol) QRectF itemRect = other->rect().adjusted(-other->rect().height()/2.0, 0, 0, 0 ); if ( other->mapToScene( itemRect ).boundingRect().contains( event->scenePos() )) { GraphicsView* view = qobject_cast( event->widget()->parentWidget() ); if ( view ) { view->addConstraint( scene()->summaryHandlingModel()->mapToSource( scene()->dragSource()->index() ), scene()->summaryHandlingModel()->mapToSource( other->index() ), event->modifiers() ); } } } else { if ( other && scene()->dragSource()!=other && other->mapToScene( other->rect() ).boundingRect().contains( event->scenePos() )) { GraphicsView* view = qobject_cast( event->widget()->parentWidget() ); if ( view ) { view->addConstraint( scene()->summaryHandlingModel()->mapToSource( scene()->dragSource()->index() ), scene()->summaryHandlingModel()->mapToSource( other->index() ), event->modifiers() ); } } } scene()->setDragSource( nullptr ); //scene()->update(); } else { if ( isEditable() ) { updateItemFromMouse(event->scenePos()); // It is important to set m_presspos to null here because // when the sceneRect updates because we move the item // a MouseMoveEvent will be delivered, and we have to // protect against that m_presspos = QPointF(); updateModel(); // without this command we sometimes get a white area at the left side of a task item // after we moved that item right-ways into a grey weekend section of the scene: scene()->update(); } } m_presspos = QPointF(); } void GraphicsItem::mouseDoubleClickEvent( QGraphicsSceneMouseEvent* event ) { const int typ = static_cast( index().model()->data( index(), ItemTypeRole ).toInt() ); StyleOptionGanttItem opt = getStyleOption(); ItemDelegate::InteractionState istate = scene()->itemDelegate()->interactionStateFor( event->pos(), opt, index() ); if ( (istate != ItemDelegate::State_None) || (typ == TypeSummary)) { scene()->itemDoubleClicked( index() ); } BASE::mouseDoubleClickEvent( event ); } void GraphicsItem::updateItemFromMouse( const QPointF& scenepos ) { //qDebug() << "GraphicsItem::updateItemFromMouse("< #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 ), grid( &default_grid ), 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() +{ + return grid.isNull() ? &default_grid : grid; +} + +const AbstractGrid *GraphicsScene::Private::getGrid() const +{ + return grid.isNull() ? &default_grid : grid; +} + 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->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->grid->setModel( d->summaryHandlingModel ); + 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->grid->setRootIndex( idx ); + d->getGrid()->setRootIndex( idx ); } QModelIndex GraphicsScene::rootIndex() const { - return d->grid->rootIndex(); + 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 ( grid == nullptr ) grid = &d->default_grid; - if ( d->grid ) { - d->grid->disconnect( this ); - model = d->grid->model(); + if ( d->getGrid() ) { + d->getGrid()->disconnect( this ); + model = d->getGrid()->model(); } d->grid = grid; - connect( d->grid, SIGNAL(gridChanged()), this, SLOT(slotGridChanged()) ); - d->grid->setModel( model ); + 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 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->grid->paintHeader( painter, headerRect, rect, 0, nullptr ); + 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->grid->paintGrid( painter, scn, rect, d->rowController ); + d->getGrid()->paintGrid( painter, scn, rect, d->rowController ); - d->grid->drawBackground(painter, rect); + d->getGrid()->drawBackground(painter, rect); } void GraphicsScene::drawForeground( QPainter* painter, const QRectF& rect ) { - d->grid->drawForeground(painter, 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 */ diff --git a/src/KGantt/kganttgraphicsscene.h b/src/KGantt/kganttgraphicsscene.h index decf068..568dec1 100644 --- a/src/KGantt/kganttgraphicsscene.h +++ b/src/KGantt/kganttgraphicsscene.h @@ -1,144 +1,145 @@ /* * 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 KGANTTGRAPHICSSCENE_H #define KGANTTGRAPHICSSCENE_H #include #include #include #include #include "kganttglobal.h" QT_BEGIN_NAMESPACE class QAbstractProxyModel; class QItemSelectionModel; class QItemSelection; class QPrinter; class QAbstractItemModel; class QGraphicsSceneMouseEvent; QT_END_NAMESPACE namespace KGantt { class AbstractGrid; class AbstractRowController; class GraphicsItem; class Constraint; class ConstraintModel; class ConstraintGraphicsItem; class ItemDelegate; class KGANTT_EXPORT GraphicsScene : public QGraphicsScene { Q_OBJECT KGANTT_DECLARE_PRIVATE_BASE_POLYMORPHIC( GraphicsScene ) public: explicit GraphicsScene( QObject* parent = nullptr ); virtual ~GraphicsScene(); //qreal dateTimeToSceneX( const QDateTime& dt ) const; //QDateTime sceneXtoDateTime( qreal x ) const; static QModelIndex mainIndex( const QModelIndex& idx ); static QModelIndex dataIndex( const QModelIndex& idx ); QAbstractItemModel* model() const; QAbstractProxyModel* summaryHandlingModel() const; QModelIndex rootIndex() const; ConstraintModel* constraintModel() const; QItemSelectionModel* selectionModel() const; void insertItem( const QPersistentModelIndex&, GraphicsItem* ); void removeItem( const QModelIndex& ); using QGraphicsScene::removeItem; GraphicsItem* findItem( const QModelIndex& ) const; GraphicsItem* findItem( const QPersistentModelIndex& ) const; void updateItems(); void clearItems(); void deleteSubtree( const QModelIndex& ); ConstraintGraphicsItem* findConstraintItem( const Constraint& ) const; QList findConstraintItems( const QModelIndex& idx ) const; void setItemDelegate( ItemDelegate* ); ItemDelegate* itemDelegate() const; void setRowController( AbstractRowController* rc ); AbstractRowController* rowController() const; void setGrid( AbstractGrid* grid ); AbstractGrid* grid() const; + const AbstractGrid *getGrid() const; bool isReadOnly() const; void updateRow( const QModelIndex& idx ); GraphicsItem* createItem( ItemType type ) const; /* used by GraphicsItem */ void itemEntered( const QModelIndex& ); void itemPressed( const QModelIndex& idx, QGraphicsSceneMouseEvent *event ); void itemClicked( const QModelIndex& ); void itemDoubleClicked( const QModelIndex& ); void setDragSource( GraphicsItem* item ); GraphicsItem* dragSource() const; /* Printing */ void print( QPrinter* printer, bool drawRowLabels = true, bool drawColumnLabels = true ); void print( QPrinter* printer, qreal start, qreal end, bool drawRowLabels = true, bool drawColumnLabels = true ); void print( QPainter* painter, const QRectF& target = QRectF(), bool drawRowLabels=true, bool drawColumnLabels = true ); void print( QPainter* painter, qreal start, qreal end, const QRectF& target = QRectF(), bool drawRowLabels=true, bool drawColumnLabels = true ); Q_SIGNALS: void gridChanged(); void clicked( const QModelIndex & index ); void qrealClicked( const QModelIndex & index ); void entered( const QModelIndex & index ); void pressed( const QModelIndex & index ); protected: /*reimp*/ void helpEvent( QGraphicsSceneHelpEvent *helpEvent ) override; /*reimp*/ void drawBackground( QPainter* painter, const QRectF& rect ) override; /*reimp*/ void drawForeground( QPainter* painter, const QRectF& rect ) override; public Q_SLOTS: void setModel( QAbstractItemModel* ); void setSummaryHandlingModel( QAbstractProxyModel* ); void setConstraintModel( ConstraintModel* ); void setRootIndex( const QModelIndex& idx ); void setSelectionModel( QItemSelectionModel* selectionmodel ); void setReadOnly( bool ); private Q_SLOTS: /* slots for ConstraintModel */ void slotConstraintAdded( const KGantt::Constraint& ); void slotConstraintRemoved( const KGantt::Constraint& ); void slotGridChanged(); void slotSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected); void selectionModelChanged(QAbstractItemModel *); private: void doPrint( QPainter* painter, const QRectF& targetRect, qreal start, qreal end, QPrinter* printer, bool drawRowLabels, bool drawColumnLabels ); }; } #endif /* KGANTTGRAPHICSSCENE_H */ diff --git a/src/KGantt/kganttgraphicsscene_p.h b/src/KGantt/kganttgraphicsscene_p.h index d7e168f..f84c97e 100644 --- a/src/KGantt/kganttgraphicsscene_p.h +++ b/src/KGantt/kganttgraphicsscene_p.h @@ -1,80 +1,84 @@ /* * 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 KGANTTGRAPHICSSCENE_P_H #define KGANTTGRAPHICSSCENE_P_H #include #include #include #include #include #include "kganttgraphicsscene.h" #include "kganttconstraintmodel.h" #include "kganttdatetimegrid.h" namespace KGantt { + class AbstractGrid; + class Q_DECL_HIDDEN GraphicsScene::Private { public: explicit Private(GraphicsScene*); void clearConstraintItems(); void resetConstraintItems(); void createConstraintItem( const Constraint& c ); void deleteConstraintItem( ConstraintGraphicsItem* citem ); void deleteConstraintItem( const Constraint& c ); ConstraintGraphicsItem* findConstraintItem( const Constraint& c ) const; void recursiveUpdateMultiItem( const Span& span, const QModelIndex& idx ); void clearItems(); + AbstractGrid *getGrid(); + const AbstractGrid *getGrid() const; GraphicsScene* q; QHash items; QList constraintItems; GraphicsItem* dragSource; QPointer itemDelegate; AbstractRowController* rowController; DateTimeGrid default_grid; QPointer grid; bool readOnly; /* printing related members */ bool isPrinting; bool drawColumnLabels; qreal labelsWidth; QPointer summaryHandlingModel; QPointer constraintModel; QPointer selectionModel; }; GraphicsScene::GraphicsScene( GraphicsScene::Private* d ) : _d( d ) { init(); } } #endif /* KGANTTGRAPHICSSCENE_P_H */ diff --git a/src/KGantt/kganttgraphicsview.cpp b/src/KGantt/kganttgraphicsview.cpp index 80e6716..24aa8da 100644 --- a/src/KGantt/kganttgraphicsview.cpp +++ b/src/KGantt/kganttgraphicsview.cpp @@ -1,813 +1,824 @@ /* * 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 "kganttdatetimetimelinedialog.h" #include #include #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 = nullptr; QAction* actionScaleMonth = nullptr; QAction* actionScaleWeek = nullptr; QAction* actionScaleDay = nullptr; QAction* actionScaleHour = nullptr; QAction* actionZoomIn = nullptr; QAction* actionZoomOut = nullptr; QAction* actionTimeline = nullptr; if ( grid != nullptr ) { 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 ); contextMenu.addSeparator(); actionTimeline = new QAction( tr( "Timeline...", "@action:inmenu" ), &contextMenu ); contextMenu.addAction( actionTimeline ); } if ( contextMenu.isEmpty() ) { event->ignore(); return; } const QAction* const action = contextMenu.exec( event->globalPos() ); if ( action == nullptr ) {} else if ( action == actionScaleAuto ) { assert( grid != nullptr ); grid->setScale( DateTimeGrid::ScaleAuto ); } else if ( action == actionScaleMonth ) { assert( grid != nullptr ); grid->setScale( DateTimeGrid::ScaleMonth ); } else if ( action == actionScaleWeek ) { assert( grid != nullptr ); grid->setScale( DateTimeGrid::ScaleWeek ); } else if ( action == actionScaleDay ) { assert( grid != nullptr ); grid->setScale( DateTimeGrid::ScaleDay ); } else if ( action == actionScaleHour ) { assert( grid != nullptr ); grid->setScale( DateTimeGrid::ScaleHour ); } else if ( action == actionZoomIn ) { assert( grid != nullptr ); grid->setDayWidth( grid->dayWidth() * 1.25 ); } else if ( action == actionZoomOut ) { assert( grid != nullptr ); // daywidth *MUST NOT* go below 1.0, it is used as an integer later on grid->setDayWidth( qMax( 1.0, grid->dayWidth() * 0.8 ) ); } else if ( action == actionTimeline ) { assert( grid != nullptr ); DateTimeTimeLineDialog dlg(grid->timeLine()); dlg.exec(); } event->accept(); } GraphicsView::Private::Private( GraphicsView* _q ) : q( _q ), rowcontroller(nullptr), 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, nullptr, 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, nullptr, 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(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 ); if (model) { 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(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(); } +/*! \returns the AbstractGrid used by this view. + */ +AbstractGrid* GraphicsView::takeGrid() +{ + AbstractGrid *grid = d->scene.grid(); + if ( grid ) { + d->scene.setGrid( nullptr) ; + } + return 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 ); } /*! Creates a new GraphicsItem * Re-iplement to create your own flavour of GraphicsItem */ GraphicsItem* GraphicsView::createItem( ItemType type ) const { Q_UNUSED(type) return new GraphicsItem; } /*! \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/kganttgraphicsview.h b/src/KGantt/kganttgraphicsview.h index a7b9ad9..628e633 100644 --- a/src/KGantt/kganttgraphicsview.h +++ b/src/KGantt/kganttgraphicsview.h @@ -1,133 +1,134 @@ /* * 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 KGANTTGRAPHICSVIEW_H #define KGANTTGRAPHICSVIEW_H #include #include "kganttglobal.h" QT_BEGIN_NAMESPACE class QPrinter; class QModelIndex; class QAbstractItemModel; class QAbstractProxyModel; class QItemSelectionModel; QT_END_NAMESPACE namespace KGantt { class AbstractRowController; class AbstractGrid; class GraphicsItem; class ConstraintModel; class ItemDelegate; class KGANTT_EXPORT GraphicsView : public QGraphicsView { Q_OBJECT KGANTT_DECLARE_PRIVATE_BASE_POLYMORPHIC(GraphicsView) Q_PROPERTY( bool readOnly READ isReadOnly WRITE setReadOnly ) Q_PRIVATE_SLOT( d, void slotGridChanged() ) Q_PRIVATE_SLOT( d, void slotHorizontalScrollValueChanged( int ) ) Q_PRIVATE_SLOT( d, void slotHeaderContextMenuRequested( const QPoint& ) ) /* slots for QAbstractItemModel signals */ Q_PRIVATE_SLOT( d, void slotColumnsInserted( const QModelIndex& parent, int start, int end ) ) Q_PRIVATE_SLOT( d, void slotColumnsRemoved( const QModelIndex& parent, int start, int end ) ) Q_PRIVATE_SLOT( d, void slotDataChanged( const QModelIndex& topLeft, const QModelIndex& bottomRight ) ) Q_PRIVATE_SLOT( d, void slotLayoutChanged() ) Q_PRIVATE_SLOT( d, void slotModelReset() ) Q_PRIVATE_SLOT( d, void slotRowsInserted( const QModelIndex& parent, int start, int end ) ) Q_PRIVATE_SLOT( d, void slotRowsAboutToBeRemoved( const QModelIndex& parent, int start, int end ) ) Q_PRIVATE_SLOT( d, void slotRowsRemoved( const QModelIndex& parent, int start, int end ) ) Q_PRIVATE_SLOT( d, void slotItemClicked( const QModelIndex& idx ) ) Q_PRIVATE_SLOT( d, void slotItemDoubleClicked( const QModelIndex& idx ) ) public: explicit GraphicsView( QWidget* parent = nullptr ); virtual ~GraphicsView(); QAbstractItemModel* model() const; QAbstractProxyModel* summaryHandlingModel() const; ConstraintModel* constraintModel() const; QModelIndex rootIndex() const; QItemSelectionModel* selectionModel() const; AbstractRowController* rowController() const; AbstractGrid* grid() const; + AbstractGrid* takeGrid(); ItemDelegate* itemDelegate() const; bool isReadOnly() const; void setHeaderContextMenuPolicy( Qt::ContextMenuPolicy ); Qt::ContextMenuPolicy headerContextMenuPolicy() const; QModelIndex indexAt( const QPoint& pos ) const; virtual void addConstraint( const QModelIndex& from, const QModelIndex& to, Qt::KeyboardModifiers modifiers ); void updateRow( const QModelIndex& ); void updateScene(); virtual GraphicsItem* createItem( ItemType type ) const; public Q_SLOTS: void updateSceneRect(); public: void deleteSubtree( const QModelIndex& ); void print( QPrinter* printer, bool drawRowLabels = true, bool drawColumnLabels = true ); void print( QPrinter* printer, qreal start, qreal end, bool drawRowLabels = true, bool drawColumnLabels = true ); void print( QPainter* painter, const QRectF& target = QRectF(), bool drawRowLabels = true, bool drawColumnLabels = true ); void print( QPainter* painter, qreal start, qreal end, const QRectF& target = QRectF(), bool drawRowLabels = true, bool drawColumnLabels = true ); public Q_SLOTS: void setModel( QAbstractItemModel* ); void setSummaryHandlingModel( QAbstractProxyModel* model ); void setConstraintModel( ConstraintModel* ); void setRootIndex( const QModelIndex& ); void setSelectionModel( QItemSelectionModel* ); void setRowController( AbstractRowController* ); void setGrid( AbstractGrid* ); void setItemDelegate( ItemDelegate* delegate ); void setReadOnly( bool ); Q_SIGNALS: void activated( const QModelIndex & index ); void clicked( const QModelIndex & index ); void qrealClicked( const QModelIndex & index ); void entered( const QModelIndex & index ); void pressed( const QModelIndex & index ); void headerContextMenuRequested( const QPoint& pt ); protected: void clearItems(); /*reimp*/void resizeEvent( QResizeEvent* ) override; private: friend class View; }; } #endif /* KGANTTGRAPHICSVIEW_H */ diff --git a/src/KGantt/kganttview.cpp b/src/KGantt/kganttview.cpp index 72d2710..fb99b20 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=nullptr ) : QHeaderView( Qt::Horizontal, parent ) { } QSize sizeHint() const 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(nullptr), gfxview( new GraphicsView( &splitter ) ), model(nullptr) { //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->setContentsMargins(0, 0, 0, 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(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)) ); } /*! 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() ); + d->gfxview->setGrid( old->takeGrid() ); 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( nullptr ); #if 0 // GUI tests do not work well on the server 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()) ); 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()) ); qApp->exec(); #endif } #endif /* KDAB_NO_UNIT_TESTS */