1 #include "ScheduleWidget.h"
3 #include <QTableWidget>
8 #include <QResizeEvent>
12 const QColor ScheduleWidget::sFreeBackground = QColor( 192, 238, 189 );
13 const QColor ScheduleWidget::sBusyBackground = QColor( 238, 147, 17 );
14 const QColor ScheduleWidget::sHeaderBackground = QColor( Qt::white );
15 const QColor ScheduleWidget::sDayHighlightColor = QColor( 255, 235, 160 );
16 const QColor ScheduleWidget::sTimeHighlightColor = QColor( Qt::blue );
17 const QColor ScheduleWidget::sMainGridColor = QColor( 140, 140, 140 );
18 const QColor ScheduleWidget::sHalfGridColor = QColor( 195, 195, 195 );
19 const QColor ScheduleWidget::sFrameColor = QColor( Qt::black );
21 ScheduleTableWidget::ScheduleTableWidget( int aRows, int aColumns, QWidget *aParent ) :
22 QTableWidget( aRows, aColumns, aParent )
24 ScheduleWidget* schedule = static_cast<ScheduleWidget*>( parent() );
26 iMeetingsByDay = new QList<MeetingContainer>[schedule->weekLengthAsDays()];
27 iTabletBlocked = false;
30 setFocusPolicy( Qt::NoFocus );
31 setFrameStyle( QFrame::NoFrame );
34 ScheduleTableWidget::~ScheduleTableWidget()
36 delete[] iMeetingsByDay;
39 void ScheduleTableWidget::paintEvent( QPaintEvent* aEvent )
41 QTableWidget::paintEvent( aEvent );
43 ScheduleWidget* schedule = static_cast<ScheduleWidget*>( parent() );
44 QPainter painter( viewport() );
45 int rowHeight = rowViewportPosition( 2 ) - rowViewportPosition( 1 ) - 1;
46 int columnWidth = columnViewportPosition( 2 ) - columnViewportPosition( 1 ) - 1;
48 // draw frame around the table
49 QRect viewportRect = viewport()->rect();
50 viewportRect.adjust( 0, 0, -1, -1 );
51 painter.setPen( ScheduleWidget::sFrameColor );
52 painter.drawRect( viewportRect );
54 // draw horizontal half grid
55 for ( int i = 1; i < rowCount(); ++i )
57 int x = columnViewportPosition( 1 );
58 int y = rowViewportPosition( i ) + ( rowHeight / 2 ) - 1;
59 painter.fillRect( QRect( x, y, width() - x - 1, 1 ), ScheduleWidget::sHalfGridColor );
62 // draw horizontal main grid
63 for ( int i = 1; i < rowCount(); ++i )
65 int y = rowViewportPosition( i ) - 1;
66 painter.fillRect( QRect( 1, y, width() - 2, 1 ), ScheduleWidget::sMainGridColor );
69 // draw vertical main grid
70 for ( int i = 1; i < columnCount(); ++i )
72 int x = columnViewportPosition( i ) - 1;
73 painter.fillRect( QRect( x, 1, 1, height() - 2 ), ScheduleWidget::sMainGridColor );
76 // draw current day highlight
77 QPen pen( ScheduleWidget::sDayHighlightColor );
79 painter.setPen( pen );
80 painter.setBrush( Qt::NoBrush );
82 for ( int i = 1; i < columnCount(); ++i )
84 if ( schedule->iCurrentDateTime.date() == schedule->iShownDate.addDays( i - 1 ) )
86 int x = columnViewportPosition( i ) + 1;
88 int w = columnWidth - 3;
90 painter.drawRect( x, y, w, h );
96 QBrush brush( ScheduleWidget::sBusyBackground );
97 painter.setBrush( brush );
98 painter.setRenderHint( QPainter::Antialiasing );
99 painter.setPen( ScheduleWidget::sFrameColor );
100 populateMeetingList();
102 for ( int day = 0; day < schedule->weekLengthAsDays(); ++day )
104 for ( int i = 0; i < iMeetingsByDay[day].size(); ++i )
106 painter.drawRoundRect( iMeetingsByDay[day][i].rect, 20, 20 );
110 // draw current time highlight
111 painter.setBrush( Qt::NoBrush );
112 painter.setRenderHint( QPainter::Antialiasing, false );
114 for ( int i = 1; i < columnCount(); ++i )
116 if ( schedule->iCurrentDateTime.date() == schedule->iShownDate.addDays( i - 1 ) )
118 int x = columnViewportPosition( i ) - 1;
119 int y = computeViewportY( schedule->iCurrentDateTime.time() );
120 int w = columnWidth + 2;
122 painter.fillRect( x, y, w, h, ScheduleWidget::sTimeHighlightColor );
128 void ScheduleTableWidget::tabletEvent( QTabletEvent* aEvent )
130 int ms = iTime.restart();
132 if ( iTabletBlocked && ms > 1000 )
134 iTabletBlocked = false;
135 qDebug() << "Tablet block released";
138 if ( iTabletBlocked == false )
140 qDebug() << "Tablet blocked released";
141 activateMeeting( aEvent->x(), aEvent->y() );
145 void ScheduleTableWidget::mouseMoveEvent( QMouseEvent* /* aEvent */ )
147 // this is overridden as empty method because otherwise
148 // unwanted behaviour would occur due to QTableWidget
151 void ScheduleTableWidget::mousePressEvent( QMouseEvent* aEvent )
153 activateMeeting( aEvent->x(), aEvent->y() );
156 void ScheduleTableWidget::populateMeetingList()
158 ScheduleWidget* schedule = static_cast<ScheduleWidget*>( parent() );
160 for ( int i = 0; i < schedule->weekLengthAsDays(); ++i )
161 iMeetingsByDay[i].clear();
163 // insert suitable meetings to list
164 for ( int i = 0; i < schedule->iMeetings.count(); ++i )
166 Meeting* meeting = schedule->iMeetings[i];
167 int day = meeting->startsAt().date().dayOfWeek() - 1;
168 if (( meeting->startsAt().date().weekNumber() == schedule->iShownDate.weekNumber() ) &&
169 ( day < schedule->weekLengthAsDays() ) &&
170 ( meeting->endsAt().time() > QTime( schedule->iStartHour, 0 ) ) &&
171 ( meeting->startsAt().time() <= QTime( schedule->iStartHour + schedule->iNumberOfHours - 1, 59 ) ) )
173 MeetingContainer container;
174 container.meeting = meeting;
175 container.rect = QRect( 0, 0, 0, 0 );
176 container.rectComputed = false;
177 iMeetingsByDay[day].append( container );
181 // compute meeting rectangles
182 for ( int day = 0; day < schedule->weekLengthAsDays(); ++day )
184 for ( int i = 0; i < iMeetingsByDay[day].size(); ++i )
186 if ( iMeetingsByDay[day][i].rectComputed )
189 QList<int> meetingIndices;
190 findOverlappingMeetings( day, iMeetingsByDay[day][i].meeting, meetingIndices );
191 meetingIndices.append( i );
193 for ( int j = 0; j < meetingIndices.size(); ++j )
195 if ( iMeetingsByDay[day][meetingIndices[j]].rectComputed )
198 int columnWidth = columnViewportPosition( 2 ) - columnViewportPosition( 1 ) - 1;
200 Meeting* meeting = iMeetingsByDay[day][meetingIndices[j]].meeting;
201 int x = columnViewportPosition( day + 1 ) + ( int )(( columnWidth / ( float )meetingIndices.size() ) * j );
202 int y = computeViewportY( meeting->startsAt().time() );
203 int width = ( int )( columnWidth / ( float )meetingIndices.size() + 0.5f );
204 int height = computeViewportY( meeting->endsAt().time() ) - y;
206 iMeetingsByDay[day][meetingIndices[j]].rect = QRect( x, y, width, height );
207 iMeetingsByDay[day][meetingIndices[j]].rectComputed = true;
213 bool ScheduleTableWidget::findOverlappingMeetings( int aDay, Meeting* aMeeting, QList<int>& aResult )
215 QSet<int> overlapSet;
217 // first find meetings that overlap with aMeeting
218 for ( int i = 0; i < iMeetingsByDay[aDay].size(); ++i )
220 Meeting* other = iMeetingsByDay[aDay][i].meeting;
221 if ( aMeeting != other && aMeeting->overlaps( *(other) ) )
222 overlapSet.insert( i );
225 // then compare overlappiong ones against every meeting to make sure that
226 // the returned set can be used to compute rectangles for all cases at once
227 foreach( int index, overlapSet )
229 Meeting* meetingInSet = iMeetingsByDay[aDay][index].meeting;
230 for ( int i = 0; i < iMeetingsByDay[aDay].size(); ++i )
232 Meeting* other = iMeetingsByDay[aDay][i].meeting;
233 if ( meetingInSet != other && aMeeting != other && meetingInSet->overlaps( *(other) ) )
234 overlapSet.insert( i );
239 foreach( int index, overlapSet )
241 aResult.append( index );
244 return !aResult.empty();
247 void ScheduleTableWidget::activateMeeting( int x, int y )
249 ScheduleWidget* schedule = static_cast<ScheduleWidget*>( parent() );
251 for ( int day = 0; day < schedule->weekLengthAsDays(); ++day )
253 for ( int i = 0; i < iMeetingsByDay[day].size(); ++i )
255 if ( iMeetingsByDay[day][i].rect.contains( x, y ) && !iTabletBlocked )
257 iTabletBlocked = true;
258 qDebug() << "Activated meeting at x" << x << "y" << y << ":" << iMeetingsByDay[day][i].meeting->toString();
259 emit schedule->meetingActivated( iMeetingsByDay[day][i].meeting );
265 int ScheduleTableWidget::computeViewportY( QTime aTime )
267 ScheduleWidget* schedule = static_cast<ScheduleWidget*>( parent() );
268 int secondsInDisplayDay = schedule->iNumberOfHours * 60 * 60;
269 int mainY = rowViewportPosition( 1 ) + 1;
270 int mainHeight = height() - mainY - 1;
272 return mainY + ( int )(( QTime( schedule->iStartHour, 0 ).secsTo( aTime ) / ( float )secondsInDisplayDay ) * mainHeight );
275 ScheduleWidget::ScheduleWidget( QDateTime aCurrentDateTime, DisplaySettings *aSettings, QWidget *aParent ) :
276 ObservedWidget( aParent ),
277 iCurrentDateTime( aCurrentDateTime ),
278 iStartHour( aSettings->dayStartsAt().hour() ),
279 iNumberOfHours( aSettings->dayEndsAt().hour() - aSettings->dayStartsAt().hour() + 1 ),
280 iDaysInSchedule( aSettings->daysInSchedule() ),
281 iLastRefresh( aCurrentDateTime.time() )
283 iStartHour = qBound( 0, iStartHour, 23 );
284 iNumberOfHours = qBound( 1, iNumberOfHours, 24 - iStartHour );
286 iScheduleTable = new ScheduleTableWidget(( iNumberOfHours + 1 ) * 1, weekLengthAsDays() + 1, this );
287 iScheduleTable->horizontalHeader()->hide();
288 iScheduleTable->verticalHeader()->hide();
289 iScheduleTable->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
290 iScheduleTable->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
291 iScheduleTable->setShowGrid( false );
294 font.setPointSize( 10 );
296 // add empty item to top-left corner, this will be updated in refresh()
297 QTableWidgetItem *weekItem = new QTableWidgetItem();
298 weekItem->setTextAlignment( Qt::AlignCenter );
299 weekItem->setFlags( Qt::ItemIsEnabled );
300 weekItem->setBackgroundColor( sHeaderBackground );
301 weekItem->setFont( font );
302 iScheduleTable->setItem( 0, 0, weekItem );
304 // add empty item to main cell
305 QTableWidgetItem *mainItem = new QTableWidgetItem();
306 mainItem->setFlags( Qt::ItemIsEnabled );
307 mainItem->setBackgroundColor( sFreeBackground );
308 iScheduleTable->setItem( 1, 1, mainItem );
310 // set row header items
311 QTime time( iStartHour, 0 );
312 for ( int i = 1; i < iScheduleTable->rowCount(); ++i )
314 QTableWidgetItem *item = new QTableWidgetItem( time.toString( "HH:mm" ) );
315 item->setTextAlignment( Qt::AlignHCenter );
316 item->setFlags( Qt::ItemIsEnabled );
317 item->setBackgroundColor( sHeaderBackground );
318 item->setFont( font );
319 iScheduleTable->setItem( i, 0, item );
320 time = time.addSecs( 60 * 60 );
323 // set empty column header items, these will be updated in refresh()
324 for ( int i = 1; i < iScheduleTable->columnCount(); ++i )
326 QTableWidgetItem *item = new QTableWidgetItem();
327 item->setTextAlignment( Qt::AlignCenter );
328 item->setFlags( Qt::ItemIsEnabled );
329 item->setBackgroundColor( sHeaderBackground );
330 item->setFont( font );
331 iScheduleTable->setItem( 0, i, item );
334 QVBoxLayout *layout = new QVBoxLayout;
335 layout->addWidget( iScheduleTable );
336 layout->setAlignment( Qt::AlignCenter );
337 layout->setMargin( 0 );
343 ScheduleWidget::~ScheduleWidget()
346 delete iScheduleTable;
349 int ScheduleWidget::shownWeek()
351 return iShownDate.weekNumber();
354 QDate ScheduleWidget::beginningOfShownWeek()
356 return iShownDate.addDays( -1 * iShownDate.dayOfWeek() + 1 );
359 Meeting* ScheduleWidget::currentMeeting()
361 return meeting( iCurrentDateTime );
364 Meeting* ScheduleWidget::meeting( QDateTime aAt )
366 for ( int i = 0; i < iMeetings.count(); ++i )
368 if ( iMeetings[i]->startsAt() <= aAt && iMeetings[i]->endsAt() >= aAt )
377 void ScheduleWidget::clear()
379 qDebug() << "ScheduleWidget::clear";
381 while ( !iMeetings.isEmpty() )
384 iMeetings.removeFirst();
386 qDebug() << "Deleted " << i << " items";
389 void ScheduleWidget::clear( QDateTime aFrom, QDateTime aUntil )
391 for ( int i = 0; i < iMeetings.count(); ++i )
393 if (( iMeetings[i]->startsAt() >= aFrom && iMeetings[i]->startsAt() <= aUntil ) ||
394 ( iMeetings[i]->startsAt() <= aFrom && iMeetings[i]->endsAt() >= aFrom ) )
396 iMeetings.removeAt( i );
402 void ScheduleWidget::refresh()
404 for ( int i = 0; i < iScheduleTable->columnCount(); ++i )
406 QTableWidgetItem* item = iScheduleTable->item( 0, i );
407 QFont font = item->font();
409 item->setText( tr( "Wk %1" ).arg( iShownDate.weekNumber() ) );
412 item->setText( iShownDate.addDays( i - 1 ).toString( tr( "ddd d MMM" ) ) );
414 if ( iCurrentDateTime.date() == iShownDate.addDays( i - 1 ) )
417 item->setBackgroundColor( sDayHighlightColor );
418 font.setItalic( true );
419 item->setFont( font );
423 item->setBackgroundColor( sHeaderBackground );
424 font.setItalic( false );
425 item->setFont( font );
429 // force repaint of the main area
430 iScheduleTable->setSpan( 1, 1, iNumberOfHours, weekLengthAsDays() );
432 iLastRefresh = iCurrentDateTime.time();
435 void ScheduleWidget::setCurrentDateTime( QDateTime aCurrentDateTime )
437 Meeting* previous = meeting( iCurrentDateTime );
438 Meeting* current = meeting( aCurrentDateTime );
439 iCurrentDateTime = aCurrentDateTime;
441 if ( iLastRefresh.secsTo( iCurrentDateTime.time() ) > sRefreshIntervalInSeconds )
443 // enough time has elapsed since last refresh
447 if ( previous != current )
449 emit currentMeetingChanged( current );
453 void ScheduleWidget::insertMeeting( Meeting *aMeeting )
455 Meeting* previous = meeting( iCurrentDateTime );
456 iMeetings.append( aMeeting );
457 Meeting* current = meeting( iCurrentDateTime );
459 qDebug() << "Inserted" << aMeeting->toString();
463 if ( previous != current )
465 emit currentMeetingChanged( current );
469 void ScheduleWidget::removeMeeting( Meeting *aMeeting )
471 Meeting* previous = meeting( iCurrentDateTime );
473 qDebug() << "Delete" << aMeeting->toString();
474 for ( int i = 0; i < iMeetings.count(); ++i )
476 if ( iMeetings[i]->equals( *(aMeeting) ) )
478 iMeetings.removeAt( i );
482 Meeting* current = meeting( iCurrentDateTime );
483 if ( previous != current )
484 emit currentMeetingChanged( current );
491 //void ScheduleWidget::updateMeeting( Meeting *aMeeting )
496 void ScheduleWidget::showPreviousWeek()
498 iShownDate = iShownDate.addDays( -7 );
500 emit shownWeekChanged( iShownDate );
503 void ScheduleWidget::showCurrentWeek()
505 iShownDate = iCurrentDateTime.date();
507 // set weekday to monday
508 iShownDate = iShownDate.addDays( -( iShownDate.dayOfWeek() - 1 ) );
511 emit shownWeekChanged( iShownDate );
514 void ScheduleWidget::showNextWeek()
516 iShownDate = iShownDate.addDays( 7 );
518 emit shownWeekChanged( iShownDate );
521 int ScheduleWidget::computeHeaderRow( QTime aTime )
523 // map given time to correct header row in the schedule table
524 return aTime.hour() - ( iStartHour - 1 );
527 int ScheduleWidget::weekLengthAsDays()
529 return ( iDaysInSchedule == DisplaySettings::WholeWeekInSchedule ) ? 7 : 5;
532 void ScheduleWidget::resizeEvent( QResizeEvent* /* aEvent */ )
534 QRect rect = iScheduleTable->contentsRect();
535 int rowHeight = ( int )( rect.height() / ( float )iScheduleTable->rowCount() );
536 int headerRowHeight = rowHeight;
537 int columnWidth = ( int )( rect.width() / ( iScheduleTable->columnCount() - 0.5f ) );
538 int headerColumnWidth = columnWidth / 2;
540 iScheduleTable->setRowHeight( 0, headerRowHeight );
541 for ( int i = 1; i < iScheduleTable->rowCount(); ++i )
543 iScheduleTable->setRowHeight( i, rowHeight );
546 iScheduleTable->setColumnWidth( 0, headerColumnWidth );
547 for ( int i = 1; i < iScheduleTable->columnCount(); ++i )
549 iScheduleTable->setColumnWidth( i, columnWidth );
552 // resize table so that frame size matches exactly
553 int leftMargin = 0, topMargin = 0, rightMargin = 0, bottomMargin = 0;
554 iScheduleTable->getContentsMargins( &leftMargin, &topMargin, &rightMargin, &bottomMargin );
555 iScheduleTable->resize( columnWidth * iScheduleTable->columnCount() - headerColumnWidth + leftMargin + rightMargin,
556 rowHeight * iScheduleTable->rowCount() + topMargin + bottomMargin );