File indexing completed on 2024-05-12 04:20:44
0001 /* 0002 * SPDX-FileCopyrightText: 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. 0003 * 0004 * This file is part of the KGantt library. 0005 * 0006 * SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "kganttitemdelegate_p.h" 0010 #include "kganttstyleoptionganttitem.h" 0011 #include "kganttconstraint.h" 0012 0013 #include <QPainter> 0014 #include <QPainterPath> 0015 #include <QModelIndex> 0016 #include <QAbstractItemModel> 0017 #include <QApplication> 0018 0019 #ifndef QT_NO_DEBUG_STREAM 0020 0021 #define PRINT_INTERACTIONSTATE(x) \ 0022 case x: dbg << #x; break; 0023 0024 0025 QDebug operator<<( QDebug dbg, KGantt::ItemDelegate::InteractionState state ) 0026 { 0027 switch ( state ) { 0028 PRINT_INTERACTIONSTATE( KGantt::ItemDelegate::State_None ); 0029 PRINT_INTERACTIONSTATE( KGantt::ItemDelegate::State_Move ); 0030 PRINT_INTERACTIONSTATE( KGantt::ItemDelegate::State_ExtendLeft ); 0031 PRINT_INTERACTIONSTATE( KGantt::ItemDelegate::State_ExtendRight ); 0032 default: 0033 break; 0034 } 0035 return dbg; 0036 } 0037 0038 #undef PRINT_INTERACTIONSTATE 0039 0040 #endif /* QT_NO_DEBUG_STREAM */ 0041 0042 using namespace KGantt; 0043 0044 0045 ItemDelegate::Private::Private() 0046 { 0047 // Brushes 0048 QLinearGradient taskgrad( 0., 0., 0., QApplication::fontMetrics().height() ); 0049 taskgrad.setColorAt( 0., Qt::green ); 0050 taskgrad.setColorAt( 1., Qt::darkGreen ); 0051 0052 QLinearGradient summarygrad( 0., 0., 0., QApplication::fontMetrics().height() ); 0053 summarygrad.setColorAt( 0., Qt::blue ); 0054 summarygrad.setColorAt( 1., Qt::darkBlue ); 0055 0056 QLinearGradient eventgrad( 0., 0., 0., QApplication::fontMetrics().height() ); 0057 eventgrad.setColorAt( 0., Qt::red ); 0058 eventgrad.setColorAt( 1., Qt::darkRed ); 0059 0060 defaultbrush[TypeTask] = taskgrad; 0061 defaultbrush[TypeSummary] = summarygrad; 0062 defaultbrush[TypeEvent] = eventgrad; 0063 0064 // Pens 0065 QPen pen( QGuiApplication::palette().windowText(), 1. ); 0066 0067 defaultpen[TypeTask] = pen; 0068 defaultpen[TypeSummary] = pen; 0069 defaultpen[TypeEvent] = pen; 0070 } 0071 0072 QPen ItemDelegate::Private::constraintPen( const QPointF& start, const QPointF& end, const Constraint& constraint, const QStyleOptionGraphicsItem& opt ) 0073 { 0074 QPen pen; 0075 QVariant dataPen; 0076 0077 // Use default pens... 0078 if ( start.x() <= end.x() ) { 0079 pen = QPen( opt.palette.windowText().color() ); 0080 dataPen = constraint.data( Constraint::ValidConstraintPen ); 0081 } else { 0082 pen = QPen( Qt::red ); 0083 dataPen = constraint.data( Constraint::InvalidConstraintPen ); 0084 } 0085 0086 // ... unless constraint.data() returned a valid pen for this case 0087 if ( dataPen.canConvert( QVariant::Pen ) ) { 0088 pen = dataPen.value< QPen >(); 0089 } 0090 0091 return pen; 0092 } 0093 0094 0095 ItemDelegate::ItemDelegate( QObject* parent ) 0096 : QItemDelegate( parent ), _d( new Private ) 0097 { 0098 } 0099 0100 0101 ItemDelegate::~ItemDelegate() 0102 { 0103 delete _d; 0104 } 0105 0106 #define d d_func() 0107 0108 0109 void ItemDelegate::setDefaultBrush( ItemType type, const QBrush& brush ) 0110 { 0111 d->defaultbrush[type] = brush; 0112 } 0113 0114 0115 QBrush ItemDelegate::defaultBrush( ItemType type ) const 0116 { 0117 return d->defaultbrush[type]; 0118 } 0119 0120 0121 void ItemDelegate::setDefaultPen( ItemType type, const QPen& pen ) 0122 { 0123 d->defaultpen[type]=pen; 0124 } 0125 0126 0127 QPen ItemDelegate::defaultPen( ItemType type ) const 0128 { 0129 return d->defaultpen[type]; 0130 } 0131 0132 0133 QString ItemDelegate::toolTip( const QModelIndex &idx ) const 0134 { 0135 if ( !idx.isValid() ) return QString(); 0136 0137 const QAbstractItemModel* model = idx.model(); 0138 if ( !model ) return QString(); 0139 QString tip = model->data( idx, Qt::ToolTipRole ).toString(); 0140 if ( !tip.isNull() ) return tip; 0141 else return tr( "%1 -> %2: %3", "start time -> end time: item name" ) 0142 .arg( model->data( idx, StartTimeRole ).toString() ) 0143 .arg( model->data( idx, EndTimeRole ).toString() ) 0144 .arg( model->data( idx, Qt::DisplayRole ).toString() ); 0145 } 0146 0147 0148 Span ItemDelegate::itemBoundingSpan( const StyleOptionGanttItem& opt, 0149 const QModelIndex& idx ) const 0150 { 0151 if ( !idx.isValid() ) return Span(); 0152 0153 const QString txt = idx.model()->data( idx, Qt::DisplayRole ).toString(); 0154 const int typ = idx.model()->data( idx, ItemTypeRole ).toInt(); 0155 QRectF itemRect = opt.itemRect; 0156 0157 0158 if ( typ == TypeEvent ) { 0159 itemRect = QRectF( itemRect.left()-itemRect.height()/2., 0160 itemRect.top(), 0161 itemRect.height(), 0162 itemRect.height() ); 0163 } 0164 0165 int tw = opt.fontMetrics.boundingRect( txt ).width(); 0166 tw += static_cast<int>( itemRect.height()/2. ); 0167 Span s; 0168 switch ( opt.displayPosition ) { 0169 case StyleOptionGanttItem::Left: 0170 s = Span( itemRect.left()-tw, itemRect.width()+tw ); break; 0171 case StyleOptionGanttItem::Right: 0172 s = Span( itemRect.left(), itemRect.width()+tw ); break; 0173 case StyleOptionGanttItem::Hidden: // fall through 0174 case StyleOptionGanttItem::Center: 0175 s = Span( itemRect.left(), itemRect.width() ); break; 0176 } 0177 return s; 0178 } 0179 0180 0181 ItemDelegate::InteractionState ItemDelegate::interactionStateFor( const QPointF& pos, 0182 const StyleOptionGanttItem& opt, 0183 const QModelIndex& idx ) const 0184 { 0185 if ( !idx.isValid() ) return State_None; 0186 if ( !( idx.model()->flags( idx ) & Qt::ItemIsEditable ) ) return State_None; 0187 0188 const int typ = static_cast<ItemType>( idx.model()->data( idx, ItemTypeRole ).toInt() ); 0189 0190 QRectF itemRect( opt.itemRect ); 0191 0192 // An event item is infinitely thin, basically just a line, because it has only one date instead of two. 0193 // It is painted with an offset of -height/2, which is taken into account here. 0194 if ( typ == TypeEvent ) 0195 itemRect = QRectF( itemRect.topLeft() - QPointF( itemRect.height() / 2.0, 0 ), QSizeF( itemRect.height(), 0196 itemRect.height() ) ); 0197 0198 if ( typ == TypeNone || typ == TypeSummary ) return State_None; 0199 if ( !itemRect.contains(pos) ) return State_None; 0200 if ( typ == TypeEvent ) 0201 return State_Move; 0202 0203 qreal delta = 5.; 0204 if ( itemRect.width() < 15 ) delta = 1.; 0205 if ( pos.x() >= itemRect.left() && pos.x() < itemRect.left()+delta ) { 0206 return State_ExtendLeft; 0207 } else if ( pos.x() <= itemRect.right() && pos.x() > itemRect.right()-delta ) { 0208 return State_ExtendRight; 0209 } else { 0210 return State_Move; 0211 } 0212 } 0213 0214 0215 void ItemDelegate::paintGanttItem( QPainter* painter, 0216 const StyleOptionGanttItem& opt, 0217 const QModelIndex& idx ) 0218 { 0219 if ( !idx.isValid() ) return; 0220 const ItemType typ = static_cast<ItemType>( idx.model()->data( idx, ItemTypeRole ).toInt() ); 0221 const QString& txt = opt.text; 0222 QRectF itemRect = opt.itemRect; 0223 QRectF boundingRect = opt.boundingRect; 0224 boundingRect.setY( itemRect.y() ); 0225 boundingRect.setHeight( itemRect.height() ); 0226 0227 //qDebug() << "itemRect="<<itemRect<<", boundingRect="<<boundingRect; 0228 //qDebug() << painter->font() << opt.fontMetrics.height() << painter->device()->width() << painter->device()->height(); 0229 0230 painter->save(); 0231 0232 QPen pen = defaultPen( typ ); 0233 if ( opt.state & QStyle::State_Selected ) pen.setWidth( 2*pen.width() ); 0234 painter->setPen( pen ); 0235 painter->setBrush( defaultBrush( typ ) ); 0236 0237 bool drawText = true; 0238 qreal pw = painter->pen().width()/2.; 0239 switch ( typ ) { 0240 case TypeTask: 0241 if ( itemRect.isValid() ) { 0242 // TODO 0243 qreal pw = painter->pen().width()/2.; 0244 pw-=1; 0245 QRectF r = itemRect; 0246 r.translate( 0., r.height()/6. ); 0247 r.setHeight( 2.*r.height()/3. ); 0248 painter->setBrushOrigin( itemRect.topLeft() ); 0249 painter->save(); 0250 painter->translate( 0.5, 0.5 ); 0251 painter->drawRect( r ); 0252 bool ok; 0253 qreal completion = idx.model()->data( idx, KGantt::TaskCompletionRole ).toReal( &ok ); 0254 if ( ok ) { 0255 qreal h = r.height(); 0256 QRectF cr( r.x(), r.y()+h/4., 0257 r.width()*completion/100., h/2.+1 /*??*/ ); 0258 QColor compcolor( painter->pen().color() ); 0259 compcolor.setAlpha( 150 ); 0260 painter->fillRect( cr, compcolor ); 0261 } 0262 painter->restore(); 0263 } 0264 break; 0265 case TypeSummary: 0266 if ( opt.itemRect.isValid() ) { 0267 // TODO 0268 pw-=1; 0269 const QRectF r = QRectF( opt.itemRect ).adjusted( -pw, -pw, pw, pw ); 0270 QPainterPath path; 0271 const qreal deltaY = r.height()/2.; 0272 const qreal deltaXBezierControl = .25*qMin( r.width(), r.height() ); 0273 const qreal deltaX = qMin( r.width()/2, r.height() ); 0274 path.moveTo( r.topLeft() ); 0275 path.lineTo( r.topRight() ); 0276 path.lineTo( QPointF( r.right(), r.top() + 2.*deltaY ) ); 0277 //path.lineTo( QPointF( r.right()-3./2.*delta, r.top() + delta ) ); 0278 path.quadTo( QPointF( r.right()-deltaXBezierControl, r.top() + deltaY ), QPointF( r.right()-deltaX, r.top() + deltaY ) ); 0279 //path.lineTo( QPointF( r.left()+3./2.*delta, r.top() + delta ) ); 0280 path.lineTo( QPointF( r.left() + deltaX, r.top() + deltaY ) ); 0281 path.quadTo( QPointF( r.left()+deltaXBezierControl, r.top() + deltaY ), QPointF( r.left(), r.top() + 2.*deltaY ) ); 0282 path.closeSubpath(); 0283 painter->setBrushOrigin( itemRect.topLeft() ); 0284 painter->save(); 0285 painter->translate( 0.5, 0.5 ); 0286 painter->drawPath( path ); 0287 painter->restore(); 0288 } 0289 break; 0290 case TypeEvent: /* TODO */ 0291 //qDebug() << opt.boundingRect << opt.itemRect; 0292 if ( opt.boundingRect.isValid() ) { 0293 const qreal pw = painter->pen().width() / 2. - 1; 0294 const QRectF r = QRectF( opt.itemRect ).adjusted( -pw, -pw, pw, pw ).translated( -opt.itemRect.height()/2, 0 ); 0295 QPainterPath path; 0296 const qreal delta = static_cast< int >( r.height() / 2 ); 0297 path.moveTo( delta, 0. ); 0298 path.lineTo( 2.*delta, delta ); 0299 path.lineTo( delta, 2.*delta ); 0300 path.lineTo( 0., delta ); 0301 path.closeSubpath(); 0302 painter->save(); 0303 painter->translate( r.topLeft() ); 0304 painter->translate( 0, 0.5 ); 0305 painter->drawPath( path ); 0306 painter->restore(); 0307 #if 0 0308 painter->setBrush( Qt::NoBrush ); 0309 painter->setPen( Qt::black ); 0310 painter->drawRect( opt.boundingRect ); 0311 painter->setPen( Qt::red ); 0312 painter->drawRect( r ); 0313 #endif 0314 } 0315 break; 0316 default: 0317 drawText = false; 0318 break; 0319 } 0320 0321 Qt::Alignment ta; 0322 switch ( opt.displayPosition ) { 0323 case StyleOptionGanttItem::Left: ta = Qt::AlignLeft; break; 0324 case StyleOptionGanttItem::Right: ta = Qt::AlignRight; break; 0325 case StyleOptionGanttItem::Center: ta = Qt::AlignCenter; break; 0326 case StyleOptionGanttItem::Hidden: drawText = false; break; 0327 } 0328 if ( drawText ) { 0329 pen = painter->pen(); 0330 pen.setColor(opt.palette.text().color()); 0331 painter->setPen(pen); 0332 painter->drawText( boundingRect, ta | Qt::AlignVCenter, txt ); 0333 } 0334 0335 painter->restore(); 0336 } 0337 0338 static const qreal TURN = 10.; 0339 static const qreal PW = 1.5; 0340 0341 0342 QRectF ItemDelegate::constraintBoundingRect( const QPointF& start, const QPointF& end, const Constraint &constraint ) const 0343 { 0344 QPolygonF poly; 0345 switch ( constraint.relationType() ) { 0346 case Constraint::FinishStart: 0347 poly = finishStartLine( start, end ) + finishStartArrow( start, end ); 0348 break; 0349 case Constraint::FinishFinish: 0350 poly = finishFinishLine( start, end ) + finishFinishArrow( start, end ); 0351 break; 0352 case Constraint::StartStart: 0353 poly = startStartLine( start, end ) + startStartArrow( start, end ); 0354 break; 0355 case Constraint::StartFinish: 0356 poly = startFinishLine( start, end ) + startFinishArrow( start, end ); 0357 break; 0358 } 0359 return poly.boundingRect().adjusted( -PW, -PW, PW, PW ); 0360 } 0361 0362 0363 0364 void ItemDelegate::paintConstraintItem( QPainter* painter, const QStyleOptionGraphicsItem& opt, 0365 const QPointF& start, const QPointF& end, const Constraint &constraint ) 0366 { 0367 //qDebug()<<"ItemDelegate::paintConstraintItem"<<start<<end<<constraint; 0368 switch ( constraint.relationType() ) { 0369 case Constraint::FinishStart: 0370 paintFinishStartConstraint( painter, opt, start, end, constraint ); 0371 break; 0372 case Constraint::FinishFinish: 0373 paintFinishFinishConstraint( painter, opt, start, end, constraint ); 0374 break; 0375 case Constraint::StartStart: 0376 paintStartStartConstraint( painter, opt, start, end, constraint ); 0377 break; 0378 case Constraint::StartFinish: 0379 paintStartFinishConstraint( painter, opt, start, end, constraint ); 0380 break; 0381 } 0382 } 0383 0384 void ItemDelegate::paintFinishStartConstraint( QPainter* painter, const QStyleOptionGraphicsItem& opt, const QPointF& start, const QPointF& end, const Constraint &constraint ) 0385 { 0386 Q_UNUSED( opt ); 0387 0388 const QPen pen = d->constraintPen( start, end, constraint, opt ); 0389 0390 painter->setPen( pen ); 0391 painter->setBrush( pen.color() ); 0392 0393 painter->drawPolyline( finishStartLine( start, end ) ); 0394 painter->drawPolygon( finishStartArrow( start, end ) ); 0395 } 0396 0397 QPolygonF ItemDelegate::finishStartLine( const QPointF& start, const QPointF& end ) const 0398 { 0399 QPolygonF poly; 0400 qreal midx = end.x() - TURN; 0401 qreal midy = ( end.y()-start.y() )/2. + start.y(); 0402 0403 if ( start.x() > end.x()-TURN ) { 0404 poly << start 0405 << QPointF( start.x()+TURN, start.y() ) 0406 << QPointF( start.x()+TURN, midy ) 0407 << QPointF( end.x()-TURN, midy ) 0408 << QPointF( end.x()-TURN, end.y() ) 0409 << end; 0410 } else { 0411 poly << start 0412 << QPointF( midx, start.y() ) 0413 << QPointF( midx, end.y() ) 0414 << end; 0415 } 0416 return poly; 0417 } 0418 0419 QPolygonF ItemDelegate::finishStartArrow( const QPointF& start, const QPointF& end ) const 0420 { 0421 Q_UNUSED(start); 0422 0423 QPolygonF poly; 0424 poly << end 0425 << QPointF( end.x()-TURN/2., end.y()-TURN/2. ) 0426 << QPointF( end.x()-TURN/2., end.y()+TURN/2. ); 0427 return poly; 0428 } 0429 0430 void ItemDelegate::paintFinishFinishConstraint( QPainter* painter, const QStyleOptionGraphicsItem& opt, const QPointF& start, const QPointF& end, const Constraint &constraint ) 0431 { 0432 Q_UNUSED( opt ); 0433 0434 const QPen pen = d->constraintPen( start, end, constraint, opt ); 0435 0436 painter->setPen( pen ); 0437 painter->setBrush( pen.color() ); 0438 0439 painter->drawPolyline( finishFinishLine( start, end ) ); 0440 painter->drawPolygon( finishFinishArrow( start, end ) ); 0441 } 0442 0443 QPolygonF ItemDelegate::finishFinishLine( const QPointF& start, const QPointF& end ) const 0444 { 0445 QPolygonF poly; 0446 qreal midx = end.x() + TURN; 0447 qreal midy = ( end.y()-start.y() )/2. + start.y(); 0448 0449 if ( start.x() > end.x()+TURN ) { 0450 poly << start 0451 << QPointF( start.x()+TURN, start.y() ) 0452 << QPointF( start.x()+TURN, end.y() ) 0453 << end; 0454 } else { 0455 poly << start 0456 << QPointF( midx, start.y() ) 0457 << QPointF( midx, midy ) 0458 << QPointF( end.x()+TURN, midy ) 0459 << QPointF( end.x()+TURN, end.y() ) 0460 << end; 0461 } 0462 return poly; 0463 } 0464 0465 QPolygonF ItemDelegate::finishFinishArrow( const QPointF& start, const QPointF& end ) const 0466 { 0467 Q_UNUSED(start); 0468 0469 QPolygonF poly; 0470 poly << end 0471 << QPointF( end.x()+TURN/2., end.y()-TURN/2. ) 0472 << QPointF( end.x()+TURN/2., end.y()+TURN/2. ); 0473 return poly; 0474 } 0475 0476 void ItemDelegate::paintStartStartConstraint( QPainter* painter, const QStyleOptionGraphicsItem& opt, const QPointF& start, const QPointF& end, const Constraint &constraint ) 0477 { 0478 Q_UNUSED( opt ); 0479 0480 const QPen pen = d->constraintPen( start, end, constraint, opt ); 0481 0482 painter->setPen( pen ); 0483 painter->setBrush( pen.color() ); 0484 0485 painter->drawPolyline( startStartLine( start, end ) ); 0486 painter->drawPolygon( startStartArrow( start, end ) ); 0487 0488 } 0489 0490 QPolygonF ItemDelegate::startStartLine( const QPointF& start, const QPointF& end ) const 0491 { 0492 Q_UNUSED(start); 0493 0494 QPolygonF poly; 0495 0496 if ( start.x() > end.x() ) { 0497 poly << start 0498 << QPointF( end.x()-TURN, start.y() ) 0499 << QPointF( end.x()-TURN, end.y() ) 0500 << end; 0501 } else { 0502 poly << start 0503 << QPointF( start.x()-TURN, start.y() ) 0504 << QPointF( start.x()-TURN, end.y() ) 0505 << QPointF( end.x()-TURN, end.y() ) 0506 << end; 0507 } 0508 return poly; 0509 } 0510 0511 QPolygonF ItemDelegate::startStartArrow( const QPointF& start, const QPointF& end ) const 0512 { 0513 Q_UNUSED(start); 0514 0515 QPolygonF poly; 0516 poly << end 0517 << QPointF( end.x()-TURN/2., end.y()-TURN/2. ) 0518 << QPointF( end.x()-TURN/2., end.y()+TURN/2. ); 0519 return poly; 0520 } 0521 0522 void ItemDelegate::paintStartFinishConstraint( QPainter* painter, const QStyleOptionGraphicsItem& opt, const QPointF& start, const QPointF& end, const Constraint &constraint ) 0523 { 0524 Q_UNUSED( opt ); 0525 0526 const QPen pen = d->constraintPen( start, end, constraint, opt); 0527 0528 painter->setPen( pen ); 0529 painter->setBrush( pen.color() ); 0530 0531 painter->drawPolyline( startFinishLine( start, end ) ); 0532 painter->drawPolygon( startFinishArrow( start, end ) ); 0533 } 0534 0535 QPolygonF ItemDelegate::startFinishLine( const QPointF& start, const QPointF& end ) const 0536 { 0537 Q_UNUSED(start); 0538 0539 QPolygonF poly; 0540 qreal midx = end.x() + TURN; 0541 qreal midy = ( end.y()-start.y() )/2. + start.y(); 0542 0543 if ( start.x()-TURN > end.x()+TURN ) { 0544 poly << start 0545 << QPointF( midx, start.y() ) 0546 << QPointF( midx, end.y() ) 0547 << end; 0548 } else { 0549 poly << start 0550 << QPointF( start.x()-TURN, start.y() ) 0551 << QPointF( start.x()-TURN, midy ) 0552 << QPointF( midx, midy ) 0553 << QPointF( end.x()+TURN, end.y() ) 0554 << end; 0555 } 0556 return poly; 0557 } 0558 0559 QPolygonF ItemDelegate::startFinishArrow( const QPointF& start, const QPointF& end ) const 0560 { 0561 Q_UNUSED(start); 0562 0563 QPolygonF poly; 0564 poly << end 0565 << QPointF( end.x()+TURN/2., end.y()-TURN/2. ) 0566 << QPointF( end.x()+TURN/2., end.y()+TURN/2. ); 0567 return poly; 0568 } 0569 0570 0571 #include "moc_kganttitemdelegate.cpp"