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 }