kdganttgraphicsitem.cpp

Go to the documentation of this file.
00001 /****************************************************************************
00002 ** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB.  All rights reserved.
00003 **
00004 ** This file is part of the KD Chart library.
00005 **
00006 ** Licensees holding valid commercial KD Chart licenses may use this file in
00007 ** accordance with the KD Chart Commercial License Agreement provided with
00008 ** the Software.
00009 **
00010 **
00011 ** This file may be distributed and/or modified under the terms of the
00012 ** GNU General Public License version 2 and version 3 as published by the
00013 ** Free Software Foundation and appearing in the file LICENSE.GPL included.
00014 **
00015 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
00016 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
00017 **
00018 ** Contact info@kdab.com if any conditions of this licensing are not
00019 ** clear to you.
00020 **
00021 **********************************************************************/
00022 
00023 #include "kdganttgraphicsitem.h"
00024 #include "kdganttgraphicsscene.h"
00025 #include "kdganttgraphicsview.h"
00026 #include "kdganttitemdelegate.h"
00027 #include "kdganttconstraintgraphicsitem.h"
00028 #include "kdganttconstraintmodel.h"
00029 #include "kdganttconstraint.h"
00030 #include "kdganttabstractgrid.h"
00031 #include "kdganttabstractrowcontroller.h"
00032 
00033 #include <cassert>
00034 #include <cmath>
00035 #include <algorithm>
00036 #include <iterator>
00037 
00038 #include <QPainter>
00039 #include <QAbstractItemModel>
00040 #include <QAbstractProxyModel>
00041 #include <QItemSelectionModel>
00042 #include <QGraphicsSceneMouseEvent>
00043 #include <QGraphicsLineItem>
00044 
00045 #include <QDebug>
00046 
00051 using namespace KDGantt;
00052 
00053 typedef QGraphicsItem BASE;
00054 
00055 namespace {
00056     class Updater {
00057         bool *u_ptr;
00058         bool oldval;
00059     public:
00060         Updater( bool* u ) : u_ptr( u ), oldval( *u ) {
00061             *u=true;
00062         }
00063         ~Updater() {
00064             *u_ptr = oldval;
00065         }
00066     };
00067 }
00068 
00069 GraphicsItem::GraphicsItem( QGraphicsItem* parent, GraphicsScene* scene )
00070     : BASE( parent, scene ),  m_isupdating( false )
00071 {
00072   init();
00073 }
00074 
00075 GraphicsItem::GraphicsItem( const QModelIndex& idx, QGraphicsItem* parent,
00076                                             GraphicsScene* scene )
00077     : BASE( parent, scene ),  m_index( idx ), m_isupdating( false )
00078 {
00079   init();
00080 }
00081 
00082 GraphicsItem::~GraphicsItem()
00083 {
00084 }
00085 
00086 void GraphicsItem::init()
00087 {
00088 #if QT_VERSION >= QT_VERSION_CHECK(4,4,0)
00089     setCacheMode( QGraphicsItem::DeviceCoordinateCache );
00090 #endif
00091     setFlags( ItemIsMovable|ItemIsSelectable|ItemIsFocusable );
00092     setAcceptsHoverEvents( true );
00093     setHandlesChildEvents( true );
00094     setZValue( 100. );
00095     m_dragline = 0;
00096 }
00097 
00098 int GraphicsItem::type() const
00099 {
00100     return Type;
00101 }
00102 
00103 StyleOptionGanttItem GraphicsItem::getStyleOption() const
00104 {
00105     StyleOptionGanttItem opt;
00106     opt.itemRect = rect();
00107     opt.boundingRect = boundingRect();
00108     QVariant tp = m_index.model()->data( m_index, TextPositionRole );
00109     if(tp.isValid()) {
00110         opt.displayPosition = static_cast<StyleOptionGanttItem::Position>(tp.toInt());
00111     } else {
00112 #if 0
00113         qDebug() << "Item" << m_index.model()->data( m_index, Qt::DisplayRole ).toString()
00114                  << ", ends="<<m_endConstraints.size() << ", starts="<<m_startConstraints.size();
00115 #endif
00116         opt.displayPosition = m_endConstraints.size()<m_startConstraints.size()?StyleOptionGanttItem::Left:StyleOptionGanttItem::Right;
00117 #if 0
00118         qDebug() << "choosing" << opt.displayPosition;
00119 #endif
00120     }
00121     QVariant da = m_index.model()->data( m_index, Qt::TextAlignmentRole );
00122     if ( da.isValid() ) {
00123         opt.displayAlignment = static_cast< Qt::Alignment >( da.toInt() );
00124     } else {
00125         switch( opt.displayPosition ) {
00126         case StyleOptionGanttItem::Left: opt.displayAlignment = Qt::AlignLeft|Qt::AlignVCenter; break;
00127         case StyleOptionGanttItem::Right: opt.displayAlignment = Qt::AlignRight|Qt::AlignVCenter; break;
00128         case StyleOptionGanttItem::Hidden: // fall through
00129         case StyleOptionGanttItem::Center: opt.displayAlignment = Qt::AlignCenter; break;
00130         }
00131     }
00132     opt.grid = scene()->grid();
00133     opt.text = m_index.model()->data( m_index, Qt::DisplayRole ).toString();
00134     if ( isEnabled() ) opt.state  |= QStyle::State_Enabled;
00135     if ( isSelected() ) opt.state |= QStyle::State_Selected;
00136     if ( hasFocus() ) opt.state   |= QStyle::State_HasFocus;
00137     return opt;
00138 }
00139 
00140 GraphicsScene* GraphicsItem::scene() const
00141 {
00142     return qobject_cast<GraphicsScene*>( QGraphicsItem::scene() );
00143 }
00144 
00145 void GraphicsItem::setRect( const QRectF& r )
00146 {
00147 #if 0
00148     qDebug() << "GraphicsItem::setRect("<<r<<"), txt="<<m_index.model()->data( m_index, Qt::DisplayRole ).toString();
00149     if ( m_index.model()->data( m_index, Qt::DisplayRole ).toString() == QLatin1String("Code Freeze" ) ) {
00150         qDebug() << "gotcha";
00151     }
00152 #endif
00153 
00154     prepareGeometryChange();
00155     m_rect = r;
00156     updateConstraintItems();
00157     update();
00158 }
00159 
00160 void GraphicsItem::setBoundingRect( const QRectF& r )
00161 {
00162     prepareGeometryChange();
00163     m_boundingrect = r;
00164     update();
00165 }
00166 
00167 bool GraphicsItem::isEditable() const
00168 {
00169     return !scene()->isReadOnly() && m_index.model()->flags( m_index ) & Qt::ItemIsEditable;
00170 }
00171 
00172 void GraphicsItem::paint( QPainter* painter, const QStyleOptionGraphicsItem* option,
00173                                   QWidget* widget )
00174 {
00175     Q_UNUSED( widget );
00176     if ( boundingRect().isValid() && scene() ) {
00177         StyleOptionGanttItem opt = getStyleOption();
00178         *static_cast<QStyleOption*>(&opt) = *static_cast<const QStyleOption*>( option );
00179         //opt.fontMetrics = painter->fontMetrics();
00180         scene()->itemDelegate()->paintGanttItem( painter, opt, index() );
00181     }
00182 }
00183 
00184 void GraphicsItem::setIndex( const QPersistentModelIndex& idx )
00185 {
00186     m_index=idx;
00187     update();
00188 }
00189 
00190 QString GraphicsItem::ganttToolTip() const
00191 {
00192     return scene()->itemDelegate()->toolTip( index() );
00193 }
00194 
00195 QRectF GraphicsItem::boundingRect() const
00196 {
00197     return m_boundingrect;
00198 }
00199 
00200 QPointF GraphicsItem::startConnector( int relationType ) const
00201 {
00202     switch ( relationType ) {
00203         case Constraint::StartStart:
00204         case Constraint::StartFinish:
00205             return mapToScene( m_rect.left(), m_rect.top()+m_rect.height()/2. );
00206         default:
00207             break;
00208     }
00209     return mapToScene( m_rect.right(), m_rect.top()+m_rect.height()/2. );
00210 }
00211 
00212 QPointF GraphicsItem::endConnector( int relationType ) const
00213 {
00214     switch ( relationType ) {
00215         case Constraint::FinishFinish:
00216         case Constraint::StartFinish:
00217             return mapToScene( m_rect.right(), m_rect.top()+m_rect.height()/2. );
00218         default:
00219             break;
00220     }
00221     return mapToScene( m_rect.left(), m_rect.top()+m_rect.height()/2. );
00222 }
00223 
00224 
00225 void GraphicsItem::constraintsChanged()
00226 {
00227     if ( !scene() || !scene()->itemDelegate() ) return;
00228     const Span bs = scene()->itemDelegate()->itemBoundingSpan( getStyleOption(), index() );
00229     const QRectF br = boundingRect();
00230     setBoundingRect( QRectF( bs.start(), 0., bs.length(), br.height() ) );
00231 }
00232 
00233 void GraphicsItem::addStartConstraint( ConstraintGraphicsItem* item )
00234 {
00235     assert( item );
00236     m_startConstraints << item;
00237     item->setStart( startConnector( item->constraint().relationType() ) );
00238     constraintsChanged();
00239 }
00240 
00241 void GraphicsItem::addEndConstraint( ConstraintGraphicsItem* item )
00242 {
00243     assert( item );
00244     m_endConstraints << item;
00245     item->setEnd( endConnector( item->constraint().relationType() ) );
00246     constraintsChanged();
00247 }
00248 
00249 void GraphicsItem::removeStartConstraint( ConstraintGraphicsItem* item )
00250 {
00251     assert( item );
00252     m_startConstraints.removeAll( item );
00253     constraintsChanged();
00254 }
00255 
00256 void GraphicsItem::removeEndConstraint( ConstraintGraphicsItem* item )
00257 {
00258     assert( item );
00259     m_endConstraints.removeAll( item );
00260     constraintsChanged();
00261 }
00262 
00263 void GraphicsItem::updateConstraintItems()
00264 {
00265     { // Workaround for multiple definition error with MSVC6
00266     Q_FOREACH( ConstraintGraphicsItem* item, m_startConstraints ) {
00267         QPointF s = startConnector( item->constraint().relationType() );
00268         item->setStart( s );
00269     }}
00270     {// Workaround for multiple definition error with MSVC6
00271     Q_FOREACH( ConstraintGraphicsItem* item, m_endConstraints ) {
00272         QPointF e = endConnector( item->constraint().relationType() );
00273         item->setEnd( e );
00274     }}
00275 }
00276 
00277 void GraphicsItem::updateItem( const Span& rowGeometry, const QPersistentModelIndex& idx )
00278 {
00279     //qDebug() << "GraphicsItem::updateItem("<<rowGeometry<<idx<<")";
00280     Updater updater( &m_isupdating );
00281     if ( !idx.isValid() || idx.data( ItemTypeRole )==TypeMulti ) {
00282         setRect( QRectF() );
00283         hide();
00284         return;
00285     }
00286 
00287     const Span s = scene()->grid()->mapToChart( idx );
00288     setPos( QPointF( s.start(), rowGeometry.start() ) );
00289     setRect( QRectF( 0., 0., s.length(), rowGeometry.length() ) );
00290     setIndex( idx );
00291     const Span bs = scene()->itemDelegate()->itemBoundingSpan( getStyleOption(), index() );
00292     //qDebug() << "boundingSpan for" << getStyleOption().text << rect() << "is" << bs;
00293     setBoundingRect( QRectF( bs.start(), 0., bs.length(), rowGeometry.length() ) );
00294     const int maxh = scene()->rowController()->maximumItemHeight();
00295     if ( maxh < rowGeometry.length() ) {
00296         QRectF r = rect();
00297         const Qt::Alignment align = getStyleOption().displayAlignment;
00298         if ( align & Qt::AlignTop ) {
00299             // Do nothing
00300         } else if ( align & Qt::AlignBottom ) {
00301             r.setY( rowGeometry.length()-maxh );
00302         } else {
00303             // Center
00304             r.setY( ( rowGeometry.length()-maxh ) / 2. );
00305         }
00306         r.setHeight( maxh );
00307         setRect( r );
00308     }
00309 
00310     //scene()->setSceneRect( scene()->sceneRect().united( mapToScene( boundingRect() ).boundingRect() ) );
00311     //updateConstraintItems();
00312 }
00313 
00314 QVariant GraphicsItem::itemChange( GraphicsItemChange change, const QVariant& value )
00315 {
00316     if ( !isUpdating() && change==ItemPositionChange && scene() ) {
00317         QPointF newPos=value.toPointF();
00318         if ( isEditable() ) {
00319             newPos.setY( pos().y() );
00320             return newPos;
00321         } else {
00322             return pos();
00323         }
00324     } else if ( change==QGraphicsItem::ItemSelectedChange ) {
00325         if ( index().isValid() && !( index().model()->flags( index() ) & Qt::ItemIsSelectable ) ) {
00326             // Reject selection attempt
00327             return qVariantFromValue( false );
00328         }
00329 
00330         if ( value.toBool() ) {
00331             scene()->selectionModel()->select( index(), QItemSelectionModel::Select );
00332         } else {
00333             scene()->selectionModel()->select( index(), QItemSelectionModel::Deselect );
00334         }
00335     }
00336 
00337     return QGraphicsItem::itemChange( change, value );
00338 }
00339 
00340 void GraphicsItem::focusInEvent( QFocusEvent* event )
00341 {
00342     Q_UNUSED( event );
00343     scene()->selectionModel()->select( index(), QItemSelectionModel::SelectCurrent );
00344 }
00345 
00346 void GraphicsItem::updateModel()
00347 {
00348     //qDebug() << "GraphicsItem::updateModel()";
00349     if ( isEditable() ) {
00350         QAbstractItemModel* model = const_cast<QAbstractItemModel*>( index().model() );
00351         ConstraintModel* cmodel = scene()->constraintModel();
00352         assert( model );
00353         assert( cmodel );
00354         if ( model ) {
00355             //ItemType typ = static_cast<ItemType>( model->data( index(),
00356             //                                                   ItemTypeRole ).toInt() );
00357             const QModelIndex sourceIdx = scene()->summaryHandlingModel()->mapToSource( index() );
00358             QList<Constraint> constraints;
00359             for( QList<ConstraintGraphicsItem*>::iterator it1 = m_startConstraints.begin() ;
00360                  it1 != m_startConstraints.end() ;
00361                  ++it1 )
00362                 constraints.push_back((*it1)->proxyConstraint());
00363             for( QList<ConstraintGraphicsItem*>::iterator it2 = m_endConstraints.begin() ;
00364                  it2 != m_endConstraints.end() ;
00365                  ++it2 )
00366                 constraints.push_back((*it2)->proxyConstraint());
00367             if ( scene()->grid()->mapFromChart( Span( scenePos().x(), rect().width() ),
00368                                                 index(),
00369                                                 constraints ) ) {
00370                 scene()->updateRow( index().parent() );
00371             }
00372         }
00373     }
00374 }
00375 
00376 void GraphicsItem::hoverMoveEvent( QGraphicsSceneHoverEvent* event )
00377 {
00378     if (  !isEditable() ) return;
00379     StyleOptionGanttItem opt = getStyleOption();
00380     ItemDelegate::InteractionState istate = scene()->itemDelegate()->interactionStateFor( event->pos(), opt, index() );
00381     switch( istate ) {
00382     case ItemDelegate::State_ExtendLeft:
00383         setCursor( Qt::SizeHorCursor );
00384         scene()->itemEntered( index() );
00385         break;
00386     case ItemDelegate::State_ExtendRight:
00387         setCursor( Qt::SizeHorCursor );
00388         scene()->itemEntered( index() );
00389         break;
00390     case ItemDelegate::State_Move:
00391         setCursor( Qt::SplitHCursor );
00392         scene()->itemEntered( index() );
00393         break;
00394     default:
00395         unsetCursor();
00396     };
00397 }
00398 
00399 void GraphicsItem::hoverLeaveEvent( QGraphicsSceneHoverEvent* )
00400 {
00401     unsetCursor();
00402 }
00403 
00404 void GraphicsItem::mousePressEvent( QGraphicsSceneMouseEvent* event )
00405 {
00406     //qDebug() << "GraphicsItem::mousePressEvent("<<event<<")";
00407     StyleOptionGanttItem opt = getStyleOption();
00408     int istate = scene()->itemDelegate()->interactionStateFor( event->pos(), opt, index() );
00409     // If State_None is returned by interactionStateFor(), we ignore this event so that
00410     // it can get forwarded to another item that's below this one. Needed, for example,
00411     // to allow items to be moved that are placed below the label of another item.
00412     if ( istate != ItemDelegate::State_None ) {
00413         m_istate = istate;
00414         m_presspos = event->pos();
00415         m_pressscenepos = event->scenePos();
00416         scene()->itemPressed( index() );
00417 
00418         switch( m_istate ) {
00419         case ItemDelegate::State_ExtendLeft:
00420         case ItemDelegate::State_ExtendRight:
00421         default: /* State_Move */
00422             BASE::mousePressEvent( event );
00423             break;
00424         }
00425     } else {
00426         event->ignore();
00427     }
00428 }
00429 
00430 void GraphicsItem::mouseReleaseEvent( QGraphicsSceneMouseEvent* event )
00431 {
00432     //qDebug() << "GraphicsItem::mouseReleaseEvent("<<event << ")";
00433     if ( !m_presspos.isNull() ) {
00434         scene()->itemClicked( index() );
00435     }
00436     delete m_dragline; m_dragline = 0;
00437     if ( scene()->dragSource() ) {
00438         // Create a new constraint
00439         GraphicsItem* other = qgraphicsitem_cast<GraphicsItem*>( scene()->itemAt( event->scenePos() ) );
00440         if ( other && scene()->dragSource()!=other &&
00441              other->mapToScene( other->rect() ).boundingRect().contains( event->scenePos() )) {
00442             GraphicsView* view = qobject_cast<GraphicsView*>( event->widget()->parentWidget() );
00443             if ( view ) {
00444                 view->addConstraint( scene()->summaryHandlingModel()->mapToSource( scene()->dragSource()->index() ),
00445                                      scene()->summaryHandlingModel()->mapToSource( other->index() ), event->modifiers() );
00446             }
00447         }
00448         scene()->setDragSource( 0 );
00449         //scene()->update();
00450     } else {
00451         if ( isEditable() ) {
00452             updateItemFromMouse(event->scenePos());
00453 
00454             // It is important to set m_presspos to null here because
00455             // when the sceneRect updates because we move the item
00456             // a MouseMoveEvent will be delivered, and we have to
00457             // protect against that
00458             m_presspos = QPointF();
00459             updateModel();
00460             // without this command we sometimes get a white area at the left side of a task item
00461             // after we moved that item right-ways into a grey weekend section of the scene:
00462             scene()->update();
00463         }
00464     }
00465 
00466     m_presspos = QPointF();
00467     BASE::mouseReleaseEvent( event );
00468 }
00469 
00470 void GraphicsItem::mouseDoubleClickEvent( QGraphicsSceneMouseEvent* event )
00471 {
00472     const int typ = static_cast<ItemType>( index().model()->data( index(), ItemTypeRole ).toInt() );
00473     StyleOptionGanttItem opt = getStyleOption();
00474     ItemDelegate::InteractionState istate = scene()->itemDelegate()->interactionStateFor( event->pos(), opt, index() );
00475     if ( (istate != ItemDelegate::State_None) || (typ == TypeSummary)) {
00476         scene()->itemDoubleClicked( index() );
00477     }
00478     BASE::mouseDoubleClickEvent( event );
00479 }
00480 
00481 void GraphicsItem::updateItemFromMouse( const QPointF& scenepos )
00482 {
00483     //qDebug() << "GraphicsItem::updateItemFromMouse("<<scenepos<<")";
00484     const QPointF p = scenepos - m_presspos;
00485     QRectF r = rect();
00486     QRectF br = boundingRect();
00487     switch( m_istate ) {
00488     case ItemDelegate::State_Move:
00489         setPos( p.x(), pos().y() );
00490         break;
00491     case ItemDelegate::State_ExtendLeft: {
00492         const qreal brr = br.right();
00493         const qreal rr = r.right();
00494         const qreal delta = pos().x()-p.x();
00495         setPos( p.x(), QGraphicsItem::pos().y() );
00496         br.setRight( brr+delta );
00497         r.setRight( rr+delta );
00498         break;
00499     }
00500     case ItemDelegate::State_ExtendRight: {
00501         const qreal rr = r.right();
00502         r.setRight( scenepos.x()-pos().x() );
00503         br.setWidth( br.width() + r.right()-rr );
00504         break;
00505     }
00506     default: return;
00507     }
00508     setRect( r );
00509     setBoundingRect( br );
00510 }
00511 
00512 void GraphicsItem::mouseMoveEvent( QGraphicsSceneMouseEvent* event )
00513 {
00514     if (  !isEditable() ) return;
00515     if ( m_presspos.isNull() ) return;
00516 
00517     //qDebug() << "GraphicsItem::mouseMoveEvent("<<event<<"), m_istate="<< static_cast<ItemDelegate::InteractionState>( m_istate );
00518     QPointF pos = event->pos() - m_presspos;
00519     switch( m_istate ) {
00520     case ItemDelegate::State_ExtendLeft:
00521     case ItemDelegate::State_ExtendRight:
00522     case ItemDelegate::State_Move:
00523         // Check for constraint drag
00524       if ( qAbs( m_pressscenepos.x()-event->scenePos().x() ) < 10.
00525            && qAbs( m_pressscenepos.y()-event->scenePos().y() ) > 5. ) {
00526             m_istate = ItemDelegate::State_DragConstraint;
00527             m_dragline = new QGraphicsLineItem( this );
00528             m_dragline->setPen( QPen( Qt::DashLine ) );
00529             m_dragline->setLine(QLineF( rect().center(), event->pos() ));
00530             scene()->setDragSource( this );
00531             break;
00532         }
00533 
00534         scene()->selectionModel()->setCurrentIndex( index(), QItemSelectionModel::Current );
00535         updateItemFromMouse(event->scenePos());
00536         //BASE::mouseMoveEvent(event);
00537         break;
00538     case ItemDelegate::State_DragConstraint: {
00539         QLineF line = m_dragline->line();
00540         m_dragline->setLine( QLineF( line.p1(), event->pos() ) );
00541         //QGraphicsItem* item = scene()->itemAt( event->scenePos() );
00542         break;
00543     }
00544     }
00545 }