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