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

Klarälvdalens Datakonsult AB (KDAB)
"The Qt, C++ and OpenGL Experts"
https://www.kdab.com/

https://www.kdab.com/development-resources/qt-tools/kd-chart/