File indexing completed on 2024-05-12 04:20:41
0001 /* 0002 * SPDX-FileCopyrightText: 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. 0003 * 0004 * This file is part of the KGantt library. 0005 * 0006 * SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "kganttgraphicsitem.h" 0010 #include "kganttgraphicsscene.h" 0011 #include "kganttgraphicsview.h" 0012 #include "kganttitemdelegate.h" 0013 #include "kganttconstraintgraphicsitem.h" 0014 #include "kganttconstraintmodel.h" 0015 #include "kganttconstraint.h" 0016 #include "kganttabstractgrid.h" 0017 #include "kganttabstractrowcontroller.h" 0018 0019 #include <cassert> 0020 #include <cmath> 0021 #include <algorithm> 0022 #include <iterator> 0023 0024 #include <QPainter> 0025 #include <QAbstractItemModel> 0026 #include <QAbstractProxyModel> 0027 #include <QGraphicsSceneMouseEvent> 0028 #include <QGraphicsLineItem> 0029 #include <QApplication> 0030 0031 #include <QDebug> 0032 0033 0034 0035 using namespace KGantt; 0036 0037 typedef QGraphicsItem BASE; 0038 0039 namespace { 0040 class Updater { 0041 bool *u_ptr; 0042 bool oldval; 0043 public: 0044 Updater( bool* u ) : u_ptr( u ), oldval( *u ) { 0045 *u=true; 0046 } 0047 ~Updater() { 0048 *u_ptr = oldval; 0049 } 0050 }; 0051 } 0052 0053 GraphicsItem::GraphicsItem( QGraphicsItem* parent, GraphicsScene* scene ) 0054 : BASE( parent ), m_isupdating( false ) 0055 { 0056 if ( scene ) 0057 scene->addItem( this ); 0058 init(); 0059 } 0060 0061 GraphicsItem::GraphicsItem( const QModelIndex& idx, QGraphicsItem* parent, 0062 GraphicsScene* scene ) 0063 : BASE( parent ), m_index( idx ), m_isupdating( false ) 0064 { 0065 init(); 0066 if ( scene ) 0067 scene->addItem( this ); 0068 } 0069 0070 GraphicsItem::~GraphicsItem() 0071 { 0072 } 0073 0074 void GraphicsItem::init() 0075 { 0076 setCacheMode( QGraphicsItem::DeviceCoordinateCache ); 0077 setFlags( ItemIsMovable|ItemIsSelectable|ItemIsFocusable ); 0078 setAcceptHoverEvents( true ); 0079 setHandlesChildEvents( true ); 0080 setZValue( 100. ); 0081 m_dragline = nullptr; 0082 } 0083 0084 int GraphicsItem::type() const 0085 { 0086 return Type; 0087 } 0088 0089 StyleOptionGanttItem GraphicsItem::getStyleOption() const 0090 { 0091 StyleOptionGanttItem opt; 0092 if (!m_index.isValid()) { 0093 // TODO: find out why we get invalid indexes 0094 //qDebug()<<"GraphicsItem::getStyleOption: Invalid index"; 0095 return opt; 0096 } 0097 opt.palette = QApplication::palette(); 0098 opt.itemRect = rect(); 0099 opt.boundingRect = boundingRect(); 0100 QVariant tp = m_index.model()->data( m_index, TextPositionRole ); 0101 if (tp.isValid()) { 0102 opt.displayPosition = static_cast<StyleOptionGanttItem::Position>(tp.toInt()); 0103 } else { 0104 #if 0 0105 qDebug() << "Item" << m_index.model()->data( m_index, Qt::DisplayRole ).toString() 0106 << ", ends="<<m_endConstraints.size() << ", starts="<<m_startConstraints.size(); 0107 #endif 0108 opt.displayPosition = m_endConstraints.size()<m_startConstraints.size()?StyleOptionGanttItem::Left:StyleOptionGanttItem::Right; 0109 #if 0 0110 qDebug() << "choosing" << opt.displayPosition; 0111 #endif 0112 } 0113 QVariant da = m_index.model()->data( m_index, Qt::TextAlignmentRole ); 0114 if ( da.isValid() ) { 0115 opt.displayAlignment = static_cast< Qt::Alignment >( da.toInt() ); 0116 } else { 0117 switch ( opt.displayPosition ) { 0118 case StyleOptionGanttItem::Left: opt.displayAlignment = Qt::AlignLeft|Qt::AlignVCenter; break; 0119 case StyleOptionGanttItem::Right: opt.displayAlignment = Qt::AlignRight|Qt::AlignVCenter; break; 0120 case StyleOptionGanttItem::Hidden: // fall through 0121 case StyleOptionGanttItem::Center: opt.displayAlignment = Qt::AlignCenter; break; 0122 } 0123 } 0124 opt.grid = const_cast<AbstractGrid*>(scene()->getGrid()); 0125 opt.text = m_index.model()->data( m_index, Qt::DisplayRole ).toString(); 0126 if ( isEnabled() ) opt.state |= QStyle::State_Enabled; 0127 if ( isSelected() ) opt.state |= QStyle::State_Selected; 0128 if ( hasFocus() ) opt.state |= QStyle::State_HasFocus; 0129 return opt; 0130 } 0131 0132 GraphicsScene* GraphicsItem::scene() const 0133 { 0134 return qobject_cast<GraphicsScene*>( QGraphicsItem::scene() ); 0135 } 0136 0137 void GraphicsItem::setRect( const QRectF& r ) 0138 { 0139 #if 0 0140 qDebug() << "GraphicsItem::setRect("<<r<<"), txt="<<m_index.model()->data( m_index, Qt::DisplayRole ).toString(); 0141 if ( m_index.model()->data( m_index, Qt::DisplayRole ).toString() == QLatin1String("Code Freeze" ) ) { 0142 qDebug() << "gotcha"; 0143 } 0144 #endif 0145 0146 prepareGeometryChange(); 0147 m_rect = r; 0148 updateConstraintItems(); 0149 update(); 0150 } 0151 0152 void GraphicsItem::setBoundingRect( const QRectF& r ) 0153 { 0154 prepareGeometryChange(); 0155 m_boundingrect = r; 0156 update(); 0157 } 0158 0159 bool GraphicsItem::isEditable() const 0160 { 0161 return !scene()->isReadOnly() && m_index.isValid() && m_index.model()->flags( m_index ) & Qt::ItemIsEditable; 0162 } 0163 0164 void GraphicsItem::paint( QPainter* painter, const QStyleOptionGraphicsItem* option, 0165 QWidget* widget ) 0166 { 0167 Q_UNUSED( widget ); 0168 if ( boundingRect().isValid() && scene() ) { 0169 StyleOptionGanttItem opt = getStyleOption(); 0170 *static_cast<QStyleOption*>(&opt) = *static_cast<const QStyleOption*>( option ); 0171 //opt.fontMetrics = painter->fontMetrics(); 0172 if (widget) { 0173 opt.palette = widget->palette(); 0174 } else { 0175 opt.palette = QApplication::palette(); 0176 } 0177 scene()->itemDelegate()->paintGanttItem( painter, opt, index() ); 0178 } 0179 } 0180 0181 void GraphicsItem::setIndex( const QPersistentModelIndex& idx ) 0182 { 0183 m_index=idx; 0184 update(); 0185 } 0186 0187 QString GraphicsItem::ganttToolTip() const 0188 { 0189 return scene()->itemDelegate()->toolTip( index() ); 0190 } 0191 0192 QRectF GraphicsItem::boundingRect() const 0193 { 0194 return m_boundingrect; 0195 } 0196 0197 QPointF GraphicsItem::startConnector( int relationType ) const 0198 { 0199 switch ( relationType ) { 0200 case Constraint::StartStart: 0201 case Constraint::StartFinish: 0202 return mapToScene( m_rect.left(), m_rect.top()+m_rect.height()/2. ); 0203 default: 0204 break; 0205 } 0206 return mapToScene( m_rect.right(), m_rect.top()+m_rect.height()/2. ); 0207 } 0208 0209 QPointF GraphicsItem::endConnector( int relationType ) const 0210 { 0211 switch ( relationType ) { 0212 case Constraint::FinishFinish: 0213 case Constraint::StartFinish: 0214 return mapToScene( m_rect.right(), m_rect.top()+m_rect.height()/2. ); 0215 default: 0216 break; 0217 } 0218 return mapToScene( m_rect.left(), m_rect.top()+m_rect.height()/2. ); 0219 } 0220 0221 0222 void GraphicsItem::constraintsChanged() 0223 { 0224 if ( !scene() || !scene()->itemDelegate() ) return; 0225 const Span bs = scene()->itemDelegate()->itemBoundingSpan( getStyleOption(), index() ); 0226 const QRectF br = boundingRect(); 0227 setBoundingRect( QRectF( bs.start(), 0., bs.length(), br.height() ) ); 0228 } 0229 0230 void GraphicsItem::addStartConstraint( ConstraintGraphicsItem* item ) 0231 { 0232 assert( item ); 0233 m_startConstraints << item; 0234 item->setStart( startConnector( item->constraint().relationType() ) ); 0235 constraintsChanged(); 0236 } 0237 0238 void GraphicsItem::addEndConstraint( ConstraintGraphicsItem* item ) 0239 { 0240 assert( item ); 0241 m_endConstraints << item; 0242 item->setEnd( endConnector( item->constraint().relationType() ) ); 0243 constraintsChanged(); 0244 } 0245 0246 void GraphicsItem::removeStartConstraint( ConstraintGraphicsItem* item ) 0247 { 0248 assert( item ); 0249 m_startConstraints.removeAll( item ); 0250 constraintsChanged(); 0251 } 0252 0253 void GraphicsItem::removeEndConstraint( ConstraintGraphicsItem* item ) 0254 { 0255 assert( item ); 0256 m_endConstraints.removeAll( item ); 0257 constraintsChanged(); 0258 } 0259 0260 void GraphicsItem::updateConstraintItems() 0261 { 0262 for ( ConstraintGraphicsItem* item : qAsConst(m_startConstraints) ) { 0263 QPointF s = startConnector( item->constraint().relationType() ); 0264 item->setStart( s ); 0265 } 0266 for ( ConstraintGraphicsItem* item : qAsConst(m_endConstraints) ) { 0267 QPointF e = endConnector( item->constraint().relationType() ); 0268 item->setEnd( e ); 0269 } 0270 } 0271 0272 void GraphicsItem::updateItem( const Span& rowGeometry, const QPersistentModelIndex& idx ) 0273 { 0274 //qDebug() << "GraphicsItem::updateItem("<<rowGeometry<<idx<<")"; 0275 Updater updater( &m_isupdating ); 0276 if ( !idx.isValid() || idx.data( ItemTypeRole )==TypeMulti ) { 0277 setRect( QRectF() ); 0278 hide(); 0279 return; 0280 } 0281 0282 const Span s = scene()->getGrid()->mapToChart( static_cast<const QModelIndex&>(idx) ); 0283 setPos( QPointF( s.start(), rowGeometry.start() ) ); 0284 setRect( QRectF( 0., 0., s.length(), rowGeometry.length() ) ); 0285 setIndex( idx ); 0286 const Span bs = scene()->itemDelegate()->itemBoundingSpan( getStyleOption(), index() ); 0287 //qDebug() << "boundingSpan for" << getStyleOption().text << rect() << "is" << bs; 0288 setBoundingRect( QRectF( bs.start(), 0., bs.length(), rowGeometry.length() ) ); 0289 const int maxh = scene()->rowController()->maximumItemHeight(); 0290 if ( maxh < rowGeometry.length() ) { 0291 QRectF r = rect(); 0292 const Qt::Alignment align = getStyleOption().displayAlignment; 0293 if ( align & Qt::AlignTop ) { 0294 // Do nothing 0295 } else if ( align & Qt::AlignBottom ) { 0296 r.setY( rowGeometry.length()-maxh ); 0297 } else { 0298 // Center 0299 r.setY( ( rowGeometry.length()-maxh ) / 2. ); 0300 } 0301 r.setHeight( maxh ); 0302 setRect( r ); 0303 } 0304 0305 //scene()->setSceneRect( scene()->sceneRect().united( mapToScene( boundingRect() ).boundingRect() ) ); 0306 //updateConstraintItems(); 0307 } 0308 0309 QVariant GraphicsItem::itemChange( GraphicsItemChange change, const QVariant& value ) 0310 { 0311 if ( !isUpdating() && change==ItemPositionChange && scene() ) { 0312 QPointF newPos=value.toPointF(); 0313 if ( isEditable() ) { 0314 newPos.setY( pos().y() ); 0315 return newPos; 0316 } else { 0317 return pos(); 0318 } 0319 } else if ( change==QGraphicsItem::ItemSelectedChange ) { 0320 if ( index().isValid() && !( index().model()->flags( index() ) & Qt::ItemIsSelectable ) ) { 0321 // Reject selection attempt 0322 return QVariant::fromValue( false ); 0323 } 0324 } 0325 0326 return QGraphicsItem::itemChange( change, value ); 0327 } 0328 0329 void GraphicsItem::focusInEvent( QFocusEvent* event ) 0330 { 0331 Q_UNUSED( event ); 0332 } 0333 0334 void GraphicsItem::updateModel() 0335 { 0336 //qDebug() << "GraphicsItem::updateModel()"; 0337 if ( isEditable() ) { 0338 QAbstractItemModel* model = const_cast<QAbstractItemModel*>( index().model() ); 0339 #if !defined(NDEBUG) 0340 ConstraintModel* cmodel = scene()->constraintModel(); 0341 #endif 0342 assert( model ); 0343 assert( cmodel ); 0344 if ( model ) { 0345 //ItemType typ = static_cast<ItemType>( model->data( index(), 0346 // ItemTypeRole ).toInt() ); 0347 QList<Constraint> constraints; 0348 for ( QList<ConstraintGraphicsItem*>::iterator it1 = m_startConstraints.begin() ; 0349 it1 != m_startConstraints.end() ; 0350 ++it1 ) 0351 constraints.push_back((*it1)->proxyConstraint()); 0352 for ( QList<ConstraintGraphicsItem*>::iterator it2 = m_endConstraints.begin() ; 0353 it2 != m_endConstraints.end() ; 0354 ++it2 ) 0355 constraints.push_back((*it2)->proxyConstraint()); 0356 if ( scene()->getGrid()->mapFromChart( Span( scenePos().x(), rect().width() ), 0357 index(), 0358 constraints ) ) { 0359 scene()->updateRow( index().parent() ); 0360 } 0361 } 0362 } 0363 } 0364 0365 void GraphicsItem::hoverMoveEvent( QGraphicsSceneHoverEvent* event ) 0366 { 0367 if ( !isEditable() ) return; 0368 StyleOptionGanttItem opt = getStyleOption(); 0369 ItemDelegate::InteractionState istate = scene()->itemDelegate()->interactionStateFor( event->pos(), opt, index() ); 0370 switch ( istate ) { 0371 case ItemDelegate::State_ExtendLeft: 0372 #ifndef QT_NO_CURSOR 0373 setCursor( Qt::SizeHorCursor ); 0374 #endif 0375 scene()->itemEntered( index() ); 0376 break; 0377 case ItemDelegate::State_ExtendRight: 0378 #ifndef QT_NO_CURSOR 0379 setCursor( Qt::SizeHorCursor ); 0380 #endif 0381 scene()->itemEntered( index() ); 0382 break; 0383 case ItemDelegate::State_Move: 0384 #ifndef QT_NO_CURSOR 0385 setCursor( Qt::SplitHCursor ); 0386 #endif 0387 scene()->itemEntered( index() ); 0388 break; 0389 default: 0390 #ifndef QT_NO_CURSOR 0391 unsetCursor(); 0392 #endif 0393 break; 0394 }; 0395 } 0396 0397 void GraphicsItem::hoverLeaveEvent( QGraphicsSceneHoverEvent* ) 0398 { 0399 #ifndef QT_NO_CURSOR 0400 unsetCursor(); 0401 #endif 0402 } 0403 0404 void GraphicsItem::mousePressEvent( QGraphicsSceneMouseEvent* event ) 0405 { 0406 //qDebug() << "GraphicsItem::mousePressEvent("<<event<<")"; 0407 StyleOptionGanttItem opt = getStyleOption(); 0408 int istate = scene()->itemDelegate()->interactionStateFor( event->pos(), opt, index() ); 0409 // If State_None is returned by interactionStateFor(), we ignore this event so that 0410 // it can get forwarded to another item that's below this one. Needed, for example, 0411 // to allow items to be moved that are placed below the label of another item. 0412 if ( istate != ItemDelegate::State_None ) { 0413 m_istate = istate; 0414 m_presspos = event->pos(); 0415 m_pressscenepos = event->scenePos(); 0416 0417 scene()->itemPressed( index(), event ); 0418 0419 switch ( m_istate ) { 0420 case ItemDelegate::State_ExtendLeft: 0421 case ItemDelegate::State_ExtendRight: 0422 default: /* State_Move */ 0423 if (!(flags() & ItemIsMovable)) { 0424 event->ignore(); 0425 } 0426 break; 0427 } 0428 } else { 0429 event->ignore(); 0430 } 0431 } 0432 0433 void GraphicsItem::mouseReleaseEvent( QGraphicsSceneMouseEvent* event ) 0434 { 0435 //qDebug() << "GraphicsItem::mouseReleaseEvent("<<event << ")"; 0436 if ( !m_presspos.isNull() ) { 0437 scene()->itemClicked( index() ); 0438 } 0439 delete m_dragline; m_dragline = nullptr; 0440 if ( scene()->dragSource() ) { 0441 // Create a new constraint 0442 GraphicsItem* other = qgraphicsitem_cast<GraphicsItem*>( scene()->itemAt( event->scenePos(), QTransform() ) ); 0443 if ( other && scene()->dragSource()!=other && 0444 other->index().data(KGantt::ItemTypeRole) == KGantt::TypeEvent ) 0445 { 0446 // The code below fixes bug KDCH-696. 0447 // Modified the code to add constraint even if the user drags and drops 0448 // constraint on left part of the TypeEvent symbol(i.e diamond symbol) 0449 QRectF itemRect = other->rect().adjusted(-other->rect().height()/2.0, 0, 0, 0 ); 0450 if ( other->mapToScene( itemRect ).boundingRect().contains( event->scenePos() )) 0451 { 0452 GraphicsView* view = qobject_cast<GraphicsView*>( event->widget()->parentWidget() ); 0453 if ( view ) { 0454 view->addConstraint( scene()->summaryHandlingModel()->mapToSource( scene()->dragSource()->index() ), 0455 scene()->summaryHandlingModel()->mapToSource( other->index() ), event->modifiers() ); 0456 } 0457 } 0458 } 0459 else 0460 { 0461 if ( other && scene()->dragSource()!=other && 0462 other->mapToScene( other->rect() ).boundingRect().contains( event->scenePos() )) { 0463 GraphicsView* view = qobject_cast<GraphicsView*>( event->widget()->parentWidget() ); 0464 if ( view ) { 0465 view->addConstraint( scene()->summaryHandlingModel()->mapToSource( scene()->dragSource()->index() ), 0466 scene()->summaryHandlingModel()->mapToSource( other->index() ), event->modifiers() ); 0467 } 0468 } 0469 } 0470 0471 scene()->setDragSource( nullptr ); 0472 //scene()->update(); 0473 } else { 0474 if ( isEditable() ) { 0475 updateItemFromMouse(event->scenePos()); 0476 0477 // It is important to set m_presspos to null here because 0478 // when the sceneRect updates because we move the item 0479 // a MouseMoveEvent will be delivered, and we have to 0480 // protect against that 0481 m_presspos = QPointF(); 0482 updateModel(); 0483 // without this command we sometimes get a white area at the left side of a task item 0484 // after we moved that item right-ways into a grey weekend section of the scene: 0485 scene()->update(); 0486 } 0487 } 0488 0489 m_presspos = QPointF(); 0490 } 0491 0492 void GraphicsItem::mouseDoubleClickEvent( QGraphicsSceneMouseEvent* event ) 0493 { 0494 const int typ = static_cast<ItemType>( index().model()->data( index(), ItemTypeRole ).toInt() ); 0495 StyleOptionGanttItem opt = getStyleOption(); 0496 ItemDelegate::InteractionState istate = scene()->itemDelegate()->interactionStateFor( event->pos(), opt, index() ); 0497 if ( (istate != ItemDelegate::State_None) || (typ == TypeSummary)) { 0498 scene()->itemDoubleClicked( index() ); 0499 } 0500 BASE::mouseDoubleClickEvent( event ); 0501 } 0502 0503 void GraphicsItem::updateItemFromMouse( const QPointF& scenepos ) 0504 { 0505 //qDebug() << "GraphicsItem::updateItemFromMouse("<<scenepos<<")"; 0506 const QPointF p = scenepos - m_presspos; 0507 QRectF r = rect(); 0508 QRectF br = boundingRect(); 0509 switch ( m_istate ) { 0510 case ItemDelegate::State_Move: 0511 setPos( p.x(), pos().y() ); 0512 break; 0513 case ItemDelegate::State_ExtendLeft: { 0514 const qreal brr = br.right(); 0515 const qreal rr = r.right(); 0516 const qreal delta = pos().x()-p.x(); 0517 setPos( p.x(), QGraphicsItem::pos().y() ); 0518 br.setRight( brr+delta ); 0519 r.setRight( rr+delta ); 0520 break; 0521 } 0522 case ItemDelegate::State_ExtendRight: { 0523 const qreal rr = r.right(); 0524 r.setRight( scenepos.x()-pos().x() ); 0525 br.setWidth( br.width() + r.right()-rr ); 0526 break; 0527 } 0528 default: return; 0529 } 0530 setRect( r ); 0531 setBoundingRect( br ); 0532 } 0533 0534 void GraphicsItem::mouseMoveEvent( QGraphicsSceneMouseEvent* event ) 0535 { 0536 if ( !isEditable() ) return; 0537 if ( m_presspos.isNull() ) return; 0538 0539 //qDebug() << "GraphicsItem::mouseMoveEvent("<<event<<"), m_istate="<< static_cast<ItemDelegate::InteractionState>( m_istate ); 0540 switch ( m_istate ) { 0541 case ItemDelegate::State_ExtendLeft: 0542 case ItemDelegate::State_ExtendRight: 0543 case ItemDelegate::State_Move: 0544 // Check for constraint drag 0545 if ( qAbs( m_pressscenepos.x()-event->scenePos().x() ) < 10. 0546 && qAbs( m_pressscenepos.y()-event->scenePos().y() ) > 5. ) { 0547 m_istate = ItemDelegate::State_DragConstraint; 0548 m_dragline = new QGraphicsLineItem( this ); 0549 m_dragline->setPen( QPen( Qt::DashLine ) ); 0550 m_dragline->setLine(QLineF( rect().center(), event->pos() )); 0551 scene()->setDragSource( this ); 0552 break; 0553 } 0554 0555 updateItemFromMouse(event->scenePos()); 0556 //BASE::mouseMoveEvent(event); 0557 break; 0558 case ItemDelegate::State_DragConstraint: { 0559 QLineF line = m_dragline->line(); 0560 m_dragline->setLine( QLineF( line.p1(), event->pos() ) ); 0561 //QGraphicsItem* item = scene()->itemAt( event->scenePos() ); 0562 break; 0563 } 0564 } 0565 }