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 }