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;
172 if (( meeting->startsAt().date().weekNumber() == schedule->iShownDate.weekNumber() ) &&
173 ( day < schedule->weekLengthAsDays() ) &&
174 ( meeting->endsAt().time() > QTime( schedule->iStartHour, 0 ) ) &&
175 ( meeting->startsAt().time() <= QTime( schedule->iStartHour + schedule->iNumberOfHours - 1, 59 ) ) )
177 MeetingContainer container;
178 container.meeting = meeting;
179 container.rect = QRect( 0, 0, 0, 0 );
180 container.rectComputed = false;
181 iMeetingsByDay[day].append( container );
185 // compute meeting rectangles
186 for ( int day = 0; day < schedule->weekLengthAsDays(); ++day )
188 for ( int i = 0; i < iMeetingsByDay[day].size(); ++i )
190 if ( iMeetingsByDay[day][i].rectComputed )
193 QList<int> meetingIndices;
194 findOverlappingMeetings( day, iMeetingsByDay[day][i].meeting, meetingIndices );
195 meetingIndices.append( i );
197 for ( int j = 0; j < meetingIndices.size(); ++j )
199 if ( iMeetingsByDay[day][meetingIndices[j]].rectComputed )
202 int columnWidth = columnViewportPosition( 2 ) - columnViewportPosition( 1 ) - 1;
204 Meeting* meeting = iMeetingsByDay[day][meetingIndices[j]].meeting;
205 int x = columnViewportPosition( day + 1 ) + ( int )(( columnWidth / ( float )meetingIndices.size() ) * j );
206 int y = computeViewportY( meeting->startsAt().time() );
207 int width = ( int )( columnWidth / ( float )meetingIndices.size() + 0.5f );
208 int height = computeViewportY( meeting->endsAt().time() ) - y;
210 iMeetingsByDay[day][meetingIndices[j]].rect = QRect( x, y, width, height );
211 iMeetingsByDay[day][meetingIndices[j]].rectComputed = true;
217 bool ScheduleTableWidget::findOverlappingMeetings( int aDay, Meeting* aMeeting, QList<int>& aResult )
219 QSet<int> overlapSet;
221 // first find meetings that overlap with aMeeting
222 for ( int i = 0; i < iMeetingsByDay[aDay].size(); ++i )
224 Meeting* other = iMeetingsByDay[aDay][i].meeting;
225 if ( aMeeting != other && aMeeting->overlaps( *(other) ) )
226 overlapSet.insert( i );
229 // then compare overlappiong ones against every meeting to make sure that
230 // the returned set can be used to compute rectangles for all cases at once
231 foreach( int index, overlapSet )
233 Meeting* meetingInSet = iMeetingsByDay[aDay][index].meeting;
234 for ( int i = 0; i < iMeetingsByDay[aDay].size(); ++i )
236 Meeting* other = iMeetingsByDay[aDay][i].meeting;
237 if ( meetingInSet != other && aMeeting != other && meetingInSet->overlaps( *(other) ) )
238 overlapSet.insert( i );
243 foreach( int index, overlapSet )
245 aResult.append( index );
248 return !aResult.empty();
251 void ScheduleTableWidget::activateMeeting( int x, int y )
253 ScheduleWidget* schedule = static_cast<ScheduleWidget*>( parent() );
255 for ( int day = 0; day < schedule->weekLengthAsDays(); ++day )
257 for ( int i = 0; i < iMeetingsByDay[day].size(); ++i )
259 if ( iMeetingsByDay[day][i].rect.contains( x, y ) && !iTabletBlocked )
261 iTabletBlocked = true;
262 qDebug() << "Activated meeting at x" << x << "y" << y << ":" << iMeetingsByDay[day][i].meeting->toString();
263 emit schedule->meetingActivated( iMeetingsByDay[day][i].meeting );
269 int ScheduleTableWidget::computeViewportY( QTime aTime )
271 ScheduleWidget* schedule = static_cast<ScheduleWidget*>( parent() );
272 int secondsInDisplayDay = schedule->iNumberOfHours * 60 * 60;
273 int mainY = rowViewportPosition( 1 ) + 1;
274 int mainHeight = height() - mainY - 1;
276 return mainY + ( int )(( QTime( schedule->iStartHour, 0 ).secsTo( aTime ) / ( float )secondsInDisplayDay ) * mainHeight );
279 ScheduleWidget::ScheduleWidget( QDateTime aCurrentDateTime, DisplaySettings *aSettings, QWidget *aParent ) :
280 ObservedWidget( aParent ),
281 iCurrentDateTime( aCurrentDateTime ),
282 iStartHour( aSettings->dayStartsAt().hour() ),
283 iNumberOfHours( aSettings->dayEndsAt().hour() - aSettings->dayStartsAt().hour() + 1 ),
284 iDaysInSchedule( aSettings->daysInSchedule() ),
285 iLastRefresh( aCurrentDateTime.time() )
287 iStartHour = qBound( 0, iStartHour, 23 );
288 iNumberOfHours = qBound( 1, iNumberOfHours, 24 - iStartHour );
290 iScheduleTable = new ScheduleTableWidget(( iNumberOfHours + 1 ) * 1, weekLengthAsDays() + 1, this );
291 iScheduleTable->horizontalHeader()->hide();
292 iScheduleTable->verticalHeader()->hide();
293 iScheduleTable->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
294 iScheduleTable->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
295 iScheduleTable->setShowGrid( false );
298 font.setPointSize( 10 );
300 // add empty item to top-left corner, this will be updated in refresh()
301 QTableWidgetItem *weekItem = new QTableWidgetItem();
302 weekItem->setTextAlignment( Qt::AlignCenter );
303 weekItem->setFlags( Qt::ItemIsEnabled );
304 weekItem->setBackgroundColor( sHeaderBackground );
305 weekItem->setFont( font );
306 iScheduleTable->setItem( 0, 0, weekItem );
308 // add empty item to main cell
309 QTableWidgetItem *mainItem = new QTableWidgetItem();
310 mainItem->setFlags( Qt::ItemIsEnabled );
311 mainItem->setBackgroundColor( sFreeBackground );
312 iScheduleTable->setItem( 1, 1, mainItem );
314 // set row header items
315 QTime time( iStartHour, 0 );
316 for ( int i = 1; i < iScheduleTable->rowCount(); ++i )
318 QTableWidgetItem *item = new QTableWidgetItem( time.toString( "HH:mm" ) );
319 item->setTextAlignment( Qt::AlignHCenter );
320 item->setFlags( Qt::ItemIsEnabled );
321 item->setBackgroundColor( sHeaderBackground );
322 item->setFont( font );
323 iScheduleTable->setItem( i, 0, item );
324 time = time.addSecs( 60 * 60 );
327 // set empty column header items, these will be updated in refresh()
328 for ( int i = 1; i < iScheduleTable->columnCount(); ++i )
330 QTableWidgetItem *item = new QTableWidgetItem();
331 item->setTextAlignment( Qt::AlignCenter );
332 item->setFlags( Qt::ItemIsEnabled );
333 item->setBackgroundColor( sHeaderBackground );
334 item->setFont( font );
335 iScheduleTable->setItem( 0, i, item );
338 QVBoxLayout *layout = new QVBoxLayout;
339 layout->addWidget( iScheduleTable );
340 layout->setAlignment( Qt::AlignCenter );
341 layout->setMargin( 0 );
347 ScheduleWidget::~ScheduleWidget()
349 delete iScheduleTable;
352 QDate ScheduleWidget::beginningOfShownWeek()
354 return iShownDate.addDays( -1 * iShownDate.dayOfWeek() + 1 );
357 void ScheduleWidget::refresh()
359 qDebug() << "ScheduleWidget::refresh()";
361 for ( int i = 0; i < iScheduleTable->columnCount(); ++i )
363 QTableWidgetItem* item = iScheduleTable->item( 0, i );
364 QFont font = item->font();
366 item->setText( tr( "Wk %1" ).arg( iShownDate.weekNumber() ) );
369 item->setText( iShownDate.addDays( i - 1 ).toString( tr( "ddd d MMM" ) ) );
371 if ( iCurrentDateTime.date() == iShownDate.addDays( i - 1 ) )
374 item->setBackgroundColor( sDayHighlightColor );
375 font.setItalic( true );
376 item->setFont( font );
380 item->setBackgroundColor( sHeaderBackground );
381 font.setItalic( false );
382 item->setFont( font );
386 // force repaint of the main area
387 iScheduleTable->setSpan( 1, 1, iNumberOfHours, weekLengthAsDays() );
389 iLastRefresh = iCurrentDateTime.time();
392 void ScheduleWidget::refreshMeetings( const QList<Meeting*> &aMeetings )
394 iMeetings = aMeetings;
395 qDebug() << "Count: " << iMeetings.size();
399 void ScheduleWidget::setCurrentDateTime( QDateTime aCurrentDateTime )
401 iCurrentDateTime = aCurrentDateTime;
403 if ( iLastRefresh.secsTo( iCurrentDateTime.time() ) > sRefreshIntervalInSeconds )
405 // enough time has elapsed since last refresh
410 void ScheduleWidget::showPreviousWeek()
412 iShownDate = iShownDate.addDays( -7 );
414 emit shownWeekChanged( iShownDate );
417 void ScheduleWidget::showCurrentWeek()
419 iShownDate = iCurrentDateTime.date();
421 // set weekday to monday
422 iShownDate = iShownDate.addDays( -( iShownDate.dayOfWeek() - 1 ) );
425 emit shownWeekChanged( iShownDate );
428 void ScheduleWidget::showNextWeek()
430 iShownDate = iShownDate.addDays( 7 );
432 emit shownWeekChanged( iShownDate );
435 int ScheduleWidget::computeHeaderRow( QTime aTime )
437 // map given time to correct header row in the schedule table
438 return aTime.hour() - ( iStartHour - 1 );
441 int ScheduleWidget::weekLengthAsDays()
443 return ( iDaysInSchedule == DisplaySettings::WholeWeekInSchedule ) ? 7 : 5;
446 void ScheduleWidget::resizeEvent( QResizeEvent* /* aEvent */ )
448 QRect rect = iScheduleTable->contentsRect();
449 int rowHeight = ( int )( rect.height() / ( float )iScheduleTable->rowCount() );
450 int headerRowHeight = rowHeight;
451 int columnWidth = ( int )( rect.width() / ( iScheduleTable->columnCount() - 0.5f ) );
452 int headerColumnWidth = columnWidth / 2;
454 iScheduleTable->setRowHeight( 0, headerRowHeight );
455 for ( int i = 1; i < iScheduleTable->rowCount(); ++i )
457 iScheduleTable->setRowHeight( i, rowHeight );
460 iScheduleTable->setColumnWidth( 0, headerColumnWidth );
461 for ( int i = 1; i < iScheduleTable->columnCount(); ++i )
463 iScheduleTable->setColumnWidth( i, columnWidth );
466 // resize table so that frame size matches exactly
467 int leftMargin = 0, topMargin = 0, rightMargin = 0, bottomMargin = 0;
468 iScheduleTable->getContentsMargins( &leftMargin, &topMargin, &rightMargin, &bottomMargin );
469 iScheduleTable->resize( columnWidth * iScheduleTable->columnCount() - headerColumnWidth + leftMargin + rightMargin,
470 rowHeight * iScheduleTable->rowCount() + topMargin + bottomMargin );