KD Chart 2
[rev.2.5]
|
00001 /**************************************************************************** 00002 ** Copyright (C) 2001-2012 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.txt 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 #ifndef QT_NO_CURSOR 00384 setCursor( Qt::SizeHorCursor ); 00385 #endif 00386 scene()->itemEntered( index() ); 00387 break; 00388 case ItemDelegate::State_ExtendRight: 00389 #ifndef QT_NO_CURSOR 00390 setCursor( Qt::SizeHorCursor ); 00391 #endif 00392 scene()->itemEntered( index() ); 00393 break; 00394 case ItemDelegate::State_Move: 00395 #ifndef QT_NO_CURSOR 00396 setCursor( Qt::SplitHCursor ); 00397 #endif 00398 scene()->itemEntered( index() ); 00399 break; 00400 default: 00401 #ifndef QT_NO_CURSOR 00402 unsetCursor(); 00403 #endif 00404 break; 00405 }; 00406 } 00407 00408 void GraphicsItem::hoverLeaveEvent( QGraphicsSceneHoverEvent* ) 00409 { 00410 #ifndef QT_NO_CURSOR 00411 unsetCursor(); 00412 #endif 00413 } 00414 00415 void GraphicsItem::mousePressEvent( QGraphicsSceneMouseEvent* event ) 00416 { 00417 //qDebug() << "GraphicsItem::mousePressEvent("<<event<<")"; 00418 StyleOptionGanttItem opt = getStyleOption(); 00419 int istate = scene()->itemDelegate()->interactionStateFor( event->pos(), opt, index() ); 00420 // If State_None is returned by interactionStateFor(), we ignore this event so that 00421 // it can get forwarded to another item that's below this one. Needed, for example, 00422 // to allow items to be moved that are placed below the label of another item. 00423 if ( istate != ItemDelegate::State_None ) { 00424 m_istate = istate; 00425 m_presspos = event->pos(); 00426 m_pressscenepos = event->scenePos(); 00427 scene()->itemPressed( index() ); 00428 00429 switch( m_istate ) { 00430 case ItemDelegate::State_ExtendLeft: 00431 case ItemDelegate::State_ExtendRight: 00432 default: /* State_Move */ 00433 BASE::mousePressEvent( event ); 00434 break; 00435 } 00436 } else { 00437 event->ignore(); 00438 } 00439 } 00440 00441 void GraphicsItem::mouseReleaseEvent( QGraphicsSceneMouseEvent* event ) 00442 { 00443 //qDebug() << "GraphicsItem::mouseReleaseEvent("<<event << ")"; 00444 if ( !m_presspos.isNull() ) { 00445 scene()->itemClicked( index() ); 00446 } 00447 delete m_dragline; m_dragline = 0; 00448 if ( scene()->dragSource() ) { 00449 // Create a new constraint 00450 GraphicsItem* other = qgraphicsitem_cast<GraphicsItem*>( scene()->itemAt( event->scenePos() ) ); 00451 if ( other && scene()->dragSource()!=other && 00452 other->index().data(KDGantt::ItemTypeRole) == KDGantt::TypeEvent ) 00453 { 00454 // The code below fixes bug KDCH-696. 00455 // Modified the code to add constraint even if the user drags and drops 00456 // constraint on left part of the TypeEvent symbol(i.e diamond symbol) 00457 QRectF itemRect = other->rect().adjusted(-other->rect().height()/2.0, 0, 0, 0 ); 00458 if ( other->mapToScene( itemRect ).boundingRect().contains( event->scenePos() )) 00459 { 00460 GraphicsView* view = qobject_cast<GraphicsView*>( event->widget()->parentWidget() ); 00461 if ( view ) { 00462 view->addConstraint( scene()->summaryHandlingModel()->mapToSource( scene()->dragSource()->index() ), 00463 scene()->summaryHandlingModel()->mapToSource( other->index() ), event->modifiers() ); 00464 } 00465 } 00466 } 00467 else 00468 { 00469 if ( other && scene()->dragSource()!=other && 00470 other->mapToScene( other->rect() ).boundingRect().contains( event->scenePos() )) { 00471 GraphicsView* view = qobject_cast<GraphicsView*>( event->widget()->parentWidget() ); 00472 if ( view ) { 00473 view->addConstraint( scene()->summaryHandlingModel()->mapToSource( scene()->dragSource()->index() ), 00474 scene()->summaryHandlingModel()->mapToSource( other->index() ), event->modifiers() ); 00475 } 00476 } 00477 } 00478 00479 scene()->setDragSource( 0 ); 00480 //scene()->update(); 00481 } else { 00482 if ( isEditable() ) { 00483 updateItemFromMouse(event->scenePos()); 00484 00485 // It is important to set m_presspos to null here because 00486 // when the sceneRect updates because we move the item 00487 // a MouseMoveEvent will be delivered, and we have to 00488 // protect against that 00489 m_presspos = QPointF(); 00490 updateModel(); 00491 // without this command we sometimes get a white area at the left side of a task item 00492 // after we moved that item right-ways into a grey weekend section of the scene: 00493 scene()->update(); 00494 } 00495 } 00496 00497 m_presspos = QPointF(); 00498 BASE::mouseReleaseEvent( event ); 00499 } 00500 00501 void GraphicsItem::mouseDoubleClickEvent( QGraphicsSceneMouseEvent* event ) 00502 { 00503 const int typ = static_cast<ItemType>( index().model()->data( index(), ItemTypeRole ).toInt() ); 00504 StyleOptionGanttItem opt = getStyleOption(); 00505 ItemDelegate::InteractionState istate = scene()->itemDelegate()->interactionStateFor( event->pos(), opt, index() ); 00506 if ( (istate != ItemDelegate::State_None) || (typ == TypeSummary)) { 00507 scene()->itemDoubleClicked( index() ); 00508 } 00509 BASE::mouseDoubleClickEvent( event ); 00510 } 00511 00512 void GraphicsItem::updateItemFromMouse( const QPointF& scenepos ) 00513 { 00514 //qDebug() << "GraphicsItem::updateItemFromMouse("<<scenepos<<")"; 00515 const QPointF p = scenepos - m_presspos; 00516 QRectF r = rect(); 00517 QRectF br = boundingRect(); 00518 switch( m_istate ) { 00519 case ItemDelegate::State_Move: 00520 setPos( p.x(), pos().y() ); 00521 break; 00522 case ItemDelegate::State_ExtendLeft: { 00523 const qreal brr = br.right(); 00524 const qreal rr = r.right(); 00525 const qreal delta = pos().x()-p.x(); 00526 setPos( p.x(), QGraphicsItem::pos().y() ); 00527 br.setRight( brr+delta ); 00528 r.setRight( rr+delta ); 00529 break; 00530 } 00531 case ItemDelegate::State_ExtendRight: { 00532 const qreal rr = r.right(); 00533 r.setRight( scenepos.x()-pos().x() ); 00534 br.setWidth( br.width() + r.right()-rr ); 00535 break; 00536 } 00537 default: return; 00538 } 00539 setRect( r ); 00540 setBoundingRect( br ); 00541 } 00542 00543 void GraphicsItem::mouseMoveEvent( QGraphicsSceneMouseEvent* event ) 00544 { 00545 if ( !isEditable() ) return; 00546 if ( m_presspos.isNull() ) return; 00547 00548 //qDebug() << "GraphicsItem::mouseMoveEvent("<<event<<"), m_istate="<< static_cast<ItemDelegate::InteractionState>( m_istate ); 00549 switch( m_istate ) { 00550 case ItemDelegate::State_ExtendLeft: 00551 case ItemDelegate::State_ExtendRight: 00552 case ItemDelegate::State_Move: 00553 // Check for constraint drag 00554 if ( qAbs( m_pressscenepos.x()-event->scenePos().x() ) < 10. 00555 && qAbs( m_pressscenepos.y()-event->scenePos().y() ) > 5. ) { 00556 m_istate = ItemDelegate::State_DragConstraint; 00557 m_dragline = new QGraphicsLineItem( this ); 00558 m_dragline->setPen( QPen( Qt::DashLine ) ); 00559 m_dragline->setLine(QLineF( rect().center(), event->pos() )); 00560 scene()->setDragSource( this ); 00561 break; 00562 } 00563 00564 scene()->selectionModel()->setCurrentIndex( index(), QItemSelectionModel::Current ); 00565 updateItemFromMouse(event->scenePos()); 00566 //BASE::mouseMoveEvent(event); 00567 break; 00568 case ItemDelegate::State_DragConstraint: { 00569 QLineF line = m_dragline->line(); 00570 m_dragline->setLine( QLineF( line.p1(), event->pos() ) ); 00571 //QGraphicsItem* item = scene()->itemAt( event->scenePos() ); 00572 break; 00573 } 00574 } 00575 }