1 /*
2  * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB.  All rights reserved.
3  *
4  * This file is part of the KGantt library.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of
9  * the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
18  */
19 
20 #include "kganttitemdelegate_p.h"
21 #include "kganttglobal.h"
22 #include "kganttstyleoptionganttitem.h"
23 #include "kganttconstraint.h"
24 
25 #include <QPainter>
26 #include <QPainterPath>
27 #include <QPen>
28 #include <QModelIndex>
29 #include <QAbstractItemModel>
30 #include <QApplication>
31 
32 #ifndef QT_NO_DEBUG_STREAM
33 
34 #define PRINT_INTERACTIONSTATE(x) \
35     case x: dbg << #x; break;
36 
37 
operator <<(QDebug dbg,KGantt::ItemDelegate::InteractionState state)38 QDebug operator<<( QDebug dbg, KGantt::ItemDelegate::InteractionState state )
39 {
40     switch ( state ) {
41         PRINT_INTERACTIONSTATE( KGantt::ItemDelegate::State_None );
42         PRINT_INTERACTIONSTATE( KGantt::ItemDelegate::State_Move );
43         PRINT_INTERACTIONSTATE( KGantt::ItemDelegate::State_ExtendLeft );
44         PRINT_INTERACTIONSTATE( KGantt::ItemDelegate::State_ExtendRight );
45     default:
46         break;
47     }
48     return dbg;
49 }
50 
51 #undef PRINT_INTERACTIONSTATE
52 
53 #endif /* QT_NO_DEBUG_STREAM */
54 
55 using namespace KGantt;
56 
57 
Private()58 ItemDelegate::Private::Private()
59 {
60     // Brushes
61     QLinearGradient taskgrad( 0., 0., 0., QApplication::fontMetrics().height() );
62     taskgrad.setColorAt( 0., Qt::green );
63     taskgrad.setColorAt( 1., Qt::darkGreen );
64 
65     QLinearGradient summarygrad( 0., 0., 0., QApplication::fontMetrics().height() );
66     summarygrad.setColorAt( 0., Qt::blue );
67     summarygrad.setColorAt( 1., Qt::darkBlue );
68 
69     QLinearGradient eventgrad( 0., 0., 0., QApplication::fontMetrics().height() );
70     eventgrad.setColorAt( 0., Qt::red );
71     eventgrad.setColorAt( 1., Qt::darkRed );
72 
73     defaultbrush[TypeTask]    = taskgrad;
74     defaultbrush[TypeSummary] = summarygrad;
75     defaultbrush[TypeEvent]   = eventgrad;
76 
77     // Pens
78     QPen pen( QGuiApplication::palette().windowText(), 1. );
79 
80     defaultpen[TypeTask]    = pen;
81     defaultpen[TypeSummary] = pen;
82     defaultpen[TypeEvent]   = pen;
83 }
84 
constraintPen(const QPointF & start,const QPointF & end,const Constraint & constraint,const QStyleOptionGraphicsItem & opt)85 QPen ItemDelegate::Private::constraintPen( const QPointF& start, const QPointF& end, const Constraint& constraint, const QStyleOptionGraphicsItem& opt )
86 {
87     QPen pen;
88     QVariant dataPen;
89 
90     // Use default pens...
91     if ( start.x() <= end.x() ) {
92         pen = QPen( opt.palette.windowText().color() );
93         dataPen = constraint.data( Constraint::ValidConstraintPen );
94     } else {
95         pen = QPen( Qt::red );
96         dataPen = constraint.data( Constraint::InvalidConstraintPen );
97     }
98 
99     // ... unless constraint.data() returned a valid pen for this case
100     if ( dataPen.canConvert( QVariant::Pen ) ) {
101         pen = dataPen.value< QPen >();
102     }
103 
104     return pen;
105 }
106 
107 
ItemDelegate(QObject * parent)108 ItemDelegate::ItemDelegate( QObject* parent )
109     : QItemDelegate( parent ), _d( new Private )
110 {
111 }
112 
113 
~ItemDelegate()114 ItemDelegate::~ItemDelegate()
115 {
116     delete _d;
117 }
118 
119 #define d d_func()
120 
121 
setDefaultBrush(ItemType type,const QBrush & brush)122 void ItemDelegate::setDefaultBrush( ItemType type, const QBrush& brush )
123 {
124     d->defaultbrush[type] = brush;
125 }
126 
127 
defaultBrush(ItemType type) const128 QBrush ItemDelegate::defaultBrush( ItemType type ) const
129 {
130     return d->defaultbrush[type];
131 }
132 
133 
setDefaultPen(ItemType type,const QPen & pen)134 void ItemDelegate::setDefaultPen( ItemType type, const QPen& pen )
135 {
136     d->defaultpen[type]=pen;
137 }
138 
139 
defaultPen(ItemType type) const140 QPen ItemDelegate::defaultPen( ItemType type ) const
141 {
142     return d->defaultpen[type];
143 }
144 
145 
toolTip(const QModelIndex & idx) const146 QString ItemDelegate::toolTip( const QModelIndex &idx ) const
147 {
148     if ( !idx.isValid() ) return QString();
149 
150     const QAbstractItemModel* model = idx.model();
151     if ( !model ) return QString();
152     QString tip = model->data( idx, Qt::ToolTipRole ).toString();
153     if ( !tip.isNull() ) return tip;
154     else return tr( "%1 -> %2: %3", "start time -> end time: item name" )
155                 .arg( model->data( idx, StartTimeRole ).toString() )
156                 .arg( model->data( idx, EndTimeRole ).toString() )
157                 .arg( model->data( idx, Qt::DisplayRole ).toString() );
158 }
159 
160 
itemBoundingSpan(const StyleOptionGanttItem & opt,const QModelIndex & idx) const161 Span ItemDelegate::itemBoundingSpan( const StyleOptionGanttItem& opt,
162                                  const QModelIndex& idx ) const
163 {
164     if ( !idx.isValid() ) return Span();
165 
166     const QString txt = idx.model()->data( idx, Qt::DisplayRole ).toString();
167     const int typ = idx.model()->data( idx, ItemTypeRole ).toInt();
168     QRectF itemRect = opt.itemRect;
169 
170 
171     if ( typ == TypeEvent ) {
172         itemRect = QRectF( itemRect.left()-itemRect.height()/2.,
173                            itemRect.top(),
174                            itemRect.height(),
175                            itemRect.height() );
176     }
177 
178     int tw = opt.fontMetrics.boundingRect( txt ).width();
179     tw += static_cast<int>( itemRect.height()/2. );
180     Span s;
181     switch ( opt.displayPosition ) {
182     case StyleOptionGanttItem::Left:
183         s = Span( itemRect.left()-tw, itemRect.width()+tw ); break;
184     case StyleOptionGanttItem::Right:
185         s = Span( itemRect.left(), itemRect.width()+tw ); break;
186     case StyleOptionGanttItem::Hidden: // fall through
187     case StyleOptionGanttItem::Center:
188         s = Span( itemRect.left(), itemRect.width() ); break;
189     }
190     return s;
191 }
192 
193 
interactionStateFor(const QPointF & pos,const StyleOptionGanttItem & opt,const QModelIndex & idx) const194 ItemDelegate::InteractionState ItemDelegate::interactionStateFor( const QPointF& pos,
195 								  const StyleOptionGanttItem& opt,
196 								  const QModelIndex& idx ) const
197 {
198     if ( !idx.isValid() ) return State_None;
199     if ( !( idx.model()->flags( idx ) & Qt::ItemIsEditable ) ) return State_None;
200 
201     const int typ = static_cast<ItemType>( idx.model()->data( idx, ItemTypeRole ).toInt() );
202 
203     QRectF itemRect( opt.itemRect );
204 
205     // An event item is infinitely thin, basically just a line, because it has only one date instead of two.
206     // It is painted with an offset of -height/2, which is taken into account here.
207     if ( typ == TypeEvent )
208         itemRect = QRectF( itemRect.topLeft() - QPointF( itemRect.height() / 2.0, 0 ), QSizeF( itemRect.height(),
209                                                                                                itemRect.height() ) );
210 
211     if ( typ == TypeNone || typ == TypeSummary ) return State_None;
212     if ( !itemRect.contains(pos) ) return State_None;
213     if ( typ == TypeEvent )
214         return State_Move;
215 
216     qreal delta = 5.;
217     if ( itemRect.width() < 15 ) delta = 1.;
218     if ( pos.x() >= itemRect.left() && pos.x() < itemRect.left()+delta ) {
219         return State_ExtendLeft;
220     } else   if ( pos.x() <= itemRect.right() && pos.x() > itemRect.right()-delta ) {
221         return State_ExtendRight;
222     } else {
223         return State_Move;
224     }
225 }
226 
227 
paintGanttItem(QPainter * painter,const StyleOptionGanttItem & opt,const QModelIndex & idx)228 void ItemDelegate::paintGanttItem( QPainter* painter,
229                                    const StyleOptionGanttItem& opt,
230                                    const QModelIndex& idx )
231 {
232     if ( !idx.isValid() ) return;
233     const ItemType typ = static_cast<ItemType>( idx.model()->data( idx, ItemTypeRole ).toInt() );
234     const QString& txt = opt.text;
235     QRectF itemRect = opt.itemRect;
236     QRectF boundingRect = opt.boundingRect;
237     boundingRect.setY( itemRect.y() );
238     boundingRect.setHeight( itemRect.height() );
239 
240     //qDebug() << "itemRect="<<itemRect<<", boundingRect="<<boundingRect;
241     //qDebug() << painter->font() << opt.fontMetrics.height() << painter->device()->width() << painter->device()->height();
242 
243     painter->save();
244 
245     QPen pen = defaultPen( typ );
246     if ( opt.state & QStyle::State_Selected ) pen.setWidth( 2*pen.width() );
247     painter->setPen( pen );
248     painter->setBrush( defaultBrush( typ ) );
249 
250     bool drawText = true;
251     qreal pw = painter->pen().width()/2.;
252     switch ( typ ) {
253     case TypeTask:
254         if ( itemRect.isValid() ) {
255             // TODO
256             qreal pw = painter->pen().width()/2.;
257             pw-=1;
258             QRectF r = itemRect;
259             r.translate( 0., r.height()/6. );
260             r.setHeight( 2.*r.height()/3. );
261             painter->setBrushOrigin( itemRect.topLeft() );
262             painter->save();
263             painter->translate( 0.5, 0.5 );
264             painter->drawRect( r );
265             bool ok;
266             qreal completion = idx.model()->data( idx, KGantt::TaskCompletionRole ).toReal( &ok );
267             if ( ok ) {
268                 qreal h = r.height();
269                 QRectF cr( r.x(), r.y()+h/4.,
270                            r.width()*completion/100., h/2.+1 /*??*/ );
271                 QColor compcolor( painter->pen().color() );
272                 compcolor.setAlpha( 150 );
273                 painter->fillRect( cr, compcolor );
274             }
275             painter->restore();
276         }
277         break;
278     case TypeSummary:
279         if ( opt.itemRect.isValid() ) {
280             // TODO
281             pw-=1;
282             const QRectF r = QRectF( opt.itemRect ).adjusted( -pw, -pw, pw, pw );
283             QPainterPath path;
284             const qreal deltaY = r.height()/2.;
285             const qreal deltaXBezierControl = .25*qMin( r.width(), r.height() );
286             const qreal deltaX = qMin( r.width()/2, r.height() );
287             path.moveTo( r.topLeft() );
288             path.lineTo( r.topRight() );
289             path.lineTo( QPointF( r.right(), r.top() + 2.*deltaY ) );
290             //path.lineTo( QPointF( r.right()-3./2.*delta, r.top() + delta ) );
291             path.quadTo( QPointF( r.right()-deltaXBezierControl, r.top() + deltaY ), QPointF( r.right()-deltaX, r.top() + deltaY ) );
292             //path.lineTo( QPointF( r.left()+3./2.*delta, r.top() + delta ) );
293             path.lineTo( QPointF( r.left() + deltaX, r.top() + deltaY ) );
294             path.quadTo( QPointF( r.left()+deltaXBezierControl, r.top() + deltaY ), QPointF( r.left(), r.top() + 2.*deltaY ) );
295             path.closeSubpath();
296             painter->setBrushOrigin( itemRect.topLeft() );
297             painter->save();
298             painter->translate( 0.5, 0.5 );
299             painter->drawPath( path );
300             painter->restore();
301         }
302         break;
303     case TypeEvent: /* TODO */
304         //qDebug() << opt.boundingRect << opt.itemRect;
305         if ( opt.boundingRect.isValid() ) {
306             const qreal pw = painter->pen().width() / 2. - 1;
307             const QRectF r = QRectF( opt.itemRect ).adjusted( -pw, -pw, pw, pw ).translated( -opt.itemRect.height()/2, 0 );
308             QPainterPath path;
309             const qreal delta = static_cast< int >( r.height() / 2 );
310             path.moveTo( delta, 0. );
311             path.lineTo( 2.*delta, delta );
312             path.lineTo( delta, 2.*delta );
313             path.lineTo( 0., delta );
314             path.closeSubpath();
315             painter->save();
316             painter->translate( r.topLeft() );
317             painter->translate( 0, 0.5 );
318             painter->drawPath( path );
319             painter->restore();
320 #if 0
321             painter->setBrush( Qt::NoBrush );
322             painter->setPen( Qt::black );
323             painter->drawRect( opt.boundingRect );
324             painter->setPen( Qt::red );
325             painter->drawRect( r );
326 #endif
327         }
328         break;
329     default:
330         drawText = false;
331         break;
332     }
333 
334     Qt::Alignment ta;
335     switch ( opt.displayPosition ) {
336         case StyleOptionGanttItem::Left: ta = Qt::AlignLeft; break;
337         case StyleOptionGanttItem::Right: ta = Qt::AlignRight; break;
338         case StyleOptionGanttItem::Center: ta = Qt::AlignCenter; break;
339         case StyleOptionGanttItem::Hidden: drawText = false; break;
340     }
341     if ( drawText ) {
342         pen = painter->pen();
343         pen.setColor(opt.palette.text().color());
344         painter->setPen(pen);
345         painter->drawText( boundingRect, ta | Qt::AlignVCenter, txt );
346     }
347 
348     painter->restore();
349 }
350 
351 static const qreal TURN = 10.;
352 static const qreal PW = 1.5;
353 
354 
constraintBoundingRect(const QPointF & start,const QPointF & end,const Constraint & constraint) const355 QRectF ItemDelegate::constraintBoundingRect( const QPointF& start, const QPointF& end, const Constraint &constraint ) const
356 {
357     QPolygonF poly;
358     switch ( constraint.relationType() ) {
359         case Constraint::FinishStart:
360             poly = finishStartLine( start, end ) + finishStartArrow( start, end );
361             break;
362         case Constraint::FinishFinish:
363             poly = finishFinishLine( start, end ) + finishFinishArrow( start, end );
364             break;
365         case Constraint::StartStart:
366             poly = startStartLine( start, end ) + startStartArrow( start, end );
367             break;
368         case Constraint::StartFinish:
369             poly = startFinishLine( start, end ) + startFinishArrow( start, end );
370             break;
371     }
372     return poly.boundingRect().adjusted( -PW, -PW, PW, PW );
373 }
374 
375 
376 
paintConstraintItem(QPainter * painter,const QStyleOptionGraphicsItem & opt,const QPointF & start,const QPointF & end,const Constraint & constraint)377 void ItemDelegate::paintConstraintItem( QPainter* painter, const QStyleOptionGraphicsItem& opt,
378                                         const QPointF& start, const QPointF& end, const Constraint &constraint )
379 {
380     //qDebug()<<"ItemDelegate::paintConstraintItem"<<start<<end<<constraint;
381     switch ( constraint.relationType() ) {
382         case Constraint::FinishStart:
383             paintFinishStartConstraint( painter, opt, start, end, constraint );
384             break;
385         case Constraint::FinishFinish:
386             paintFinishFinishConstraint( painter, opt, start, end, constraint );
387             break;
388         case Constraint::StartStart:
389             paintStartStartConstraint( painter, opt, start, end, constraint );
390             break;
391         case Constraint::StartFinish:
392             paintStartFinishConstraint( painter, opt, start, end, constraint );
393             break;
394     }
395 }
396 
paintFinishStartConstraint(QPainter * painter,const QStyleOptionGraphicsItem & opt,const QPointF & start,const QPointF & end,const Constraint & constraint)397 void ItemDelegate::paintFinishStartConstraint( QPainter* painter, const QStyleOptionGraphicsItem& opt, const QPointF& start, const QPointF& end, const Constraint &constraint )
398 {
399     Q_UNUSED( opt );
400 
401     const QPen pen = d->constraintPen( start, end, constraint, opt );
402 
403     painter->setPen( pen );
404     painter->setBrush( pen.color() );
405 
406     painter->drawPolyline( finishStartLine( start, end ) );
407     painter->drawPolygon( finishStartArrow( start, end ) );
408 }
409 
finishStartLine(const QPointF & start,const QPointF & end) const410 QPolygonF ItemDelegate::finishStartLine( const QPointF& start, const QPointF& end ) const
411 {
412     QPolygonF poly;
413     qreal midx = end.x() - TURN;
414     qreal midy = ( end.y()-start.y() )/2. + start.y();
415 
416     if ( start.x() > end.x()-TURN ) {
417         poly << start
418                 << QPointF( start.x()+TURN, start.y() )
419                 << QPointF( start.x()+TURN, midy )
420                 << QPointF( end.x()-TURN, midy )
421                 << QPointF( end.x()-TURN, end.y() )
422                 << end;
423     } else {
424         poly << start
425                 << QPointF( midx, start.y() )
426                 << QPointF( midx, end.y() )
427                 << end;
428     }
429     return poly;
430 }
431 
finishStartArrow(const QPointF & start,const QPointF & end) const432 QPolygonF ItemDelegate::finishStartArrow( const QPointF& start, const QPointF& end ) const
433 {
434     Q_UNUSED(start);
435 
436     QPolygonF poly;
437     poly << end
438             << QPointF( end.x()-TURN/2., end.y()-TURN/2. )
439             << QPointF( end.x()-TURN/2., end.y()+TURN/2. );
440     return poly;
441 }
442 
paintFinishFinishConstraint(QPainter * painter,const QStyleOptionGraphicsItem & opt,const QPointF & start,const QPointF & end,const Constraint & constraint)443 void ItemDelegate::paintFinishFinishConstraint( QPainter* painter, const QStyleOptionGraphicsItem& opt, const QPointF& start, const QPointF& end, const Constraint &constraint )
444 {
445     Q_UNUSED( opt );
446 
447     const QPen pen = d->constraintPen( start, end, constraint, opt );
448 
449     painter->setPen( pen );
450     painter->setBrush( pen.color() );
451 
452     painter->drawPolyline( finishFinishLine( start, end ) );
453     painter->drawPolygon( finishFinishArrow( start, end ) );
454 }
455 
finishFinishLine(const QPointF & start,const QPointF & end) const456 QPolygonF ItemDelegate::finishFinishLine( const QPointF& start, const QPointF& end ) const
457 {
458     QPolygonF poly;
459     qreal midx = end.x() + TURN;
460     qreal midy = ( end.y()-start.y() )/2. + start.y();
461 
462     if ( start.x() > end.x()+TURN ) {
463         poly << start
464                 << QPointF( start.x()+TURN, start.y() )
465                 << QPointF( start.x()+TURN, end.y() )
466                 << end;
467     } else {
468         poly << start
469                 << QPointF( midx, start.y() )
470                 << QPointF( midx, midy )
471                 << QPointF( end.x()+TURN, midy )
472                 << QPointF( end.x()+TURN, end.y() )
473                 << end;
474     }
475     return poly;
476 }
477 
finishFinishArrow(const QPointF & start,const QPointF & end) const478 QPolygonF ItemDelegate::finishFinishArrow( const QPointF& start, const QPointF& end ) const
479 {
480     Q_UNUSED(start);
481 
482     QPolygonF poly;
483     poly << end
484             << QPointF( end.x()+TURN/2., end.y()-TURN/2. )
485             << QPointF( end.x()+TURN/2., end.y()+TURN/2. );
486     return poly;
487 }
488 
paintStartStartConstraint(QPainter * painter,const QStyleOptionGraphicsItem & opt,const QPointF & start,const QPointF & end,const Constraint & constraint)489 void ItemDelegate::paintStartStartConstraint( QPainter* painter, const QStyleOptionGraphicsItem& opt, const QPointF& start, const QPointF& end, const Constraint &constraint )
490 {
491     Q_UNUSED( opt );
492 
493     const QPen pen = d->constraintPen( start, end, constraint, opt );
494 
495     painter->setPen( pen );
496     painter->setBrush( pen.color() );
497 
498     painter->drawPolyline( startStartLine( start, end ) );
499     painter->drawPolygon( startStartArrow( start, end ) );
500 
501 }
502 
startStartLine(const QPointF & start,const QPointF & end) const503 QPolygonF ItemDelegate::startStartLine( const QPointF& start, const QPointF& end ) const
504 {
505     Q_UNUSED(start);
506 
507     QPolygonF poly;
508 
509     if ( start.x() > end.x() ) {
510         poly << start
511                 << QPointF( end.x()-TURN, start.y() )
512                 << QPointF( end.x()-TURN, end.y() )
513                 << end;
514     } else {
515         poly << start
516                 << QPointF( start.x()-TURN, start.y() )
517                 << QPointF( start.x()-TURN, end.y() )
518                 << QPointF( end.x()-TURN, end.y() )
519                 << end;
520     }
521     return poly;
522 }
523 
startStartArrow(const QPointF & start,const QPointF & end) const524 QPolygonF ItemDelegate::startStartArrow( const QPointF& start, const QPointF& end ) const
525 {
526     Q_UNUSED(start);
527 
528     QPolygonF poly;
529     poly << end
530             << QPointF( end.x()-TURN/2., end.y()-TURN/2. )
531             << QPointF( end.x()-TURN/2., end.y()+TURN/2. );
532     return poly;
533 }
534 
paintStartFinishConstraint(QPainter * painter,const QStyleOptionGraphicsItem & opt,const QPointF & start,const QPointF & end,const Constraint & constraint)535 void ItemDelegate::paintStartFinishConstraint( QPainter* painter, const QStyleOptionGraphicsItem& opt, const QPointF& start, const QPointF& end, const Constraint &constraint )
536 {
537     Q_UNUSED( opt );
538 
539     const QPen pen = d->constraintPen( start, end, constraint, opt);
540 
541     painter->setPen( pen );
542     painter->setBrush( pen.color() );
543 
544     painter->drawPolyline( startFinishLine( start, end ) );
545     painter->drawPolygon( startFinishArrow( start, end ) );
546 }
547 
startFinishLine(const QPointF & start,const QPointF & end) const548 QPolygonF ItemDelegate::startFinishLine( const QPointF& start, const QPointF& end ) const
549 {
550     Q_UNUSED(start);
551 
552     QPolygonF poly;
553     qreal midx = end.x() + TURN;
554     qreal midy = ( end.y()-start.y() )/2. + start.y();
555 
556     if ( start.x()-TURN > end.x()+TURN ) {
557         poly << start
558                 << QPointF( midx, start.y() )
559                 << QPointF( midx, end.y() )
560                 << end;
561     } else {
562         poly << start
563                 << QPointF( start.x()-TURN, start.y() )
564                 << QPointF( start.x()-TURN, midy )
565                 << QPointF( midx, midy )
566                 << QPointF( end.x()+TURN, end.y() )
567                 << end;
568     }
569     return poly;
570 }
571 
startFinishArrow(const QPointF & start,const QPointF & end) const572 QPolygonF ItemDelegate::startFinishArrow( const QPointF& start, const QPointF& end ) const
573 {
574     Q_UNUSED(start);
575 
576     QPolygonF poly;
577     poly << end
578             << QPointF( end.x()+TURN/2., end.y()-TURN/2. )
579             << QPointF( end.x()+TURN/2., end.y()+TURN/2. );
580     return poly;
581 }
582 
583 
584 #include "moc_kganttitemdelegate.cpp"
585