KGantt

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

KDE's Doxygen guidelines are available online.