KD Chart API Documentation  3.1
kdganttgraphicsscene.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 "kdganttgraphicsscene.h"
13 #include "kdganttconstraint.h"
15 #include "kdganttdatetimegrid.h"
16 #include "kdganttgraphicsitem.h"
17 #include "kdganttgraphicsscene_p.h"
18 #include "kdganttitemdelegate.h"
20 
21 #include <QApplication>
22 #include <QGraphicsSceneHelpEvent>
23 #include <QPainter>
24 #include <QPrinter>
25 #include <QSet>
26 #include <QTextDocument>
27 #include <QToolTip>
28 
29 #include <QDebug>
30 
31 #include <algorithm>
32 #include <cassert>
33 #include <functional>
34 
35 // defines HAVE_PRINTER if support for printing should be included
36 #ifdef _WIN32_WCE
37 // There is no printer support under wince even if QT_NO_PRINTER is not set
38 #else
39 #ifndef QT_NO_PRINTER
40 #define HAVE_PRINTER
41 #endif
42 #endif
43 
48 using namespace KDGantt;
49 
50 GraphicsScene::Private::Private(GraphicsScene *_q)
51  : q(_q)
52  , dragSource(nullptr)
53  , itemDelegate(new ItemDelegate(_q))
54  , rowController(nullptr)
55  , grid(&default_grid)
56  , readOnly(false)
57  , isPrinting(false)
58  , drawColumnLabels(true)
59  , labelsWidth(0.0)
60  , summaryHandlingModel(new SummaryHandlingProxyModel(_q))
61  , selectionModel(nullptr)
62 {
63  default_grid.setStartDateTime(QDateTime::currentDateTime().addDays(-1));
64 }
65 
66 void GraphicsScene::Private::resetConstraintItems()
67 {
68  q->clearConstraintItems();
69  if (constraintModel.isNull())
70  return;
71  const QList<Constraint> clst = constraintModel->constraints();
72  for (const Constraint &c : clst) {
73  createConstraintItem(c);
74  }
75  q->updateItems();
76 }
77 
78 void GraphicsScene::Private::createConstraintItem(const Constraint &c)
79 {
80  GraphicsItem *sitem = q->findItem(summaryHandlingModel->mapFromSource(c.startIndex()));
81  GraphicsItem *eitem = q->findItem(summaryHandlingModel->mapFromSource(c.endIndex()));
82 
83  if (sitem && eitem) {
84  auto *citem = new ConstraintGraphicsItem(c);
85  sitem->addStartConstraint(citem);
86  eitem->addEndConstraint(citem);
87  q->addItem(citem);
88  }
89 
90  // q->insertConstraintItem( c, citem );
91 }
92 
93 // Delete the constraint item, and clean up pointers in the start- and end item
94 void GraphicsScene::Private::deleteConstraintItem(ConstraintGraphicsItem *citem)
95 {
96  // qDebug()<<"GraphicsScene::Private::deleteConstraintItem citem="<<citem;
97  if (citem == nullptr) {
98  return;
99  }
100  Constraint c = citem->constraint();
101  GraphicsItem *item = items.value(summaryHandlingModel->mapFromSource(c.startIndex()), 0);
102  if (item) {
103  item->removeStartConstraint(citem);
104  }
105  item = items.value(summaryHandlingModel->mapFromSource(c.endIndex()), 0);
106  if (item) {
107  item->removeEndConstraint(citem);
108  }
109  delete citem;
110 }
111 
112 void GraphicsScene::Private::deleteConstraintItem(const Constraint &c)
113 {
114  deleteConstraintItem(findConstraintItem(c));
115 }
116 
117 ConstraintGraphicsItem *GraphicsScene::Private::findConstraintItem(const Constraint &c) const
118 {
119  GraphicsItem *item = items.value(summaryHandlingModel->mapFromSource(c.startIndex()), 0);
120  if (item) {
121  const QList<ConstraintGraphicsItem *> clst = item->startConstraints();
122  QList<ConstraintGraphicsItem *>::const_iterator it = clst.begin();
123  for (; it != clst.end(); ++it) {
124  if (c.compareIndexes((*it)->constraint()))
125  break;
126  }
127  if (it != clst.end()) {
128  return *it;
129  }
130  }
131  item = items.value(summaryHandlingModel->mapFromSource(c.endIndex()), 0);
132  if (item) {
133  const QList<ConstraintGraphicsItem *> clst = item->endConstraints();
134  QList<ConstraintGraphicsItem *>::const_iterator it = clst.begin();
135  for (; it != clst.end(); ++it) {
136  if (c.compareIndexes((*it)->constraint()))
137  break;
138  }
139  if (it != clst.end()) {
140  return *it;
141  }
142  }
143  return nullptr;
144 }
145 
146 GraphicsScene::GraphicsScene(QObject *parent)
147  : QGraphicsScene(parent)
148  , _d(new Private(this))
149 {
150  init();
151 }
152 
154 {
156  qDeleteAll(items());
157  delete _d;
158 }
159 
160 #define d d_func()
161 
162 void GraphicsScene::init()
163 {
164  setItemIndexMethod(QGraphicsScene::NoIndex);
166  connect(d->grid, &AbstractGrid::gridChanged, this, &GraphicsScene::slotGridChanged);
167 }
168 
169 /* NOTE: The delegate should really be a property
170  * of the view, but that doesn't really fit at
171  * this time
172  */
174 {
175  if (!d->itemDelegate.isNull() && d->itemDelegate->parent() == this)
176  delete d->itemDelegate;
177  d->itemDelegate = delegate;
178  update();
179 }
180 
182 {
183  return d->itemDelegate;
184 }
185 
186 QAbstractItemModel *GraphicsScene::model() const
187 {
188  assert(!d->summaryHandlingModel.isNull());
189  return d->summaryHandlingModel->sourceModel();
190 }
191 
192 void GraphicsScene::setModel(QAbstractItemModel *model)
193 {
194  assert(!d->summaryHandlingModel.isNull());
195  d->summaryHandlingModel->setSourceModel(model);
196  d->grid->setModel(d->summaryHandlingModel);
197  setSelectionModel(new QItemSelectionModel(model, this));
198 }
199 
200 QAbstractProxyModel *GraphicsScene::summaryHandlingModel() const
201 {
202  return d->summaryHandlingModel;
203 }
204 
205 void GraphicsScene::setSummaryHandlingModel(QAbstractProxyModel *proxyModel)
206 {
207  proxyModel->setSourceModel(model());
208  d->summaryHandlingModel = proxyModel;
209 }
210 
211 void GraphicsScene::setRootIndex(const QModelIndex &idx)
212 {
213  d->grid->setRootIndex(idx);
214 }
215 
216 QModelIndex GraphicsScene::rootIndex() const
217 {
218  return d->grid->rootIndex();
219 }
220 
222 {
223  return d->constraintModel;
224 }
225 
227 {
228  if (!d->constraintModel.isNull()) {
229  d->constraintModel->disconnect(this);
230  }
231  d->constraintModel = cm;
232 
234  this, &GraphicsScene::slotConstraintAdded);
236  this, &GraphicsScene::slotConstraintRemoved);
237  d->resetConstraintItems();
238 }
239 
240 void GraphicsScene::setSelectionModel(QItemSelectionModel *smodel)
241 {
242  d->selectionModel = smodel;
243  // TODO: update selection from model and connect signals
244 }
245 
246 QItemSelectionModel *GraphicsScene::selectionModel() const
247 {
248  return d->selectionModel;
249 }
250 
252 {
253  d->rowController = rc;
254 }
255 
257 {
258  return d->rowController;
259 }
260 
262 {
263  QAbstractItemModel *model = nullptr;
264  if (grid == nullptr)
265  grid = &d->default_grid;
266  if (d->grid) {
267  d->grid->disconnect(this);
268  model = d->grid->model();
269  }
270  d->grid = grid;
271  connect(d->grid, &AbstractGrid::gridChanged, this, &GraphicsScene::slotGridChanged);
272  d->grid->setModel(model);
273  slotGridChanged();
274 }
275 
277 {
278  return d->grid;
279 }
280 
282 {
283  d->readOnly = ro;
284 }
285 
287 {
288  return d->readOnly;
289 }
290 
291 /* Returns the index with column=0 from the
292  * same row as idx and with the same parent.
293  * This is used to traverse the tree-structure
294  * of the model
295  */
296 QModelIndex GraphicsScene::mainIndex(const QModelIndex &idx)
297 {
298 #if 0
299  if ( idx.isValid() ) {
300  return idx.model()->index( idx.row(), 0,idx.parent() );
301  } else {
302  return QModelIndex();
303  }
304 #else
305  return idx;
306 #endif
307 }
308 
313 QModelIndex GraphicsScene::dataIndex(const QModelIndex &idx)
314 {
315 #if 0
316  if ( idx.isValid() ) {
317  const QAbstractItemModel* model = idx.model();
318  return model->index( idx.row(), model->columnCount( idx.parent() )-1,idx.parent() );
319  } else {
320  return QModelIndex();
321  }
322 #else
323  return idx;
324 #endif
325 }
326 
332 {
333 #if 0
334  switch ( type ) {
335  case TypeEvent: return 0;
336  case TypeTask: return new TaskItem;
337  case TypeSummary: return new SummaryItem;
338  default: return 0;
339  }
340 #endif
341  // qDebug() << "GraphicsScene::createItem("<<type<<")";
342  Q_UNUSED(type);
343  return new GraphicsItem;
344 }
345 
346 void GraphicsScene::Private::recursiveUpdateMultiItem(const Span &span, const QModelIndex &idx)
347 {
348  // qDebug() << "recursiveUpdateMultiItem("<<span<<idx<<")";
349  GraphicsItem *item = q->findItem(idx);
350  const int itemtype = summaryHandlingModel->data(idx, ItemTypeRole).toInt();
351  if (!item) {
352  item = q->createItem(static_cast<ItemType>(itemtype));
353  item->setIndex(idx);
354  q->insertItem(idx, item);
355  }
356  item->updateItem(span, idx);
357  QModelIndex child;
358  int cr = 0;
359  while ((child = idx.model()->index(cr, 0, idx)).isValid()) {
360  recursiveUpdateMultiItem(span, child);
361  ++cr;
362  }
363 }
364 
365 void GraphicsScene::updateRow(const QModelIndex &rowidx)
366 {
367  // qDebug() << "GraphicsScene::updateRow("<<rowidx<<")" << rowidx.data( Qt::DisplayRole );
368  if (!rowidx.isValid())
369  return;
370 #if !defined(NDEBUG)
371  const QAbstractItemModel *model = rowidx.model(); // why const?
372 #endif
373  assert(model);
374  assert(rowController());
375  assert(model == summaryHandlingModel());
376 
377  const QModelIndex sidx = summaryHandlingModel()->mapToSource(rowidx);
378  Span rg = rowController()->rowGeometry(sidx);
379  for (QModelIndex treewalkidx = sidx; treewalkidx.isValid(); treewalkidx = treewalkidx.parent()) {
380  if (treewalkidx.data(ItemTypeRole).toInt() == TypeMulti
381  && !rowController()->isRowExpanded(treewalkidx)) {
382  rg = rowController()->rowGeometry(treewalkidx);
383  }
384  }
385 
386  bool blocked = blockSignals(true);
387  for (int col = 0; col < summaryHandlingModel()->columnCount(rowidx.parent()); ++col) {
388  const QModelIndex idx = summaryHandlingModel()->index(rowidx.row(), col, rowidx.parent());
389  const QModelIndex sidx = summaryHandlingModel()->mapToSource(idx);
390  const int itemtype = summaryHandlingModel()->data(idx, ItemTypeRole).toInt();
391  const bool isExpanded = rowController()->isRowExpanded(sidx);
392  if (itemtype == TypeNone) {
393  removeItem(idx);
394  continue;
395  }
396  if (itemtype == TypeMulti && !isExpanded) {
397  d->recursiveUpdateMultiItem(rg, idx);
398  } else {
399  if (summaryHandlingModel()->data(rowidx.parent(), ItemTypeRole).toInt() == TypeMulti && !isExpanded) {
400  // continue;
401  }
402 
403  GraphicsItem *item = findItem(idx);
404  if (!item) {
405  item = createItem(static_cast<ItemType>(itemtype));
406  item->setIndex(idx);
407  insertItem(idx, item);
408  }
409  const Span span = rowController()->rowGeometry(sidx);
410  item->updateItem(span, idx);
411  }
412  }
413  blockSignals(blocked);
414 }
415 
416 void GraphicsScene::insertItem(const QPersistentModelIndex &idx, GraphicsItem *item)
417 {
418  if (!d->constraintModel.isNull()) {
419  // Create items for constraints
420  const QModelIndex sidx = summaryHandlingModel()->mapToSource(idx);
421  const QList<Constraint> clst = d->constraintModel->constraintsForIndex(sidx);
422  for (const Constraint &c : clst) {
423  QModelIndex other_idx;
424  if (c.startIndex() == sidx) {
425  other_idx = c.endIndex();
426  GraphicsItem *other_item = d->items.value(summaryHandlingModel()->mapFromSource(other_idx), 0);
427  if (!other_item)
428  continue;
429  auto *citem = new ConstraintGraphicsItem(c);
430  item->addStartConstraint(citem);
431  other_item->addEndConstraint(citem);
432  addItem(citem);
433  } else if (c.endIndex() == sidx) {
434  other_idx = c.startIndex();
435  GraphicsItem *other_item = d->items.value(summaryHandlingModel()->mapFromSource(other_idx), 0);
436  if (!other_item)
437  continue;
438  auto *citem = new ConstraintGraphicsItem(c);
439  other_item->addStartConstraint(citem);
440  item->addEndConstraint(citem);
441  addItem(citem);
442  } else {
443  assert(0); // Impossible
444  }
445  }
446  }
447  d->items.insert(idx, item);
448  addItem(item);
449 }
450 
451 void GraphicsScene::removeItem(const QModelIndex &idx)
452 {
453  // qDebug() << "GraphicsScene::removeItem("<<idx<<")";
454  QHash<QPersistentModelIndex, GraphicsItem *>::iterator it = d->items.find(idx);
455  if (it != d->items.end()) {
456  GraphicsItem *item = *it;
457  assert(item);
458  // We have to remove the item from the list first because
459  // there is a good chance there will be reentrant calls
460  d->items.erase(it);
461  {
462  const auto startConstraints = item->startConstraints();
463  const auto endConstraints = item->endConstraints();
464  // Remove any constraintitems attached
465  const QSet<ConstraintGraphicsItem *> clst =
466 #if QT_VERSION > QT_VERSION_CHECK(5, 14, 0)
467  QSet<ConstraintGraphicsItem *>(startConstraints.begin(), startConstraints.end()) + QSet<ConstraintGraphicsItem *>(endConstraints.begin(), endConstraints.end());
468 #else
469  QSet<ConstraintGraphicsItem *>::fromList(startConstraints) + QSet<ConstraintGraphicsItem *>::fromList(endConstraints);
470 #endif
471 
472  for (ConstraintGraphicsItem *citem : clst) {
473  d->deleteConstraintItem(citem);
474  }
475  }
476  // Get rid of the item
477  delete item;
478  }
479 }
480 
481 GraphicsItem *GraphicsScene::findItem(const QModelIndex &idx) const
482 {
483  if (!idx.isValid())
484  return nullptr;
485  assert(idx.model() == summaryHandlingModel());
486  QHash<QPersistentModelIndex, GraphicsItem *>::const_iterator it = d->items.find(idx);
487  return (it != d->items.end()) ? *it : 0;
488 }
489 
490 GraphicsItem *GraphicsScene::findItem(const QPersistentModelIndex &idx) const
491 {
492  if (!idx.isValid())
493  return nullptr;
494  assert(idx.model() == summaryHandlingModel());
495  QHash<QPersistentModelIndex, GraphicsItem *>::const_iterator it = d->items.find(idx);
496  return (it != d->items.end()) ? *it : 0;
497 }
498 
500 {
501  QHash<QPersistentModelIndex, GraphicsItem *>::const_iterator it = d->items.constBegin();
502  for (; it != d->items.constEnd(); ++it) {
503  delete *it;
504  }
505  d->items.clear();
506 
507  // Clear constraints
508  QList<QGraphicsItem *> items = d->q->items();
509  qDeleteAll(items);
510 }
511 
513 {
514  for (QHash<QPersistentModelIndex, GraphicsItem *>::iterator it = d->items.begin();
515  it != d->items.end(); ++it) {
516  GraphicsItem *const item = it.value();
517  const QPersistentModelIndex &idx = it.key();
518  item->updateItem(Span(item->pos().y(), item->rect().height()), idx);
519  }
520  invalidate(QRectF(), QGraphicsScene::BackgroundLayer);
521 }
522 
523 void GraphicsScene::deleteSubtree(const QModelIndex &_idx)
524 {
525  QModelIndex idx = dataIndex(_idx);
526  if (!idx.model())
527  return;
528  const QModelIndex parent(idx.parent());
529  const int colcount = idx.model()->columnCount(parent);
530  {
531  for (int i = 0; i < colcount; ++i) {
532  removeItem(parent.model()->index(idx.row(), i, parent));
533  }
534  }
535  const int rowcount = summaryHandlingModel()->rowCount(_idx);
536  {
537  for (int i = 0; i < rowcount; ++i) {
538  deleteSubtree(summaryHandlingModel()->index(i, summaryHandlingModel()->columnCount(_idx) - 1, _idx));
539  }
540  }
541 }
542 
544 {
545  return d->findConstraintItem(c);
546 }
547 
549 {
550  // TODO
551  // d->constraintItems.clearConstraintItems();
552 }
553 
554 void GraphicsScene::slotConstraintAdded(const KDGantt::Constraint &c)
555 {
556  d->createConstraintItem(c);
557 }
558 
559 void GraphicsScene::slotConstraintRemoved(const KDGantt::Constraint &c)
560 {
561  d->deleteConstraintItem(c);
562 }
563 
564 void GraphicsScene::slotGridChanged()
565 {
566  updateItems();
567  update();
568  Q_EMIT gridChanged();
569 }
570 
571 void GraphicsScene::helpEvent(QGraphicsSceneHelpEvent *helpEvent)
572 {
573 #ifndef QT_NO_TOOLTIP
574  QGraphicsItem *item = itemAt(helpEvent->scenePos(), QTransform());
575  if (auto *gitem = qgraphicsitem_cast<GraphicsItem *>(item)) {
576  QToolTip::showText(helpEvent->screenPos(), gitem->ganttToolTip());
577  } else if (auto *citem = qgraphicsitem_cast<ConstraintGraphicsItem *>(item)) {
578  QToolTip::showText(helpEvent->screenPos(), citem->ganttToolTip());
579  } else {
580  QGraphicsScene::helpEvent(helpEvent);
581  }
582 #endif /* QT_NO_TOOLTIP */
583 }
584 
585 void GraphicsScene::drawBackground(QPainter *painter, const QRectF &_rect)
586 {
587  QRectF scn(sceneRect());
588  QRectF rect(_rect);
589  if (d->isPrinting && d->drawColumnLabels) {
590  QRectF headerRect(scn.topLeft() + QPointF(d->labelsWidth, 0),
591  QSizeF(scn.width() - d->labelsWidth, d->rowController->headerHeight()));
592 
593  d->grid->paintHeader(painter, headerRect, rect, 0, nullptr);
594 
595 #if 0
596  /* We have to blank out the part of the header that is invisible during
597  * normal rendering when we are printing.
598  */
599  QRectF labelsTabRect( scn.topLeft(), QSizeF( d->labelsWidth, headerRect.height() ) );
600 
601  QStyleOptionHeader opt;
602  opt.rect = labelsTabRect.toRect();
603  opt.text = QLatin1String("");
604  opt.textAlignment = Qt::AlignCenter;
605  style()->drawControl(QStyle::CE_Header, &opt, painter, 0);
606 #endif
607 
608  scn.setTop(headerRect.bottom());
609  scn.setLeft(headerRect.left());
610  rect = rect.intersected(scn);
611  }
612  d->grid->paintGrid(painter, scn, rect, d->rowController);
613 
614  d->grid->drawBackground(painter, rect);
615 }
616 
617 void GraphicsScene::drawForeground(QPainter *painter, const QRectF &rect)
618 {
619  d->grid->drawForeground(painter, rect);
620 }
621 
622 void GraphicsScene::itemEntered(const QModelIndex &idx)
623 {
624  Q_EMIT entered(idx);
625 }
626 
627 void GraphicsScene::itemPressed(const QModelIndex &idx)
628 {
629  Q_EMIT pressed(idx);
630 }
631 
632 void GraphicsScene::itemClicked(const QModelIndex &idx)
633 {
634  Q_EMIT clicked(idx);
635 }
636 
637 void GraphicsScene::itemDoubleClicked(const QModelIndex &idx)
638 {
639  Q_EMIT qrealClicked(idx);
640 }
641 
643 {
644  d->dragSource = item;
645 }
646 
648 {
649  return d->dragSource;
650 }
651 
660 void GraphicsScene::print(QPrinter *printer, bool drawRowLabels, bool drawColumnLabels)
661 {
662 #ifndef HAVE_PRINTER
663  Q_UNUSED(printer);
664  Q_UNUSED(drawRowLabels);
665  Q_UNUSED(drawColumnLabels);
666 #else
667  QPainter painter(printer);
668  doPrint(&painter, printer->pageLayout().paintRectPixels(printer->resolution()), sceneRect().left(), sceneRect().right(), printer, drawRowLabels, drawColumnLabels);
669 #endif
670 }
671 
684 void GraphicsScene::print(QPrinter *printer, qreal start, qreal end, bool drawRowLabels, bool drawColumnLabels)
685 {
686 #ifndef HAVE_PRINTER
687  Q_UNUSED(printer);
688  Q_UNUSED(start);
689  Q_UNUSED(end);
690  Q_UNUSED(drawRowLabels);
691  Q_UNUSED(drawColumnLabels);
692 #else
693  QPainter painter(printer);
694  doPrint(&painter, printer->pageLayout().paintRectPixels(printer->resolution()), start, end, printer, drawRowLabels, drawColumnLabels);
695 #endif
696 }
697 
704 void GraphicsScene::print(QPainter *painter, const QRectF &_targetRect, bool drawRowLabels, bool drawColumnLabels)
705 {
706  QRectF targetRect(_targetRect);
707  if (targetRect.isNull()) {
708  targetRect = sceneRect();
709  }
710 
711  doPrint(painter, targetRect, sceneRect().left(), sceneRect().right(), nullptr, drawRowLabels, drawColumnLabels);
712 }
713 
724 void GraphicsScene::print(QPainter *painter, qreal start, qreal end,
725  const QRectF &_targetRect, bool drawRowLabels, bool drawColumnLabels)
726 {
727  QRectF targetRect(_targetRect);
728  if (targetRect.isNull()) {
729  targetRect = sceneRect();
730  }
731 
732  doPrint(painter, targetRect, start, end, nullptr, drawRowLabels, drawColumnLabels);
733 }
734 
737 void GraphicsScene::doPrint(QPainter *painter, const QRectF &targetRect,
738  qreal start, qreal end,
739  QPrinter *printer, bool drawRowLabels, bool drawColumnLabels)
740 {
741  assert(painter);
742  d->isPrinting = true;
743  d->drawColumnLabels = drawColumnLabels;
744  d->labelsWidth = 0.0;
745  QFont sceneFont(font());
746 #ifdef HAVE_PRINTER
747  if (printer) {
748  sceneFont = QFont(font(), printer);
749  if (font().pointSizeF() >= 0.0)
750  sceneFont.setPointSizeF(font().pointSizeF());
751  else if (font().pointSize() >= 0)
752  sceneFont.setPointSize(font().pointSize());
753  else
754  sceneFont.setPixelSize(font().pixelSize());
755  }
756 #endif
757 
758  QGraphicsTextItem dummyTextItem(QLatin1String("X"));
759  dummyTextItem.adjustSize();
760  QFontMetrics fm(dummyTextItem.font());
761  sceneFont.setPixelSize(fm.height());
762 
763  const QRectF oldScnRect(sceneRect());
764  QRectF scnRect(oldScnRect);
765  scnRect.setLeft(start);
766  scnRect.setRight(end);
767  bool b = blockSignals(true);
768 
769  /* column labels */
770  if (d->drawColumnLabels) {
771  QRectF headerRect(scnRect);
772  headerRect.setHeight(-d->rowController->headerHeight());
773  scnRect.setTop(scnRect.top() - d->rowController->headerHeight());
774  }
775 
776  /* row labels */
777  QVector<QGraphicsTextItem *> textLabels;
778  if (drawRowLabels) {
779  qreal textWidth = 0.;
780  QModelIndex sidx = summaryHandlingModel()->mapToSource(summaryHandlingModel()->index(0, 0, rootIndex()));
781  do {
782  QModelIndex idx = summaryHandlingModel()->mapFromSource(sidx);
783  const Span rg = rowController()->rowGeometry(sidx);
784  const QString txt = idx.data(Qt::DisplayRole).toString();
785  auto *item = new QGraphicsTextItem(txt);
786  addItem(item);
787  textLabels << item;
788  item->adjustSize();
789  textWidth = qMax(item->textWidth(), textWidth);
790  item->setPos(0, rg.start());
791  } while ((sidx = rowController()->indexBelow(sidx)).isValid());
792  // Add a little margin to textWidth
793  textWidth += QFontMetricsF(sceneFont).horizontalAdvance(QString::fromLatin1("X"));
794  for (QGraphicsTextItem *item : qAsConst(textLabels)) {
795  item->setPos(scnRect.left() - textWidth, item->y());
796  item->show();
797  }
798  scnRect.setLeft(scnRect.left() - textWidth);
799  d->labelsWidth = textWidth;
800  }
801 
802  setSceneRect(scnRect);
803 
804  painter->save();
805  painter->setClipRect(targetRect);
806 
807  qreal yratio = targetRect.height() / scnRect.height();
808  /* If we're not printing multiple pages,
809  * check if the span fits and adjust:
810  */
811  if (!printer && targetRect.width() / scnRect.width() < yratio) {
812  yratio = targetRect.width() / scnRect.width();
813  }
814 
815  qreal offset = scnRect.left();
816  int pagecount = 0;
817  while (offset < scnRect.right()) {
818  painter->setFont(sceneFont);
819  render(painter, targetRect, QRectF(QPointF(offset, scnRect.top()), QSizeF(targetRect.width() / yratio, scnRect.height())));
820  offset += targetRect.width() / yratio;
821  ++pagecount;
822  if (printer && offset < scnRect.right()) {
823 #ifdef HAVE_PRINTER
824  printer->newPage();
825 #endif
826  } else {
827  break;
828  }
829  }
830 
831  d->isPrinting = false;
832  d->drawColumnLabels = true;
833  d->labelsWidth = 0.0;
834  qDeleteAll(textLabels);
835  blockSignals(b);
836  setSceneRect(oldScnRect);
837  painter->restore();
838 }
839 
840 #include "moc_kdganttgraphicsscene.cpp"
841 
842 #ifndef KDAB_NO_UNIT_TESTS
843 #include "unittest/test.h"
844 
845 #include <QGraphicsLineItem>
846 #include <QPointer>
847 #include <QStandardItemModel>
848 
849 #include "kdganttgraphicsview.h"
850 
851 class SceneTestRowController : public KDGantt::AbstractRowController
852 {
853 private:
854  static const int ROW_HEIGHT;
855  QPointer<QAbstractItemModel> m_model;
856 
857 public:
858  SceneTestRowController()
859  {
860  }
861 
862  void setModel(QAbstractItemModel *model)
863  {
864  m_model = model;
865  }
866 
867  /*reimp*/ int headerHeight() const override
868  {
869  return 40;
870  }
871 
872  /*reimp*/ bool isRowVisible(const QModelIndex &) const override
873  {
874  return true;
875  }
876  /*reimp*/ bool isRowExpanded(const QModelIndex &) const override
877  {
878  return false;
879  }
880  /*reimp*/ KDGantt::Span rowGeometry(const QModelIndex &idx) const override
881  {
882  return KDGantt::Span(idx.row() * ROW_HEIGHT, ROW_HEIGHT);
883  }
884  /*reimp*/ int maximumItemHeight() const override
885  {
886  return ROW_HEIGHT / 2;
887  }
888  /*reimp*/ int totalHeight() const override
889  {
890  return m_model->rowCount() * ROW_HEIGHT;
891  }
892 
893  /*reimp*/ QModelIndex indexAt(int height) const override
894  {
895  return m_model->index(height / ROW_HEIGHT, 0);
896  }
897 
898  /*reimp*/ QModelIndex indexBelow(const QModelIndex &idx) const override
899  {
900  if (!idx.isValid())
901  return QModelIndex();
902  return idx.model()->index(idx.row() + 1, idx.column(), idx.parent());
903  }
904  /*reimp*/ QModelIndex indexAbove(const QModelIndex &idx) const override
905  {
906  if (!idx.isValid())
907  return QModelIndex();
908  return idx.model()->index(idx.row() - 1, idx.column(), idx.parent());
909  }
910 };
911 
912 class TestLineItem : public QGraphicsLineItem
913 {
914 public:
915  TestLineItem(bool *destroyedFlag)
916  : QGraphicsLineItem(0, 0, 10, 10)
917  , // geometry doesn't matter
918  m_destroyedFlag(destroyedFlag)
919  {
920  }
921 
922  ~TestLineItem() override
923  {
924  *m_destroyedFlag = true;
925  }
926 
927 private:
928  bool *m_destroyedFlag;
929 };
930 
931 const int SceneTestRowController::ROW_HEIGHT = 30;
932 
934 {
935  QStandardItemModel model;
936 
937  auto *item = new QStandardItem();
939  item->setData(QString::fromLatin1("Decide on new product"));
940  item->setData(QDate(2007, 3, 1).startOfDay(), KDGantt::StartTimeRole);
941  item->setData(QDate(2007, 3, 3).startOfDay(), KDGantt::EndTimeRole);
942 
943  auto *item2 = new QStandardItem();
944  item2->setData(KDGantt::TypeTask, KDGantt::ItemTypeRole);
945  item2->setData(QString::fromLatin1("Educate personnel"));
946  item2->setData(QDate(2007, 3, 3).startOfDay(), KDGantt::StartTimeRole);
947  item2->setData(QDate(2007, 3, 6).startOfDay(), KDGantt::EndTimeRole);
948 
949  model.appendRow(item);
950  model.appendRow(item2);
951 
952  SceneTestRowController rowController;
953  rowController.setModel(&model);
954 
955  KDGantt::GraphicsView graphicsView;
956  graphicsView.setRowController(&rowController);
957  graphicsView.setModel(&model);
958 
959  // Now the interesting stuff - the items above are just for a "realistic environment"
960 
961  bool foreignItemDestroyed = false;
962  auto *foreignItem = new TestLineItem(&foreignItemDestroyed);
963  graphicsView.scene()->addItem(foreignItem);
964 
965  assertFalse(foreignItemDestroyed);
966  graphicsView.updateScene();
967  assertFalse(foreignItemDestroyed);
968 }
969 #endif /* KDAB_NO_UNIT_TESTS */
Abstract baseclass for grids.
Abstract baseclass for row controllers.
virtual bool isRowExpanded(const QModelIndex &idx) const =0
virtual Span rowGeometry(const QModelIndex &idx) const =0
The ConstraintModel keeps track of the interdependencies between gantt items in a View.
void constraintRemoved(const KDGantt::Constraint &)
void constraintAdded(const KDGantt::Constraint &)
A class used to represent a dependency.
QModelIndex endIndex() const
bool compareIndexes(const Constraint &other) const
QModelIndex startIndex() const
void removeStartConstraint(ConstraintGraphicsItem *)
void addEndConstraint(ConstraintGraphicsItem *)
void updateItem(const Span &rowgeometry, const QPersistentModelIndex &idx)
void removeEndConstraint(ConstraintGraphicsItem *)
QList< ConstraintGraphicsItem * > endConstraints() const
void setIndex(const QPersistentModelIndex &idx)
void addStartConstraint(ConstraintGraphicsItem *)
QList< ConstraintGraphicsItem * > startConstraints() const
void entered(const QModelIndex &index)
void itemDoubleClicked(const QModelIndex &)
void setConstraintModel(ConstraintModel *)
AbstractRowController * rowController() const
ItemDelegate * itemDelegate() const
void itemClicked(const QModelIndex &)
void removeItem(const QModelIndex &)
ConstraintModel * constraintModel() const
void itemEntered(const QModelIndex &)
void clicked(const QModelIndex &index)
void setSummaryHandlingModel(QAbstractProxyModel *)
void qrealClicked(const QModelIndex &index)
void updateRow(const QModelIndex &idx)
void drawBackground(QPainter *painter, const QRectF &rect) override
static QModelIndex dataIndex(const QModelIndex &idx)
Returns the index pointing to the last column in the same row as idx.
void setRowController(AbstractRowController *rc)
void pressed(const QModelIndex &index)
void drawForeground(QPainter *painter, const QRectF &rect) override
void setModel(QAbstractItemModel *)
void setGrid(AbstractGrid *grid)
GraphicsItem * findItem(const QModelIndex &) const
void setItemDelegate(ItemDelegate *)
static QModelIndex mainIndex(const QModelIndex &idx)
void deleteSubtree(const QModelIndex &)
GraphicsItem * createItem(ItemType type) const
Creates a new item of type type.
GraphicsItem * dragSource() const
void helpEvent(QGraphicsSceneHelpEvent *helpEvent) override
void itemPressed(const QModelIndex &)
void print(QPrinter *printer, bool drawRowLabels=true, bool drawColumnLabels=true)
Print the Gantt chart using printer.
void setSelectionModel(QItemSelectionModel *selectionmodel)
ConstraintGraphicsItem * findConstraintItem(const Constraint &) const
QModelIndex rootIndex() const
AbstractGrid * grid() const
void setRootIndex(const QModelIndex &idx)
QItemSelectionModel * selectionModel() const
void insertItem(const QPersistentModelIndex &, GraphicsItem *)
void setDragSource(GraphicsItem *item)
QAbstractItemModel * model() const
QAbstractProxyModel * summaryHandlingModel() const
The GraphicsView class provides a model/view implementation of a gantt chart.
void setModel(QAbstractItemModel *)
Sets the model to be displayed in this view to model.
void setRowController(AbstractRowController *)
Sets the AbstractRowController used by this view.
Class used to render gantt items in a KDGantt::GraphicsView.
A class representing a start point and a length.
qreal start() const
Proxy model that supports summary gantt items.
#define d
KDAB_SCOPED_UNITTEST_SIMPLE(KDGantt, GraphicsView, "test")
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