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

© 2001 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/
Generated by doxygen 1.9.1