1 #include "ScheduleWidget.h"
3 #include <QTableWidget>
8 #include <QResizeEvent>
12 const QColor ScheduleWidget::sFreeBackground = QColor( Qt::white );
13 const QColor ScheduleWidget::sBusyBackground = QColor( 238, 147, 17 );
14 const QColor ScheduleWidget::sBusyBackgroundStart = QColor( 254, 193, 104 );
15 const QColor ScheduleWidget::sCurrentBackgroundStart = QColor( 237, 124, 125 );
16 const QColor ScheduleWidget::sCurrentBackground = QColor( 161, 1, 1 );
17 const QColor ScheduleWidget::sHeaderBackground = QColor( Qt::white );
18 const QColor ScheduleWidget::sDayHighlightColor = QColor( 255, 235, 160 );
19 const QColor ScheduleWidget::sTimeHighlightColor = QColor( Qt::black );
20 const QColor ScheduleWidget::sMainGridColor = QColor( 140, 140, 140 );
21 const QColor ScheduleWidget::sHalfGridColor = QColor( 195, 195, 195 );
22 const QColor ScheduleWidget::sFrameColor = QColor( Qt::black );
24 ScheduleTableWidget::ScheduleTableWidget( int aRows, int aColumns, QWidget *aParent ) :
25 QTableWidget( aRows, aColumns, aParent )
27 ScheduleWidget* schedule = static_cast<ScheduleWidget*>( parent() );
29 iMeetingsByDay = new QList<MeetingContainer>[schedule->weekLengthAsDays()];
30 iTabletBlocked = false;
33 setFocusPolicy( Qt::NoFocus );
34 setFrameStyle( QFrame::NoFrame );
37 ScheduleTableWidget::~ScheduleTableWidget()
39 delete[] iMeetingsByDay;
42 void ScheduleTableWidget::paintEvent( QPaintEvent* aEvent )
44 qDebug() << "ScheduleTableWidget::paintEvent()";
45 QTableWidget::paintEvent( aEvent );
47 ScheduleWidget* schedule = static_cast<ScheduleWidget*>( parent() );
48 QPainter painter( viewport() );
49 int rowHeight = rowViewportPosition( 2 ) - rowViewportPosition( 1 ) - 1;
50 int columnWidth = columnViewportPosition( 2 ) - columnViewportPosition( 1 ) - 1;
52 // draw frame around the table
53 QRect viewportRect = viewport()->rect();
54 viewportRect.adjust( 0, 0, -1, -1 );
55 painter.setPen( ScheduleWidget::sFrameColor );
56 painter.drawRect( viewportRect );
58 // draw horizontal half grid
59 for ( int i = 1; i < rowCount(); ++i )
61 int x = columnViewportPosition( 1 );
62 int y = rowViewportPosition( i ) + ( rowHeight / 2 ) - 1;
63 painter.fillRect( QRect( x, y, width() - x - 1, 1 ), ScheduleWidget::sHalfGridColor );
66 // draw horizontal main grid
67 for ( int i = 1; i < rowCount(); ++i )
69 int y = rowViewportPosition( i ) - 1;
70 painter.fillRect( QRect( 1, y, width() - 2, 1 ), ScheduleWidget::sMainGridColor );
73 // draw vertical main grid
74 for ( int i = 1; i < columnCount(); ++i )
76 int x = columnViewportPosition( i ) - 1;
77 painter.fillRect( QRect( x, 1, 1, height() - 2 ), ScheduleWidget::sMainGridColor );
80 // draw current day highlight
81 QPen pen( ScheduleWidget::sDayHighlightColor );
83 painter.setPen( pen );
84 painter.setBrush( Qt::NoBrush );
86 for ( int i = 1; i < columnCount(); ++i )
88 if ( schedule->iCurrentDateTime.date() == schedule->iShownDate.addDays( i - 1 ) )
90 int x = columnViewportPosition( i ) + 1;
92 int w = columnWidth - 3;
94 painter.drawRect( x, y, w, h );
100 painter.setRenderHint( QPainter::Antialiasing );
101 painter.setPen( ScheduleWidget::sFrameColor );
102 populateMeetingList();
104 for ( int day = 0; day < schedule->weekLengthAsDays(); ++day )
106 for ( int i = 0; i < iMeetingsByDay[day].size(); ++i )
108 QLinearGradient linearGrad( QPoint(iMeetingsByDay[day][i].rect.x(),iMeetingsByDay[day][i].rect.y()) , QPoint(iMeetingsByDay[day][i].rect.x(),iMeetingsByDay[day][i].rect.bottom()) );
109 // draw meeting with red if it is ongoing
110 if ( iMeetingsByDay[day][i].meeting->startsAt() <= QDateTime::currentDateTime() &&
111 iMeetingsByDay[day][i].meeting->endsAt() >= QDateTime::currentDateTime() )
113 linearGrad.setColorAt(0, ScheduleWidget::sCurrentBackgroundStart);
114 linearGrad.setColorAt(1, ScheduleWidget::sCurrentBackground);
118 linearGrad.setColorAt(0, ScheduleWidget::sBusyBackgroundStart);
119 linearGrad.setColorAt(1, ScheduleWidget::sBusyBackground);
121 painter.setBrush(linearGrad);
123 painter.drawRoundRect( iMeetingsByDay[day][i].rect, 20, 20 );
127 // draw current time highlight
128 painter.setBrush( Qt::NoBrush );
129 painter.setRenderHint( QPainter::Antialiasing, false );
131 for ( int i = 1; i < columnCount(); ++i )
133 if ( schedule->iCurrentDateTime.date() == schedule->iShownDate.addDays( i - 1 ) )
135 int x = columnViewportPosition( i ) - 1;
136 int y = computeViewportY( schedule->iCurrentDateTime.time() );
137 int w = columnWidth + 2;
139 painter.fillRect( x, y, w, h, ScheduleWidget::sTimeHighlightColor );
145 void ScheduleTableWidget::tabletEvent( QTabletEvent* aEvent )
147 int ms = iTime.restart();
149 if ( iTabletBlocked && ms > 1000 )
151 iTabletBlocked = false;
152 qDebug() << "Tablet block released";
155 if ( iTabletBlocked == false )
157 qDebug() << "Tablet blocked released";
158 activateMeeting( aEvent->x(), aEvent->y() );
162 void ScheduleTableWidget::mouseMoveEvent( QMouseEvent* /* aEvent */ )
164 // this is overridden as empty method because otherwise
165 // unwanted behaviour would occur due to QTableWidget
168 void ScheduleTableWidget::mousePressEvent( QMouseEvent* aEvent )
170 activateMeeting( aEvent->x(), aEvent->y() );
171 iTabletBlocked = false;
174 void ScheduleTableWidget::populateMeetingList()
176 qDebug() << "ScheduleTableWidget::populateMeetingList()";
177 ScheduleWidget* schedule = static_cast<ScheduleWidget*>( parent() );
179 for ( int i = 0; i < schedule->weekLengthAsDays(); ++i )
180 iMeetingsByDay[i].clear();
182 // insert suitable meetings to list
183 for ( int i = 0; i < schedule->iMeetings.count(); ++i )
185 Meeting* meeting = schedule->iMeetings[i];
186 int day = meeting->startsAt().date().dayOfWeek() - 1;
188 if (( meeting->startsAt().date().weekNumber() == schedule->iShownDate.weekNumber() ) &&
189 ( day < schedule->weekLengthAsDays() ) &&
190 ( meeting->endsAt().time() > QTime( schedule->iStartHour, 0 ) ) &&
191 ( meeting->startsAt().time() <= QTime( schedule->iStartHour + schedule->iNumberOfHours - 1, 59 ) ) )
193 MeetingContainer container;
194 container.meeting = meeting;
195 container.rect = QRect( 0, 0, 0, 0 );
196 container.rectComputed = false;
197 iMeetingsByDay[day].append( container );
201 // compute meeting rectangles
202 for ( int day = 0; day < schedule->weekLengthAsDays(); ++day )
204 for ( int i = 0; i < iMeetingsByDay[day].size(); ++i )
206 if ( iMeetingsByDay[day][i].rectComputed )
209 QList<int> meetingIndices;
210 findOverlappingMeetings( day, iMeetingsByDay[day][i].meeting, meetingIndices );
211 meetingIndices.append( i );
213 for ( int j = 0; j < meetingIndices.size(); ++j )
215 if ( iMeetingsByDay[day][meetingIndices[j]].rectComputed )
218 int columnWidth = columnViewportPosition( 2 ) - columnViewportPosition( 1 ) - 1;
220 Meeting* meeting = iMeetingsByDay[day][meetingIndices[j]].meeting;
221 int x = columnViewportPosition( day + 1 ) + ( int )(( columnWidth / ( float )meetingIndices.size() ) * j );
222 int y = computeViewportY( meeting->startsAt().time() );
223 int width = ( int )( columnWidth / ( float )meetingIndices.size() + 0.5f );
224 int height = computeViewportY( meeting->endsAt().time() ) - y;
226 iMeetingsByDay[day][meetingIndices[j]].rect = QRect( x, y, width, height );
227 iMeetingsByDay[day][meetingIndices[j]].rectComputed = true;
233 bool ScheduleTableWidget::findOverlappingMeetings( int aDay, Meeting* aMeeting, QList<int>& aResult )
235 QSet<int> overlapSet;
237 // first find meetings that overlap with aMeeting
238 for ( int i = 0; i < iMeetingsByDay[aDay].size(); ++i )
240 Meeting* other = iMeetingsByDay[aDay][i].meeting;
241 if ( aMeeting != other && aMeeting->overlaps( *(other) ) )
242 overlapSet.insert( i );
245 // then compare overlappiong ones against every meeting to make sure that
246 // the returned set can be used to compute rectangles for all cases at once
247 foreach( int index, overlapSet )
249 Meeting* meetingInSet = iMeetingsByDay[aDay][index].meeting;
250 for ( int i = 0; i < iMeetingsByDay[aDay].size(); ++i )
252 Meeting* other = iMeetingsByDay[aDay][i].meeting;
253 if ( meetingInSet != other && aMeeting != other && meetingInSet->overlaps( *(other) ) )
254 overlapSet.insert( i );
259 foreach( int index, overlapSet )
261 aResult.append( index );
264 return !aResult.empty();
267 void ScheduleTableWidget::activateMeeting( int x, int y )
269 ScheduleWidget* schedule = static_cast<ScheduleWidget*>( parent() );
271 for ( int day = 0; day < schedule->weekLengthAsDays(); ++day )
273 for ( int i = 0; i < iMeetingsByDay[day].size(); ++i )
275 if ( iMeetingsByDay[day][i].rect.contains( x, y ) && !iTabletBlocked )
277 iTabletBlocked = true;
278 qDebug() << "Activated meeting at x" << x << "y" << y << ":" << iMeetingsByDay[day][i].meeting->toString();
279 emit schedule->meetingActivated( iMeetingsByDay[day][i].meeting );
285 int ScheduleTableWidget::computeViewportY( QTime aTime )
287 ScheduleWidget* schedule = static_cast<ScheduleWidget*>( parent() );
288 int secondsInDisplayDay = schedule->iNumberOfHours * 60 * 60;
289 int mainY = rowViewportPosition( 1 ) + 1;
290 int mainHeight = height() - mainY - 1;
292 return mainY + ( int )(( QTime( schedule->iStartHour, 0 ).secsTo( aTime ) / ( float )secondsInDisplayDay ) * mainHeight );
295 ScheduleWidget::ScheduleWidget( QDateTime aCurrentDateTime, DisplaySettings *aSettings, QWidget *aParent ) :
296 ObservedWidget( aParent ),
297 iCurrentDateTime( aCurrentDateTime ),
298 iStartHour( aSettings->dayStartsAt().hour() ),
299 iNumberOfHours( aSettings->dayEndsAt().hour() - aSettings->dayStartsAt().hour() + 1 ),
300 iDaysInSchedule( aSettings->daysInSchedule() ),
301 iLastRefresh( aCurrentDateTime.time() )
303 iStartHour = qBound( 0, iStartHour, 23 );
304 iNumberOfHours = qBound( 1, iNumberOfHours, 24 - iStartHour );
306 iScheduleTable = new ScheduleTableWidget(( iNumberOfHours + 1 ) * 1, weekLengthAsDays() + 1, this );
307 iScheduleTable->horizontalHeader()->hide();
308 iScheduleTable->verticalHeader()->hide();
309 iScheduleTable->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
310 iScheduleTable->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
311 iScheduleTable->setShowGrid( false );
314 font.setStyleHint( QFont::Helvetica );
315 font.setBold( true );
316 font.setPixelSize( 20 );
318 // add empty item to top-left corner, this will be updated in refresh()
319 QTableWidgetItem *weekItem = new QTableWidgetItem();
320 weekItem->setTextAlignment( Qt::AlignCenter );
321 weekItem->setFlags( Qt::ItemIsEnabled );
322 weekItem->setBackgroundColor( sHeaderBackground );
323 weekItem->setFont( font );
324 iScheduleTable->setItem( 0, 0, weekItem );
326 // add empty item to main cell
327 QTableWidgetItem *mainItem = new QTableWidgetItem();
328 mainItem->setFlags( Qt::ItemIsEnabled );
329 mainItem->setBackgroundColor( sFreeBackground );
330 iScheduleTable->setItem( 1, 1, mainItem );
332 // set row header items
333 QTime time( iStartHour, 0 );
334 for ( int i = 1; i < iScheduleTable->rowCount(); ++i )
336 QTableWidgetItem *item = new QTableWidgetItem( time.toString( "HH:mm" ) );
337 item->setTextAlignment( Qt::AlignHCenter );
338 item->setFlags( Qt::ItemIsEnabled );
339 item->setBackgroundColor( sHeaderBackground );
340 item->setFont( font );
341 iScheduleTable->setItem( i, 0, item );
342 time = time.addSecs( 60 * 60 );
345 // set empty column header items, these will be updated in refresh()
346 for ( int i = 1; i < iScheduleTable->columnCount(); ++i )
348 QTableWidgetItem *item = new QTableWidgetItem();
349 item->setTextAlignment( Qt::AlignCenter );
350 item->setFlags( Qt::ItemIsEnabled );
351 item->setBackgroundColor( sHeaderBackground );
352 item->setFont( font );
353 iScheduleTable->setItem( 0, i, item );
356 QVBoxLayout *layout = new QVBoxLayout;
357 layout->addWidget( iScheduleTable );
358 layout->setAlignment( Qt::AlignCenter );
359 layout->setMargin( 0 );
365 ScheduleWidget::~ScheduleWidget()
367 if ( iScheduleTable )
369 delete iScheduleTable;
374 QDate ScheduleWidget::beginningOfShownWeek()
376 return iShownDate.addDays( -1 * iShownDate.dayOfWeek() + 1 );
379 void ScheduleWidget::refresh()
381 qDebug() << "ScheduleWidget::refresh()";
383 for ( int i = 0; i < iScheduleTable->columnCount(); ++i )
385 QTableWidgetItem* item = iScheduleTable->item( 0, i );
386 QFont font = item->font();
388 item->setText( tr( "Wk %1" ).arg( iShownDate.weekNumber() ) );
391 item->setText( iShownDate.addDays( i - 1 ).toString( tr( "ddd d/M" ) ) );
393 if ( iCurrentDateTime.date() == iShownDate.addDays( i - 1 ) )
396 item->setBackgroundColor( sDayHighlightColor );
397 font.setItalic( true );
398 item->setFont( font );
402 item->setBackgroundColor( sHeaderBackground );
403 font.setItalic( false );
404 item->setFont( font );
408 // force repaint of the main area
409 iScheduleTable->setSpan( 1, 1, iNumberOfHours, weekLengthAsDays() );
411 iLastRefresh = iCurrentDateTime.time();
414 void ScheduleWidget::refreshMeetings( const QList<Meeting*> &aMeetings )
416 iMeetings = aMeetings;
417 qDebug() << "Count: " << iMeetings.size();
421 void ScheduleWidget::setCurrentDateTime( QDateTime aCurrentDateTime )
423 iCurrentDateTime = aCurrentDateTime;
425 if ( iLastRefresh.secsTo( iCurrentDateTime.time() ) > sRefreshIntervalInSeconds )
427 // enough time has elapsed since last refresh
432 void ScheduleWidget::showPreviousWeek()
434 iShownDate = iShownDate.addDays( -7 );
436 emit shownWeekChanged( iShownDate );
439 void ScheduleWidget::showCurrentWeek()
441 iShownDate = iCurrentDateTime.date();
443 // set weekday to monday
444 iShownDate = iShownDate.addDays( -( iShownDate.dayOfWeek() - 1 ) );
447 emit shownWeekChanged( iShownDate );
450 void ScheduleWidget::showNextWeek()
452 iShownDate = iShownDate.addDays( 7 );
454 emit shownWeekChanged( iShownDate );
457 int ScheduleWidget::computeHeaderRow( QTime aTime )
459 // map given time to correct header row in the schedule table
460 return aTime.hour() - ( iStartHour - 1 );
463 int ScheduleWidget::weekLengthAsDays()
465 return ( iDaysInSchedule == DisplaySettings::WholeWeekInSchedule ) ? 7 : 5;
468 void ScheduleWidget::resizeEvent( QResizeEvent* /* aEvent */ )
470 QRect rect = iScheduleTable->contentsRect();
471 int rowHeight = ( int )( rect.height() / ( float )( iScheduleTable->rowCount() + 1 ) );
472 int headerRowHeight = ( int )rowHeight*2;
473 int headerColumnWidth = rect.width() * 0.15f;
474 int columnWidth = ( rect.width() - headerColumnWidth ) / ( iScheduleTable->columnCount() - 1 );
476 iScheduleTable->setRowHeight( 0, headerRowHeight );
477 for ( int i = 1; i < iScheduleTable->rowCount(); ++i )
479 iScheduleTable->setRowHeight( i, rowHeight );
482 iScheduleTable->setColumnWidth( 0, headerColumnWidth );
483 for ( int i = 1; i < iScheduleTable->columnCount(); ++i )
485 iScheduleTable->setColumnWidth( i, columnWidth );
488 // resize table so that frame size matches exactly
489 int leftMargin = 0, topMargin = 0, rightMargin = 0, bottomMargin = 0;
490 iScheduleTable->getContentsMargins( &leftMargin, &topMargin, &rightMargin, &bottomMargin );
491 iScheduleTable->resize( columnWidth * ( iScheduleTable->columnCount() - 1 ) +
492 headerColumnWidth + leftMargin + rightMargin,
493 rowHeight * ( iScheduleTable->rowCount() + 1 ) + topMargin + bottomMargin );