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