1 /****************************************************************************
2 ** Copyright (C) 2001-2006 Klarälvdalens Datakonsult AB. All rights reserved.
4 ** This file is part of the KD Gantt library.
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
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>
34 #include <QStyleOptionHeader>
40 using namespace KDGantt
;
42 QDebug
operator<<( QDebug dbg
, KDGantt::DateTimeScaleFormatter::Range 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;
57 /*!\class KDGantt::DateTimeGrid
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. );
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. ) );
91 /*!\class KDGantt::DateTimeScaleFormatter
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)
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" );
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()
139 DateTimeScaleFormatter
& DateTimeScaleFormatter::operator=( const DateTimeScaleFormatter
& other
)
142 _d
= new Private( other
.range(), other
.format(), other
.d
->templ
, other
.alignment() );
146 /*! \returns The format being used for formatting dates and times.
148 QString
DateTimeScaleFormatter::format() const
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
);
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
179 Qt::Alignment
DateTimeScaleFormatter::alignment() const
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
;
193 result
= result
.addSecs( 60 );
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 );
201 // set it to the begin of the next hour
202 result
.setTime( QTime( result
.time().hour(), 0 ) );
203 result
= result
.addSecs( 60 * 60 );
206 // set it to midnight the next day
207 result
.setTime( QTime( 0, 0 ) );
208 result
= result
.addDays( 1 );
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 );
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 ) );
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 ) );
233 //result = result.toLocalTime();
234 assert( result
!= datetime
);
235 //qDebug() << "DateTimeScaleFormatter::nextRangeBegin("<<datetime<<")="<<d->range<<result;
239 /*! \returns the QDateTime being the begin of the range containing \a datetime
242 QDateTime
DateTimeScaleFormatter::currentRangeBegin( const QDateTime
& datetime
) const
244 QDateTime result
= datetime
;
250 // set it to the begin of the current minute
251 result
.setTime( QTime( result
.time().hour(), result
.time().minute() ) );
254 // set it to the begin of the current hour
255 result
.setTime( QTime( result
.time().hour(), 0 ) );
258 // set it to midnight the current day
259 result
.setTime( QTime( 0, 0 ) );
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 );
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 ) );
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 ) );
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
;
315 /*! \returns The width in pixels for each day in the grid.
317 * The default is 100 pixels.
319 qreal
DateTimeGrid::dayWidth() const
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
)
349 /*! \param s The scale to be used to paint the grid.
351 * The signal gridChanged() is emitted after the scale has changed.
354 void DateTimeGrid::setScale( Scale s
)
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.
366 DateTimeGrid::Scale
DateTimeGrid::scale() const
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
)
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
)
399 /*! \return The DateTimeScaleFormatter being used to render the lower scale.
401 DateTimeScaleFormatter
* DateTimeGrid::userDefinedLowerScale() const
406 /*! \return The DateTimeScaleFormatter being used to render the upper scale.
408 DateTimeScaleFormatter
* DateTimeGrid::userDefinedUpperScale() const
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
)
424 /*! \returns The start day of the week */
425 Qt::DayOfWeek
DateTimeGrid::weekStart() const
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
)
442 /*! \returns The days marked as free in the grid. */
443 QSet
<Qt::DayOfWeek
> DateTimeGrid::freeDays() const
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
;
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
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 );
512 static void debug_print_idx( const QModelIndex
& idx
)
514 if ( !idx
.isValid() ) {
515 qDebug() << "[Invalid]";
518 QDateTime st
= idx
.data( StartTimeRole
).toDateTime();
519 QDateTime et
= idx
.data( StartTimeRole
).toDateTime();
520 qDebug() << idx
<< "["<<st
<<et
<<"]";
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
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
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
,
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
);
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
,
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
);
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
,
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
,
647 // TODO: Support hours
650 d
->paintVerticalDayLines( painter
, sceneRect
, exposedRect
, widget
);
653 d
->paintVerticalHourLines( painter
, sceneRect
, exposedRect
, widget
);
656 d
->paintVerticalUserDefinedLines( painter
, sceneRect
, exposedRect
, &d
->week_lower
, widget
);
659 d
->paintVerticalUserDefinedLines( painter
, sceneRect
, exposedRect
, &d
->month_lower
, widget
);
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
);
675 d
->paintVerticalUserDefinedLines( painter
, sceneRect
, exposedRect
, &d
->year_lower
, widget
);
679 case ScaleUserDefined
:
680 d
->paintVerticalUserDefinedLines( painter
, sceneRect
, exposedRect
, d
->lower
, widget
);
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
);
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
);
715 if ( widget
) style
= widget
->style();
716 else style
= QApplication::style();
717 QSize s
= style
->sizeFromContents(QStyle::CT_HeaderSection
, &opt
, QSize(), widget
);
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
) {
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
;
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
)
751 QPainterPath clipPath
;
752 clipPath
.addRect( headerRect
);
753 painter
->setClipPath( clipPath
, Qt::IntersectClip
);
757 paintHourScaleHeader( painter
, headerRect
, exposedRect
, offset
, widget
);
760 paintDayScaleHeader( painter
, headerRect
, exposedRect
, offset
, widget
);
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
);
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
);
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
);
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
);
824 void DateTimeGrid::paintUserDefinedHeader( QPainter
* painter
,
825 const QRectF
& headerRect
, const QRectF
& exposedRect
,
826 qreal offset
, const DateTimeScaleFormatter
* formatter
,
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
);
851 /*! Paints the hour scale header.
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
;
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.
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
;
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
;
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
);
927 #ifndef KDAB_NO_UNIT_TESTS
929 #include <QStandardItemModel>
930 #include "unittest/test.h"
933 std::ostream
& operator<<( std::ostream
& os
, const QDateTime
& dt
)
936 os
<< dt
.toString().toLatin1().constData();
938 os
<< dt
.toString().toStdString();
944 KDAB_SCOPED_UNITTEST_SIMPLE( KDGantt
, DateTimeGrid
, "test" ) {
945 QStandardItemModel
model( 3, 2 );
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 ) );
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"