Fix assert when Mail-Followup-To contains two emails, as Ossi's Mutt does
[kdepim.git] / kdgantt2 / kdganttdatetimegrid.cpp
bloba691358e479118798c67dd8179508bdc75da1ca7
1 /****************************************************************************
2 ** Copyright (C) 2001-2006 Klarälvdalens Datakonsult AB. All rights reserved.
3 **
4 ** This file is part of the KD Gantt library.
5 **
6 ** This file may be distributed and/or modified under the terms of the
7 ** GNU General Public License version 2 as published by the Free Software
8 ** Foundation and appearing in the file LICENSE.GPL included in the
9 ** packaging of this file.
11 ** Licensees holding valid commercial KD Gantt licenses may use this file in
12 ** accordance with the KD Gantt Commercial License Agreement provided with
13 ** the Software.
15 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
16 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
18 ** See http://www.kdab.net/kdgantt for
19 ** information about KD Gantt Commercial License Agreements.
21 ** Contact info@kdab.net if any conditions of this
22 ** licensing are not clear to you.
24 **********************************************************************/
25 #include "kdganttdatetimegrid.h"
26 #include "kdganttdatetimegrid_p.h"
28 #include "kdganttabstractrowcontroller.h"
30 #include <QApplication>
31 #include <QDateTime>
32 #include <QPainter>
33 #include <QStyle>
34 #include <QStyleOptionHeader>
35 #include <QWidget>
36 #include <QDebug>
38 #include <cassert>
40 using namespace KDGantt;
42 QDebug operator<<( QDebug dbg, KDGantt::DateTimeScaleFormatter::Range range )
44 switch( range ) {
45 case KDGantt::DateTimeScaleFormatter::Second: dbg << "KDGantt::DateTimeScaleFormatter::Second"; break;
46 case KDGantt::DateTimeScaleFormatter::Minute: dbg << "KDGantt::DateTimeScaleFormatter::Minute"; break;
47 case KDGantt::DateTimeScaleFormatter::Hour: dbg << "KDGantt::DateTimeScaleFormatter::Hour"; break;
48 case KDGantt::DateTimeScaleFormatter::Day: dbg << "KDGantt::DateTimeScaleFormatter::Day"; break;
49 case KDGantt::DateTimeScaleFormatter::Week: dbg << "KDGantt::DateTimeScaleFormatter::Week"; break;
50 case KDGantt::DateTimeScaleFormatter::Month: dbg << "KDGantt::DateTimeScaleFormatter::Month"; break;
51 case KDGantt::DateTimeScaleFormatter::Year: dbg << "KDGantt::DateTimeScaleFormatter::Year"; break;
53 return dbg;
57 /*!\class KDGantt::DateTimeGrid
58 * \ingroup KDGantt
60 * This implementation of AbstractGrid works with QDateTime
61 * and shows days and week numbers in the header
64 // TODO: I think maybe this class should be responsible
65 // for unit-transformation of the scene...
67 qreal DateTimeGrid::Private::dateTimeToChartX( const QDateTime& dt ) const
69 assert( startDateTime.isValid() );
70 qreal result = startDateTime.date().daysTo(dt.date())*24.*60.*60.;
71 result += startDateTime.time().msecsTo(dt.time())/1000.;
72 result *= dayWidth/( 24.*60.*60. );
74 return result;
77 QDateTime DateTimeGrid::Private::chartXtoDateTime( qreal x ) const
79 assert( startDateTime.isValid() );
80 int days = static_cast<int>( x/dayWidth );
81 qreal secs = x*( 24.*60.*60. )/dayWidth;
82 QDateTime dt = startDateTime;
83 QDateTime result = dt.addDays( days )
84 .addSecs( static_cast<int>(secs-(days*24.*60.*60.) ) )
85 .addMSecs( qRound( ( secs-static_cast<int>( secs ) )*1000. ) );
86 return result;
89 #define d d_func()
91 /*!\class KDGantt::DateTimeScaleFormatter
92 * \ingroup KDGantt
94 * This class formats dates and times used in DateTimeGrid follawing a given format.
96 * The format follows the format of QDateTime::toString(), with one addition:
97 * "w" is replaced with the week number of the date as number without a leading zero (1-53)
98 * "ww" is replaced with the week number of the date as number with a leading zero (01-53)
100 * For example:
102 * \code
103 * // formatter to print the complete date over the current week
104 * // This leads to the first day of the week being printed
105 * DateTimeScaleFormatter formatter = DateTimeScaleFormatter( DateTimeScaleFormatter::Week, "yyyy-MM-dd" );
106 * \endcode
108 * Optionally, you can set an user defined text alignment flag. The default value is Qt::AlignCenter.
109 * \sa DateTimeScaleFormatter::DateTimeScaleFormatter
111 * This class even controls the range of the grid sections.
112 * \sa KDGanttDateTimeScaleFormatter::Range
115 /*! Creates a DateTimeScaleFormatter using \a range and \a format.
116 * The text on the header is aligned following \a alignment.
118 DateTimeScaleFormatter::DateTimeScaleFormatter( Range range, const QString& format,
119 const QString& templ, Qt::Alignment alignment )
120 : _d( new Private( range, format, templ, alignment ) )
124 DateTimeScaleFormatter::DateTimeScaleFormatter( Range range, const QString& format, Qt::Alignment alignment )
125 : _d( new Private( range, format, QString::fromLatin1( "%1" ), alignment ) )
129 DateTimeScaleFormatter::DateTimeScaleFormatter( const DateTimeScaleFormatter& other )
130 : _d( new Private( other.range(), other.format(), other.d->templ, other.alignment() ) )
134 DateTimeScaleFormatter::~DateTimeScaleFormatter()
136 delete _d;
139 DateTimeScaleFormatter& DateTimeScaleFormatter::operator=( const DateTimeScaleFormatter& other )
141 delete _d;
142 _d = new Private( other.range(), other.format(), other.d->templ, other.alignment() );
143 return *this;
146 /*! \returns The format being used for formatting dates and times.
148 QString DateTimeScaleFormatter::format() const
150 return d->format;
153 /*! \returns The \a datetime as string respecting the format.
155 QString DateTimeScaleFormatter::format( const QDateTime& datetime ) const
157 QString result = d->format;
158 // additional feature: Weeknumber
159 const QString shortWeekNumber = QString::number( datetime.date().weekNumber() );
160 const QString longWeekNumber = ( shortWeekNumber.length() == 1 ? QString::fromLatin1( "0" ) : QString() ) + shortWeekNumber;
161 result.replace( QString::fromLatin1( "ww" ), longWeekNumber );
162 result.replace( QString::fromLatin1( "w" ), shortWeekNumber );
163 result = datetime.toLocalTime().toString( result );
164 return result;
167 QString DateTimeScaleFormatter::text( const QDateTime& datetime ) const
169 return d->templ.arg( format( datetime ) );
172 /*! \returns The range of each item on a DateTimeGrid header.
173 * \sa DateTimeScaleFormatter::Range */
174 DateTimeScaleFormatter::Range DateTimeScaleFormatter::range() const
176 return d->range;
179 Qt::Alignment DateTimeScaleFormatter::alignment() const
181 return d->alignment;
184 /*! \returns the QDateTime being the begin of the range after the one containing \a datetime
185 * \sa currentRangeBegin
187 QDateTime DateTimeScaleFormatter::nextRangeBegin( const QDateTime& datetime ) const
189 QDateTime result = datetime;
190 switch( d->range )
192 case Second:
193 result = result.addSecs( 60 );
194 break;
195 case Minute:
196 // set it to the begin of the next minute
197 result.setTime( QTime( result.time().hour(), result.time().minute() ) );
198 result = result.addSecs( 60 );
199 break;
200 case Hour:
201 // set it to the begin of the next hour
202 result.setTime( QTime( result.time().hour(), 0 ) );
203 result = result.addSecs( 60 * 60 );
204 break;
205 case Day:
206 // set it to midnight the next day
207 result.setTime( QTime( 0, 0 ) );
208 result = result.addDays( 1 );
209 break;
210 case Week:
211 // set it to midnight
212 result.setTime( QTime( 0, 0 ) );
213 // iterate day-wise, until weekNumber changes
215 const int weekNumber = result.date().weekNumber();
216 while( weekNumber == result.date().weekNumber() )
217 result = result.addDays( 1 );
219 break;
220 case Month:
221 // set it to midnight
222 result.setTime( QTime( 0, 0 ) );
223 // set it to the first of the next month
224 result.setDate( QDate( result.date().year(), result.date().month(), 1 ).addMonths( 1 ) );
225 break;
226 case Year:
227 // set it to midnight
228 result.setTime( QTime( 0, 0 ) );
229 // set it to the first of the next year
230 result.setDate( QDate( result.date().year(), 1, 1 ).addYears( 1 ) );
231 break;
233 //result = result.toLocalTime();
234 assert( result != datetime );
235 //qDebug() << "DateTimeScaleFormatter::nextRangeBegin("<<datetime<<")="<<d->range<<result;
236 return result;
239 /*! \returns the QDateTime being the begin of the range containing \a datetime
240 * \sa nextRangeBegin
242 QDateTime DateTimeScaleFormatter::currentRangeBegin( const QDateTime& datetime ) const
244 QDateTime result = datetime;
245 switch( d->range )
247 case Second:
248 break; // nothing
249 case Minute:
250 // set it to the begin of the current minute
251 result.setTime( QTime( result.time().hour(), result.time().minute() ) );
252 break;
253 case Hour:
254 // set it to the begin of the current hour
255 result.setTime( QTime( result.time().hour(), 0 ) );
256 break;
257 case Day:
258 // set it to midnight the current day
259 result.setTime( QTime( 0, 0 ) );
260 break;
261 case Week:
262 // set it to midnight
263 result.setTime( QTime( 0, 0 ) );
264 // iterate day-wise, as long weekNumber is the same
266 const int weekNumber = result.date().weekNumber();
267 while( weekNumber == result.date().addDays( -1 ).weekNumber() )
268 result = result.addDays( -1 );
270 break;
271 case Month:
272 // set it to midnight
273 result.setTime( QTime( 0, 0 ) );
274 // set it to the first of the current month
275 result.setDate( QDate( result.date().year(), result.date().month(), 1 ) );
276 break;
277 case Year:
278 // set it to midnight
279 result.setTime( QTime( 0, 0 ) );
280 // set it to the first of the current year
281 result.setDate( QDate( result.date().year(), 1, 1 ) );
282 break;
284 return result;
287 DateTimeGrid::DateTimeGrid() : AbstractGrid( new Private )
291 DateTimeGrid::~DateTimeGrid()
295 /*! \returns The QDateTime used as start date for the grid.
297 * The default is three days before the current date.
299 QDateTime DateTimeGrid::startDateTime() const
301 return d->startDateTime;
304 /*! \param dt The start date of the grid. It is used as the beginning of the
305 * horizontal scrollbar in the view.
307 * Emits gridChanged() after the start date has changed.
309 void DateTimeGrid::setStartDateTime( const QDateTime& dt )
311 d->startDateTime = dt;
312 emit gridChanged();
315 /*! \returns The width in pixels for each day in the grid.
317 * The default is 100 pixels.
319 qreal DateTimeGrid::dayWidth() const
321 return d->dayWidth;
324 /*! Maps a given point in time \a dt to an X value in the scene.
326 qreal DateTimeGrid::mapFromDateTime( const QDateTime& dt) const
328 return d->dateTimeToChartX( dt );
331 /*! Maps a given X value \a x in scene coordinates to a point in time.
333 QDateTime DateTimeGrid::mapToDateTime( qreal x ) const
335 return d->chartXtoDateTime( x );
338 /*! \param w The width in pixels for each day in the grid.
340 * The signal gridChanged() is emitted after the day width is changed.
342 void DateTimeGrid::setDayWidth( qreal w )
344 assert( w>0 );
345 d->dayWidth = w;
346 emit gridChanged();
349 /*! \param s The scale to be used to paint the grid.
351 * The signal gridChanged() is emitted after the scale has changed.
352 * \sa Scale
354 void DateTimeGrid::setScale( Scale s )
356 d->scale = s;
357 emit gridChanged();
360 /*! \returns The scale used to paint the grid.
362 * The default is ScaleAuto, which means the day scale will be used
363 * as long as the day width is less or equal to 500.
364 * \sa Scale
366 DateTimeGrid::Scale DateTimeGrid::scale() const
368 return d->scale;
371 /*! Sets the scale formatter for the lower part of the header to the
372 * user defined formatter to \a lower. The DateTimeGrid object takes
373 * ownership of the formatter, which has to be allocated with new.
375 * You have to set the scale to ScaleUserDefined for this setting to take effect.
376 * \sa DateTimeScaleFormatter
378 void DateTimeGrid::setUserDefinedLowerScale( DateTimeScaleFormatter* lower )
380 delete d->lower;
381 d->lower = lower;
382 emit gridChanged();
385 /*! Sets the scale formatter for the upper part of the header to the
386 * user defined formatter to \a upper. The DateTimeGrid object takes
387 * ownership of the formatter, which has to be allocated with new.
389 * You have to set the scale to ScaleUserDefined for this setting to take effect.
390 * \sa DateTimeScaleFormatter
392 void DateTimeGrid::setUserDefinedUpperScale( DateTimeScaleFormatter* upper )
394 delete d->upper;
395 d->upper = upper;
396 emit gridChanged();
399 /*! \return The DateTimeScaleFormatter being used to render the lower scale.
401 DateTimeScaleFormatter* DateTimeGrid::userDefinedLowerScale() const
403 return d->lower;
406 /*! \return The DateTimeScaleFormatter being used to render the upper scale.
408 DateTimeScaleFormatter* DateTimeGrid::userDefinedUpperScale() const
410 return d->upper;
413 /*! \param ws The start day of the week.
415 * A solid line is drawn on the grid to mark the beginning of a new week.
416 * Emits gridChanged() after the start day has changed.
418 void DateTimeGrid::setWeekStart( Qt::DayOfWeek ws )
420 d->weekStart = ws;
421 emit gridChanged();
424 /*! \returns The start day of the week */
425 Qt::DayOfWeek DateTimeGrid::weekStart() const
427 return d->weekStart;
430 /*! \param fd A set of days to mark as free in the grid.
432 * Free days are filled with the alternate base brush of the
433 * palette used by the view.
434 * The signal gridChanged() is emitted after the free days are changed.
436 void DateTimeGrid::setFreeDays( const QSet<Qt::DayOfWeek>& fd )
438 d->freeDays = fd;
439 emit gridChanged();
442 /*! \returns The days marked as free in the grid. */
443 QSet<Qt::DayOfWeek> DateTimeGrid::freeDays() const
445 return d->freeDays;
448 /*! \returns true if row separators are used. */
449 bool DateTimeGrid::rowSeparators() const
451 return d->rowSeparators;
453 /*! \param enable Whether to use row separators or not. */
454 void DateTimeGrid::setRowSeparators( bool enable )
456 d->rowSeparators = enable;
459 /*! Sets the brush used to display rows where no data is found.
460 * Default is a red pattern. If set to QBrush() rows with no
461 * information will not be marked.
463 void DateTimeGrid::setNoInformationBrush( const QBrush& brush )
465 d->noInformationBrush = brush;
466 emit gridChanged();
469 /*! \returns the brush used to mark rows with no information.
471 QBrush DateTimeGrid::noInformationBrush() const
473 return d->noInformationBrush;
476 /*! \param idx The index to get the Span for.
477 * \returns The start and end pixels, in a Span, of the specified index.
479 Span DateTimeGrid::mapToChart( const QModelIndex& idx ) const
481 assert( model() );
482 if ( !idx.isValid() ) return Span();
483 assert( idx.model()==model() );
484 const QVariant sv = model()->data( idx, StartTimeRole );
485 const QVariant ev = model()->data( idx, EndTimeRole );
486 if( qVariantCanConvert<QDateTime>(sv) &&
487 qVariantCanConvert<QDateTime>(ev) &&
488 !(sv.type() == QVariant::String && qVariantValue<QString>(sv).isEmpty()) &&
489 !(ev.type() == QVariant::String && qVariantValue<QString>(ev).isEmpty())
491 QDateTime st = sv.toDateTime();
492 QDateTime et = ev.toDateTime();
493 if ( et.isValid() && st.isValid() ) {
494 qreal sx = d->dateTimeToChartX( st );
495 qreal ex = d->dateTimeToChartX( et )-sx;
496 //qDebug() << "DateTimeGrid::mapToChart("<<st<<et<<") => "<< Span( sx, ex );
497 return Span( sx, ex);
500 // Special case for Events with only a start date
501 if( qVariantCanConvert<QDateTime>(sv) && !(sv.type() == QVariant::String && qVariantValue<QString>(sv).isEmpty()) ) {
502 QDateTime st = sv.toDateTime();
503 if ( st.isValid() ) {
504 qreal sx = d->dateTimeToChartX( st );
505 return Span( sx, 0 );
508 return Span();
511 #if 0
512 static void debug_print_idx( const QModelIndex& idx )
514 if ( !idx.isValid() ) {
515 qDebug() << "[Invalid]";
516 return;
518 QDateTime st = idx.data( StartTimeRole ).toDateTime();
519 QDateTime et = idx.data( StartTimeRole ).toDateTime();
520 qDebug() << idx << "["<<st<<et<<"]";
522 #endif
524 /*! Maps the supplied Span to QDateTimes, and puts them as start time and
525 * end time for the supplied index.
527 * \param span The span used to map from.
528 * \param idx The index used for setting the start time and end time in the model.
529 * \param constraints A list of hard constraints to match against the start time and
530 * end time mapped from the span.
532 * \returns true if the start time and time was successfully added to the model, or false
533 * if unsucessful.
534 * Also returns false if any of the constraints isn't satisfied. That is, if the start time of
535 * the constrained index is before the end time of the dependency index, or the end time of the
536 * constrained index is before the start time of the dependency index.
538 bool DateTimeGrid::mapFromChart( const Span& span, const QModelIndex& idx,
539 const QList<Constraint>& constraints ) const
541 assert( model() );
542 if ( !idx.isValid() ) return false;
543 assert( idx.model()==model() );
545 QDateTime st = d->chartXtoDateTime(span.start());
546 QDateTime et = d->chartXtoDateTime(span.start()+span.length());
547 //qDebug() << "DateTimeGrid::mapFromChart("<<span<<") => "<< st << et;
548 Q_FOREACH( const Constraint& c, constraints ) {
549 if ( c.type() != Constraint::TypeHard || !isSatisfiedConstraint( c )) continue;
550 if ( c.startIndex() == idx ) {
551 QDateTime tmpst = model()->data( c.endIndex(), StartTimeRole ).toDateTime();
552 //qDebug() << tmpst << "<" << et <<"?";
553 if ( tmpst<et ) return false;
554 } else if ( c.endIndex() == idx ) {
555 QDateTime tmpet = model()->data( c.startIndex(), EndTimeRole ).toDateTime();
556 //qDebug() << tmpet << ">" << st <<"?";
557 if ( tmpet>st ) return false;
560 return model()->setData( idx, qVariantFromValue(st), StartTimeRole )
561 && model()->setData( idx, qVariantFromValue(et), EndTimeRole );
564 void DateTimeGrid::Private::paintVerticalDayLines( QPainter* painter,
565 const QRectF& sceneRect,
566 const QRectF& exposedRect,
567 QWidget* widget )
569 QDateTime dt = chartXtoDateTime( exposedRect.left() );
570 dt.setTime( QTime( 0, 0, 0, 0 ) );
571 for ( qreal x = dateTimeToChartX( dt ); x < exposedRect.right();
572 dt = dt.addDays( 1 ),x=dateTimeToChartX( dt ) ) {
573 if ( x >= exposedRect.left() ) {
574 QPen pen = painter->pen();
575 pen.setBrush( QApplication::palette().dark() );
576 if ( dt.date().dayOfWeek() == weekStart ) {
577 pen.setStyle( Qt::SolidLine );
578 } else {
579 pen.setStyle( Qt::DashLine );
581 painter->setPen( pen );
582 if ( freeDays.contains( static_cast<Qt::DayOfWeek>( dt.date().dayOfWeek() ) ) ) {
583 painter->setBrush( widget?widget->palette().midlight()
584 :QApplication::palette().midlight() );
585 painter->fillRect( QRectF( x, exposedRect.top(), dayWidth, exposedRect.height() ), painter->brush() );
587 painter->drawLine( QPointF( x, sceneRect.top() ), QPointF( x, sceneRect.bottom() ) );
592 void DateTimeGrid::Private::paintVerticalHourLines( QPainter* painter,
593 const QRectF& sceneRect,
594 const QRectF& exposedRect,
595 QWidget* widget )
597 QDateTime dt = chartXtoDateTime( exposedRect.left() );
598 dt.setTime( QTime( 0, 0, 0, 0 ) );
599 for ( qreal x = dateTimeToChartX( dt ); x < exposedRect.right();
600 dt = dt.addSecs( 60*60 ),x=dateTimeToChartX( dt ) ) {
601 if ( x >= exposedRect.left() ) {
602 QPen pen = painter->pen();
603 pen.setBrush( QApplication::palette().dark() );
604 if ( dt.time().hour() == 0 ) {
605 pen.setStyle( Qt::SolidLine );
606 } else {
607 pen.setStyle( Qt::DashLine );
609 painter->setPen( pen );
610 if ( freeDays.contains( static_cast<Qt::DayOfWeek>( dt.date().dayOfWeek() ) ) ) {
611 painter->setBrush( widget?widget->palette().midlight()
612 :QApplication::palette().midlight() );
613 painter->fillRect( QRectF( x, exposedRect.top(), dayWidth, exposedRect.height() ), painter->brush() );
615 painter->drawLine( QPointF( x, sceneRect.top() ), QPointF( x, sceneRect.bottom() ) );
620 void DateTimeGrid::Private::paintVerticalUserDefinedLines( QPainter* painter,
621 const QRectF& sceneRect,
622 const QRectF& exposedRect,
623 const DateTimeScaleFormatter* formatter,
624 QWidget* widget )
626 Q_UNUSED( widget );
627 QDateTime dt = chartXtoDateTime( exposedRect.left() );
628 dt = formatter->currentRangeBegin( dt );
629 QPen pen = painter->pen();
630 pen.setBrush( QApplication::palette().dark() );
631 pen.setStyle( Qt::DashLine );
632 painter->setPen( pen );
633 for ( qreal x = dateTimeToChartX( dt ); x < exposedRect.right();
634 dt = formatter->nextRangeBegin( dt ),x=dateTimeToChartX( dt ) ) {
635 if ( x >= exposedRect.left() ) {
636 painter->drawLine( QPointF( x, sceneRect.top() ), QPointF( x, sceneRect.bottom() ) );
641 void DateTimeGrid::paintGrid( QPainter* painter,
642 const QRectF& sceneRect,
643 const QRectF& exposedRect,
644 AbstractRowController* rowController,
645 QWidget* widget )
647 // TODO: Support hours
648 switch( scale() ) {
649 case ScaleDay:
650 d->paintVerticalDayLines( painter, sceneRect, exposedRect, widget );
651 break;
652 case ScaleHour:
653 d->paintVerticalHourLines( painter, sceneRect, exposedRect, widget );
654 break;
655 case ScaleWeek:
656 d->paintVerticalUserDefinedLines( painter, sceneRect, exposedRect, &d->week_lower, widget );
657 break;
658 case ScaleMonth:
659 d->paintVerticalUserDefinedLines( painter, sceneRect, exposedRect, &d->month_lower, widget );
660 break;
661 case ScaleAuto: {
662 const qreal tabw = QApplication::fontMetrics().width( QLatin1String( "XXXXX" ) );
663 const qreal dayw = dayWidth();
664 if ( dayw > 24*60*60*tabw ) {
665 d->paintVerticalUserDefinedLines( painter, sceneRect, exposedRect, &d->minute_lower, widget );
666 } else if ( dayw > 24*60*tabw ) {
667 d->paintVerticalHourLines( painter, sceneRect, exposedRect, widget );
668 } else if ( dayw > 24*tabw ) {
669 d->paintVerticalDayLines( painter, sceneRect, exposedRect, widget );
670 } else if ( dayw > tabw ) {
671 d->paintVerticalUserDefinedLines( painter, sceneRect, exposedRect, &d->week_lower, widget );
672 } else if ( 4*dayw > tabw ) {
673 d->paintVerticalUserDefinedLines( painter, sceneRect, exposedRect, &d->month_lower, widget );
674 } else {
675 d->paintVerticalUserDefinedLines( painter, sceneRect, exposedRect, &d->year_lower, widget );
677 break;
679 case ScaleUserDefined:
680 d->paintVerticalUserDefinedLines( painter, sceneRect, exposedRect, d->lower, widget );
681 break;
683 if ( rowController ) {
684 // First draw the rows
685 QPen pen = painter->pen();
686 pen.setBrush( QApplication::palette().dark() );
687 pen.setStyle( Qt::DashLine );
688 painter->setPen( pen );
689 QModelIndex idx = rowController->indexAt( qRound( exposedRect.top() ) );
690 if ( rowController->indexAbove( idx ).isValid() ) idx = rowController->indexAbove( idx );
691 qreal y = 0;
692 while ( y < exposedRect.bottom() && idx.isValid() ) {
693 const Span s = rowController->rowGeometry( idx );
694 y = s.start()+s.length();
695 if ( d->rowSeparators ) {
696 painter->drawLine( QPointF( sceneRect.left(), y ),
697 QPointF( sceneRect.right(), y ) );
699 if ( !idx.data( ItemTypeRole ).isValid() && d->noInformationBrush.style() != Qt::NoBrush ) {
700 painter->fillRect( QRectF( exposedRect.left(), s.start(), exposedRect.width(), s.length() ), d->noInformationBrush );
702 // Is alternating background better?
703 //if ( idx.row()%2 ) painter->fillRect( QRectF( exposedRect.x(), s.start(), exposedRect.width(), s.length() ), QApplication::palette().alternateBase() );
704 idx = rowController->indexBelow( idx );
709 int DateTimeGrid::Private::tabHeight( const QString& txt, QWidget* widget ) const
711 QStyleOptionHeader opt;
712 if ( widget ) opt.initFrom( widget );
713 opt.text = txt;
714 QStyle* style;
715 if ( widget ) style = widget->style();
716 else style = QApplication::style();
717 QSize s = style->sizeFromContents(QStyle::CT_HeaderSection, &opt, QSize(), widget);
718 return s.height();
721 void DateTimeGrid::Private::getAutomaticFormatters( DateTimeScaleFormatter** lower, DateTimeScaleFormatter** upper)
723 const qreal tabw = QApplication::fontMetrics().width( QLatin1String( "XXXXX" ) );
724 const qreal dayw = dayWidth;
725 if ( dayw > 24*60*60*tabw ) {
726 *lower = &minute_lower;
727 *upper = &minute_upper;
728 } else if ( dayw > 24*60*tabw ) {
729 *lower = &hour_lower;
730 *upper = &hour_upper;
731 } else if ( dayw > 24*tabw ) {
732 *lower = &day_lower;
733 *upper = &day_upper;
734 } else if ( dayw > tabw ) {
735 *lower = &week_lower;
736 *upper = &week_upper;
737 } else if ( 4*dayw > tabw ) {
738 *lower = &month_lower;
739 *upper = &month_upper;
740 } else {
741 *lower = &year_lower;
742 *upper = &year_upper;
747 void DateTimeGrid::paintHeader( QPainter* painter, const QRectF& headerRect, const QRectF& exposedRect,
748 qreal offset, QWidget* widget )
750 painter->save();
751 QPainterPath clipPath;
752 clipPath.addRect( headerRect );
753 painter->setClipPath( clipPath, Qt::IntersectClip );
754 switch( scale() )
756 case ScaleHour:
757 paintHourScaleHeader( painter, headerRect, exposedRect, offset, widget );
758 break;
759 case ScaleDay:
760 paintDayScaleHeader( painter, headerRect, exposedRect, offset, widget );
761 break;
762 case ScaleWeek:
764 DateTimeScaleFormatter *lower = &d->week_lower;
765 DateTimeScaleFormatter *upper = &d->week_upper;
766 const qreal lowerHeight = d->tabHeight( lower->text( startDateTime() ) );
767 const qreal upperHeight = d->tabHeight( upper->text( startDateTime() ) );
768 const qreal upperRatio = upperHeight/( lowerHeight+upperHeight );
770 const QRectF upperHeaderRect( headerRect.x(), headerRect.top(), headerRect.width()-1, headerRect.height() * upperRatio );
771 const QRectF lowerHeaderRect( headerRect.x(), upperHeaderRect.bottom()+1, headerRect.width()-1, headerRect.height()-upperHeaderRect.height()-1 );
773 paintUserDefinedHeader( painter, lowerHeaderRect, exposedRect, offset, lower, widget );
774 paintUserDefinedHeader( painter, upperHeaderRect, exposedRect, offset, upper, widget );
775 break;
777 case ScaleMonth:
779 DateTimeScaleFormatter *lower = &d->month_lower;
780 DateTimeScaleFormatter *upper = &d->month_upper;
781 const qreal lowerHeight = d->tabHeight( lower->text( startDateTime() ) );
782 const qreal upperHeight = d->tabHeight( upper->text( startDateTime() ) );
783 const qreal upperRatio = upperHeight/( lowerHeight+upperHeight );
785 const QRectF upperHeaderRect( headerRect.x(), headerRect.top(), headerRect.width()-1, headerRect.height() * upperRatio );
786 const QRectF lowerHeaderRect( headerRect.x(), upperHeaderRect.bottom()+1, headerRect.width()-1, headerRect.height()-upperHeaderRect.height()-1 );
788 paintUserDefinedHeader( painter, lowerHeaderRect, exposedRect, offset, lower, widget );
789 paintUserDefinedHeader( painter, upperHeaderRect, exposedRect, offset, upper, widget );
790 break;
792 case ScaleAuto:
794 DateTimeScaleFormatter *lower, *upper;
795 d->getAutomaticFormatters( &lower, &upper );
796 const qreal lowerHeight = d->tabHeight( lower->text( startDateTime() ) );
797 const qreal upperHeight = d->tabHeight( upper->text( startDateTime() ) );
798 const qreal upperRatio = upperHeight/( lowerHeight+upperHeight );
800 const QRectF upperHeaderRect( headerRect.x(), headerRect.top(), headerRect.width()-1, headerRect.height() * upperRatio );
801 const QRectF lowerHeaderRect( headerRect.x(), upperHeaderRect.bottom()+1, headerRect.width()-1, headerRect.height()-upperHeaderRect.height()-1 );
803 paintUserDefinedHeader( painter, lowerHeaderRect, exposedRect, offset, lower, widget );
804 paintUserDefinedHeader( painter, upperHeaderRect, exposedRect, offset, upper, widget );
805 break;
807 case ScaleUserDefined:
809 const qreal lowerHeight = d->tabHeight( d->lower->text( startDateTime() ) );
810 const qreal upperHeight = d->tabHeight( d->upper->text( startDateTime() ) );
811 const qreal upperRatio = upperHeight/( lowerHeight+upperHeight );
813 const QRectF upperHeaderRect( headerRect.x(), headerRect.top(), headerRect.width()-1, headerRect.height() * upperRatio );
814 const QRectF lowerHeaderRect( headerRect.x(), upperHeaderRect.bottom()+1, headerRect.width()-1, headerRect.height()-upperHeaderRect.height()-1 );
816 paintUserDefinedHeader( painter, lowerHeaderRect, exposedRect, offset, d->lower, widget );
817 paintUserDefinedHeader( painter, upperHeaderRect, exposedRect, offset, d->upper, widget );
819 break;
821 painter->restore();
824 void DateTimeGrid::paintUserDefinedHeader( QPainter* painter,
825 const QRectF& headerRect, const QRectF& exposedRect,
826 qreal offset, const DateTimeScaleFormatter* formatter,
827 QWidget* widget )
829 const QStyle* const style = widget ? widget->style() : QApplication::style();
831 QDateTime dt = formatter->currentRangeBegin( d->chartXtoDateTime( offset + exposedRect.left() ) ).toUTC();
832 qreal x = d->dateTimeToChartX( dt );
834 while( x < exposedRect.right() + offset )
836 const QDateTime next = formatter->nextRangeBegin( dt );
837 const qreal nextx = d->dateTimeToChartX( next );
839 QStyleOptionHeader opt;
840 if ( widget ) opt.init( widget );
841 opt.rect = QRectF( x - offset+1, headerRect.top(), qMax<qreal>( 1., nextx-x-1 ), headerRect.height() ).toAlignedRect();
842 opt.textAlignment = formatter->alignment();
843 opt.text = formatter->text( dt );
844 style->drawControl( QStyle::CE_Header, &opt, painter, widget );
846 dt = next;
847 x = nextx;
851 /*! Paints the hour scale header.
852 * \sa paintHeader()
854 void DateTimeGrid::paintHourScaleHeader( QPainter* painter,
855 const QRectF& headerRect, const QRectF& exposedRect,
856 qreal offset, QWidget* widget )
858 QStyle* style = widget?widget->style():QApplication::style();
860 // Paint a section for each hour
861 QDateTime dt = d->chartXtoDateTime( offset+exposedRect.left() );
862 dt.setTime( QTime( dt.time().hour(), 0, 0, 0 ) );
863 for ( qreal x = d->dateTimeToChartX( dt ); x < exposedRect.right()+offset;
864 dt = dt.addSecs( 60*60 /*1 hour*/ ),x=d->dateTimeToChartX( dt ) ) {
865 QStyleOptionHeader opt;
866 if ( widget ) opt.init( widget );
867 opt.rect = QRectF( x-offset+1, headerRect.top()+headerRect.height()/2., dayWidth()/24., headerRect.height()/2. ).toAlignedRect();
868 opt.text = dt.time().toString( QString::fromAscii( "hh" ) );
869 opt.textAlignment = Qt::AlignCenter;
870 style->drawControl(QStyle::CE_Header, &opt, painter, widget);
873 dt = d->chartXtoDateTime( offset+exposedRect.left() );
874 dt.setTime( QTime( 0, 0, 0, 0 ) );
875 // Paint a section for each day
876 for ( qreal x2 = d->dateTimeToChartX( dt ); x2 < exposedRect.right()+offset;
877 dt = dt.addDays( 1 ),x2=d->dateTimeToChartX( dt ) ) {
878 QStyleOptionHeader opt;
879 opt.init( widget );
880 opt.rect = QRectF( x2-offset, headerRect.top(), dayWidth(), headerRect.height()/2. ).toRect();
881 opt.text = dt.date().toString();
882 opt.textAlignment = Qt::AlignCenter;
883 style->drawControl(QStyle::CE_Header, &opt, painter, widget);
887 /*! Paints the day scale header.
888 * \sa paintHeader()
890 void DateTimeGrid::paintDayScaleHeader( QPainter* painter, const QRectF& headerRect, const QRectF& exposedRect,
891 qreal offset, QWidget* widget )
893 // For starters, support only the regular tab-per-day look
894 QStyle* style = widget?widget->style():QApplication::style();
896 // Paint a section for each day
897 QDateTime dt = d->chartXtoDateTime( offset+exposedRect.left() );
898 dt.setTime( QTime( 0, 0, 0, 0 ) );
899 for ( qreal x = d->dateTimeToChartX( dt ); x < exposedRect.right()+offset;
900 dt = dt.addDays( 1 ),x=d->dateTimeToChartX( dt ) ) {
901 QStyleOptionHeader opt;
902 opt.init( widget );
903 opt.rect = QRectF( x-offset+1, headerRect.top()+headerRect.height()/2., dayWidth(), headerRect.height()/2. ).toAlignedRect();
904 opt.text = dt.toString( QString::fromAscii( "ddd" ) ).left( 1 );
905 opt.textAlignment = Qt::AlignCenter;
906 style->drawControl(QStyle::CE_Header, &opt, painter, widget);
909 dt = d->chartXtoDateTime( offset+exposedRect.left() );
910 dt.setTime( QTime( 0, 0, 0, 0 ) );
911 // Go backwards until start of week
912 while ( dt.date().dayOfWeek() != d->weekStart ) dt = dt.addDays( -1 );
913 // Paint a section for each week
914 for ( qreal x2 = d->dateTimeToChartX( dt ); x2 < exposedRect.right()+offset;
915 dt = dt.addDays( 7 ),x2=d->dateTimeToChartX( dt ) ) {
916 QStyleOptionHeader opt;
917 opt.init( widget );
918 opt.rect = QRectF( x2-offset, headerRect.top(), dayWidth()*7., headerRect.height()/2. ).toRect();
919 opt.text = QString::number( dt.date().weekNumber() );
920 opt.textAlignment = Qt::AlignCenter;
921 style->drawControl(QStyle::CE_Header, &opt, painter, widget);
925 #undef d
927 #ifndef KDAB_NO_UNIT_TESTS
929 #include <QStandardItemModel>
930 #include "unittest/test.h"
932 namespace {
933 std::ostream& operator<<( std::ostream& os, const QDateTime& dt )
935 #ifdef QT_NO_STL
936 os << dt.toString().toLatin1().constData();
937 #else
938 os << dt.toString().toStdString();
939 #endif
940 return os;
944 KDAB_SCOPED_UNITTEST_SIMPLE( KDGantt, DateTimeGrid, "test" ) {
945 QStandardItemModel model( 3, 2 );
946 DateTimeGrid grid;
947 QDateTime dt = QDateTime::currentDateTime();
948 grid.setModel( &model );
949 QDateTime startdt = dt.addDays( -10 );
950 grid.setStartDateTime( startdt );
952 model.setData( model.index( 0, 0 ), dt, StartTimeRole );
953 model.setData( model.index( 0, 0 ), dt.addDays( 17 ), EndTimeRole );
955 model.setData( model.index( 2, 0 ), dt.addDays( 18 ), StartTimeRole );
956 model.setData( model.index( 2, 0 ), dt.addDays( 19 ), EndTimeRole );
958 Span s = grid.mapToChart( model.index( 0, 0 ) );
959 //qDebug() << "span="<<s;
961 assertTrue( s.start()>0 );
962 assertTrue( s.length()>0 );
964 assertTrue( startdt == grid.mapToDateTime( grid.mapFromDateTime( startdt ) ) );
966 grid.mapFromChart( s, model.index( 1, 0 ) );
968 QDateTime s1 = model.data( model.index( 0, 0 ), StartTimeRole ).toDateTime();
969 QDateTime e1 = model.data( model.index( 0, 0 ), EndTimeRole ).toDateTime();
970 QDateTime s2 = model.data( model.index( 1, 0 ), StartTimeRole ).toDateTime();
971 QDateTime e2 = model.data( model.index( 1, 0 ), EndTimeRole ).toDateTime();
973 assertTrue( s1.isValid() );
974 assertTrue( e1.isValid() );
975 assertTrue( s2.isValid() );
976 assertTrue( e2.isValid() );
978 assertEqual( s1, s2 );
979 assertEqual( e1, e2 );
981 assertTrue( grid.isSatisfiedConstraint( Constraint( model.index( 0, 0 ), model.index( 2, 0 ) ) ) );
982 assertFalse( grid.isSatisfiedConstraint( Constraint( model.index( 2, 0 ), model.index( 0, 0 ) ) ) );
984 s = grid.mapToChart( model.index( 0, 0 ) );
985 s.setEnd( s.end()+100000. );
986 bool rc = grid.mapFromChart( s, model.index( 0, 0 ) );
987 assertTrue( rc );
988 assertEqual( s1, model.data( model.index( 0, 0 ), StartTimeRole ).toDateTime() );
989 Span newspan = grid.mapToChart( model.index( 0, 0 ) );
990 assertEqual( newspan.start(), s.start() );
991 assertEqual( newspan.length(), s.length() );
994 QDateTime startDateTime = QDateTime::currentDateTime();
995 qreal dayWidth = 100;
996 QDate currentDate = QDate::currentDate();
997 QDateTime dt( QDate(currentDate.year(), 1, 1), QTime( 0, 0, 0, 0 ) );
998 assert( dt.isValid() );
999 qreal result = startDateTime.date().daysTo(dt.date())*24.*60.*60.;
1000 result += startDateTime.time().msecsTo(dt.time())/1000.;
1001 result *= dayWidth/( 24.*60.*60. );
1003 int days = static_cast<int>( result/dayWidth );
1004 qreal secs = result*( 24.*60.*60. )/dayWidth;
1005 QDateTime dt2 = startDateTime;
1006 QDateTime result2 = dt2.addDays( days ).addSecs( static_cast<int>(secs-(days*24.*60.*60.) ) ).addMSecs( qRound( ( secs-static_cast<int>( secs ) )*1000. ) );
1008 assertEqual( dt, result2 );
1012 #endif /* KDAB_NO_UNIT_TESTS */
1014 #include "moc_kdganttdatetimegrid.cpp"