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::sHeaderBackground = QColor( Qt::white );
16 const QColor ScheduleWidget::sDayHighlightColor = QColor( 255, 235, 160 );
17 const QColor ScheduleWidget::sTimeHighlightColor = QColor( Qt::black );
18 const QColor ScheduleWidget::sMainGridColor = QColor( 140, 140, 140 );
19 const QColor ScheduleWidget::sHalfGridColor = QColor( 195, 195, 195 );
20 const QColor ScheduleWidget::sFrameColor = QColor( Qt::black );
22 ScheduleTableWidget::ScheduleTableWidget( int aRows, int aColumns, QWidget *aParent ) :
23 QTableWidget( aRows, aColumns, aParent )
25 ScheduleWidget* schedule = static_cast<ScheduleWidget*>( parent() );
27 iMeetingsByDay = new QList<MeetingContainer>[schedule->weekLengthAsDays()];
28 iTabletBlocked = false;
31 setFocusPolicy( Qt::NoFocus );
32 setFrameStyle( QFrame::NoFrame );
35 ScheduleTableWidget::~ScheduleTableWidget()
37 delete[] iMeetingsByDay;
40 void ScheduleTableWidget::paintEvent( QPaintEvent* aEvent )
42 qDebug() << "ScheduleTableWidget::paintEvent()";
43 QTableWidget::paintEvent( aEvent );
45 ScheduleWidget* schedule = static_cast<ScheduleWidget*>( parent() );
46 QPainter painter( viewport() );
47 int rowHeight = rowViewportPosition( 2 ) - rowViewportPosition( 1 ) - 1;
48 int columnWidth = columnViewportPosition( 2 ) - columnViewportPosition( 1 ) - 1;
50 // draw frame around the table
51 QRect viewportRect = viewport()->rect();
52 viewportRect.adjust( 0, 0, -1, -1 );
53 painter.setPen( ScheduleWidget::sFrameColor );
54 painter.drawRect( viewportRect );
56 // draw horizontal half grid
57 for ( int i = 1; i < rowCount(); ++i )
59 int x = columnViewportPosition( 1 );
60 int y = rowViewportPosition( i ) + ( rowHeight / 2 ) - 1;
61 painter.fillRect( QRect( x, y, width() - x - 1, 1 ), ScheduleWidget::sHalfGridColor );
64 // draw horizontal main grid
65 for ( int i = 1; i < rowCount(); ++i )
67 int y = rowViewportPosition( i ) - 1;
68 painter.fillRect( QRect( 1, y, width() - 2, 1 ), ScheduleWidget::sMainGridColor );
71 // draw vertical main grid
72 for ( int i = 1; i < columnCount(); ++i )
74 int x = columnViewportPosition( i ) - 1;
75 painter.fillRect( QRect( x, 1, 1, height() - 2 ), ScheduleWidget::sMainGridColor );
78 // draw current day highlight
79 QPen pen( ScheduleWidget::sDayHighlightColor );
81 painter.setPen( pen );
82 painter.setBrush( Qt::NoBrush );
84 for ( int i = 1; i < columnCount(); ++i )
86 if ( schedule->iCurrentDateTime.date() == schedule->iShownDate.addDays( i - 1 ) )
88 int x = columnViewportPosition( i ) + 1;
90 int w = columnWidth - 3;
92 painter.drawRect( x, y, w, h );
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 QLinearGradient linearGrad( QPoint(iMeetingsByDay[day][i].rect.x(),iMeetingsByDay[day][i].rect.y()) , QPoint(iMeetingsByDay[day][i].rect.x(),iMeetingsByDay[day][i].rect.bottom()) );
107 linearGrad.setColorAt(0, ScheduleWidget::sBusyBackgroundStart);
108 linearGrad.setColorAt(1, ScheduleWidget::sBusyBackground);
109 painter.setBrush(linearGrad);
111 painter.drawRoundRect( iMeetingsByDay[day][i].rect, 20, 20 );
115 // draw current time highlight
116 painter.setBrush( Qt::NoBrush );
117 painter.setRenderHint( QPainter::Antialiasing, false );
119 for ( int i = 1; i < columnCount(); ++i )
121 if ( schedule->iCurrentDateTime.date() == schedule->iShownDate.addDays( i - 1 ) )
123 int x = columnViewportPosition( i ) - 1;
124 int y = computeViewportY( schedule->iCurrentDateTime.time() );
125 int w = columnWidth + 2;
127 painter.fillRect( x, y, w, h, ScheduleWidget::sTimeHighlightColor );
133 void ScheduleTableWidget::tabletEvent( QTabletEvent* aEvent )
135 int ms = iTime.restart();
137 if ( iTabletBlocked && ms > 1000 )
139 iTabletBlocked = false;
140 qDebug() << "Tablet block released";
143 if ( iTabletBlocked == false )
145 qDebug() << "Tablet blocked released";
146 activateMeeting( aEvent->x(), aEvent->y() );
150 void ScheduleTableWidget::mouseMoveEvent( QMouseEvent* /* aEvent */ )
152 // this is overridden as empty method because otherwise
153 // unwanted behaviour would occur due to QTableWidget
156 void ScheduleTableWidget::mousePressEvent( QMouseEvent* aEvent )
158 activateMeeting( aEvent->x(), aEvent->y() );
159 iTabletBlocked = false;
162 void ScheduleTableWidget::populateMeetingList()
164 qDebug() << "ScheduleTableWidget::populateMeetingList()";
165 ScheduleWidget* schedule = static_cast<ScheduleWidget*>( parent() );
167 for ( int i = 0; i < schedule->weekLengthAsDays(); ++i )
168 iMeetingsByDay[i].clear();
170 // insert suitable meetings to list
171 for ( int i = 0; i < schedule->iMeetings.count(); ++i )
173 Meeting* meeting = schedule->iMeetings[i];
174 int day = meeting->startsAt().date().dayOfWeek() - 1;
176 if (( meeting->startsAt().date().weekNumber() == schedule->iShownDate.weekNumber() ) &&
177 ( day < schedule->weekLengthAsDays() ) &&
178 ( meeting->endsAt().time() > QTime( schedule->iStartHour, 0 ) ) &&
179 ( meeting->startsAt().time() <= QTime( schedule->iStartHour + schedule->iNumberOfHours - 1, 59 ) ) )
181 MeetingContainer container;
182 container.meeting = meeting;
183 container.rect = QRect( 0, 0, 0, 0 );
184 container.rectComputed = false;
185 iMeetingsByDay[day].append( container );
189 // compute meeting rectangles
190 for ( int day = 0; day < schedule->weekLengthAsDays(); ++day )
192 for ( int i = 0; i < iMeetingsByDay[day].size(); ++i )
194 if ( iMeetingsByDay[day][i].rectComputed )
197 QList<int> meetingIndices;
198 findOverlappingMeetings( day, iMeetingsByDay[day][i].meeting, meetingIndices );
199 meetingIndices.append( i );
201 for ( int j = 0; j < meetingIndices.size(); ++j )
203 if ( iMeetingsByDay[day][meetingIndices[j]].rectComputed )
206 int columnWidth = columnViewportPosition( 2 ) - columnViewportPosition( 1 ) - 1;
208 Meeting* meeting = iMeetingsByDay[day][meetingIndices[j]].meeting;
209 int x = columnViewportPosition( day + 1 ) + ( int )(( columnWidth / ( float )meetingIndices.size() ) * j );
210 int y = computeViewportY( meeting->startsAt().time() );
211 int width = ( int )( columnWidth / ( float )meetingIndices.size() + 0.5f );
212 int height = computeViewportY( meeting->endsAt().time() ) - y;
214 iMeetingsByDay[day][meetingIndices[j]].rect = QRect( x, y, width, height );
215 iMeetingsByDay[day][meetingIndices[j]].rectComputed = true;
221 bool ScheduleTableWidget::findOverlappingMeetings( int aDay, Meeting* aMeeting, QList<int>& aResult )
223 QSet<int> overlapSet;
225 // first find meetings that overlap with aMeeting
226 for ( int i = 0; i < iMeetingsByDay[aDay].size(); ++i )
228 Meeting* other = iMeetingsByDay[aDay][i].meeting;
229 if ( aMeeting != other && aMeeting->overlaps( *(other) ) )
230 overlapSet.insert( i );
233 // then compare overlappiong ones against every meeting to make sure that
234 // the returned set can be used to compute rectangles for all cases at once
235 foreach( int index, overlapSet )
237 Meeting* meetingInSet = iMeetingsByDay[aDay][index].meeting;
238 for ( int i = 0; i < iMeetingsByDay[aDay].size(); ++i )
240 Meeting* other = iMeetingsByDay[aDay][i].meeting;
241 if ( meetingInSet != other && aMeeting != other && meetingInSet->overlaps( *(other) ) )
242 overlapSet.insert( i );
247 foreach( int index, overlapSet )
249 aResult.append( index );
252 return !aResult.empty();
255 void ScheduleTableWidget::activateMeeting( int x, int y )
257 ScheduleWidget* schedule = static_cast<ScheduleWidget*>( parent() );
259 for ( int day = 0; day < schedule->weekLengthAsDays(); ++day )
261 for ( int i = 0; i < iMeetingsByDay[day].size(); ++i )
263 if ( iMeetingsByDay[day][i].rect.contains( x, y ) && !iTabletBlocked )
265 iTabletBlocked = true;
266 qDebug() << "Activated meeting at x" << x << "y" << y << ":" << iMeetingsByDay[day][i].meeting->toString();
267 emit schedule->meetingActivated( iMeetingsByDay[day][i].meeting );
273 int ScheduleTableWidget::computeViewportY( QTime aTime )
275 ScheduleWidget* schedule = static_cast<ScheduleWidget*>( parent() );
276 int secondsInDisplayDay = schedule->iNumberOfHours * 60 * 60;
277 int mainY = rowViewportPosition( 1 ) + 1;
278 int mainHeight = height() - mainY - 1;
280 return mainY + ( int )(( QTime( schedule->iStartHour, 0 ).secsTo( aTime ) / ( float )secondsInDisplayDay ) * mainHeight );
283 ScheduleWidget::ScheduleWidget( QDateTime aCurrentDateTime, DisplaySettings *aSettings, QWidget *aParent ) :
284 ObservedWidget( aParent ),
285 iCurrentDateTime( aCurrentDateTime ),
286 iStartHour( aSettings->dayStartsAt().hour() ),
287 iNumberOfHours( aSettings->dayEndsAt().hour() - aSettings->dayStartsAt().hour() + 1 ),
288 iDaysInSchedule( aSettings->daysInSchedule() ),
289 iLastRefresh( aCurrentDateTime.time() )
291 iStartHour = qBound( 0, iStartHour, 23 );
292 iNumberOfHours = qBound( 1, iNumberOfHours, 24 - iStartHour );
294 iScheduleTable = new ScheduleTableWidget(( iNumberOfHours + 1 ) * 1, weekLengthAsDays() + 1, this );
295 iScheduleTable->horizontalHeader()->hide();
296 iScheduleTable->verticalHeader()->hide();
297 iScheduleTable->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
298 iScheduleTable->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
299 iScheduleTable->setShowGrid( false );
302 font.setPointSize( 10 );
304 // add empty item to top-left corner, this will be updated in refresh()
305 QTableWidgetItem *weekItem = new QTableWidgetItem();
306 weekItem->setTextAlignment( Qt::AlignCenter );
307 weekItem->setFlags( Qt::ItemIsEnabled );
308 weekItem->setBackgroundColor( sHeaderBackground );
309 weekItem->setFont( font );
310 iScheduleTable->setItem( 0, 0, weekItem );
312 // add empty item to main cell
313 QTableWidgetItem *mainItem = new QTableWidgetItem();
314 mainItem->setFlags( Qt::ItemIsEnabled );
315 mainItem->setBackgroundColor( sFreeBackground );
316 iScheduleTable->setItem( 1, 1, mainItem );
318 // set row header items
319 QTime time( iStartHour, 0 );
320 for ( int i = 1; i < iScheduleTable->rowCount(); ++i )
322 QTableWidgetItem *item = new QTableWidgetItem( time.toString( "HH:mm" ) );
323 item->setTextAlignment( Qt::AlignHCenter );
324 item->setFlags( Qt::ItemIsEnabled );
325 item->setBackgroundColor( sHeaderBackground );
326 item->setFont( font );
327 iScheduleTable->setItem( i, 0, item );
328 time = time.addSecs( 60 * 60 );
331 // set empty column header items, these will be updated in refresh()
332 for ( int i = 1; i < iScheduleTable->columnCount(); ++i )
334 QTableWidgetItem *item = new QTableWidgetItem();
335 item->setTextAlignment( Qt::AlignCenter );
336 item->setFlags( Qt::ItemIsEnabled );
337 item->setBackgroundColor( sHeaderBackground );
338 item->setFont( font );
339 iScheduleTable->setItem( 0, i, item );
342 QVBoxLayout *layout = new QVBoxLayout;
343 layout->addWidget( iScheduleTable );
344 layout->setAlignment( Qt::AlignCenter );
345 layout->setMargin( 0 );
351 ScheduleWidget::~ScheduleWidget()
353 if ( iScheduleTable )
355 delete iScheduleTable;
360 QDate ScheduleWidget::beginningOfShownWeek()
362 return iShownDate.addDays( -1 * iShownDate.dayOfWeek() + 1 );
365 void ScheduleWidget::refresh()
367 qDebug() << "ScheduleWidget::refresh()";
369 for ( int i = 0; i < iScheduleTable->columnCount(); ++i )
371 QTableWidgetItem* item = iScheduleTable->item( 0, i );
372 QFont font = item->font();
374 item->setText( tr( "Wk %1" ).arg( iShownDate.weekNumber() ) );
377 item->setText( iShownDate.addDays( i - 1 ).toString( tr( "ddd d MMM" ) ) );
379 if ( iCurrentDateTime.date() == iShownDate.addDays( i - 1 ) )
382 item->setBackgroundColor( sDayHighlightColor );
383 font.setItalic( true );
384 item->setFont( font );
388 item->setBackgroundColor( sHeaderBackground );
389 font.setItalic( false );
390 item->setFont( font );
394 // force repaint of the main area
395 iScheduleTable->setSpan( 1, 1, iNumberOfHours, weekLengthAsDays() );
397 iLastRefresh = iCurrentDateTime.time();
400 void ScheduleWidget::refreshMeetings( const QList<Meeting*> &aMeetings )
402 iMeetings = aMeetings;
403 qDebug() << "Count: " << iMeetings.size();
407 void ScheduleWidget::setCurrentDateTime( QDateTime aCurrentDateTime )
409 iCurrentDateTime = aCurrentDateTime;
411 if ( iLastRefresh.secsTo( iCurrentDateTime.time() ) > sRefreshIntervalInSeconds )
413 // enough time has elapsed since last refresh
418 void ScheduleWidget::showPreviousWeek()
420 iShownDate = iShownDate.addDays( -7 );
422 emit shownWeekChanged( iShownDate );
425 void ScheduleWidget::showCurrentWeek()
427 iShownDate = iCurrentDateTime.date();
429 // set weekday to monday
430 iShownDate = iShownDate.addDays( -( iShownDate.dayOfWeek() - 1 ) );
433 emit shownWeekChanged( iShownDate );
436 void ScheduleWidget::showNextWeek()
438 iShownDate = iShownDate.addDays( 7 );
440 emit shownWeekChanged( iShownDate );
443 int ScheduleWidget::computeHeaderRow( QTime aTime )
445 // map given time to correct header row in the schedule table
446 return aTime.hour() - ( iStartHour - 1 );
449 int ScheduleWidget::weekLengthAsDays()
451 return ( iDaysInSchedule == DisplaySettings::WholeWeekInSchedule ) ? 7 : 5;
454 void ScheduleWidget::resizeEvent( QResizeEvent* /* aEvent */ )
456 QRect rect = iScheduleTable->contentsRect();
457 int rowHeight = ( int )( rect.height() / ( float )iScheduleTable->rowCount() );
458 int headerRowHeight = rowHeight;
459 int columnWidth = ( int )( rect.width() / ( iScheduleTable->columnCount() - 0.5f ) );
460 int headerColumnWidth = columnWidth / 2;
462 iScheduleTable->setRowHeight( 0, headerRowHeight );
463 for ( int i = 1; i < iScheduleTable->rowCount(); ++i )
465 iScheduleTable->setRowHeight( i, rowHeight );
468 iScheduleTable->setColumnWidth( 0, headerColumnWidth );
469 for ( int i = 1; i < iScheduleTable->columnCount(); ++i )
471 iScheduleTable->setColumnWidth( i, columnWidth );
474 // resize table so that frame size matches exactly
475 int leftMargin = 0, topMargin = 0, rightMargin = 0, bottomMargin = 0;
476 iScheduleTable->getContentsMargins( &leftMargin, &topMargin, &rightMargin, &bottomMargin );
477 iScheduleTable->resize( columnWidth * iScheduleTable->columnCount() - headerColumnWidth + leftMargin + rightMargin,
478 rowHeight * iScheduleTable->rowCount() + topMargin + bottomMargin );