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"