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() );
154 iTabletBlocked = false;
157 void ScheduleTableWidget::populateMeetingList()
159 ScheduleWidget* schedule = static_cast<ScheduleWidget*>( parent() );
161 for ( int i = 0; i < schedule->weekLengthAsDays(); ++i )
162 iMeetingsByDay[i].clear();
164 // insert suitable meetings to list
165 for ( int i = 0; i < schedule->iMeetings.count(); ++i )
167 Meeting* meeting = schedule->iMeetings[i];
168 int day = meeting->startsAt().date().dayOfWeek() - 1;
169 if (( meeting->startsAt().date().weekNumber() == schedule->iShownDate.weekNumber() ) &&
170 ( day < schedule->weekLengthAsDays() ) &&
171 ( meeting->endsAt().time() > QTime( schedule->iStartHour, 0 ) ) &&
172 ( meeting->startsAt().time() <= QTime( schedule->iStartHour + schedule->iNumberOfHours - 1, 59 ) ) )
174 MeetingContainer container;
175 container.meeting = meeting;
176 container.rect = QRect( 0, 0, 0, 0 );
177 container.rectComputed = false;
178 iMeetingsByDay[day].append( container );
182 // compute meeting rectangles
183 for ( int day = 0; day < schedule->weekLengthAsDays(); ++day )
185 for ( int i = 0; i < iMeetingsByDay[day].size(); ++i )
187 if ( iMeetingsByDay[day][i].rectComputed )
190 QList<int> meetingIndices;
191 findOverlappingMeetings( day, iMeetingsByDay[day][i].meeting, meetingIndices );
192 meetingIndices.append( i );
194 for ( int j = 0; j < meetingIndices.size(); ++j )
196 if ( iMeetingsByDay[day][meetingIndices[j]].rectComputed )
199 int columnWidth = columnViewportPosition( 2 ) - columnViewportPosition( 1 ) - 1;
201 Meeting* meeting = iMeetingsByDay[day][meetingIndices[j]].meeting;
202 int x = columnViewportPosition( day + 1 ) + ( int )(( columnWidth / ( float )meetingIndices.size() ) * j );
203 int y = computeViewportY( meeting->startsAt().time() );
204 int width = ( int )( columnWidth / ( float )meetingIndices.size() + 0.5f );
205 int height = computeViewportY( meeting->endsAt().time() ) - y;
207 iMeetingsByDay[day][meetingIndices[j]].rect = QRect( x, y, width, height );
208 iMeetingsByDay[day][meetingIndices[j]].rectComputed = true;
214 bool ScheduleTableWidget::findOverlappingMeetings( int aDay, Meeting* aMeeting, QList<int>& aResult )
216 QSet<int> overlapSet;
218 // first find meetings that overlap with aMeeting
219 for ( int i = 0; i < iMeetingsByDay[aDay].size(); ++i )
221 Meeting* other = iMeetingsByDay[aDay][i].meeting;
222 if ( aMeeting != other && aMeeting->overlaps( *(other) ) )
223 overlapSet.insert( i );
226 // then compare overlappiong ones against every meeting to make sure that
227 // the returned set can be used to compute rectangles for all cases at once
228 foreach( int index, overlapSet )
230 Meeting* meetingInSet = iMeetingsByDay[aDay][index].meeting;
231 for ( int i = 0; i < iMeetingsByDay[aDay].size(); ++i )
233 Meeting* other = iMeetingsByDay[aDay][i].meeting;
234 if ( meetingInSet != other && aMeeting != other && meetingInSet->overlaps( *(other) ) )
235 overlapSet.insert( i );
240 foreach( int index, overlapSet )
242 aResult.append( index );
245 return !aResult.empty();
248 void ScheduleTableWidget::activateMeeting( int x, int y )
250 ScheduleWidget* schedule = static_cast<ScheduleWidget*>( parent() );
252 for ( int day = 0; day < schedule->weekLengthAsDays(); ++day )
254 for ( int i = 0; i < iMeetingsByDay[day].size(); ++i )
256 if ( iMeetingsByDay[day][i].rect.contains( x, y ) && !iTabletBlocked )
258 iTabletBlocked = true;
259 qDebug() << "Activated meeting at x" << x << "y" << y << ":" << iMeetingsByDay[day][i].meeting->toString();
260 emit schedule->meetingActivated( iMeetingsByDay[day][i].meeting );
266 int ScheduleTableWidget::computeViewportY( QTime aTime )
268 ScheduleWidget* schedule = static_cast<ScheduleWidget*>( parent() );
269 int secondsInDisplayDay = schedule->iNumberOfHours * 60 * 60;
270 int mainY = rowViewportPosition( 1 ) + 1;
271 int mainHeight = height() - mainY - 1;
273 return mainY + ( int )(( QTime( schedule->iStartHour, 0 ).secsTo( aTime ) / ( float )secondsInDisplayDay ) * mainHeight );
276 ScheduleWidget::ScheduleWidget( QDateTime aCurrentDateTime, DisplaySettings *aSettings, QWidget *aParent ) :
277 ObservedWidget( aParent ),
278 iCurrentDateTime( aCurrentDateTime ),
279 iStartHour( aSettings->dayStartsAt().hour() ),
280 iNumberOfHours( aSettings->dayEndsAt().hour() - aSettings->dayStartsAt().hour() + 1 ),
281 iDaysInSchedule( aSettings->daysInSchedule() ),
282 iLastRefresh( aCurrentDateTime.time() )
284 iStartHour = qBound( 0, iStartHour, 23 );
285 iNumberOfHours = qBound( 1, iNumberOfHours, 24 - iStartHour );
287 iScheduleTable = new ScheduleTableWidget(( iNumberOfHours + 1 ) * 1, weekLengthAsDays() + 1, this );
288 iScheduleTable->horizontalHeader()->hide();
289 iScheduleTable->verticalHeader()->hide();
290 iScheduleTable->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
291 iScheduleTable->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
292 iScheduleTable->setShowGrid( false );
295 font.setPointSize( 10 );
297 // add empty item to top-left corner, this will be updated in refresh()
298 QTableWidgetItem *weekItem = new QTableWidgetItem();
299 weekItem->setTextAlignment( Qt::AlignCenter );
300 weekItem->setFlags( Qt::ItemIsEnabled );
301 weekItem->setBackgroundColor( sHeaderBackground );
302 weekItem->setFont( font );
303 iScheduleTable->setItem( 0, 0, weekItem );
305 // add empty item to main cell
306 QTableWidgetItem *mainItem = new QTableWidgetItem();
307 mainItem->setFlags( Qt::ItemIsEnabled );
308 mainItem->setBackgroundColor( sFreeBackground );
309 iScheduleTable->setItem( 1, 1, mainItem );
311 // set row header items
312 QTime time( iStartHour, 0 );
313 for ( int i = 1; i < iScheduleTable->rowCount(); ++i )
315 QTableWidgetItem *item = new QTableWidgetItem( time.toString( "HH:mm" ) );
316 item->setTextAlignment( Qt::AlignHCenter );
317 item->setFlags( Qt::ItemIsEnabled );
318 item->setBackgroundColor( sHeaderBackground );
319 item->setFont( font );
320 iScheduleTable->setItem( i, 0, item );
321 time = time.addSecs( 60 * 60 );
324 // set empty column header items, these will be updated in refresh()
325 for ( int i = 1; i < iScheduleTable->columnCount(); ++i )
327 QTableWidgetItem *item = new QTableWidgetItem();
328 item->setTextAlignment( Qt::AlignCenter );
329 item->setFlags( Qt::ItemIsEnabled );
330 item->setBackgroundColor( sHeaderBackground );
331 item->setFont( font );
332 iScheduleTable->setItem( 0, i, item );
335 QVBoxLayout *layout = new QVBoxLayout;
336 layout->addWidget( iScheduleTable );
337 layout->setAlignment( Qt::AlignCenter );
338 layout->setMargin( 0 );
344 ScheduleWidget::~ScheduleWidget()
347 delete iScheduleTable;
350 int ScheduleWidget::shownWeek()
352 return iShownDate.weekNumber();
355 QDate ScheduleWidget::beginningOfShownWeek()
357 return iShownDate.addDays( -1 * iShownDate.dayOfWeek() + 1 );
360 Meeting* ScheduleWidget::currentMeeting()
362 return meeting( iCurrentDateTime );
365 Meeting* ScheduleWidget::meeting( QDateTime aAt )
367 for ( int i = 0; i < iMeetings.count(); ++i )
369 if ( iMeetings[i]->startsAt() <= aAt && iMeetings[i]->endsAt() >= aAt )
378 void ScheduleWidget::clear()
380 qDebug() << "ScheduleWidget::clear";
382 while ( !iMeetings.isEmpty() )
385 iMeetings.removeFirst();
387 qDebug() << "Deleted " << i << " items";
390 void ScheduleWidget::clear( QDateTime aFrom, QDateTime aUntil )
392 for ( int i = 0; i < iMeetings.count(); ++i )
394 if (( iMeetings[i]->startsAt() >= aFrom && iMeetings[i]->startsAt() <= aUntil ) ||
395 ( iMeetings[i]->startsAt() <= aFrom && iMeetings[i]->endsAt() >= aFrom ) )
397 iMeetings.removeAt( i );
403 void ScheduleWidget::refresh()
405 for ( int i = 0; i < iScheduleTable->columnCount(); ++i )
407 QTableWidgetItem* item = iScheduleTable->item( 0, i );
408 QFont font = item->font();
410 item->setText( tr( "Wk %1" ).arg( iShownDate.weekNumber() ) );
413 item->setText( iShownDate.addDays( i - 1 ).toString( tr( "ddd d MMM" ) ) );
415 if ( iCurrentDateTime.date() == iShownDate.addDays( i - 1 ) )
418 item->setBackgroundColor( sDayHighlightColor );
419 font.setItalic( true );
420 item->setFont( font );
424 item->setBackgroundColor( sHeaderBackground );
425 font.setItalic( false );
426 item->setFont( font );
430 // force repaint of the main area
431 iScheduleTable->setSpan( 1, 1, iNumberOfHours, weekLengthAsDays() );
433 iLastRefresh = iCurrentDateTime.time();
436 void ScheduleWidget::setCurrentDateTime( QDateTime aCurrentDateTime )
438 Meeting* previous = meeting( iCurrentDateTime );
439 Meeting* current = meeting( aCurrentDateTime );
440 iCurrentDateTime = aCurrentDateTime;
442 if ( iLastRefresh.secsTo( iCurrentDateTime.time() ) > sRefreshIntervalInSeconds )
444 // enough time has elapsed since last refresh
448 if ( previous != current )
450 emit currentMeetingChanged( current );
454 void ScheduleWidget::insertMeeting( Meeting *aMeeting )
456 Meeting* previous = meeting( iCurrentDateTime );
457 iMeetings.append( aMeeting );
458 Meeting* current = meeting( iCurrentDateTime );
460 qDebug() << "Inserted" << aMeeting->toString();
464 if ( previous != current )
466 emit currentMeetingChanged( current );
470 void ScheduleWidget::removeMeeting( Meeting *aMeeting )
472 Meeting* previous = meeting( iCurrentDateTime );
474 qDebug() << "Delete" << aMeeting->toString();
475 for ( int i = 0; i < iMeetings.count(); ++i )
477 if ( iMeetings[i]->equals( *(aMeeting) ) )
479 iMeetings.removeAt( i );
483 Meeting* current = meeting( iCurrentDateTime );
484 if ( previous != current )
485 emit currentMeetingChanged( current );
492 //void ScheduleWidget::updateMeeting( Meeting *aMeeting )
497 void ScheduleWidget::showPreviousWeek()
499 iShownDate = iShownDate.addDays( -7 );
501 emit shownWeekChanged( iShownDate );
504 void ScheduleWidget::showCurrentWeek()
506 iShownDate = iCurrentDateTime.date();
508 // set weekday to monday
509 iShownDate = iShownDate.addDays( -( iShownDate.dayOfWeek() - 1 ) );
512 emit shownWeekChanged( iShownDate );
515 void ScheduleWidget::showNextWeek()
517 iShownDate = iShownDate.addDays( 7 );
519 emit shownWeekChanged( iShownDate );
522 int ScheduleWidget::computeHeaderRow( QTime aTime )
524 // map given time to correct header row in the schedule table
525 return aTime.hour() - ( iStartHour - 1 );
528 int ScheduleWidget::weekLengthAsDays()
530 return ( iDaysInSchedule == DisplaySettings::WholeWeekInSchedule ) ? 7 : 5;
533 void ScheduleWidget::resizeEvent( QResizeEvent* /* aEvent */ )
535 QRect rect = iScheduleTable->contentsRect();
536 int rowHeight = ( int )( rect.height() / ( float )iScheduleTable->rowCount() );
537 int headerRowHeight = rowHeight;
538 int columnWidth = ( int )( rect.width() / ( iScheduleTable->columnCount() - 0.5f ) );
539 int headerColumnWidth = columnWidth / 2;
541 iScheduleTable->setRowHeight( 0, headerRowHeight );
542 for ( int i = 1; i < iScheduleTable->rowCount(); ++i )
544 iScheduleTable->setRowHeight( i, rowHeight );
547 iScheduleTable->setColumnWidth( 0, headerColumnWidth );
548 for ( int i = 1; i < iScheduleTable->columnCount(); ++i )
550 iScheduleTable->setColumnWidth( i, columnWidth );
553 // resize table so that frame size matches exactly
554 int leftMargin = 0, topMargin = 0, rightMargin = 0, bottomMargin = 0;
555 iScheduleTable->getContentsMargins( &leftMargin, &topMargin, &rightMargin, &bottomMargin );
556 iScheduleTable->resize( columnWidth * iScheduleTable->columnCount() - headerColumnWidth + leftMargin + rightMargin,
557 rowHeight * iScheduleTable->rowCount() + topMargin + bottomMargin );