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