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