kdganttdatetimegrid.cpp

Go to the documentation of this file.
00001 /****************************************************************************
00002  ** Copyright (C) 2001-2006 Klarälvdalens Datakonsult AB.  All rights reserved.
00003  **
00004  ** This file is part of the KD Gantt library.
00005  **
00006  ** This file may be distributed and/or modified under the terms of the
00007  ** GNU General Public License version 2 as published by the Free Software
00008  ** Foundation and appearing in the file LICENSE.GPL included in the
00009  ** packaging of this file.
00010  **
00011  ** Licensees holding valid commercial KD Gantt licenses may use this file in
00012  ** accordance with the KD Gantt Commercial License Agreement provided with
00013  ** the Software.
00014  **
00015  ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
00016  ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
00017  **
00018  ** See http://www.kdab.net/kdgantt for
00019  **   information about KD Gantt Commercial License Agreements.
00020  **
00021  ** Contact info@kdab.net if any conditions of this
00022  ** licensing are not clear to you.
00023  **
00024  **********************************************************************/
00025 #include "kdganttdatetimegrid.h"
00026 #include "kdganttdatetimegrid_p.h"
00027 
00028 #include "kdganttabstractrowcontroller.h"
00029 
00030 #include <QApplication>
00031 #include <QDateTime>
00032 #include <QPainter>
00033 #include <QStyle>
00034 #include <QStyleOptionHeader>
00035 #include <QWidget>
00036 #include <QDebug>
00037 
00038 #include <cassert>
00039 
00040 using namespace KDGantt;
00041 
00042 QDebug operator<<( QDebug dbg, KDGantt::DateTimeScaleFormatter::Range range )
00043 {
00044     switch( range ) {
00045     case KDGantt::DateTimeScaleFormatter::Second: dbg << "KDGantt::DateTimeScaleFormatter::Second"; break;
00046     case KDGantt::DateTimeScaleFormatter::Minute: dbg << "KDGantt::DateTimeScaleFormatter::Minute"; break;
00047     case KDGantt::DateTimeScaleFormatter::Hour:   dbg << "KDGantt::DateTimeScaleFormatter::Hour"; break;
00048     case KDGantt::DateTimeScaleFormatter::Day:    dbg << "KDGantt::DateTimeScaleFormatter::Day"; break;
00049     case KDGantt::DateTimeScaleFormatter::Week:   dbg << "KDGantt::DateTimeScaleFormatter::Week"; break;
00050     case KDGantt::DateTimeScaleFormatter::Month:  dbg << "KDGantt::DateTimeScaleFormatter::Month"; break;
00051     case KDGantt::DateTimeScaleFormatter::Year:   dbg << "KDGantt::DateTimeScaleFormatter::Year"; break;
00052     }
00053     return dbg;
00054 }
00055 
00056 
00064 // TODO: I think maybe this class should be responsible
00065 // for unit-transformation of the scene...
00066 
00067 qreal DateTimeGrid::Private::dateTimeToChartX( const QDateTime& dt ) const
00068 {
00069     assert( startDateTime.isValid() );
00070     qreal result = startDateTime.date().daysTo(dt.date())*24.*60.*60.;
00071     result += startDateTime.time().msecsTo(dt.time())/1000.;
00072     result *= dayWidth/( 24.*60.*60. );
00073 
00074     return result;
00075 }
00076 
00077 QDateTime DateTimeGrid::Private::chartXtoDateTime( qreal x ) const
00078 {
00079     assert( startDateTime.isValid() );
00080     int days = static_cast<int>( x/dayWidth );
00081     qreal secs = x*( 24.*60.*60. )/dayWidth;
00082     QDateTime dt = startDateTime;
00083     QDateTime result = dt.addDays( days )
00084                        .addSecs( static_cast<int>(secs-(days*24.*60.*60.) ) )
00085                        .addMSecs( qRound( ( secs-static_cast<int>( secs ) )*1000. ) );
00086     return result;
00087 }
00088 
00089 #define d d_func()
00090 
00118 DateTimeScaleFormatter::DateTimeScaleFormatter( Range range, const QString& format,
00119                                                 const QString& templ, Qt::Alignment alignment )
00120     : _d( new Private( range, format, templ, alignment ) )
00121 {
00122 }
00123 
00124 DateTimeScaleFormatter::DateTimeScaleFormatter( Range range, const QString& format, Qt::Alignment alignment )
00125     : _d( new Private( range, format, QString::fromLatin1( "%1" ), alignment ) )
00126 {
00127 }
00128 
00129 DateTimeScaleFormatter::DateTimeScaleFormatter( const DateTimeScaleFormatter& other )
00130     : _d( new Private( other.range(), other.format(), other.d->templ, other.alignment() ) )
00131 {
00132 }
00133 
00134 DateTimeScaleFormatter::~DateTimeScaleFormatter()
00135 {
00136     delete _d;
00137 }
00138 
00139 DateTimeScaleFormatter& DateTimeScaleFormatter::operator=( const DateTimeScaleFormatter& other )
00140 {
00141     delete _d;
00142     _d = new Private( other.range(), other.format(), other.d->templ, other.alignment() );
00143     return *this;
00144 }
00145 
00148 QString DateTimeScaleFormatter::format() const
00149 {
00150     return d->format;
00151 }
00152 
00155 QString DateTimeScaleFormatter::format( const QDateTime& datetime ) const
00156 {
00157     QString result = d->format;
00158     // additional feature: Weeknumber
00159     const QString shortWeekNumber = QString::number( datetime.date().weekNumber() );
00160     const QString longWeekNumber = ( shortWeekNumber.length() == 1 ? QString::fromLatin1( "0" ) : QString() ) + shortWeekNumber;
00161     result.replace( QString::fromLatin1( "ww" ), longWeekNumber );
00162     result.replace( QString::fromLatin1( "w" ), shortWeekNumber );
00163     result = datetime.toLocalTime().toString( result );
00164     return result;
00165 }
00166 
00167 QString DateTimeScaleFormatter::text( const QDateTime& datetime ) const
00168 {
00169     return d->templ.arg( format( datetime ) );
00170 }
00171 
00174 DateTimeScaleFormatter::Range DateTimeScaleFormatter::range() const
00175 {
00176     return d->range;
00177 }
00178 
00179 Qt::Alignment DateTimeScaleFormatter::alignment() const
00180 {
00181     return d->alignment;
00182 }
00183 
00187 QDateTime DateTimeScaleFormatter::nextRangeBegin( const QDateTime& datetime ) const
00188 {
00189     QDateTime result = datetime;
00190     switch( d->range )
00191     {
00192     case Second:
00193         result = result.addSecs( 60 );
00194         break;
00195     case Minute:
00196         // set it to the begin of the next minute
00197         result.setTime( QTime( result.time().hour(), result.time().minute() ) );
00198         result = result.addSecs( 60 );
00199         break;
00200     case Hour:
00201         // set it to the begin of the next hour
00202         result.setTime( QTime( result.time().hour(), 0 ) );
00203         result = result.addSecs( 60 * 60 );
00204         break;
00205     case Day:
00206         // set it to midnight the next day
00207         result.setTime( QTime( 0, 0 ) );
00208         result = result.addDays( 1 );
00209         break;
00210     case Week:
00211         // set it to midnight
00212         result.setTime( QTime( 0, 0 ) );
00213         // iterate day-wise, until weekNumber changes
00214         {
00215             const int weekNumber = result.date().weekNumber();
00216             while( weekNumber == result.date().weekNumber() )
00217                 result = result.addDays( 1 );
00218         }
00219         break;
00220     case Month:
00221         // set it to midnight
00222         result.setTime( QTime( 0, 0 ) );
00223         // set it to the first of the next month
00224         result.setDate( QDate( result.date().year(), result.date().month(), 1 ).addMonths( 1 ) );
00225         break;
00226     case Year:
00227         // set it to midnight
00228         result.setTime( QTime( 0, 0 ) );
00229         // set it to the first of the next year
00230         result.setDate( QDate( result.date().year(), 1, 1 ).addYears( 1 ) );
00231         break;
00232     }
00233     //result = result.toLocalTime();
00234     assert(  result != datetime );
00235     //qDebug() << "DateTimeScaleFormatter::nextRangeBegin("<<datetime<<")="<<d->range<<result;
00236     return result;
00237 }
00238 
00242 QDateTime DateTimeScaleFormatter::currentRangeBegin( const QDateTime& datetime ) const
00243 {
00244     QDateTime result = datetime;
00245     switch( d->range )
00246     {
00247     case Second:
00248         break; // nothing
00249     case Minute:
00250         // set it to the begin of the current minute
00251         result.setTime( QTime( result.time().hour(), result.time().minute() ) );
00252         break;
00253     case Hour:
00254         // set it to the begin of the current hour
00255         result.setTime( QTime( result.time().hour(), 0 ) );
00256         break;
00257     case Day:
00258         // set it to midnight the current day
00259         result.setTime( QTime( 0, 0 ) );
00260         break;
00261     case Week:
00262         // set it to midnight
00263         result.setTime( QTime( 0, 0 ) );
00264         // iterate day-wise, as long weekNumber is the same
00265         {
00266             const int weekNumber = result.date().weekNumber();
00267             while( weekNumber == result.date().addDays( -1 ).weekNumber() )
00268                 result = result.addDays( -1 );
00269         }
00270         break;
00271     case Month:
00272         // set it to midnight
00273         result.setTime( QTime( 0, 0 ) );
00274         // set it to the first of the current month
00275         result.setDate( QDate( result.date().year(), result.date().month(), 1 ) );
00276         break;
00277     case Year:
00278         // set it to midnight
00279         result.setTime( QTime( 0, 0 ) );
00280         // set it to the first of the current year
00281         result.setDate( QDate( result.date().year(), 1, 1 ) );
00282         break;
00283     }
00284     return result;
00285 }
00286 
00287 DateTimeGrid::DateTimeGrid() : AbstractGrid( new Private )
00288 {
00289 }
00290 
00291 DateTimeGrid::~DateTimeGrid()
00292 {
00293 }
00294 
00299 QDateTime DateTimeGrid::startDateTime() const
00300 {
00301     return d->startDateTime;
00302 }
00303 
00309 void DateTimeGrid::setStartDateTime( const QDateTime& dt )
00310 {
00311     d->startDateTime = dt;
00312     emit gridChanged();
00313 }
00314 
00319 qreal DateTimeGrid::dayWidth() const
00320 {
00321     return d->dayWidth;
00322 }
00323 
00326 qreal DateTimeGrid::mapFromDateTime( const QDateTime& dt) const
00327 {
00328     return d->dateTimeToChartX( dt );
00329 }
00330 
00333 QDateTime DateTimeGrid::mapToDateTime( qreal x ) const
00334 {
00335     return d->chartXtoDateTime( x );
00336 }
00337 
00342 void DateTimeGrid::setDayWidth( qreal w )
00343 {
00344     assert( w>0 );
00345     d->dayWidth = w;
00346     emit gridChanged();
00347 }
00348 
00354 void DateTimeGrid::setScale( Scale s )
00355 {
00356     d->scale = s;
00357     emit gridChanged();
00358 }
00359 
00366 DateTimeGrid::Scale DateTimeGrid::scale() const
00367 {
00368     return d->scale;
00369 }
00370 
00378 void DateTimeGrid::setUserDefinedLowerScale( DateTimeScaleFormatter* lower )
00379 {
00380     delete d->lower;
00381     d->lower = lower;
00382     emit gridChanged();
00383 }
00384 
00392 void DateTimeGrid::setUserDefinedUpperScale( DateTimeScaleFormatter* upper )
00393 {
00394     delete d->upper;
00395     d->upper = upper;
00396     emit gridChanged();
00397 }
00398 
00401 DateTimeScaleFormatter* DateTimeGrid::userDefinedLowerScale() const
00402 {
00403     return d->lower;
00404 }
00405 
00408 DateTimeScaleFormatter* DateTimeGrid::userDefinedUpperScale() const
00409 {
00410     return d->upper;
00411 }
00412 
00418 void DateTimeGrid::setWeekStart( Qt::DayOfWeek ws )
00419 {
00420     d->weekStart = ws;
00421     emit gridChanged();
00422 }
00423 
00425 Qt::DayOfWeek DateTimeGrid::weekStart() const
00426 {
00427     return d->weekStart;
00428 }
00429 
00436 void DateTimeGrid::setFreeDays( const QSet<Qt::DayOfWeek>& fd )
00437 {
00438     d->freeDays = fd;
00439     emit gridChanged();
00440 }
00441 
00443 QSet<Qt::DayOfWeek> DateTimeGrid::freeDays() const
00444 {
00445     return d->freeDays;
00446 }
00447 
00449 bool DateTimeGrid::rowSeparators() const
00450 {
00451     return d->rowSeparators;
00452 }
00454 void DateTimeGrid::setRowSeparators( bool enable )
00455 {
00456     d->rowSeparators = enable;
00457 }
00458 
00463 void DateTimeGrid::setNoInformationBrush( const QBrush& brush )
00464 {
00465     d->noInformationBrush = brush;
00466     emit gridChanged();
00467 }
00468 
00471 QBrush DateTimeGrid::noInformationBrush() const
00472 {
00473     return d->noInformationBrush;
00474 }
00475 
00479 Span DateTimeGrid::mapToChart( const QModelIndex& idx ) const
00480 {
00481     assert( model() );
00482     if ( !idx.isValid() ) return Span();
00483     assert( idx.model()==model() );
00484     const QVariant sv = model()->data( idx, StartTimeRole );
00485     const QVariant ev = model()->data( idx, EndTimeRole );
00486     if( qVariantCanConvert<QDateTime>(sv) &&
00487     qVariantCanConvert<QDateTime>(ev) &&
00488     !(sv.type() == QVariant::String && qVariantValue<QString>(sv).isEmpty()) &&
00489     !(ev.type() == QVariant::String && qVariantValue<QString>(ev).isEmpty())
00490     ) {
00491       QDateTime st = sv.toDateTime();
00492       QDateTime et = ev.toDateTime();
00493       if ( et.isValid() && st.isValid() ) {
00494         qreal sx = d->dateTimeToChartX( st );
00495         qreal ex = d->dateTimeToChartX( et )-sx;
00496         //qDebug() << "DateTimeGrid::mapToChart("<<st<<et<<") => "<< Span( sx, ex );
00497         return Span( sx, ex);
00498       }
00499     }
00500     // Special case for Events with only a start date
00501     if( qVariantCanConvert<QDateTime>(sv) && !(sv.type() == QVariant::String && qVariantValue<QString>(sv).isEmpty()) ) {
00502       QDateTime st = sv.toDateTime();
00503       if ( st.isValid() ) {
00504         qreal sx = d->dateTimeToChartX( st );
00505         return Span( sx, 0 );
00506       }
00507     }
00508     return Span();
00509 }
00510 
00511 #if 0
00512 static void debug_print_idx( const QModelIndex& idx )
00513 {
00514     if ( !idx.isValid() ) {
00515         qDebug() << "[Invalid]";
00516         return;
00517     }
00518     QDateTime st = idx.data( StartTimeRole ).toDateTime();
00519     QDateTime et = idx.data( StartTimeRole ).toDateTime();
00520     qDebug() << idx << "["<<st<<et<<"]";
00521 }
00522 #endif
00523 
00538 bool DateTimeGrid::mapFromChart( const Span& span, const QModelIndex& idx,
00539     const QList<Constraint>& constraints ) const
00540 {
00541     assert( model() );
00542     if ( !idx.isValid() ) return false;
00543     assert( idx.model()==model() );
00544 
00545     QDateTime st = d->chartXtoDateTime(span.start());
00546     QDateTime et = d->chartXtoDateTime(span.start()+span.length());
00547     //qDebug() << "DateTimeGrid::mapFromChart("<<span<<") => "<< st << et;
00548     Q_FOREACH( const Constraint& c, constraints ) {
00549         if ( c.type() != Constraint::TypeHard || !isSatisfiedConstraint( c )) continue;
00550         if ( c.startIndex() == idx ) {
00551             QDateTime tmpst = model()->data( c.endIndex(), StartTimeRole ).toDateTime();
00552             //qDebug() << tmpst << "<" << et <<"?";
00553             if ( tmpst<et ) return false;
00554         } else if ( c.endIndex() == idx ) {
00555             QDateTime tmpet = model()->data( c.startIndex(), EndTimeRole ).toDateTime();
00556             //qDebug() << tmpet << ">" << st <<"?";
00557             if ( tmpet>st ) return false;
00558         }
00559     }
00560     return model()->setData( idx, qVariantFromValue(st), StartTimeRole )
00561         && model()->setData( idx, qVariantFromValue(et), EndTimeRole );
00562 }
00563 
00564 Qt::PenStyle DateTimeGrid::Private::gridLinePenStyle( QDateTime dt, Private::HeaderType headerType ) const
00565 {
00566     switch ( headerType ) {
00567         case Private::HeaderHour:
00568             // Midnight
00569             if ( dt.time().hour() == 0 )
00570                 return Qt::SolidLine;
00571             return Qt::DashLine;
00572         case Private::HeaderDay:
00573             // First day of the week
00574             if ( dt.date().dayOfWeek() == weekStart )
00575                 return Qt::SolidLine;
00576             return Qt::DashLine;
00577         case Private::HeaderWeek:
00578             // First day of the month
00579             if ( dt.date().day() == 1 )
00580                 return Qt::SolidLine;
00581             // First day of the week
00582             if ( dt.date().dayOfWeek() == weekStart )
00583                 return Qt::DashLine;
00584             return Qt::NoPen;
00585         case Private::HeaderMonth:
00586             // First day of the year
00587             if ( dt.date().dayOfYear() == 1 )
00588                 return Qt::SolidLine;
00589             // First day of the month
00590             if ( dt.date().day() == 1 )
00591                 return Qt::DashLine;
00592             return Qt::NoPen;
00593         default:
00594             // Nothing to do here
00595             break;
00596    }
00597 
00598     // Default
00599     return Qt::NoPen;
00600 }
00601 
00602 QDateTime DateTimeGrid::Private::adjustDateTimeForHeader( QDateTime dt, Private::HeaderType headerType ) const
00603 {
00604     // In any case, set time to 00:00:00:00
00605     dt.setTime( QTime( 0, 0, 0, 0 ) );
00606 
00607     switch ( headerType ) {
00608         case Private::HeaderWeek:
00609             // Set day to beginning of the week
00610             while ( dt.date().dayOfWeek() != weekStart )
00611                 dt = dt.addDays( -1 );
00612             break;
00613         case Private::HeaderMonth:
00614             // Set day to beginning of the month
00615             dt = dt.addDays( 1 - dt.date().day() );
00616             break;
00617         case Private::HeaderYear:
00618             // Set day to first day of the year
00619             dt = dt.addDays( 1 - dt.date().dayOfYear() );
00620             break;
00621         default:
00622             // In any other case, we don't need to adjust the date time
00623             break;
00624     }
00625 
00626     return dt;
00627 }
00628 
00629 void DateTimeGrid::Private::paintVerticalLines( QPainter* painter,
00630                                                 const QRectF& sceneRect,
00631                                                 const QRectF& exposedRect,
00632                                                 QWidget* widget,
00633                                                 Private::HeaderType headerType )
00634 {
00635         QDateTime dt = chartXtoDateTime( exposedRect.left() );
00636         dt = adjustDateTimeForHeader( dt, headerType );
00637 
00638         int offsetSeconds = 0;
00639         int offsetDays = 0;
00640         // Determine the time step per grid line
00641         if ( headerType == Private::HeaderHour )
00642             offsetSeconds = 60*60;
00643         else
00644             offsetDays = 1;
00645 
00646         for ( qreal x = dateTimeToChartX( dt ); x < exposedRect.right();
00647               dt = dt.addSecs( offsetSeconds ), dt = dt.addDays( offsetDays ), x = dateTimeToChartX( dt ) ) {
00648             if ( x >= exposedRect.left() ) {
00649                 QPen pen = painter->pen();
00650                 pen.setBrush( QApplication::palette().dark() );
00651                 pen.setStyle( gridLinePenStyle( dt, headerType ) );
00652                 painter->setPen( pen );
00653                 if ( freeDays.contains( static_cast<Qt::DayOfWeek>( dt.date().dayOfWeek() ) ) ) {
00654                     painter->setBrush( widget?widget->palette().midlight()
00655                                        :QApplication::palette().midlight() );
00656                     painter->fillRect( QRectF( x, exposedRect.top(), dayWidth, exposedRect.height() ), painter->brush() );
00657                 }
00658                 painter->drawLine( QPointF( x, sceneRect.top() ), QPointF( x, sceneRect.bottom() ) );
00659             }
00660         }
00661 }
00662 
00663 void DateTimeGrid::Private::paintVerticalUserDefinedLines( QPainter* painter,
00664                                                            const QRectF& sceneRect,
00665                                                            const QRectF& exposedRect,
00666                                                            const DateTimeScaleFormatter* formatter,
00667                                                            QWidget* widget )
00668 {
00669     Q_UNUSED( widget );
00670     QDateTime dt = chartXtoDateTime( exposedRect.left() );
00671     dt = formatter->currentRangeBegin( dt );
00672     QPen pen = painter->pen();
00673     pen.setBrush( QApplication::palette().dark() );
00674     pen.setStyle( Qt::DashLine );
00675     painter->setPen( pen );
00676     for ( qreal x = dateTimeToChartX( dt ); x < exposedRect.right();
00677           dt = formatter->nextRangeBegin( dt ),x=dateTimeToChartX( dt ) ) {
00678         if ( x >= exposedRect.left() ) {
00679             // FIXME: Also fill area between this and the next vertical line to indicate free days? (Johannes)
00680             painter->drawLine( QPointF( x, sceneRect.top() ), QPointF( x, sceneRect.bottom() ) );
00681         }
00682     }
00683 }
00684 
00685 DateTimeGrid::Private::HeaderType DateTimeGrid::Private::headerTypeForScale( DateTimeGrid::Scale scale )
00686 {
00687     switch ( scale ) {
00688         case ScaleHour:
00689             return Private::HeaderHour;
00690         case ScaleDay:
00691             return Private::HeaderDay;
00692         case ScaleWeek:
00693             return Private::HeaderWeek;
00694         case ScaleMonth:
00695             return Private::HeaderMonth;
00696         default:
00697             // There are no specific header types for any other scale!
00698             assert( false );
00699             break;
00700     }
00701 }
00702 
00703 void DateTimeGrid::paintGrid( QPainter* painter,
00704                               const QRectF& sceneRect,
00705                               const QRectF& exposedRect,
00706                               AbstractRowController* rowController,
00707                               QWidget* widget )
00708 {
00709     // TODO: Support hours and weeks
00710     switch( scale() ) {
00711     case ScaleHour:
00712     case ScaleDay:
00713     case ScaleWeek:
00714     case ScaleMonth:
00715         d->paintVerticalLines( painter, sceneRect, exposedRect, widget, d->headerTypeForScale( scale() ) );
00716         break;
00717     case ScaleAuto: {
00718         const qreal tabw = QApplication::fontMetrics().width( QLatin1String( "XXXXX" ) );
00719         const qreal dayw = dayWidth();
00720         if ( dayw > 24*60*60*tabw ) {
00721             d->paintVerticalUserDefinedLines( painter, sceneRect, exposedRect, &d->minute_lower, widget );
00722         } else if ( dayw > 24*60*tabw ) {
00723             d->paintVerticalLines( painter, sceneRect, exposedRect, widget, Private::HeaderHour );
00724         } else if ( dayw > 24*tabw ) {
00725         d->paintVerticalLines( painter, sceneRect, exposedRect, widget, Private::HeaderDay );
00726         } else if ( dayw > tabw ) {
00727             d->paintVerticalUserDefinedLines( painter, sceneRect, exposedRect, &d->week_lower, widget );
00728         } else if ( 4*dayw > tabw ) {
00729             d->paintVerticalUserDefinedLines( painter, sceneRect, exposedRect, &d->month_lower, widget );
00730         } else {
00731             d->paintVerticalUserDefinedLines( painter, sceneRect, exposedRect, &d->year_lower, widget );
00732         }
00733         break;
00734     }
00735     case ScaleUserDefined:
00736         d->paintVerticalUserDefinedLines( painter, sceneRect, exposedRect, d->lower, widget );
00737         break;
00738     }
00739     if ( rowController ) {
00740         // First draw the rows
00741         QPen pen = painter->pen();
00742         pen.setBrush( QApplication::palette().dark() );
00743         pen.setStyle( Qt::DashLine );
00744         painter->setPen( pen );
00745         QModelIndex idx = rowController->indexAt( qRound( exposedRect.top() ) );
00746         if ( rowController->indexAbove( idx ).isValid() ) idx = rowController->indexAbove( idx );
00747         qreal y = 0;
00748         while ( y < exposedRect.bottom() && idx.isValid() ) {
00749             const Span s = rowController->rowGeometry( idx );
00750             y = s.start()+s.length();
00751             if ( d->rowSeparators ) {
00752                 painter->drawLine( QPointF( sceneRect.left(), y ),
00753                                    QPointF( sceneRect.right(), y ) );
00754             }
00755             if ( !idx.data( ItemTypeRole ).isValid() && d->noInformationBrush.style() != Qt::NoBrush ) {
00756                 painter->fillRect( QRectF( exposedRect.left(), s.start(), exposedRect.width(), s.length() ), d->noInformationBrush );
00757             }
00758             // Is alternating background better?
00759             //if ( idx.row()%2 ) painter->fillRect( QRectF( exposedRect.x(), s.start(), exposedRect.width(), s.length() ), QApplication::palette().alternateBase() );
00760             idx =  rowController->indexBelow( idx );
00761         }
00762     }
00763 }
00764 
00765 int DateTimeGrid::Private::tabHeight( const QString& txt, QWidget* widget ) const
00766 {
00767     QStyleOptionHeader opt;
00768     if ( widget ) opt.initFrom( widget );
00769     opt.text = txt;
00770     QStyle* style;
00771     if ( widget ) style = widget->style();
00772     else style = QApplication::style();
00773     QSize s = style->sizeFromContents(QStyle::CT_HeaderSection, &opt, QSize(), widget);
00774     return s.height();
00775 }
00776 
00777 void DateTimeGrid::Private::getAutomaticFormatters( DateTimeScaleFormatter** lower, DateTimeScaleFormatter** upper)
00778 {
00779     const qreal tabw = QApplication::fontMetrics().width( QLatin1String( "XXXXX" ) );
00780     const qreal dayw = dayWidth;
00781     if ( dayw > 24*60*60*tabw ) {
00782         *lower = &minute_lower;
00783         *upper = &minute_upper;
00784     } else if ( dayw > 24*60*tabw ) {
00785         *lower = &hour_lower;
00786         *upper = &hour_upper;
00787     } else if ( dayw > 24*tabw ) {
00788         *lower = &day_lower;
00789         *upper = &day_upper;
00790     } else if ( dayw > tabw ) {
00791         *lower = &week_lower;
00792         *upper = &week_upper;
00793     } else if ( 4*dayw > tabw ) {
00794         *lower = &month_lower;
00795         *upper = &month_upper;
00796     } else {
00797         *lower = &year_lower;
00798         *upper = &year_upper;
00799     }
00800 }
00801 
00802 
00803 void DateTimeGrid::paintHeader( QPainter* painter,  const QRectF& headerRect, const QRectF& exposedRect,
00804                                 qreal offset, QWidget* widget )
00805 {
00806     painter->save();
00807     QPainterPath clipPath;
00808     clipPath.addRect( headerRect );
00809     painter->setClipPath( clipPath, Qt::IntersectClip );
00810     switch( scale() )
00811     {
00812     case ScaleHour:
00813         paintHourScaleHeader( painter, headerRect, exposedRect, offset, widget );
00814         break;
00815     case ScaleDay:
00816         paintDayScaleHeader( painter, headerRect, exposedRect, offset, widget );
00817         break;
00818     case ScaleWeek:
00819         paintWeekScaleHeader( painter, headerRect, exposedRect, offset, widget );
00820         break;
00821     case ScaleMonth:
00822         paintMonthScaleHeader( painter, headerRect, exposedRect, offset, widget );
00823         break;
00824     case ScaleAuto:
00825         {
00826             DateTimeScaleFormatter *lower, *upper;
00827             d->getAutomaticFormatters( &lower, &upper );
00828             const qreal lowerHeight = d->tabHeight( lower->text( startDateTime() ) );
00829             const qreal upperHeight = d->tabHeight( upper->text( startDateTime() ) );
00830             const qreal upperRatio = upperHeight/( lowerHeight+upperHeight );
00831 
00832             const QRectF upperHeaderRect( headerRect.x(), headerRect.top(), headerRect.width()-1, headerRect.height() * upperRatio );
00833             const QRectF lowerHeaderRect( headerRect.x(), upperHeaderRect.bottom()+1, headerRect.width()-1,  headerRect.height()-upperHeaderRect.height()-1 );
00834 
00835             paintUserDefinedHeader( painter, lowerHeaderRect, exposedRect, offset, lower, widget );
00836             paintUserDefinedHeader( painter, upperHeaderRect, exposedRect, offset, upper, widget );
00837             break;
00838         }
00839     case ScaleUserDefined:
00840         {
00841             const qreal lowerHeight = d->tabHeight( d->lower->text( startDateTime() ) );
00842             const qreal upperHeight = d->tabHeight( d->upper->text( startDateTime() ) );
00843             const qreal upperRatio = upperHeight/( lowerHeight+upperHeight );
00844 
00845             const QRectF upperHeaderRect( headerRect.x(), headerRect.top(), headerRect.width()-1, headerRect.height() * upperRatio );
00846             const QRectF lowerHeaderRect( headerRect.x(), upperHeaderRect.bottom()+1, headerRect.width()-1,  headerRect.height()-upperHeaderRect.height()-1 );
00847 
00848             paintUserDefinedHeader( painter, lowerHeaderRect, exposedRect, offset, d->lower, widget );
00849             paintUserDefinedHeader( painter, upperHeaderRect, exposedRect, offset, d->upper, widget );
00850         }
00851         break;
00852     }
00853     painter->restore();
00854 }
00855 
00856 void DateTimeGrid::paintUserDefinedHeader( QPainter* painter,
00857                                            const QRectF& headerRect, const QRectF& exposedRect,
00858                                            qreal offset, const DateTimeScaleFormatter* formatter,
00859                                            QWidget* widget )
00860 {
00861     const QStyle* const style = widget ? widget->style() : QApplication::style();
00862 
00863     QDateTime dt = formatter->currentRangeBegin( d->chartXtoDateTime( offset + exposedRect.left() ) ).toUTC();
00864     qreal x = d->dateTimeToChartX( dt );
00865 
00866     while( x < exposedRect.right() + offset )
00867     {
00868         const QDateTime next = formatter->nextRangeBegin( dt );
00869         const qreal nextx = d->dateTimeToChartX( next );
00870 
00871         QStyleOptionHeader opt;
00872         if ( widget ) opt.init( widget );
00873         opt.rect = QRectF( x - offset+1, headerRect.top(), qMax( 1., nextx-x-1 ), headerRect.height() ).toAlignedRect();
00874         opt.textAlignment = formatter->alignment();
00875         opt.text = formatter->text( dt );
00876         style->drawControl( QStyle::CE_Header, &opt, painter, widget );
00877 
00878         dt = next;
00879         x = nextx;
00880     }
00881 }
00882 
00883 void DateTimeGrid::Private::paintHeader( QPainter* painter,
00884                                          const QRectF& headerRect, const QRectF& exposedRect,
00885                                          qreal offset, QWidget* widget,
00886                                          Private::HeaderType headerType,
00887                                          DateTextFormatter *formatter )
00888 {
00889     QStyle* style = widget?widget->style():QApplication::style();
00890 
00891     const qreal left = exposedRect.left() + offset;
00892     const qreal right = exposedRect.right() + offset;
00893 
00894     // Paint a section for each hour
00895     QDateTime dt = chartXtoDateTime( left );
00896     dt = adjustDateTimeForHeader( dt, headerType );
00897     // Determine the time step per grid line
00898     int offsetSeconds = 0;
00899     int offsetDays = 0;
00900     int offsetMonths = 0;
00901     
00902     switch ( headerType ) {
00903         case Private::HeaderHour:
00904             offsetSeconds = 60*60;
00905             break;
00906         case Private::HeaderDay:
00907             offsetDays = 1;
00908             break;
00909         case Private::HeaderWeek:
00910             offsetDays = 7;
00911             break;
00912         case Private::HeaderMonth:
00913             offsetMonths = 1;
00914             break;
00915         case Private::HeaderYear:
00916             offsetMonths = 12;
00917             break;
00918         default:
00919             // Other scales cannot be painted with this method!
00920             assert( false );
00921             break;
00922     }
00923 
00924     for ( qreal x = dateTimeToChartX( dt ); x < right;
00925           dt = dt.addSecs( offsetSeconds ), dt = dt.addDays( offsetDays ), dt = dt.addMonths( offsetMonths ),
00926           x = dateTimeToChartX( dt ) ) {
00927         QStyleOptionHeader opt;
00928         if ( widget ) opt.init( widget );
00929         opt.rect = formatter->textRect( x, offset, dayWidth, headerRect, dt );
00930         opt.text = formatter->format( dt );
00931         opt.textAlignment = Qt::AlignCenter;
00932         style->drawControl(QStyle::CE_Header, &opt, painter, widget);
00933     }
00934 }
00935 
00939 void DateTimeGrid::paintHourScaleHeader( QPainter* painter,
00940                                          const QRectF& headerRect, const QRectF& exposedRect,
00941                                          qreal offset, QWidget* widget )
00942 {
00943     class HourFormatter : public Private::DateTextFormatter {
00944     public:
00945         QString format( const QDateTime& dt ) {
00946             return dt.time().toString( QString::fromAscii( "hh" ) );
00947         }
00948         QRect textRect( qreal x, qreal offset, qreal dayWidth, const QRectF& headerRect, const QDateTime& dt ) {
00949             return QRectF( QPointF( x, headerRect.top() ) + QPointF( -offset + 1.0, headerRect.height() / 2.0 ),
00950                            QSizeF( dayWidth / 24.0, headerRect.height() / 2.0 ) ).toAlignedRect();
00951         }
00952     };
00953     d->paintHeader( painter, headerRect, exposedRect, offset, widget, // General parameters
00954                     Private::HeaderHour, new HourFormatter ); // Custom parameters
00955 
00956     class DayFormatter : public Private::DateTextFormatter {
00957     public:
00958         QString format( const QDateTime& dt ) {
00959             return dt.date().toString();
00960         }
00961         QRect textRect( qreal x, qreal offset, qreal dayWidth, const QRectF& headerRect, const QDateTime& dt ) {
00962             return QRectF( QPointF( x, headerRect.top() ) + QPointF( -offset, 0.0 ),
00963                            QSizeF( dayWidth, headerRect.height() / 2.0 ) ).toRect();
00964         }
00965     };
00966     d->paintHeader( painter, headerRect, exposedRect, offset, widget, // General parameters
00967                     Private::HeaderDay, new DayFormatter ); // Custom parameters
00968 }
00969 
00973 void DateTimeGrid::paintDayScaleHeader( QPainter* painter,  const QRectF& headerRect, const QRectF& exposedRect,
00974                                 qreal offset, QWidget* widget )
00975 {
00976     class DayFormatter : public Private::DateTextFormatter {
00977     public:
00978         QString format( const QDateTime& dt ) {
00979             return dt.toString( QString::fromAscii( "ddd" ) ).left( 1 );
00980         }
00981         QRect textRect( qreal x, qreal offset, qreal dayWidth, const QRectF& headerRect, const QDateTime& dt ) {
00982             return QRectF( QPointF( x, headerRect.top() ) + QPointF( -offset + 1.0, headerRect.height() / 2.0 ),
00983                            QSizeF( dayWidth, headerRect.height() / 2.0 ) ).toAlignedRect();
00984         }
00985     };
00986     d->paintHeader( painter, headerRect, exposedRect, offset, widget, // General parameters
00987                     Private::HeaderDay, new DayFormatter ); // Custom parameters
00988 
00989     class WeekFormatter : public Private::DateTextFormatter {
00990     public:
00991         QString format( const QDateTime& dt ) {
00992             return QString::number( dt.date().weekNumber() );
00993         }
00994         QRect textRect( qreal x, qreal offset, qreal dayWidth, const QRectF& headerRect, const QDateTime& dt ) {
00995             return QRectF( QPointF( x, headerRect.top() ) + QPointF( -offset, 0.0 ),
00996                            QSizeF( dayWidth * 7, headerRect.height() / 2.0 ) ).toRect();
00997         }
00998     };
00999     d->paintHeader( painter, headerRect, exposedRect, offset, widget, // General parameters
01000                     Private::HeaderWeek, new WeekFormatter ); // Custom parameters
01001 }
01002 
01006 void DateTimeGrid::paintWeekScaleHeader( QPainter* painter,  const QRectF& headerRect, const QRectF& exposedRect,
01007                                         qreal offset, QWidget* widget )
01008 {
01009     class WeekFormatter : public Private::DateTextFormatter {
01010     public:
01011         QString format( const QDateTime& dt ) {
01012             return QString::number( dt.date().weekNumber() );
01013         }
01014         QRect textRect( qreal x, qreal offset, qreal dayWidth, const QRectF& headerRect, const QDateTime& dt ) {
01015             return QRectF( QPointF( x, headerRect.top() ) + QPointF( -offset, headerRect.height() / 2.0 ),
01016                            QSizeF( dayWidth * 7, headerRect.height() / 2.0 ) ).toRect();
01017         }
01018     };
01019     d->paintHeader( painter, headerRect, exposedRect, offset, widget, // General parameters
01020                     Private::HeaderWeek, new WeekFormatter ); // Custom parameters
01021 
01022     class MonthFormatter : public Private::DateTextFormatter {
01023     public:
01024         QString format( const QDateTime& dt ) {
01025             return QDate::longMonthName( dt.date().month() );
01026         }
01027         QRect textRect( qreal x, qreal offset, qreal dayWidth, const QRectF& headerRect, const QDateTime& dt ) {
01028             return QRectF( QPointF( x, headerRect.top() ) + QPointF( -offset, 0.0 ),
01029                            QSizeF( dayWidth * dt.date().daysInMonth(), headerRect.height() / 2.0 ) ).toRect();
01030         }
01031     };
01032     d->paintHeader( painter, headerRect, exposedRect, offset, widget, // General parameters
01033                     Private::HeaderMonth, new MonthFormatter ); // Custom parameters
01034 }
01035 
01039 void DateTimeGrid::paintMonthScaleHeader( QPainter* painter,  const QRectF& headerRect, const QRectF& exposedRect,
01040                                         qreal offset, QWidget* widget )
01041 {
01042     class MonthFormatter : public Private::DateTextFormatter {
01043     public:
01044         QString format( const QDateTime& dt ) {
01045             return QDate::shortMonthName( dt.date().month() );
01046         }
01047         QRect textRect( qreal x, qreal offset, qreal dayWidth, const QRectF& headerRect, const QDateTime& dt ) {
01048             return QRectF( QPointF( x, headerRect.top() ) + QPointF( -offset, headerRect.height() / 2.0 ),
01049                            QSizeF( dayWidth * dt.date().daysInMonth(), headerRect.height() / 2.0 ) ).toRect();
01050         }
01051     };
01052     d->paintHeader( painter, headerRect, exposedRect, offset, widget, // General parameters
01053                     Private::HeaderMonth, new MonthFormatter ); // Custom parameters
01054 
01055     class YearFormatter : public Private::DateTextFormatter {
01056     public:
01057         QString format( const QDateTime& dt ) {
01058             return QString::number( dt.date().year() );
01059         }
01060         QRect textRect( qreal x, qreal offset, qreal dayWidth, const QRectF& headerRect, const QDateTime& dt ) {
01061             return QRectF( QPointF( x, headerRect.top() ) + QPointF( -offset, 0.0 ),
01062                            QSizeF( dayWidth * dt.date().daysInYear(), headerRect.height() / 2.0 ) ).toRect();
01063         }
01064     };
01065     d->paintHeader( painter, headerRect, exposedRect, offset, widget, // General parameters
01066                     Private::HeaderYear, new YearFormatter ); // Custom parameters
01067 }
01068 
01069 #undef d
01070 
01071 #ifndef KDAB_NO_UNIT_TESTS
01072 
01073 #include <QStandardItemModel>
01074 #include "unittest/test.h"
01075 
01076 namespace {
01077     std::ostream& operator<<( std::ostream& os, const QDateTime& dt )
01078     {
01079 #ifdef QT_NO_STL
01080         os << dt.toString().toLatin1().constData();
01081 #else
01082         os << dt.toString().toStdString();
01083 #endif
01084         return os;
01085     }
01086 }
01087 
01088 KDAB_SCOPED_UNITTEST_SIMPLE( KDGantt, DateTimeGrid, "test" ) {
01089     QStandardItemModel model( 3, 2 );
01090     DateTimeGrid grid;
01091     QDateTime dt = QDateTime::currentDateTime();
01092     grid.setModel( &model );
01093     QDateTime startdt = dt.addDays( -10 );
01094     grid.setStartDateTime( startdt );
01095 
01096     model.setData( model.index( 0, 0 ), dt,               StartTimeRole );
01097     model.setData( model.index( 0, 0 ), dt.addDays( 17 ), EndTimeRole );
01098 
01099     model.setData( model.index( 2, 0 ), dt.addDays( 18 ), StartTimeRole );
01100     model.setData( model.index( 2, 0 ), dt.addDays( 19 ), EndTimeRole );
01101 
01102     Span s = grid.mapToChart( model.index( 0, 0 ) );
01103     //qDebug() << "span="<<s;
01104 
01105     assertTrue( s.start()>0 );
01106     assertTrue( s.length()>0 );
01107 
01108     assertTrue( startdt == grid.mapToDateTime( grid.mapFromDateTime( startdt ) ) );
01109 
01110     grid.mapFromChart( s, model.index( 1, 0 ) );
01111 
01112     QDateTime s1 = model.data( model.index( 0, 0 ), StartTimeRole ).toDateTime();
01113     QDateTime e1 = model.data( model.index( 0, 0 ), EndTimeRole ).toDateTime();
01114     QDateTime s2 = model.data( model.index( 1, 0 ), StartTimeRole ).toDateTime();
01115     QDateTime e2 = model.data( model.index( 1, 0 ), EndTimeRole ).toDateTime();
01116 
01117     assertTrue( s1.isValid() );
01118     assertTrue( e1.isValid() );
01119     assertTrue( s2.isValid() );
01120     assertTrue( e2.isValid() );
01121 
01122     assertEqual( s1, s2 );
01123     assertEqual( e1, e2 );
01124 
01125     assertTrue( grid.isSatisfiedConstraint( Constraint( model.index( 0, 0 ), model.index( 2, 0 ) ) ) );
01126     assertFalse( grid.isSatisfiedConstraint( Constraint( model.index( 2, 0 ), model.index( 0, 0 ) ) ) );
01127 
01128     s = grid.mapToChart( model.index( 0, 0 ) );
01129     s.setEnd( s.end()+100000. );
01130     bool rc = grid.mapFromChart( s, model.index( 0, 0 ) );
01131     assertTrue( rc );
01132     assertEqual( s1, model.data( model.index( 0, 0 ), StartTimeRole ).toDateTime() );
01133     Span newspan = grid.mapToChart( model.index( 0, 0 ) );
01134     assertEqual( newspan.start(), s.start() );
01135     assertEqual( newspan.length(), s.length() );
01136 
01137     {
01138         QDateTime startDateTime = QDateTime::currentDateTime();
01139         qreal dayWidth = 100;
01140         QDate currentDate = QDate::currentDate();
01141         QDateTime dt( QDate(currentDate.year(), 1, 1),  QTime( 0, 0, 0, 0 ) );
01142         assert( dt.isValid() );
01143         qreal result = startDateTime.date().daysTo(dt.date())*24.*60.*60.;
01144         result += startDateTime.time().msecsTo(dt.time())/1000.;
01145         result *= dayWidth/( 24.*60.*60. );
01146 
01147         int days = static_cast<int>( result/dayWidth );
01148         qreal secs = result*( 24.*60.*60. )/dayWidth;
01149         QDateTime dt2 = startDateTime;
01150         QDateTime result2 = dt2.addDays( days ).addSecs( static_cast<int>(secs-(days*24.*60.*60.) ) ).addMSecs( qRound( ( secs-static_cast<int>( secs ) )*1000. ) );
01151 
01152         assertEqual( dt, result2 );
01153     }
01154 }
01155 
01156 #endif /* KDAB_NO_UNIT_TESTS */
01157 
01158 #include "moc_kdganttdatetimegrid.cpp"

Generated on Thu Mar 4 23:19:13 2010 for KD Chart 2 by  doxygen 1.5.4