1 /*
2  * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB.  All rights reserved.
3  *
4  * This file is part of the KGantt library.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of
9  * the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
18  */
19 
20 #include "kganttgraphicsitem.h"
21 #include "kganttgraphicsscene.h"
22 #include "kganttgraphicsview.h"
23 #include "kganttitemdelegate.h"
24 #include "kganttconstraintgraphicsitem.h"
25 #include "kganttconstraintmodel.h"
26 #include "kganttconstraint.h"
27 #include "kganttabstractgrid.h"
28 #include "kganttabstractrowcontroller.h"
29 
30 #include <cassert>
31 #include <cmath>
32 #include <algorithm>
33 #include <iterator>
34 
35 #include <QPainter>
36 #include <QAbstractItemModel>
37 #include <QAbstractProxyModel>
38 #include <QGraphicsSceneMouseEvent>
39 #include <QGraphicsLineItem>
40 #include <QApplication>
41 
42 #include <QDebug>
43 
44 
45 
46 using namespace KGantt;
47 
48 typedef QGraphicsItem BASE;
49 
50 namespace {
51     class Updater {
52         bool *u_ptr;
53         bool oldval;
54     public:
Updater(bool * u)55         Updater( bool* u ) : u_ptr( u ), oldval( *u ) {
56             *u=true;
57         }
~Updater()58         ~Updater() {
59             *u_ptr = oldval;
60         }
61     };
62 }
63 
GraphicsItem(QGraphicsItem * parent,GraphicsScene * scene)64 GraphicsItem::GraphicsItem( QGraphicsItem* parent, GraphicsScene* scene )
65     : BASE( parent ),  m_isupdating( false )
66 {
67   if ( scene )
68     scene->addItem( this );
69   init();
70 }
71 
GraphicsItem(const QModelIndex & idx,QGraphicsItem * parent,GraphicsScene * scene)72 GraphicsItem::GraphicsItem( const QModelIndex& idx, QGraphicsItem* parent,
73                                             GraphicsScene* scene )
74     : BASE( parent ),  m_index( idx ), m_isupdating( false )
75 {
76   init();
77   if ( scene )
78     scene->addItem( this );
79 }
80 
~GraphicsItem()81 GraphicsItem::~GraphicsItem()
82 {
83 }
84 
init()85 void GraphicsItem::init()
86 {
87     setCacheMode( QGraphicsItem::DeviceCoordinateCache );
88     setFlags( ItemIsMovable|ItemIsSelectable|ItemIsFocusable );
89     setAcceptHoverEvents( true );
90     setHandlesChildEvents( true );
91     setZValue( 100. );
92     m_dragline = nullptr;
93 }
94 
type() const95 int GraphicsItem::type() const
96 {
97     return Type;
98 }
99 
getStyleOption() const100 StyleOptionGanttItem GraphicsItem::getStyleOption() const
101 {
102     StyleOptionGanttItem opt;
103     if (!m_index.isValid()) {
104         // TODO: find out why we get invalid indexes
105         //qDebug()<<"GraphicsItem::getStyleOption: Invalid index";
106         return opt;
107     }
108     opt.palette = QApplication::palette();
109     opt.itemRect = rect();
110     opt.boundingRect = boundingRect();
111     QVariant tp = m_index.model()->data( m_index, TextPositionRole );
112     if (tp.isValid()) {
113         opt.displayPosition = static_cast<StyleOptionGanttItem::Position>(tp.toInt());
114     } else {
115 #if 0
116         qDebug() << "Item" << m_index.model()->data( m_index, Qt::DisplayRole ).toString()
117                  << ", ends="<<m_endConstraints.size() << ", starts="<<m_startConstraints.size();
118 #endif
119         opt.displayPosition = m_endConstraints.size()<m_startConstraints.size()?StyleOptionGanttItem::Left:StyleOptionGanttItem::Right;
120 #if 0
121         qDebug() << "choosing" << opt.displayPosition;
122 #endif
123     }
124     QVariant da = m_index.model()->data( m_index, Qt::TextAlignmentRole );
125     if ( da.isValid() ) {
126         opt.displayAlignment = static_cast< Qt::Alignment >( da.toInt() );
127     } else {
128         switch ( opt.displayPosition ) {
129         case StyleOptionGanttItem::Left: opt.displayAlignment = Qt::AlignLeft|Qt::AlignVCenter; break;
130         case StyleOptionGanttItem::Right: opt.displayAlignment = Qt::AlignRight|Qt::AlignVCenter; break;
131         case StyleOptionGanttItem::Hidden: // fall through
132         case StyleOptionGanttItem::Center: opt.displayAlignment = Qt::AlignCenter; break;
133         }
134     }
135     opt.grid = const_cast<AbstractGrid*>(scene()->getGrid());
136     opt.text = m_index.model()->data( m_index, Qt::DisplayRole ).toString();
137     if ( isEnabled() ) opt.state  |= QStyle::State_Enabled;
138     if ( isSelected() ) opt.state |= QStyle::State_Selected;
139     if ( hasFocus() ) opt.state   |= QStyle::State_HasFocus;
140     return opt;
141 }
142 
scene() const143 GraphicsScene* GraphicsItem::scene() const
144 {
145     return qobject_cast<GraphicsScene*>( QGraphicsItem::scene() );
146 }
147 
setRect(const QRectF & r)148 void GraphicsItem::setRect( const QRectF& r )
149 {
150 #if 0
151     qDebug() << "GraphicsItem::setRect("<<r<<"), txt="<<m_index.model()->data( m_index, Qt::DisplayRole ).toString();
152     if ( m_index.model()->data( m_index, Qt::DisplayRole ).toString() == QLatin1String("Code Freeze" ) ) {
153         qDebug() << "gotcha";
154     }
155 #endif
156 
157     prepareGeometryChange();
158     m_rect = r;
159     updateConstraintItems();
160     update();
161 }
162 
setBoundingRect(const QRectF & r)163 void GraphicsItem::setBoundingRect( const QRectF& r )
164 {
165     prepareGeometryChange();
166     m_boundingrect = r;
167     update();
168 }
169 
isEditable() const170 bool GraphicsItem::isEditable() const
171 {
172     return !scene()->isReadOnly() && m_index.isValid() && m_index.model()->flags( m_index ) & Qt::ItemIsEditable;
173 }
174 
paint(QPainter * painter,const QStyleOptionGraphicsItem * option,QWidget * widget)175 void GraphicsItem::paint( QPainter* painter, const QStyleOptionGraphicsItem* option,
176                                   QWidget* widget )
177 {
178     Q_UNUSED( widget );
179     if ( boundingRect().isValid() && scene() ) {
180         StyleOptionGanttItem opt = getStyleOption();
181         *static_cast<QStyleOption*>(&opt) = *static_cast<const QStyleOption*>( option );
182         //opt.fontMetrics = painter->fontMetrics();
183         if (widget) {
184             opt.palette = widget->palette();
185         } else {
186             opt.palette = QApplication::palette();
187         }
188         scene()->itemDelegate()->paintGanttItem( painter, opt, index() );
189     }
190 }
191 
setIndex(const QPersistentModelIndex & idx)192 void GraphicsItem::setIndex( const QPersistentModelIndex& idx )
193 {
194     m_index=idx;
195     update();
196 }
197 
ganttToolTip() const198 QString GraphicsItem::ganttToolTip() const
199 {
200     return scene()->itemDelegate()->toolTip( index() );
201 }
202 
boundingRect() const203 QRectF GraphicsItem::boundingRect() const
204 {
205     return m_boundingrect;
206 }
207 
startConnector(int relationType) const208 QPointF GraphicsItem::startConnector( int relationType ) const
209 {
210     switch ( relationType ) {
211         case Constraint::StartStart:
212         case Constraint::StartFinish:
213             return mapToScene( m_rect.left(), m_rect.top()+m_rect.height()/2. );
214         default:
215             break;
216     }
217     return mapToScene( m_rect.right(), m_rect.top()+m_rect.height()/2. );
218 }
219 
endConnector(int relationType) const220 QPointF GraphicsItem::endConnector( int relationType ) const
221 {
222     switch ( relationType ) {
223         case Constraint::FinishFinish:
224         case Constraint::StartFinish:
225             return mapToScene( m_rect.right(), m_rect.top()+m_rect.height()/2. );
226         default:
227             break;
228     }
229     return mapToScene( m_rect.left(), m_rect.top()+m_rect.height()/2. );
230 }
231 
232 
constraintsChanged()233 void GraphicsItem::constraintsChanged()
234 {
235     if ( !scene() || !scene()->itemDelegate() ) return;
236     const Span bs = scene()->itemDelegate()->itemBoundingSpan( getStyleOption(), index() );
237     const QRectF br = boundingRect();
238     setBoundingRect( QRectF( bs.start(), 0., bs.length(), br.height() ) );
239 }
240 
addStartConstraint(ConstraintGraphicsItem * item)241 void GraphicsItem::addStartConstraint( ConstraintGraphicsItem* item )
242 {
243     assert( item );
244     m_startConstraints << item;
245     item->setStart( startConnector( item->constraint().relationType() ) );
246     constraintsChanged();
247 }
248 
addEndConstraint(ConstraintGraphicsItem * item)249 void GraphicsItem::addEndConstraint( ConstraintGraphicsItem* item )
250 {
251     assert( item );
252     m_endConstraints << item;
253     item->setEnd( endConnector( item->constraint().relationType() ) );
254     constraintsChanged();
255 }
256 
removeStartConstraint(ConstraintGraphicsItem * item)257 void GraphicsItem::removeStartConstraint( ConstraintGraphicsItem* item )
258 {
259     assert( item );
260     m_startConstraints.removeAll( item );
261     constraintsChanged();
262 }
263 
removeEndConstraint(ConstraintGraphicsItem * item)264 void GraphicsItem::removeEndConstraint( ConstraintGraphicsItem* item )
265 {
266     assert( item );
267     m_endConstraints.removeAll( item );
268     constraintsChanged();
269 }
270 
updateConstraintItems()271 void GraphicsItem::updateConstraintItems()
272 {
273     { // Workaround for multiple definition error with MSVC6
274     Q_FOREACH( ConstraintGraphicsItem* item, m_startConstraints ) {
275         QPointF s = startConnector( item->constraint().relationType() );
276         item->setStart( s );
277     }}
278     {// Workaround for multiple definition error with MSVC6
279     Q_FOREACH( ConstraintGraphicsItem* item, m_endConstraints ) {
280         QPointF e = endConnector( item->constraint().relationType() );
281         item->setEnd( e );
282     }}
283 }
284 
updateItem(const Span & rowGeometry,const QPersistentModelIndex & idx)285 void GraphicsItem::updateItem( const Span& rowGeometry, const QPersistentModelIndex& idx )
286 {
287     //qDebug() << "GraphicsItem::updateItem("<<rowGeometry<<idx<<")";
288     Updater updater( &m_isupdating );
289     if ( !idx.isValid() || idx.data( ItemTypeRole )==TypeMulti ) {
290         setRect( QRectF() );
291         hide();
292         return;
293     }
294 
295     const Span s = scene()->getGrid()->mapToChart( static_cast<const QModelIndex&>(idx) );
296     setPos( QPointF( s.start(), rowGeometry.start() ) );
297     setRect( QRectF( 0., 0., s.length(), rowGeometry.length() ) );
298     setIndex( idx );
299     const Span bs = scene()->itemDelegate()->itemBoundingSpan( getStyleOption(), index() );
300     //qDebug() << "boundingSpan for" << getStyleOption().text << rect() << "is" << bs;
301     setBoundingRect( QRectF( bs.start(), 0., bs.length(), rowGeometry.length() ) );
302     const int maxh = scene()->rowController()->maximumItemHeight();
303     if ( maxh < rowGeometry.length() ) {
304         QRectF r = rect();
305         const Qt::Alignment align = getStyleOption().displayAlignment;
306         if ( align & Qt::AlignTop ) {
307             // Do nothing
308         } else if ( align & Qt::AlignBottom ) {
309             r.setY( rowGeometry.length()-maxh );
310         } else {
311             // Center
312             r.setY( ( rowGeometry.length()-maxh ) / 2. );
313         }
314         r.setHeight( maxh );
315         setRect( r );
316     }
317 
318     //scene()->setSceneRect( scene()->sceneRect().united( mapToScene( boundingRect() ).boundingRect() ) );
319     //updateConstraintItems();
320 }
321 
itemChange(GraphicsItemChange change,const QVariant & value)322 QVariant GraphicsItem::itemChange( GraphicsItemChange change, const QVariant& value )
323 {
324     if ( !isUpdating() && change==ItemPositionChange && scene() ) {
325         QPointF newPos=value.toPointF();
326         if ( isEditable() ) {
327             newPos.setY( pos().y() );
328             return newPos;
329         } else {
330             return pos();
331         }
332     } else if ( change==QGraphicsItem::ItemSelectedChange ) {
333         if ( index().isValid() && !( index().model()->flags( index() ) & Qt::ItemIsSelectable ) ) {
334             // Reject selection attempt
335             return QVariant::fromValue( false );
336         }
337     }
338 
339     return QGraphicsItem::itemChange( change, value );
340 }
341 
focusInEvent(QFocusEvent * event)342 void GraphicsItem::focusInEvent( QFocusEvent* event )
343 {
344     Q_UNUSED( event );
345 }
346 
updateModel()347 void GraphicsItem::updateModel()
348 {
349     //qDebug() << "GraphicsItem::updateModel()";
350     if ( isEditable() ) {
351         QAbstractItemModel* model = const_cast<QAbstractItemModel*>( index().model() );
352 #if !defined(NDEBUG)
353         ConstraintModel* cmodel = scene()->constraintModel();
354 #endif
355         assert( model );
356         assert( cmodel );
357         if ( model ) {
358             //ItemType typ = static_cast<ItemType>( model->data( index(),
359             //                                                   ItemTypeRole ).toInt() );
360             QList<Constraint> constraints;
361             for ( QList<ConstraintGraphicsItem*>::iterator it1 = m_startConstraints.begin() ;
362                  it1 != m_startConstraints.end() ;
363                  ++it1 )
364                 constraints.push_back((*it1)->proxyConstraint());
365             for ( QList<ConstraintGraphicsItem*>::iterator it2 = m_endConstraints.begin() ;
366                  it2 != m_endConstraints.end() ;
367                  ++it2 )
368                 constraints.push_back((*it2)->proxyConstraint());
369             if ( scene()->getGrid()->mapFromChart( Span( scenePos().x(), rect().width() ),
370                                                 index(),
371                                                 constraints ) ) {
372                 scene()->updateRow( index().parent() );
373             }
374         }
375     }
376 }
377 
hoverMoveEvent(QGraphicsSceneHoverEvent * event)378 void GraphicsItem::hoverMoveEvent( QGraphicsSceneHoverEvent* event )
379 {
380     if ( !isEditable() ) return;
381     StyleOptionGanttItem opt = getStyleOption();
382     ItemDelegate::InteractionState istate = scene()->itemDelegate()->interactionStateFor( event->pos(), opt, index() );
383     switch ( istate ) {
384     case ItemDelegate::State_ExtendLeft:
385 #ifndef QT_NO_CURSOR
386         setCursor( Qt::SizeHorCursor );
387 #endif
388         scene()->itemEntered( index() );
389         break;
390     case ItemDelegate::State_ExtendRight:
391 #ifndef QT_NO_CURSOR
392         setCursor( Qt::SizeHorCursor );
393 #endif
394         scene()->itemEntered( index() );
395         break;
396     case ItemDelegate::State_Move:
397 #ifndef QT_NO_CURSOR
398         setCursor( Qt::SplitHCursor );
399 #endif
400         scene()->itemEntered( index() );
401         break;
402     default:
403 #ifndef QT_NO_CURSOR
404         unsetCursor();
405 #endif
406         break;
407     };
408 }
409 
hoverLeaveEvent(QGraphicsSceneHoverEvent *)410 void GraphicsItem::hoverLeaveEvent( QGraphicsSceneHoverEvent* )
411 {
412 #ifndef QT_NO_CURSOR
413     unsetCursor();
414 #endif
415 }
416 
mousePressEvent(QGraphicsSceneMouseEvent * event)417 void GraphicsItem::mousePressEvent( QGraphicsSceneMouseEvent* event )
418 {
419     //qDebug() << "GraphicsItem::mousePressEvent("<<event<<")";
420     StyleOptionGanttItem opt = getStyleOption();
421     int istate = scene()->itemDelegate()->interactionStateFor( event->pos(), opt, index() );
422     // If State_None is returned by interactionStateFor(), we ignore this event so that
423     // it can get forwarded to another item that's below this one. Needed, for example,
424     // to allow items to be moved that are placed below the label of another item.
425     if ( istate != ItemDelegate::State_None ) {
426         m_istate = istate;
427         m_presspos = event->pos();
428         m_pressscenepos = event->scenePos();
429 
430         scene()->itemPressed( index(), event );
431 
432         switch ( m_istate ) {
433         case ItemDelegate::State_ExtendLeft:
434         case ItemDelegate::State_ExtendRight:
435         default: /* State_Move */
436             if (!(flags() & ItemIsMovable)) {
437                 event->ignore();
438             }
439             break;
440         }
441     } else {
442         event->ignore();
443     }
444 }
445 
mouseReleaseEvent(QGraphicsSceneMouseEvent * event)446 void GraphicsItem::mouseReleaseEvent( QGraphicsSceneMouseEvent* event )
447 {
448     //qDebug() << "GraphicsItem::mouseReleaseEvent("<<event << ")";
449     if ( !m_presspos.isNull() ) {
450         scene()->itemClicked( index() );
451     }
452     delete m_dragline; m_dragline = nullptr;
453     if ( scene()->dragSource() ) {
454         // Create a new constraint
455         GraphicsItem* other = qgraphicsitem_cast<GraphicsItem*>( scene()->itemAt( event->scenePos(), QTransform() ) );
456         if ( other && scene()->dragSource()!=other &&
457              other->index().data(KGantt::ItemTypeRole) == KGantt::TypeEvent )
458         {
459             // The code below fixes bug KDCH-696.
460             // Modified the code to add constraint even if the user drags and drops
461             // constraint on left part of the TypeEvent symbol(i.e diamond symbol)
462             QRectF itemRect = other->rect().adjusted(-other->rect().height()/2.0, 0, 0, 0 );
463             if ( other->mapToScene( itemRect ).boundingRect().contains( event->scenePos() ))
464             {
465                 GraphicsView* view = qobject_cast<GraphicsView*>( event->widget()->parentWidget() );
466                 if ( view ) {
467                     view->addConstraint( scene()->summaryHandlingModel()->mapToSource( scene()->dragSource()->index() ),
468                                          scene()->summaryHandlingModel()->mapToSource( other->index() ), event->modifiers() );
469                 }
470             }
471         }
472         else
473         {
474             if ( other && scene()->dragSource()!=other &&
475                  other->mapToScene( other->rect() ).boundingRect().contains( event->scenePos() )) {
476                 GraphicsView* view = qobject_cast<GraphicsView*>( event->widget()->parentWidget() );
477                 if ( view ) {
478                     view->addConstraint( scene()->summaryHandlingModel()->mapToSource( scene()->dragSource()->index() ),
479                                          scene()->summaryHandlingModel()->mapToSource( other->index() ), event->modifiers() );
480                 }
481             }
482         }
483 
484         scene()->setDragSource( nullptr );
485         //scene()->update();
486     } else {
487         if ( isEditable() ) {
488             updateItemFromMouse(event->scenePos());
489 
490             // It is important to set m_presspos to null here because
491             // when the sceneRect updates because we move the item
492             // a MouseMoveEvent will be delivered, and we have to
493             // protect against that
494             m_presspos = QPointF();
495             updateModel();
496             // without this command we sometimes get a white area at the left side of a task item
497             // after we moved that item right-ways into a grey weekend section of the scene:
498             scene()->update();
499         }
500     }
501 
502     m_presspos = QPointF();
503 }
504 
mouseDoubleClickEvent(QGraphicsSceneMouseEvent * event)505 void GraphicsItem::mouseDoubleClickEvent( QGraphicsSceneMouseEvent* event )
506 {
507     const int typ = static_cast<ItemType>( index().model()->data( index(), ItemTypeRole ).toInt() );
508     StyleOptionGanttItem opt = getStyleOption();
509     ItemDelegate::InteractionState istate = scene()->itemDelegate()->interactionStateFor( event->pos(), opt, index() );
510     if ( (istate != ItemDelegate::State_None) || (typ == TypeSummary)) {
511         scene()->itemDoubleClicked( index() );
512     }
513     BASE::mouseDoubleClickEvent( event );
514 }
515 
updateItemFromMouse(const QPointF & scenepos)516 void GraphicsItem::updateItemFromMouse( const QPointF& scenepos )
517 {
518     //qDebug() << "GraphicsItem::updateItemFromMouse("<<scenepos<<")";
519     const QPointF p = scenepos - m_presspos;
520     QRectF r = rect();
521     QRectF br = boundingRect();
522     switch ( m_istate ) {
523     case ItemDelegate::State_Move:
524         setPos( p.x(), pos().y() );
525         break;
526     case ItemDelegate::State_ExtendLeft: {
527         const qreal brr = br.right();
528         const qreal rr = r.right();
529         const qreal delta = pos().x()-p.x();
530         setPos( p.x(), QGraphicsItem::pos().y() );
531         br.setRight( brr+delta );
532         r.setRight( rr+delta );
533         break;
534     }
535     case ItemDelegate::State_ExtendRight: {
536         const qreal rr = r.right();
537         r.setRight( scenepos.x()-pos().x() );
538         br.setWidth( br.width() + r.right()-rr );
539         break;
540     }
541     default: return;
542     }
543     setRect( r );
544     setBoundingRect( br );
545 }
546 
mouseMoveEvent(QGraphicsSceneMouseEvent * event)547 void GraphicsItem::mouseMoveEvent( QGraphicsSceneMouseEvent* event )
548 {
549     if ( !isEditable() ) return;
550     if ( m_presspos.isNull() ) return;
551 
552     //qDebug() << "GraphicsItem::mouseMoveEvent("<<event<<"), m_istate="<< static_cast<ItemDelegate::InteractionState>( m_istate );
553     switch ( m_istate ) {
554     case ItemDelegate::State_ExtendLeft:
555     case ItemDelegate::State_ExtendRight:
556     case ItemDelegate::State_Move:
557         // Check for constraint drag
558       if ( qAbs( m_pressscenepos.x()-event->scenePos().x() ) < 10.
559            && qAbs( m_pressscenepos.y()-event->scenePos().y() ) > 5. ) {
560             m_istate = ItemDelegate::State_DragConstraint;
561             m_dragline = new QGraphicsLineItem( this );
562             m_dragline->setPen( QPen( Qt::DashLine ) );
563             m_dragline->setLine(QLineF( rect().center(), event->pos() ));
564             scene()->setDragSource( this );
565             break;
566         }
567 
568         updateItemFromMouse(event->scenePos());
569         //BASE::mouseMoveEvent(event);
570         break;
571     case ItemDelegate::State_DragConstraint: {
572         QLineF line = m_dragline->line();
573         m_dragline->setLine( QLineF( line.p1(), event->pos() ) );
574         //QGraphicsItem* item = scene()->itemAt( event->scenePos() );
575         break;
576     }
577     }
578 }
579