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"