KGantt

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

KDE's Doxygen guidelines are available online.