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 qDebug() << "ScheduleTableWidget::paintEvent()";
42 QTableWidget::paintEvent( aEvent );
44 ScheduleWidget* schedule = static_cast<ScheduleWidget*>( parent() );
45 QPainter painter( viewport() );
46 int rowHeight = rowViewportPosition( 2 ) - rowViewportPosition( 1 ) - 1;
47 int columnWidth = columnViewportPosition( 2 ) - columnViewportPosition( 1 ) - 1;
49 // draw frame around the table
50 QRect viewportRect = viewport()->rect();
51 viewportRect.adjust( 0, 0, -1, -1 );
52 painter.setPen( ScheduleWidget::sFrameColor );
53 painter.drawRect( viewportRect );
55 // draw horizontal half grid
56 for ( int i = 1; i < rowCount(); ++i )
58 int x = columnViewportPosition( 1 );
59 int y = rowViewportPosition( i ) + ( rowHeight / 2 ) - 1;
60 painter.fillRect( QRect( x, y, width() - x - 1, 1 ), ScheduleWidget::sHalfGridColor );
63 // draw horizontal main grid
64 for ( int i = 1; i < rowCount(); ++i )
66 int y = rowViewportPosition( i ) - 1;
67 painter.fillRect( QRect( 1, y, width() - 2, 1 ), ScheduleWidget::sMainGridColor );
70 // draw vertical main grid
71 for ( int i = 1; i < columnCount(); ++i )
73 int x = columnViewportPosition( i ) - 1;
74 painter.fillRect( QRect( x, 1, 1, height() - 2 ), ScheduleWidget::sMainGridColor );
77 // draw current day highlight
78 QPen pen( ScheduleWidget::sDayHighlightColor );
80 painter.setPen( pen );
81 painter.setBrush( Qt::NoBrush );
83 for ( int i = 1; i < columnCount(); ++i )
85 if ( schedule->iCurrentDateTime.date() == schedule->iShownDate.addDays( i - 1 ) )
87 int x = columnViewportPosition( i ) + 1;
89 int w = columnWidth - 3;
91 painter.drawRect( x, y, w, h );
97 QBrush brush( ScheduleWidget::sBusyBackground );
98 painter.setBrush( brush );
99 painter.setRenderHint( QPainter::Antialiasing );
100 painter.setPen( ScheduleWidget::sFrameColor );
101 populateMeetingList();
103 for ( int day = 0; day < schedule->weekLengthAsDays(); ++day )
105 for ( int i = 0; i < iMeetingsByDay[day].size(); ++i )
107 painter.drawRoundRect( iMeetingsByDay[day][i].rect, 20, 20 );
111 // draw current time highlight
112 painter.setBrush( Qt::NoBrush );
113 painter.setRenderHint( QPainter::Antialiasing, false );
115 for ( int i = 1; i < columnCount(); ++i )
117 if ( schedule->iCurrentDateTime.date() == schedule->iShownDate.addDays( i - 1 ) )
119 int x = columnViewportPosition( i ) - 1;
120 int y = computeViewportY( schedule->iCurrentDateTime.time() );
121 int w = columnWidth + 2;
123 painter.fillRect( x, y, w, h, ScheduleWidget::sTimeHighlightColor );
129 void ScheduleTableWidget::tabletEvent( QTabletEvent* aEvent )
131 int ms = iTime.restart();
133 if ( iTabletBlocked && ms > 1000 )
135 iTabletBlocked = false;
136 qDebug() << "Tablet block released";
139 if ( iTabletBlocked == false )
141 qDebug() << "Tablet blocked released";
142 activateMeeting( aEvent->x(), aEvent->y() );
146 void ScheduleTableWidget::mouseMoveEvent( QMouseEvent* /* aEvent */ )
148 // this is overridden as empty method because otherwise
149 // unwanted behaviour would occur due to QTableWidget
152 void ScheduleTableWidget::mousePressEvent( QMouseEvent* aEvent )
154 activateMeeting( aEvent->x(), aEvent->y() );
155 iTabletBlocked = false;
158 void ScheduleTableWidget::populateMeetingList()
160 qDebug() << "ScheduleTableWidget::populateMeetingList()";
161 ScheduleWidget* schedule = static_cast<ScheduleWidget*>( parent() );
163 for ( int i = 0; i < schedule->weekLengthAsDays(); ++i )
164 iMeetingsByDay[i].clear();
166 // insert suitable meetings to list
167 for ( int i = 0; i < schedule->iMeetings.count(); ++i )
169 Meeting* meeting = schedule->iMeetings[i];
170 int day = meeting->startsAt().date().dayOfWeek() - 1;
171 if (( meeting->startsAt().date().weekNumber() == schedule->iShownDate.weekNumber() ) &&
172 ( day < schedule->weekLengthAsDays() ) &&
173 ( meeting->endsAt().time() > QTime( schedule->iStartHour, 0 ) ) &&
174 ( meeting->startsAt().time() <= QTime( schedule->iStartHour + schedule->iNumberOfHours - 1, 59 ) ) )
176 MeetingContainer container;
177 container.meeting = meeting;
178 container.rect = QRect( 0, 0, 0, 0 );
179 container.rectComputed = false;
180 iMeetingsByDay[day].append( container );
184 // compute meeting rectangles
185 for ( int day = 0; day < schedule->weekLengthAsDays(); ++day )
187 for ( int i = 0; i < iMeetingsByDay[day].size(); ++i )
189 if ( iMeetingsByDay[day][i].rectComputed )
192 QList<int> meetingIndices;
193 findOverlappingMeetings( day, iMeetingsByDay[day][i].meeting, meetingIndices );
194 meetingIndices.append( i );
196 for ( int j = 0; j < meetingIndices.size(); ++j )
198 if ( iMeetingsByDay[day][meetingIndices[j]].rectComputed )
201 int columnWidth = columnViewportPosition( 2 ) - columnViewportPosition( 1 ) - 1;
203 Meeting* meeting = iMeetingsByDay[day][meetingIndices[j]].meeting;
204 int x = columnViewportPosition( day + 1 ) + ( int )(( columnWidth / ( float )meetingIndices.size() ) * j );
205 int y = computeViewportY( meeting->startsAt().time() );
206 int width = ( int )( columnWidth / ( float )meetingIndices.size() + 0.5f );
207 int height = computeViewportY( meeting->endsAt().time() ) - y;
209 iMeetingsByDay[day][meetingIndices[j]].rect = QRect( x, y, width, height );
210 iMeetingsByDay[day][meetingIndices[j]].rectComputed = true;
216 bool ScheduleTableWidget::findOverlappingMeetings( int aDay, Meeting* aMeeting, QList<int>& aResult )
218 QSet<int> overlapSet;
220 // first find meetings that overlap with aMeeting
221 for ( int i = 0; i < iMeetingsByDay[aDay].size(); ++i )
223 Meeting* other = iMeetingsByDay[aDay][i].meeting;
224 if ( aMeeting != other && aMeeting->overlaps( *(other) ) )
225 overlapSet.insert( i );
228 // then compare overlappiong ones against every meeting to make sure that
229 // the returned set can be used to compute rectangles for all cases at once
230 foreach( int index, overlapSet )
232 Meeting* meetingInSet = iMeetingsByDay[aDay][index].meeting;
233 for ( int i = 0; i < iMeetingsByDay[aDay].size(); ++i )
235 Meeting* other = iMeetingsByDay[aDay][i].meeting;
236 if ( meetingInSet != other && aMeeting != other && meetingInSet->overlaps( *(other) ) )
237 overlapSet.insert( i );
242 foreach( int index, overlapSet )
244 aResult.append( index );
247 return !aResult.empty();
250 void ScheduleTableWidget::activateMeeting( int x, int y )
252 ScheduleWidget* schedule = static_cast<ScheduleWidget*>( parent() );
254 for ( int day = 0; day < schedule->weekLengthAsDays(); ++day )
256 for ( int i = 0; i < iMeetingsByDay[day].size(); ++i )
258 if ( iMeetingsByDay[day][i].rect.contains( x, y ) && !iTabletBlocked )
260 iTabletBlocked = true;
261 qDebug() << "Activated meeting at x" << x << "y" << y << ":" << iMeetingsByDay[day][i].meeting->toString();
262 emit schedule->meetingActivated( iMeetingsByDay[day][i].meeting );
268 int ScheduleTableWidget::computeViewportY( QTime aTime )
270 ScheduleWidget* schedule = static_cast<ScheduleWidget*>( parent() );
271 int secondsInDisplayDay = schedule->iNumberOfHours * 60 * 60;
272 int mainY = rowViewportPosition( 1 ) + 1;
273 int mainHeight = height() - mainY - 1;
275 return mainY + ( int )(( QTime( schedule->iStartHour, 0 ).secsTo( aTime ) / ( float )secondsInDisplayDay ) * mainHeight );
278 ScheduleWidget::ScheduleWidget( QDateTime aCurrentDateTime, DisplaySettings *aSettings, QWidget *aParent ) :
279 ObservedWidget( aParent ),
280 iCurrentDateTime( aCurrentDateTime ),
281 iStartHour( aSettings->dayStartsAt().hour() ),
282 iNumberOfHours( aSettings->dayEndsAt().hour() - aSettings->dayStartsAt().hour() + 1 ),
283 iDaysInSchedule( aSettings->daysInSchedule() ),
284 iLastRefresh( aCurrentDateTime.time() )
286 iStartHour = qBound( 0, iStartHour, 23 );
287 iNumberOfHours = qBound( 1, iNumberOfHours, 24 - iStartHour );
289 iScheduleTable = new ScheduleTableWidget(( iNumberOfHours + 1 ) * 1, weekLengthAsDays() + 1, this );
290 iScheduleTable->horizontalHeader()->hide();
291 iScheduleTable->verticalHeader()->hide();
292 iScheduleTable->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
293 iScheduleTable->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
294 iScheduleTable->setShowGrid( false );
297 font.setPointSize( 10 );
299 // add empty item to top-left corner, this will be updated in refresh()
300 QTableWidgetItem *weekItem = new QTableWidgetItem();
301 weekItem->setTextAlignment( Qt::AlignCenter );
302 weekItem->setFlags( Qt::ItemIsEnabled );
303 weekItem->setBackgroundColor( sHeaderBackground );
304 weekItem->setFont( font );
305 iScheduleTable->setItem( 0, 0, weekItem );
307 // add empty item to main cell
308 QTableWidgetItem *mainItem = new QTableWidgetItem();
309 mainItem->setFlags( Qt::ItemIsEnabled );
310 mainItem->setBackgroundColor( sFreeBackground );
311 iScheduleTable->setItem( 1, 1, mainItem );
313 // set row header items
314 QTime time( iStartHour, 0 );
315 for ( int i = 1; i < iScheduleTable->rowCount(); ++i )
317 QTableWidgetItem *item = new QTableWidgetItem( time.toString( "HH:mm" ) );
318 item->setTextAlignment( Qt::AlignHCenter );
319 item->setFlags( Qt::ItemIsEnabled );
320 item->setBackgroundColor( sHeaderBackground );
321 item->setFont( font );
322 iScheduleTable->setItem( i, 0, item );
323 time = time.addSecs( 60 * 60 );
326 // set empty column header items, these will be updated in refresh()
327 for ( int i = 1; i < iScheduleTable->columnCount(); ++i )
329 QTableWidgetItem *item = new QTableWidgetItem();
330 item->setTextAlignment( Qt::AlignCenter );
331 item->setFlags( Qt::ItemIsEnabled );
332 item->setBackgroundColor( sHeaderBackground );
333 item->setFont( font );
334 iScheduleTable->setItem( 0, i, item );
337 QVBoxLayout *layout = new QVBoxLayout;
338 layout->addWidget( iScheduleTable );
339 layout->setAlignment( Qt::AlignCenter );
340 layout->setMargin( 0 );
346 ScheduleWidget::~ScheduleWidget()
348 delete iScheduleTable;
351 int ScheduleWidget::shownWeek()
353 return iShownDate.weekNumber();
356 QDate ScheduleWidget::beginningOfShownWeek()
358 return iShownDate.addDays( -1 * iShownDate.dayOfWeek() + 1 );
361 void ScheduleWidget::refresh()
363 qDebug() << "ScheduleWidget::refresh()";
365 for ( int i = 0; i < iScheduleTable->columnCount(); ++i )
367 QTableWidgetItem* item = iScheduleTable->item( 0, i );
368 QFont font = item->font();
370 item->setText( tr( "Wk %1" ).arg( iShownDate.weekNumber() ) );
373 item->setText( iShownDate.addDays( i - 1 ).toString( tr( "ddd d MMM" ) ) );
375 if ( iCurrentDateTime.date() == iShownDate.addDays( i - 1 ) )
378 item->setBackgroundColor( sDayHighlightColor );
379 font.setItalic( true );
380 item->setFont( font );
384 item->setBackgroundColor( sHeaderBackground );
385 font.setItalic( false );
386 item->setFont( font );
390 // force repaint of the main area
391 iScheduleTable->setSpan( 1, 1, iNumberOfHours, weekLengthAsDays() );
393 iLastRefresh = iCurrentDateTime.time();
396 void ScheduleWidget::refreshMeetings( const QList<Meeting*> &aMeetings )
398 iMeetings = aMeetings;
399 qDebug() << "Count: " << iMeetings.size();
403 void ScheduleWidget::setCurrentDateTime( QDateTime aCurrentDateTime )
405 iCurrentDateTime = aCurrentDateTime;
407 if ( iLastRefresh.secsTo( iCurrentDateTime.time() ) > sRefreshIntervalInSeconds )
409 // enough time has elapsed since last refresh
414 void ScheduleWidget::showPreviousWeek()
416 iShownDate = iShownDate.addDays( -7 );
418 emit shownWeekChanged( iShownDate );
421 void ScheduleWidget::showCurrentWeek()
423 iShownDate = iCurrentDateTime.date();
425 // set weekday to monday
426 iShownDate = iShownDate.addDays( -( iShownDate.dayOfWeek() - 1 ) );
429 emit shownWeekChanged( iShownDate );
432 void ScheduleWidget::showNextWeek()
434 iShownDate = iShownDate.addDays( 7 );
436 emit shownWeekChanged( iShownDate );
439 int ScheduleWidget::computeHeaderRow( QTime aTime )
441 // map given time to correct header row in the schedule table
442 return aTime.hour() - ( iStartHour - 1 );
445 int ScheduleWidget::weekLengthAsDays()
447 return ( iDaysInSchedule == DisplaySettings::WholeWeekInSchedule ) ? 7 : 5;
450 void ScheduleWidget::resizeEvent( QResizeEvent* /* aEvent */ )
452 QRect rect = iScheduleTable->contentsRect();
453 int rowHeight = ( int )( rect.height() / ( float )iScheduleTable->rowCount() );
454 int headerRowHeight = rowHeight;
455 int columnWidth = ( int )( rect.width() / ( iScheduleTable->columnCount() - 0.5f ) );
456 int headerColumnWidth = columnWidth / 2;
458 iScheduleTable->setRowHeight( 0, headerRowHeight );
459 for ( int i = 1; i < iScheduleTable->rowCount(); ++i )
461 iScheduleTable->setRowHeight( i, rowHeight );
464 iScheduleTable->setColumnWidth( 0, headerColumnWidth );
465 for ( int i = 1; i < iScheduleTable->columnCount(); ++i )
467 iScheduleTable->setColumnWidth( i, columnWidth );
470 // resize table so that frame size matches exactly
471 int leftMargin = 0, topMargin = 0, rightMargin = 0, bottomMargin = 0;
472 iScheduleTable->getContentsMargins( &leftMargin, &topMargin, &rightMargin, &bottomMargin );
473 iScheduleTable->resize( columnWidth * iScheduleTable->columnCount() - headerColumnWidth + leftMargin + rightMargin,
474 rowHeight * iScheduleTable->rowCount() + topMargin + bottomMargin );