KGantt

kganttgraphicsscene.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 "kganttgraphicsscene.h"
10#include "kganttgraphicsscene_p.h"
11#include "kganttgraphicsitem.h"
12#include "kganttconstraint.h"
13#include "kganttconstraintgraphicsitem.h"
14#include "kganttitemdelegate.h"
15#include "kganttabstractrowcontroller.h"
16#include "kganttabstractgrid.h"
17#include "kganttdatetimegrid.h"
18#include "kganttsummaryhandlingproxymodel.h"
19#include "kganttgraphicsview.h"
20#include "kganttprintingcontext.h"
21
22#include <QApplication>
23#include <QGraphicsSceneHelpEvent>
24#include <QPainter>
25#include <QPrinter>
26#include <QTextDocument>
27#include <QToolTip>
28#include <QSet>
29
30#include <QDebug>
31
32#include <functional>
33#include <algorithm>
34#include <cassert>
35
36// defines HAVE_PRINTER if support for printing should be included
37#ifdef _WIN32_WCE
38 // There is no printer support under wince even if QT_NO_PRINTER is not set
39#else
40#ifndef QT_NO_PRINTER
41 #define HAVE_PRINTER
42#endif
43#endif
44
45
46
47using namespace KGantt;
48
49GraphicsScene::Private::Private( GraphicsScene* _q )
50 : q( _q ),
51 dragSource( nullptr ),
52 itemDelegate( new ItemDelegate( _q ) ),
53 rowController( nullptr ),
54 readOnly( false ),
55 isPrinting( false ),
56 drawColumnLabels( true ),
57 labelsWidth( 0.0 ),
58 summaryHandlingModel( new SummaryHandlingProxyModel( _q ) ),
59 selectionModel( nullptr )
60{
61 default_grid.setStartDateTime( QDateTime::currentDateTime().addDays( -1 ) );
62}
63
64GraphicsScene::Private::~Private()
65{
66 delete grid;
67}
68
69void GraphicsScene::Private::clearConstraintItems()
70{
71 for(ConstraintGraphicsItem *citem : std::as_const(constraintItems)) {
72 // remove constraint from items first
73 for(GraphicsItem *item : std::as_const(items)) {
74 item->removeStartConstraint(citem);
75 item->removeEndConstraint(citem);
76 }
77 q->removeItem(citem);
78 delete citem;
79 }
80 constraintItems.clear();
81}
82
83void GraphicsScene::Private::resetConstraintItems()
84{
85 clearConstraintItems();
86 if ( constraintModel.isNull() ) return;
87 const QList<Constraint> clst = constraintModel->constraints();
88 for ( const Constraint& c : clst ) {
89 createConstraintItem( c );
90 }
91 q->updateItems();
92}
93
94void GraphicsScene::Private::createConstraintItem( const Constraint& c )
95{
96 GraphicsItem* sitem = q->findItem( summaryHandlingModel->mapFromSource( c.startIndex() ) );
97 GraphicsItem* eitem = q->findItem( summaryHandlingModel->mapFromSource( c.endIndex() ) );
98
99 if ( sitem && eitem ) {
101 sitem->addStartConstraint( citem );
102 eitem->addEndConstraint( citem );
103 constraintItems.append( citem );
104 q->addItem( citem );
105 }
106
107 //q->insertConstraintItem( c, citem );
108}
109
110// Delete the constraint item, and clean up pointers in the start- and end item
111void GraphicsScene::Private::deleteConstraintItem( ConstraintGraphicsItem *citem )
112{
113 //qDebug()<<"GraphicsScene::Private::deleteConstraintItem citem="<<citem;
114 if ( citem == nullptr ) {
115 return;
116 }
117 Constraint c = citem->constraint();
118 GraphicsItem* item = items.value( summaryHandlingModel->mapFromSource( c.startIndex() ), nullptr );
119 if ( item ) {
120 item->removeStartConstraint( citem );
121 }
122 item = items.value( summaryHandlingModel->mapFromSource( c.endIndex() ), nullptr );
123 if ( item ) {
124 item->removeEndConstraint( citem );
125 }
126 constraintItems.removeAt(constraintItems.indexOf(citem));
127 delete citem;
128}
129
130void GraphicsScene::Private::deleteConstraintItem( const Constraint& c )
131{
132 deleteConstraintItem( findConstraintItem( c ) );
133}
134
135ConstraintGraphicsItem* GraphicsScene::Private::findConstraintItem( const Constraint& c ) const
136{
137 GraphicsItem* item = items.value( summaryHandlingModel->mapFromSource( c.startIndex() ), nullptr );
138 if ( item ) {
139 const QList<ConstraintGraphicsItem*> clst = item->startConstraints();
141 for ( ; it != clst.end() ; ++it ) {
142 if ( c.compareIndexes((*it)->constraint()) )
143 break;
144 }
145 if ( it != clst.end() ) {
146 return *it;
147 }
148 }
149 item = items.value( summaryHandlingModel->mapFromSource( c.endIndex() ), nullptr );
150 if ( item ) {
151 const QList<ConstraintGraphicsItem*> clst = item->endConstraints();
153 for ( ; it != clst.end() ; ++it ) {
154 if ( c.compareIndexes( (*it)->constraint() ) )
155 break;
156 }
157 if ( it != clst.end() ) {
158 return *it;
159 }
160 }
161 return nullptr;
162}
163
164// NOTE: we might get here after indexes are invalidated, so cannot do any controlled cleanup
165void GraphicsScene::Private::clearItems()
166{
167 for(GraphicsItem *item : std::as_const(items)) {
168 q->removeItem(item);
169 delete item;
170 }
171 items.clear();
172 // do last to avoid cleaning up items
173 clearConstraintItems();
174}
175
176AbstractGrid *GraphicsScene::Private::getGrid()
177{
178 if (grid.isNull()) {
179 return static_cast<AbstractGrid*>(&default_grid);
180 }
181 return grid.data();
182}
183
184const AbstractGrid *GraphicsScene::Private::getGrid() const
185{
186 if (grid.isNull()) {
187 return static_cast<const AbstractGrid*>(&default_grid);
188 }
189 return grid.data();
190}
191
192GraphicsScene::GraphicsScene( QObject* parent )
193 : QGraphicsScene( parent ), _d( new Private( this ) )
194{
195 init();
196}
197
198GraphicsScene::~GraphicsScene()
199{
200 qDeleteAll( items() );
201 delete _d;
202}
203
204#define d d_func()
205
206void GraphicsScene::init()
207{
209 setConstraintModel( new ConstraintModel( this ) );
210 connect( d->getGrid(), SIGNAL(gridChanged()), this, SLOT(slotGridChanged()) );
211}
212
213/* NOTE: The delegate should really be a property
214 * of the view, but that doesn't really fit at
215 * this time
216 */
217void GraphicsScene::setItemDelegate( ItemDelegate* delegate )
218{
219 if ( !d->itemDelegate.isNull() && d->itemDelegate->parent()==this ) delete d->itemDelegate;
220 d->itemDelegate = delegate;
221 update();
222}
223
224ItemDelegate* GraphicsScene::itemDelegate() const
225{
226 return d->itemDelegate;
227}
228
229QAbstractItemModel* GraphicsScene::model() const
230{
231 assert(!d->summaryHandlingModel.isNull());
232 return d->summaryHandlingModel->sourceModel();
233}
234
235void GraphicsScene::setModel( QAbstractItemModel* model )
236{
237 assert(!d->summaryHandlingModel.isNull());
238 d->summaryHandlingModel->setSourceModel(model);
239 d->getGrid()->setModel( d->summaryHandlingModel );
240 setSelectionModel( new QItemSelectionModel( model, this ) );
241}
242
243QAbstractProxyModel* GraphicsScene::summaryHandlingModel() const
244{
245 return d->summaryHandlingModel;
246}
247
248void GraphicsScene::setSummaryHandlingModel( QAbstractProxyModel* proxyModel )
249{
250 proxyModel->setSourceModel( model() );
251 d->summaryHandlingModel = proxyModel;
252}
253
254void GraphicsScene::setRootIndex( const QModelIndex& idx )
255{
256 d->getGrid()->setRootIndex( idx );
257}
258
259QModelIndex GraphicsScene::rootIndex() const
260{
261 return d->getGrid()->rootIndex();
262}
263
264ConstraintModel* GraphicsScene::constraintModel() const
265{
266 return d->constraintModel;
267}
268
269void GraphicsScene::setConstraintModel( ConstraintModel* cm )
270{
271 if ( !d->constraintModel.isNull() ) {
272 d->constraintModel->disconnect( this );
273 d->clearConstraintItems();
274 }
275 d->constraintModel = cm;
276
277 connect( cm, SIGNAL(constraintAdded(KGantt::Constraint)),
278 this, SLOT(slotConstraintAdded(KGantt::Constraint)) );
279 connect( cm, SIGNAL(constraintRemoved(KGantt::Constraint)),
280 this, SLOT(slotConstraintRemoved(KGantt::Constraint)) );
281 d->resetConstraintItems();
282}
283
284void GraphicsScene::setSelectionModel( QItemSelectionModel* smodel )
285{
286 if (d->selectionModel) {
287 d->selectionModel->disconnect( this );
288 }
289 d->selectionModel = smodel;
290 if (smodel) {
291 connect(d->selectionModel, SIGNAL(modelChanged(QAbstractItemModel*)),
292 this, SLOT(selectionModelChanged(QAbstractItemModel*)));
294 this, SLOT(slotSelectionChanged(QItemSelection,QItemSelection)) );
295 }
296}
297
298QItemSelectionModel* GraphicsScene::selectionModel() const
299{
300 return d->selectionModel;
301}
302
303void GraphicsScene::setRowController( AbstractRowController* rc )
304{
305 d->rowController = rc;
306}
307
308AbstractRowController* GraphicsScene::rowController() const
309{
310 return d->rowController;
311}
312
314{
315 AbstractGrid *grid = d->grid;
316 grid->disconnect( this );
317 d->grid = nullptr;
318 if (grid) {
319 // revert to the default_grid
320 connect( &d->default_grid, SIGNAL(gridChanged()), this, SLOT(slotGridChanged()) );
321 }
322 return grid;
323}
324
326{
327 QAbstractItemModel* model = nullptr;
328 if ( d->getGrid() ) {
329 d->getGrid()->disconnect( this );
330 model = d->getGrid()->model();
331 }
332 delete d->grid;
333 d->grid = grid;
334 connect( d->getGrid(), SIGNAL(gridChanged()), this, SLOT(slotGridChanged()) );
335 d->getGrid()->setModel( model );
336 slotGridChanged();
337}
338
339// Returns the explicitly set grid
341{
342 return d->grid;
343}
344
345// May also return the default_grid if a grid has not been set
347{
348 return d->getGrid();
349}
350
351void GraphicsScene::setReadOnly( bool ro )
352{
353 d->readOnly = ro;
354}
355
356bool GraphicsScene::isReadOnly() const
357{
358 return d->readOnly;
359}
360
361/* Returns the index with column=0 fromt the
362 * same row as idx and with the same parent.
363 * This is used to traverse the tree-structure
364 * of the model
365 */
366QModelIndex GraphicsScene::mainIndex( const QModelIndex& idx )
367{
368#if 0
369 if ( idx.isValid() ) {
370 return idx.model()->index( idx.row(), 0,idx.parent() );
371 } else {
372 return QModelIndex();
373 }
374#else
375 return idx;
376#endif
377}
378
379
381{
382#if 0
383 if ( idx.isValid() ) {
384 const QAbstractItemModel* model = idx.model();
385 return model->index( idx.row(), model->columnCount( idx.parent() )-1,idx.parent() );
386 } else {
387 return QModelIndex();
388 }
389#else
390 return idx;
391#endif
392}
393
394
396{
397#if 0
398 TODO For 3.0
399 assert(views().count() == 1);
401 assert(v);
402 return v->createItem(type);
403#else
404 Q_UNUSED(type)
405 return new GraphicsItem;
406#endif
407}
408
409void GraphicsScene::Private::recursiveUpdateMultiItem( const Span& span, const QModelIndex& idx )
410{
411 //qDebug() << "recursiveUpdateMultiItem("<<span<<idx<<")";
412 GraphicsItem* item = q->findItem( idx );
413 const int itemtype = summaryHandlingModel->data( idx, ItemTypeRole ).toInt();
414 if (!item) {
415 item = q->createItem( static_cast<ItemType>( itemtype ) );
416 item->setIndex( idx );
417 q->insertItem( idx, item);
418 }
419 item->updateItem( span, idx );
420 QModelIndex child;
421 int cr = 0;
422 while ( ( child = summaryHandlingModel->index( cr, 0, idx ) ).isValid() ) {
423 recursiveUpdateMultiItem( span, child );
424 ++cr;
425 }
426}
427
428void GraphicsScene::updateRow( const QModelIndex& rowidx )
429{
430 //qDebug() << "GraphicsScene::updateRow("<<rowidx<<")" << rowidx.data( Qt::DisplayRole );
431 if ( !rowidx.isValid() ) return;
432#if !defined(NDEBUG)
433 const QAbstractItemModel* model = rowidx.model(); // why const?
434#endif
435 assert( model );
436 assert( rowController() );
437 assert( model == summaryHandlingModel() );
438
439 const QModelIndex sidx = summaryHandlingModel()->mapToSource( rowidx );
440 Span rg = rowController()->rowGeometry( sidx );
441 for ( QModelIndex treewalkidx = sidx; treewalkidx.isValid(); treewalkidx = treewalkidx.parent() ) {
442 if ( treewalkidx.data( ItemTypeRole ).toInt() == TypeMulti
443 && !rowController()->isRowExpanded( treewalkidx )) {
444 rg = rowController()->rowGeometry( treewalkidx );
445 }
446 }
447
448 bool blocked = blockSignals( true );
449 for ( int col = 0; col < summaryHandlingModel()->columnCount( rowidx.parent() ); ++col ) {
450 const QModelIndex idx = summaryHandlingModel()->index( rowidx.row(), col, rowidx.parent() );
451 const QModelIndex sidx = summaryHandlingModel()->mapToSource( idx );
452 const int itemtype = summaryHandlingModel()->data( idx, ItemTypeRole ).toInt();
453 const bool isExpanded = rowController()->isRowExpanded( sidx );
454 if ( itemtype == TypeNone ) {
455 removeItem( idx );
456 continue;
457 }
458 if ( itemtype == TypeMulti && !isExpanded ) {
459 d->recursiveUpdateMultiItem( rg, idx );
460 } else {
461 if ( summaryHandlingModel()->data( rowidx.parent(), ItemTypeRole ).toInt() == TypeMulti && !isExpanded ) {
462 //continue;
463 }
464
465 GraphicsItem* item = findItem( idx );
466 if (!item) {
467 item = createItem( static_cast<ItemType>( itemtype ) );
468 item->setIndex( idx );
469 insertItem(idx, item);
470 }
471 const Span span = rowController()->rowGeometry( sidx );
472 item->updateItem( span, idx );
473 }
474 }
475 blockSignals( blocked );
476}
477
478void GraphicsScene::insertItem( const QPersistentModelIndex& idx, GraphicsItem* item )
479{
480 if ( !d->constraintModel.isNull() ) {
481 // Create items for constraints
482 const QModelIndex sidx = summaryHandlingModel()->mapToSource( idx );
483 const QList<Constraint> clst = d->constraintModel->constraintsForIndex( sidx );
484 for ( const Constraint& c : clst ) {
485 QModelIndex other_idx;
486 if ( c.startIndex() == sidx ) {
487 other_idx = c.endIndex();
488 GraphicsItem* other_item = d->items.value(summaryHandlingModel()->mapFromSource( other_idx ),nullptr);
489 if ( !other_item ) continue;
491 item->addStartConstraint( citem );
492 other_item->addEndConstraint( citem );
493 d->constraintItems.append( citem );
494 addItem( citem );
495 } else if ( c.endIndex() == sidx ) {
496 other_idx = c.startIndex();
497 GraphicsItem* other_item = d->items.value(summaryHandlingModel()->mapFromSource( other_idx ),nullptr);
498 if ( !other_item ) continue;
500 other_item->addStartConstraint( citem );
501 item->addEndConstraint( citem );
502 d->constraintItems.append( citem );
503 addItem( citem );
504 } else {
505 assert( 0 ); // Impossible
506 }
507 }
508 }
509 d->items.insert( idx, item );
510 addItem( item );
511}
512
513void GraphicsScene::removeItem( const QModelIndex& idx )
514{
515 //qDebug() << "GraphicsScene::removeItem("<<idx<<")";
517 if ( it != d->items.end() ) {
518 GraphicsItem* item = *it;
519 assert( item );
520 // We have to remove the item from the list first because
521 // there is a good chance there will be reentrant calls
522 d->items.erase( it );
523 {
524 // Remove any constraintitems attached
525 const QList<ConstraintGraphicsItem*> lst1 = item->startConstraints();
526 const QList<ConstraintGraphicsItem*> lst2 = item->endConstraints();
528 QSet<ConstraintGraphicsItem*>( lst2.begin(), lst2.end() );
529 for ( ConstraintGraphicsItem* citem : clst ) {
530 d->deleteConstraintItem( citem );
531 }
532 }
533 // Get rid of the item
534 delete item;
535 }
536}
537
538GraphicsItem* GraphicsScene::findItem( const QModelIndex& idx ) const
539{
540 if ( !idx.isValid() ) return nullptr;
541 assert( idx.model() == summaryHandlingModel() );
543 return ( it != d->items.end() )?*it:nullptr;
544}
545
546GraphicsItem* GraphicsScene::findItem( const QPersistentModelIndex& idx ) const
547{
548 if ( !idx.isValid() ) return nullptr;
549 assert( idx.model() == summaryHandlingModel() );
551 return ( it != d->items.end() )?*it:nullptr;
552}
553
554void GraphicsScene::clearItems()
555{
556 d->clearItems();
557}
558
559void GraphicsScene::updateItems()
560{
562 it != d->items.end(); ++it ) {
563 GraphicsItem* const item = it.value();
564 const QPersistentModelIndex& idx = it.key();
565 item->updateItem( Span( item->pos().y(), item->rect().height() ), idx );
566 }
568}
569
570void GraphicsScene::deleteSubtree( const QModelIndex& _idx )
571{
572 QModelIndex idx = dataIndex( _idx );
573 if ( !idx.model() ) return;
574 const QModelIndex parent( idx.parent() );
575 const int colcount = idx.model()->columnCount( parent );
576 {for ( int i = 0; i < colcount; ++i ) {
577 removeItem( summaryHandlingModel()->index(idx.row(), i, parent ) );
578 }}
579 const int rowcount = summaryHandlingModel()->rowCount( _idx );
580 {for ( int i = 0; i < rowcount; ++i ) {
581 deleteSubtree( summaryHandlingModel()->index( i, summaryHandlingModel()->columnCount(_idx)-1, _idx ) );
582 }}
583}
584
585
586ConstraintGraphicsItem* GraphicsScene::findConstraintItem( const Constraint& c ) const
587{
588 return d->findConstraintItem( c );
589}
590
591void GraphicsScene::slotConstraintAdded( const KGantt::Constraint& c )
592{
593 d->createConstraintItem( c );
594}
595
596void GraphicsScene::slotConstraintRemoved( const KGantt::Constraint& c )
597{
598 d->deleteConstraintItem( c );
599}
600
601void GraphicsScene::slotGridChanged()
602{
603 updateItems();
604 update();
605 Q_EMIT gridChanged();
606}
607
608void GraphicsScene::selectionModelChanged(QAbstractItemModel *model)
609{
610 Q_UNUSED(model)
611}
612
613void GraphicsScene::slotSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
614{
615 const auto desel = deselected.indexes();
616 for (const QModelIndex &idx : desel) {
617 GraphicsItem *item = findItem(idx.model() == d->summaryHandlingModel ? idx : d->summaryHandlingModel->mapFromSource(idx));
618 if (item) {
619 item->setSelected(false);
620 }
621 }
622 const auto sel = selected.indexes();
623 for (const QModelIndex &idx : sel) {
624 GraphicsItem *item = findItem(idx.model() == d->summaryHandlingModel ? idx : d->summaryHandlingModel->mapFromSource(idx));
625 if (item) {
626 item->setSelected(true);
627 }
628 }
629 update();
630}
631
632void GraphicsScene::helpEvent( QGraphicsSceneHelpEvent *helpEvent )
633{
634#ifndef QT_NO_TOOLTIP
635 QGraphicsItem *item = itemAt( helpEvent->scenePos(), QTransform() );
636 if ( GraphicsItem* gitem = qgraphicsitem_cast<GraphicsItem*>( item ) ) {
637 QToolTip::showText(helpEvent->screenPos(), gitem->ganttToolTip());
638 } else if ( ConstraintGraphicsItem* citem = qgraphicsitem_cast<ConstraintGraphicsItem*>( item ) ) {
639 QToolTip::showText(helpEvent->screenPos(), citem->ganttToolTip());
640 } else {
641 QGraphicsScene::helpEvent( helpEvent );
642 }
643#endif /* QT_NO_TOOLTIP */
644}
645
646void GraphicsScene::drawBackground( QPainter* painter, const QRectF& _rect )
647{
648 QRectF scn( sceneRect() );
649 QRectF rect( _rect );
650 if ( d->isPrinting && d->drawColumnLabels ) {
651 QRectF headerRect( scn.topLeft()+QPointF( d->labelsWidth, 0 ),
652 QSizeF( scn.width()-d->labelsWidth, d->rowController->headerHeight() ));
653
654 d->getGrid()->paintHeader( painter, headerRect, rect, 0, nullptr );
655
656#if 0
657 /* We have to blank out the part of the header that is invisible during
658 * normal rendering when we are printing.
659 */
660 QRectF labelsTabRect( scn.topLeft(), QSizeF( d->labelsWidth, headerRect.height() ) );
661
663 opt.rect = labelsTabRect.toRect();
664 opt.text = QLatin1String("");
665 opt.textAlignment = Qt::AlignCenter;
666 style()->drawControl(QStyle::CE_Header, &opt, painter, 0);
667#endif
668
669 scn.setTop( headerRect.bottom() );
670 scn.setLeft( headerRect.left() );
671 rect = rect.intersected( scn );
672 }
673 d->getGrid()->paintGrid( painter, scn, rect, d->rowController );
674
675 d->getGrid()->drawBackground(painter, rect);
676}
677
678void GraphicsScene::drawForeground( QPainter* painter, const QRectF& rect )
679{
680 d->getGrid()->drawForeground(painter, rect);
681}
682
683void GraphicsScene::itemEntered( const QModelIndex& idx )
684{
685 Q_EMIT entered( idx );
686}
687
688void GraphicsScene::itemPressed( const QModelIndex& idx, QGraphicsSceneMouseEvent *event )
689{
690 if (event->button() == Qt::LeftButton) {
692 if (event->modifiers() & Qt::ControlModifier) {
694 } else {
696 }
697 d->selectionModel->select(d->summaryHandlingModel->mapToSource(idx), flags);
698 }
699 Q_EMIT pressed( idx );
700}
701
702void GraphicsScene::itemClicked( const QModelIndex& idx )
703{
704 Q_EMIT clicked( idx );
705}
706
707void GraphicsScene::itemDoubleClicked( const QModelIndex& idx )
708{
709 Q_EMIT qrealClicked( idx );
710}
711
712void GraphicsScene::setDragSource( GraphicsItem* item )
713{
714 d->dragSource = item;
715}
716
717GraphicsItem* GraphicsScene::dragSource() const
718{
719 return d->dragSource;
720}
721
722
723void GraphicsScene::print( QPrinter* printer, bool drawRowLabels, bool drawColumnLabels )
724{
725#ifndef HAVE_PRINTER
726 Q_UNUSED( printer );
727 Q_UNUSED( drawRowLabels );
728 Q_UNUSED( drawColumnLabels );
729#else
730 QPainter painter( printer );
731 const auto resolution = printer->resolution();
732 const auto pageRect = printer->pageLayout().paintRectPixels(resolution);
733 doPrint( &painter, pageRect, sceneRect().left(), sceneRect().right(), printer, drawRowLabels, drawColumnLabels );
734#endif
735}
736
737
738void GraphicsScene::print( QPrinter* printer, qreal start, qreal end, bool drawRowLabels, bool drawColumnLabels )
739{
740#ifndef HAVE_PRINTER
741 Q_UNUSED( printer );
742 Q_UNUSED( start );
743 Q_UNUSED( end );
744 Q_UNUSED( drawRowLabels );
745 Q_UNUSED( drawColumnLabels );
746#else
747 QPainter painter( printer );
748 const auto resolution = printer->resolution();
749 const auto pageRect = printer->pageLayout().paintRectPixels(resolution);
750 doPrint( &painter, pageRect, start, end, printer, drawRowLabels, drawColumnLabels );
751#endif
752}
753
754
755void GraphicsScene::print( QPainter* painter, const QRectF& _targetRect, bool drawRowLabels, bool drawColumnLabels )
756{
757 QRectF targetRect( _targetRect );
758 if ( targetRect.isNull() ) {
759 targetRect = sceneRect();
760 }
761
762 doPrint( painter, targetRect, sceneRect().left(), sceneRect().right(), nullptr, drawRowLabels, drawColumnLabels );
763}
764
765
766void GraphicsScene::print( QPainter* painter, qreal start, qreal end,
767 const QRectF& _targetRect, bool drawRowLabels, bool drawColumnLabels )
768{
769 QRectF targetRect( _targetRect );
770 if ( targetRect.isNull() ) {
771 targetRect = sceneRect();
772 }
773
774 doPrint( painter, targetRect, start, end, nullptr, drawRowLabels, drawColumnLabels );
775}
776
778{
779#ifndef HAVE_PRINTER
780 Q_UNUSED( printer );
781 Q_UNUSED( context );
782#else
783 PrintingContext ctx( context );
784 if (ctx.sceneRect().isNull()) {
785 ctx.setSceneRect(sceneRect());
786 }
787 QRectF targetRect = printer->pageRect( QPrinter::DevicePixel );
788 if ( printer->fullPage() ) {
789 // Handle margins
790 QPageLayout pl = printer->pageLayout();
791 targetRect = targetRect.marginsRemoved( pl.marginsPixels( printer->resolution() ) );
792 }
793 QPainter painter( printer );
794 doPrintScene( printer, &painter, targetRect, ctx );
795#endif
796}
797
798void GraphicsScene::doPrint( QPainter* painter, const QRectF& targetRect,
799 qreal start, qreal end,
800 QPrinter* printer, bool drawRowLabels, bool drawColumnLabels )
801{
802 assert( painter );
803 PrintingContext ctx;
804 ctx.setFitting(PrintingContext::FitPageHeight); // keep old behavior (?)
805 ctx.setDrawRowLabels( drawRowLabels );
806 ctx.setDrawColumnLabels( drawColumnLabels );
807 ctx.setSceneRect( sceneRect() );
808 ctx.setLeft( start );
809 ctx.setRight( end );
810 doPrintScene( printer, painter, targetRect, ctx );
811}
812
813void GraphicsScene::doPrintScene( QPrinter *printer, QPainter *painter, const QRectF &targetRect, const PrintingContext &context )
814{
815 assert( painter );
816
817 bool b = blockSignals( true );
818
819 d->isPrinting = true;
820 d->drawColumnLabels = context.drawColumnLabels();
821 d->labelsWidth = 0.0;
822
823 QFont sceneFont( font() );
824#ifdef HAVE_PRINTER
825 if ( printer ) {
826 sceneFont = QFont( font(), printer );
827 if ( font().pointSizeF() >= 0.0 )
828 sceneFont.setPointSizeF( font().pointSizeF() );
829 else if ( font().pointSize() >= 0 )
830 sceneFont.setPointSize( font().pointSize() );
831 else
832 sceneFont.setPixelSize( font().pixelSize() );
833 }
834#endif
835
836 QGraphicsTextItem dummyTextItem( QLatin1String("X") );
837 dummyTextItem.adjustSize();
838 QFontMetrics fm(dummyTextItem.font());
839 sceneFont.setPixelSize( fm.height() );
840
841 const QRectF oldScnRect( sceneRect() );
842 QRectF scnRect( oldScnRect );
843 QRectF sceneHeaderRect;
844 QRectF labelsHeaderRect;
845 QRectF labelsRect;
846
847 /* column labels */
848 qreal headerHeight = 0.0;
849 if ( context.drawColumnLabels() ) {
850 headerHeight = d->rowController->headerHeight();
851 sceneHeaderRect = context.sceneRect();
852 sceneHeaderRect.setLeft( context.left() );
853 sceneHeaderRect.setTop( -headerHeight );
854 sceneHeaderRect.setHeight( headerHeight );
855 scnRect.setTop(scnRect.top() - headerHeight);
856 }
857
858 /* row labels */
860 if ( context.drawRowLabels() ) {
861 qreal textWidth = 0.;
862 qreal charWidth = QFontMetricsF(sceneFont).boundingRect( QString::fromLatin1( "X" ) ).width();
863 QModelIndex sidx = summaryHandlingModel()->mapToSource( summaryHandlingModel()->index( 0, 0, rootIndex()) );
864 do {
865 QModelIndex idx = summaryHandlingModel()->mapFromSource( sidx );
866 const Span rg=rowController()->rowGeometry( sidx );
867 const QString txt = idx.data( Qt::DisplayRole ).toString();
868 QGraphicsTextItem* item = new QGraphicsTextItem( txt );
869 addItem( item );
870 textLabels << item;
871 item->setTextWidth( QFontMetricsF(sceneFont).boundingRect( txt ).width() + charWidth );
872 textWidth = qMax( item->textWidth(), textWidth );
873 item->setPos( 0, rg.start() );
874 } while ( ( sidx = rowController()->indexBelow( sidx ) ).isValid() );
875 d->labelsWidth = textWidth;
876 scnRect.setLeft( scnRect.left() - textWidth );
877 for( QGraphicsTextItem* item : std::as_const(textLabels) ) {
878 item->setPos( scnRect.left(), item->y() );
879 item->show();
880 }
881 if ( context.drawColumnLabels() ) {
882 labelsHeaderRect = sceneHeaderRect;
883 labelsHeaderRect.translate( -textWidth, 0.0 );
884 labelsHeaderRect.setWidth( textWidth );
885 }
886 labelsRect = QRectF( scnRect.left(), context.top(), textWidth, context.sceneRect().height() );
887 }
888 setSceneRect( scnRect );
889 scnRect.setRight( context.right() );
890
891 // The scene looks like this:
892 // Labels Do not print Print Behind end
893 // 1 2 3 4
894 // !-------!---------------!------------!-----------
895 // sceneWidth is 1 to 2 + 3 to 4
896 qreal sceneWidth = d->labelsWidth + context.right() - context.left();
897 qreal sceneHeight = context.sceneRect().height() + sceneHeaderRect.height();
898 // qInfo()<<Q_FUNC_INFO<<targetRect<<scnRect<<sceneWidth;
899
900 int horPages = 1;
901 int vertPages = 1;
902 qreal scaleFactor = targetRect.height() / scnRect.height(); // FitPageHeight (default)
903 if ( printer ) {
904 if ( context.fitting() & PrintingContext::NoFitting ) {
905 scaleFactor = printer->logicalDpiX() / views().at(0)->logicalDpiX(); // always have only one view
906 vertPages = qRound( ( sceneHeight * scaleFactor / targetRect.height() ) + 0.5 );
907 horPages = qRound( ( sceneWidth * scaleFactor / targetRect.width() ) + 0.5 );
908 } else if ( context.fitting() & PrintingContext::FitSinglePage ) {
909 scaleFactor = std::min( scaleFactor, targetRect.width() / sceneWidth );
910 } else /*FitPageHeight (default)*/ {
911 horPages = qRound( ( sceneWidth * scaleFactor / targetRect.width() ) + 0.5 );
912 }
913 } else {
914 // paint device has no pages so just fit inside the target
915 scaleFactor = std::min( scaleFactor, targetRect.width() / sceneWidth );
916 }
917 // qInfo()<<Q_FUNC_INFO<<"labels header:"<<labelsHeaderRect<<"labels:"<<labelsRect<<"scene header:"<<sceneHeaderRect<<"scene:"<<scnRect<<"scaleFactor:"<<scaleFactor;
918 painter->save();
919 painter->setFont( sceneFont );
920
921 // qInfo()<<Q_FUNC_INFO<<'s'<<scaleFactor<<"pages="<<((sceneWidth * scaleFactor)/targetRect.width())<<'h'<<horPages<<'v'<<vertPages<<'s'<<scnRect<<'t'<<(targetRect.size()/scaleFactor);
922 qreal yPos = labelsRect.top();
923 for ( int vpage = 0; vpage < vertPages && yPos < context.bottom(); ++vpage ) {
924 // qInfo()<<Q_FUNC_INFO<<"print vertical page"<<vpage;
925 // Disable painting of noInformation during labels printing
926 // or else labels might be painted over
927 QBrush noInfoBrush;
929 if (dateTimeGrid) {
930 noInfoBrush = dateTimeGrid->noInformationBrush();
931 dateTimeGrid->setNoInformationBrush(QBrush());
932 }
933 int hpage = 0;
934 qreal targetLabelsOffset = 0.0;
935 qreal labelsOffsetX = 0.0;
936 while ( labelsOffsetX < labelsHeaderRect.width() ) {
937 // qInfo()<<Q_FUNC_INFO<<"print labels"<<"vert page:"<<vpage<<','<<hpage<<"yPos"<<yPos<<"label x:"<<labelsOffsetX;
938 // print labels, they might span multiple pages
939 QRectF target = targetRect;
940 target.setWidth(std::min(target.width(), (labelsHeaderRect.width() - labelsOffsetX) * scaleFactor) );
941 if ( vpage == 0 && headerHeight > 0.0 ) {
942 QRectF sourceHeader = labelsHeaderRect;
943 sourceHeader.translate( labelsOffsetX, 0.0 );
944 QRectF targetHeader = target;
945 targetHeader.setSize( sourceHeader.size() * scaleFactor );
946 drawLabelsHeader( painter, sourceHeader, targetHeader );
947 target.adjust( 0.0, targetHeader.height(), 0.0, 0.0 );
948 }
949 QRectF rect = labelsRect;
950 rect.setLeft( rect.left() + labelsOffsetX );
951 rect.setTop( yPos );
952 rect.setHeight( std::min(rect.height(), target.height() / scaleFactor ) );
953 painter->setClipRect(target);
954 // disable header, it has been drawn above
955 bool drawColumnLabels = d->drawColumnLabels;
956 d->drawColumnLabels = false;
957 // qInfo()<<Q_FUNC_INFO<<"print labels"<<"vert page:"<<vpage<<','<<hpage<<"scene rect:"<<rect<<"target:"<<target;
958 render( painter, target, rect );
959 d->drawColumnLabels = drawColumnLabels;
960 labelsOffsetX += rect.width();
961 if ( targetRect.right() <= target.right() ) {
962 // we have used the whole page
963 ++hpage;
964#ifdef HAVE_PRINTER
965 if ( printer ) {
966 printer->newPage();
967 }
968#endif
969 } else {
970 // labels might take part of the page
971 targetLabelsOffset = target.width();
972 // qInfo()<<Q_FUNC_INFO<<"print labels finished"<<"vert page:"<<vpage<<"hor page:"<<hpage<<"target offset:"<<targetLabelsOffset;
973 break;
974 }
975 }
976 if (dateTimeGrid) {
977 dateTimeGrid->setNoInformationBrush(noInfoBrush);
978 }
979 qreal xPos = context.left();
980 // qInfo()<<Q_FUNC_INFO<<"print diagram"<<"page:"<<vpage<<','<<hpage<<"xPos"<<xPos<<"yPos:"<<yPos;
981 for ( ; hpage < horPages && xPos < context.right(); ++hpage ) {
982 // Adjust for row labels (first time only)
983 QRectF target = targetRect.adjusted(targetLabelsOffset, 0., 0., 0.);
984 targetLabelsOffset = 0.0;
985 if (!sceneHeaderRect.isNull() && vpage == 0) {
986 // draw header
987 QRectF rect = sceneHeaderRect;
988 rect.setLeft( xPos );
989 QRectF targetHeader = target;
990 targetHeader.setHeight( rect.height() * scaleFactor );
991 rect.setWidth( std::min( rect.width(), target.width() / scaleFactor) );
992 // qInfo()<<Q_FUNC_INFO<<"scene header:"<<"page:"<<vpage<<','<<hpage<<"source:"<<rect<<"target:"<<targetHeader;
993 render( painter, targetHeader, rect );
994 target.adjust( 0.0, targetHeader.height(), 0.0, 0.0 );
995 }
996 QRectF rect = context.sceneRect();
997 rect.setLeft( xPos );
998 rect.setTop( yPos );
999 rect.setWidth( std::min( rect.width(), target.width() / scaleFactor) );
1000 rect.setHeight( std::min( rect.height(), target.height() / scaleFactor ) );
1001 target.setWidth( rect.width() * scaleFactor );
1002 painter->setClipRect( target );
1003 // disable header, it has been drawn above
1004 bool drawColumnLabels = d->drawColumnLabels;
1005 d->drawColumnLabels = false;
1006 // qInfo()<<Q_FUNC_INFO<<"scene:"<<"page:"<<vpage<<','<<hpage<<"source:"<<rect<<"target:"<<target;
1007 render( painter, target, rect );
1008 d->drawColumnLabels = drawColumnLabels;
1009
1010 xPos += rect.width();
1011 // qInfo()<<Q_FUNC_INFO<<context<<"xPos:"<<xPos;
1012 if ( printer && xPos < context.right() ) {
1013#ifdef HAVE_PRINTER
1014 printer->newPage();
1015#endif
1016 } else {
1017 // qInfo()<<Q_FUNC_INFO<<"print horizontal finished if"<<xPos<<">="<<scnRect.right();
1018 break;
1019 }
1020 }
1021 yPos += targetRect.height() / scaleFactor;
1022 if ( vpage == 0 ) {
1023 yPos -= headerHeight;
1024 }
1025 // qInfo()<<Q_FUNC_INFO<<"yPos:"<<yPos<<"bottom:"<<context.bottom();
1026 if ( printer && yPos < context.bottom() ) {
1027#ifdef HAVE_PRINTER
1028 // next vertical page
1029 printer->newPage();
1030#endif
1031 }
1032 // qInfo()<<Q_FUNC_INFO<<"next vertical page if"<<yPos<<'<'<<scnRect.bottom();
1033 }
1034
1035 d->isPrinting = false;
1036 d->drawColumnLabels = true;
1037 d->labelsWidth = 0.0;
1038 qDeleteAll( textLabels );
1039 blockSignals( b );
1040 setSceneRect( oldScnRect );
1041 painter->restore();
1042}
1043
1044void GraphicsScene::drawLabelsHeader( QPainter *painter, const QRectF &sourceRect, const QRectF &targetRect )
1045{
1046 // qInfo()<<Q_FUNC_INFO<<"header:"<<sourceRect<<targetRect;
1047 // TODO This should paint itemview header
1048 painter->setClipRect( targetRect );
1049 render( painter, targetRect, sourceRect );
1050}
1051
1052#include "moc_kganttgraphicsscene.cpp"
1053
1054
1055#ifndef KDAB_NO_UNIT_TESTS
1056#include "unittest/test.h"
1057
1058#include <QGraphicsLineItem>
1059#include <QPointer>
1060#include <QStandardItemModel>
1061
1062#include "kganttgraphicsview.h"
1063
1064class SceneTestRowController : public KGantt::AbstractRowController {
1065private:
1066 static const int ROW_HEIGHT;
1068
1069public:
1070 SceneTestRowController()
1071 {
1072 }
1073
1074 void setModel( QAbstractItemModel* model )
1075 {
1076 m_model = model;
1077 }
1078
1079 /*reimp*/int headerHeight() const override { return 40; }
1080
1081 /*reimp*/ bool isRowVisible( const QModelIndex& ) const override { return true;}
1082 /*reimp*/ bool isRowExpanded( const QModelIndex& ) const override { return false; }
1083 /*reimp*/ KGantt::Span rowGeometry( const QModelIndex& idx ) const override
1084 {
1085 return KGantt::Span( idx.row() * ROW_HEIGHT, ROW_HEIGHT );
1086 }
1087 /*reimp*/ int maximumItemHeight() const override {
1088 return ROW_HEIGHT/2;
1089 }
1090 /*reimp*/int totalHeight() const override {
1091 return m_model->rowCount()* ROW_HEIGHT;
1092 }
1093
1094 /*reimp*/ QModelIndex indexAt( int height ) const override {
1095 return m_model->index( height/ROW_HEIGHT, 0 );
1096 }
1097
1098 /*reimp*/ QModelIndex indexBelow( const QModelIndex& idx ) const override {
1099 if ( !idx.isValid() )return QModelIndex();
1100 return idx.model()->index( idx.row()+1, idx.column(), idx.parent() );
1101 }
1102 /*reimp*/ QModelIndex indexAbove( const QModelIndex& idx ) const override {
1103 if ( !idx.isValid() )return QModelIndex();
1104 return idx.model()->index( idx.row()-1, idx.column(), idx.parent() );
1105 }
1106
1107};
1108
1109class TestLineItem : public QGraphicsLineItem
1110{
1111public:
1112 TestLineItem( bool *destroyedFlag )
1113 : QGraphicsLineItem( 0, 0, 10, 10 ), // geometry doesn't matter
1114 m_destroyedFlag( destroyedFlag )
1115 {}
1116
1117 ~TestLineItem() override
1118 { *m_destroyedFlag = true; }
1119
1120private:
1121 bool *m_destroyedFlag;
1122};
1123
1124const int SceneTestRowController::ROW_HEIGHT = 30;
1125
1126KDAB_SCOPED_UNITTEST_SIMPLE( KGantt, GraphicsView, "test" ) {
1127 QStandardItemModel model;
1128
1129 QStandardItem* item = new QStandardItem();
1130 item->setData( KGantt::TypeTask, KGantt::ItemTypeRole );
1131 item->setData( QString::fromLatin1( "Decide on new product" ) );
1132 item->setData( QDateTime( QDate( 2007, 3, 1 ), QTime() ), KGantt::StartTimeRole );
1133 item->setData( QDateTime( QDate( 2007, 3, 3 ), QTime() ), KGantt::EndTimeRole );
1134
1135 QStandardItem* item2 = new QStandardItem();
1136 item2->setData( KGantt::TypeTask, KGantt::ItemTypeRole );
1137 item2->setData( QString::fromLatin1( "Educate personnel" ) );
1138 item2->setData( QDateTime( QDate( 2007, 3, 3 ), QTime() ), KGantt::StartTimeRole );
1139 item2->setData( QDateTime( QDate( 2007, 3, 6 ), QTime() ), KGantt::EndTimeRole );
1140
1141 model.appendRow( item );
1142 model.appendRow( item2 );
1143
1144 SceneTestRowController rowController;
1145 rowController.setModel( &model );
1146
1147 KGantt::GraphicsView graphicsView;
1148 graphicsView.setRowController( &rowController );
1149 graphicsView.setModel( &model );
1150
1151 // Now the interesting stuff - the items above are just for a "realistic environment"
1152
1153 bool foreignItemDestroyed = false;
1154 TestLineItem *foreignItem = new TestLineItem( &foreignItemDestroyed );
1155 graphicsView.scene()->addItem( foreignItem );
1156
1157 assertFalse( foreignItemDestroyed );
1158 graphicsView.updateScene();
1159 assertFalse( foreignItemDestroyed );
1160}
1161#endif /* KDAB_NO_UNIT_TESTS */
Abstract baseclass for grids. A grid is used to convert between QModelIndex'es and gantt chart values...
Abstract baseclass for row controllers. A row controller is used by the GraphicsView to nagivate the ...
virtual Span rowGeometry(const QModelIndex &idx) const =0
A class used to represent a dependency.
QModelIndex endIndex() const
QModelIndex startIndex() const
QBrush noInformationBrush() const
void setNoInformationBrush(const QBrush &brush)
static QModelIndex dataIndex(const QModelIndex &idx)
void printDiagram(QPrinter *printer, const PrintingContext &context)
void setGrid(AbstractGrid *grid)
Set the grid to grid.
GraphicsItem * createItem(ItemType type) const
void print(QPrinter *printer, bool drawRowLabels=true, bool drawColumnLabels=true)
AbstractGrid * grid() const
const AbstractGrid * getGrid() const
The GraphicsView class provides a model/view implementation of a gantt chart.
void setModel(QAbstractItemModel *)
void setRowController(KGantt::AbstractRowController *)
The PrintingContext class provides options for printing the gantt chart.
void setSceneRect(const QRectF &rect)
void setFitting(const Fitting &value)
@ FitSinglePage
Scale diagram to fit on a single page.
@ NoFitting
No scaling, print as many pages as needed.
@ FitPageHeight
Scale diagram height to fit one page.
A class representing a start point and a length.
Proxy model that supports summary gantt items.
Q_SCRIPTABLE Q_NOREPLY void start()
Global namespace.
@ ItemTypeRole
The item type.
@ StartTimeRole
Start time (or other start value) for a gantt item.
@ EndTimeRole
End time (or other end value) for a gantt item.
bool isValid(QStringView ifopt)
QCA_EXPORT void init()
virtual int columnCount(const QModelIndex &parent) const const=0
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const=0
virtual int rowCount(const QModelIndex &parent) const const=0
virtual QVariant data(const QModelIndex &proxyIndex, int role) const const override
virtual QModelIndex mapFromSource(const QModelIndex &sourceIndex) const const=0
virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const const=0
virtual void setSourceModel(QAbstractItemModel *sourceModel)
QDateTime currentDateTime()
QRectF boundingRect(QChar ch) const const
QVariant data(int key) const const
QPointF pos() const const
void setPos(const QPointF &pos)
void setSelected(bool selected)
void addItem(QGraphicsItem *item)
virtual bool event(QEvent *event) override
virtual void helpEvent(QGraphicsSceneHelpEvent *helpEvent)
void invalidate(const QRectF &rect, SceneLayers layers)
QGraphicsItem * itemAt(const QPointF &position, const QTransform &deviceTransform) const const
void setItemIndexMethod(ItemIndexMethod method)
QList< QGraphicsItem * > items(Qt::SortOrder order) const const
void render(QPainter *painter, const QRectF &target, const QRectF &source, Qt::AspectRatioMode aspectRatioMode)
void selectionChanged()
QStyle * style() const const
void update(const QRectF &rect)
QList< QGraphicsView * > views() const const
qreal width() const const
QPointF scenePos() const const
QPoint screenPos() const const
void setTextWidth(qreal width)
qreal textWidth() const const
QGraphicsScene * scene() const const
QModelIndexList indexes() const const
iterator begin()
iterator end()
int column() const const
QVariant data(int role) const const
bool isValid() const const
const QAbstractItemModel * model() const const
QModelIndex parent() const const
int row() const const
Q_EMITQ_EMIT
bool blockSignals(bool block)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
QObject * parent() const const
T qobject_cast(QObject *object)
QPageLayout pageLayout() const const
QMargins marginsPixels(int resolution) const const
QRect paintRectPixels(int resolution) const const
int logicalDpiX() const const
void restore()
void save()
void setClipRect(const QRect &rectangle, Qt::ClipOperation operation)
void setFont(const QFont &font)
bool isValid() const const
const QAbstractItemModel * model() const const
qreal y() const const
bool fullPage() const const
virtual bool newPage() override
QRectF pageRect(Unit unit) const const
int resolution() const const
void adjust(qreal dx1, qreal dy1, qreal dx2, qreal dy2)
QRectF adjusted(qreal dx1, qreal dy1, qreal dx2, qreal dy2) const const
qreal height() const const
bool isNull() const const
qreal left() const const
QRectF marginsRemoved(const QMarginsF &margins) const const
qreal right() const const
void setHeight(qreal height)
void setLeft(qreal x)
void setSize(const QSizeF &size)
void setTop(qreal y)
void setWidth(qreal width)
QSizeF size() const const
qreal top() const const
void translate(const QPointF &offset)
qreal width() const const
virtual void setData(const QVariant &value, int role)
void appendRow(QStandardItem *item)
QString fromLatin1(QByteArrayView str)
virtual void drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const const=0
AlignCenter
DisplayRole
ControlModifier
LeftButton
void showText(const QPoint &pos, const QString &text, QWidget *w, const QRect &rect, int msecDisplayTime)
int toInt(bool *ok) const const
QString toString() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:09:26 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.