Merge commit 'refs/1_0_6_candidate' of https://git.maemo.org/projects/qtmeetings...
[qtmeetings] / src / UserInterface / Components / ScheduleWidget.cpp
1 #include "ScheduleWidget.h"
2
3 #include <QTableWidget>
4 #include <QHeaderView>
5 #include <QVBoxLayout>
6 #include <QTime>
7 #include <QtDebug>
8 #include <QResizeEvent>
9 #include <QPainter>
10 #include "Meeting.h"
11
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 );
23
24 ScheduleTableWidget::ScheduleTableWidget( int aRows, int aColumns, QWidget *aParent ) :
25                 QTableWidget( aRows, aColumns, aParent )
26 {
27         ScheduleWidget* schedule = static_cast<ScheduleWidget*>( parent() );
28
29         iMeetingsByDay = new QList<MeetingContainer>[schedule->weekLengthAsDays()];
30         iTabletBlocked = false;
31         iTime.start();
32
33         setFocusPolicy( Qt::NoFocus );
34         setFrameStyle( QFrame::NoFrame );
35 }
36
37 ScheduleTableWidget::~ScheduleTableWidget()
38 {
39         delete[] iMeetingsByDay;
40 }
41
42 void ScheduleTableWidget::paintEvent( QPaintEvent* aEvent )
43 {
44         qDebug() << "ScheduleTableWidget::paintEvent()";
45         QTableWidget::paintEvent( aEvent );
46
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;
51
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 );
57
58         // draw horizontal half grid
59         for ( int i = 1; i < rowCount(); ++i )
60         {
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 );
64         }
65
66         // draw horizontal main grid
67         for ( int i = 1; i < rowCount(); ++i )
68         {
69                 int y = rowViewportPosition( i ) - 1;
70                 painter.fillRect( QRect( 1, y, width() - 2, 1 ), ScheduleWidget::sMainGridColor );
71         }
72
73         // draw vertical main grid
74         for ( int i = 1; i < columnCount(); ++i )
75         {
76                 int x = columnViewportPosition( i ) - 1;
77                 painter.fillRect( QRect( x, 1, 1, height() - 2 ), ScheduleWidget::sMainGridColor );
78         }
79
80         // draw current day highlight
81         QPen pen( ScheduleWidget::sDayHighlightColor );
82         pen.setWidth( 3 );
83         painter.setPen( pen );
84         painter.setBrush( Qt::NoBrush );
85
86         for ( int i = 1; i < columnCount(); ++i )
87         {
88                 if ( schedule->iCurrentDateTime.date() == schedule->iShownDate.addDays( i - 1 ) )
89                 {
90                         int x = columnViewportPosition( i ) + 1;
91                         int y = 2;
92                         int w = columnWidth - 3;
93                         int h = height() - 5;
94                         painter.drawRect( x, y, w, h );
95                         break;
96                 }
97         }
98
99         // draw meetings
100         painter.setRenderHint( QPainter::Antialiasing );
101         painter.setPen( ScheduleWidget::sFrameColor );
102         populateMeetingList();
103
104         for ( int day = 0; day < schedule->weekLengthAsDays(); ++day )
105         {
106                 for ( int i = 0; i < iMeetingsByDay[day].size(); ++i )
107                 {
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() )
112                         {
113                                 linearGrad.setColorAt(0, ScheduleWidget::sCurrentBackgroundStart);
114                                 linearGrad.setColorAt(1, ScheduleWidget::sCurrentBackground);
115                         }
116                         else
117                         {
118                                 linearGrad.setColorAt(0, ScheduleWidget::sBusyBackgroundStart);
119                                 linearGrad.setColorAt(1, ScheduleWidget::sBusyBackground);
120                         }
121                         painter.setBrush(linearGrad);
122
123                         painter.drawRoundRect( iMeetingsByDay[day][i].rect, 20, 20 );
124                 }
125         }
126
127         // draw current time highlight
128         painter.setBrush( Qt::NoBrush );
129         painter.setRenderHint( QPainter::Antialiasing, false );
130
131         for ( int i = 1; i < columnCount(); ++i )
132         {
133                 if ( schedule->iCurrentDateTime.date() == schedule->iShownDate.addDays( i - 1 ) )
134                 {
135                         int x = columnViewportPosition( i ) - 1;
136                         int y = computeViewportY( schedule->iCurrentDateTime.time() );
137                         int w = columnWidth + 2;
138                         int h = 4;
139                         painter.fillRect( x, y, w, h, ScheduleWidget::sTimeHighlightColor );
140                         break;
141                 }
142         }
143 }
144
145 void ScheduleTableWidget::tabletEvent( QTabletEvent* aEvent )
146 {
147         int ms = iTime.restart();
148
149         if ( iTabletBlocked && ms > 1000 )
150         {
151                 iTabletBlocked = false;
152                 qDebug() << "Tablet block released";
153         }
154
155         if ( iTabletBlocked == false )
156         {
157                 qDebug() << "Tablet blocked released";
158                 activateMeeting( aEvent->x(), aEvent->y() );
159         }
160 }
161
162 void ScheduleTableWidget::mouseMoveEvent( QMouseEvent* /* aEvent */ )
163 {
164         // this is overridden as empty method because otherwise
165         // unwanted behaviour would occur due to QTableWidget
166 }
167
168 void ScheduleTableWidget::mousePressEvent( QMouseEvent* aEvent )
169 {
170         activateMeeting( aEvent->x(), aEvent->y() );
171         iTabletBlocked = false;
172 }
173
174 void ScheduleTableWidget::populateMeetingList()
175 {
176         qDebug() << "ScheduleTableWidget::populateMeetingList()";
177         ScheduleWidget* schedule = static_cast<ScheduleWidget*>( parent() );
178
179         for ( int i = 0; i < schedule->weekLengthAsDays(); ++i )
180                 iMeetingsByDay[i].clear();
181
182         // insert suitable meetings to list
183         for ( int i = 0; i < schedule->iMeetings.count(); ++i )
184         {
185                 Meeting* meeting = schedule->iMeetings[i];
186                 int day = meeting->startsAt().date().dayOfWeek() - 1;
187                 
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 ) ) )
192                 {
193                         MeetingContainer container;
194                         container.meeting = meeting;
195                         container.rect = QRect( 0, 0, 0, 0 );
196                         container.rectComputed = false;
197                         iMeetingsByDay[day].append( container );
198                 }
199         }
200
201         // compute meeting rectangles
202         for ( int day = 0; day < schedule->weekLengthAsDays(); ++day )
203         {
204                 for ( int i = 0; i < iMeetingsByDay[day].size(); ++i )
205                 {
206                         if ( iMeetingsByDay[day][i].rectComputed )
207                                 continue;
208
209                         QList<int> meetingIndices;
210                         findOverlappingMeetings( day, iMeetingsByDay[day][i].meeting, meetingIndices );
211                         meetingIndices.append( i );
212
213                         for ( int j = 0; j < meetingIndices.size(); ++j )
214                         {
215                                 if ( iMeetingsByDay[day][meetingIndices[j]].rectComputed )
216                                         continue;
217
218                                 int columnWidth = columnViewportPosition( 2 ) - columnViewportPosition( 1 ) - 1;
219
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;
225
226                                 iMeetingsByDay[day][meetingIndices[j]].rect = QRect( x, y, width, height );
227                                 iMeetingsByDay[day][meetingIndices[j]].rectComputed = true;
228                         }
229                 }
230         }
231 }
232
233 bool ScheduleTableWidget::findOverlappingMeetings( int aDay, Meeting* aMeeting, QList<int>& aResult )
234 {
235         QSet<int> overlapSet;
236
237         // first find meetings that overlap with aMeeting
238         for ( int i = 0; i < iMeetingsByDay[aDay].size(); ++i )
239         {
240                 Meeting* other = iMeetingsByDay[aDay][i].meeting;
241                 if ( aMeeting != other && aMeeting->overlaps( *(other) ) )
242                         overlapSet.insert( i );
243         }
244
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 )
248         {
249                 Meeting* meetingInSet = iMeetingsByDay[aDay][index].meeting;
250                 for ( int i = 0; i < iMeetingsByDay[aDay].size(); ++i )
251                 {
252                         Meeting* other = iMeetingsByDay[aDay][i].meeting;
253                         if ( meetingInSet != other && aMeeting != other && meetingInSet->overlaps( *(other) ) )
254                                 overlapSet.insert( i );
255                 }
256         }
257
258         aResult.clear();
259         foreach( int index, overlapSet )
260         {
261                 aResult.append( index );
262         }
263
264         return !aResult.empty();
265 }
266
267 void ScheduleTableWidget::activateMeeting( int x, int y )
268 {
269         ScheduleWidget* schedule = static_cast<ScheduleWidget*>( parent() );
270
271         for ( int day = 0; day < schedule->weekLengthAsDays(); ++day )
272         {
273                 for ( int i = 0; i < iMeetingsByDay[day].size(); ++i )
274                 {
275                         if ( iMeetingsByDay[day][i].rect.contains( x, y ) && !iTabletBlocked )
276                         {
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 );
280                         }
281                 }
282         }
283 }
284
285 int ScheduleTableWidget::computeViewportY( QTime aTime )
286 {
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;
291
292         return mainY + ( int )(( QTime( schedule->iStartHour, 0 ).secsTo( aTime ) / ( float )secondsInDisplayDay ) * mainHeight );
293 }
294
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() )
302 {
303         iStartHour = qBound( 0, iStartHour, 23 );
304         iNumberOfHours = qBound( 1, iNumberOfHours, 24 - iStartHour );
305
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 );
312
313         QFont font;
314         font.setStyleHint( QFont::Helvetica );
315         font.setBold( true );
316         font.setPixelSize( 20 );
317
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 );
325
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 );
331
332         // set row header items
333         QTime time( iStartHour, 0 );
334         for ( int i = 1; i < iScheduleTable->rowCount(); ++i )
335         {
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 );
343         }
344
345         // set empty column header items, these will be updated in refresh()
346         for ( int i = 1; i < iScheduleTable->columnCount(); ++i )
347         {
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 );
354         }
355
356         QVBoxLayout *layout = new QVBoxLayout;
357         layout->addWidget( iScheduleTable );
358         layout->setAlignment( Qt::AlignCenter );
359         layout->setMargin( 0 );
360         setLayout( layout );
361
362         showCurrentWeek();
363 }
364
365 ScheduleWidget::~ScheduleWidget()
366 {
367         if ( iScheduleTable )
368         {
369                 delete iScheduleTable;
370                 iScheduleTable = 0;
371         }
372 }
373
374 QDate ScheduleWidget::beginningOfShownWeek()
375 {
376         return iShownDate.addDays( -1 * iShownDate.dayOfWeek() + 1 );
377 }
378
379 void ScheduleWidget::refresh()
380 {
381         qDebug() << "ScheduleWidget::refresh()";
382         
383         for ( int i = 0; i < iScheduleTable->columnCount(); ++i )
384         {
385                 QTableWidgetItem* item = iScheduleTable->item( 0, i );
386                 QFont font = item->font();
387                 if ( i == 0 ) {
388                         item->setText( tr( "Wk %1" ).arg( iShownDate.weekNumber() ) );
389                         continue;
390                 }
391                 item->setText( iShownDate.addDays( i - 1 ).toString( tr( "ddd d/M" ) ) );
392
393                 if ( iCurrentDateTime.date() == iShownDate.addDays( i - 1 ) )
394                 {
395                         // mark current day
396                         item->setBackgroundColor( sDayHighlightColor );
397                         font.setItalic( true );
398                         item->setFont( font );
399                 }
400                 else
401                 {
402                         item->setBackgroundColor( sHeaderBackground );
403                         font.setItalic( false );
404                         item->setFont( font );
405                 }
406         }
407         
408         // force repaint of the main area
409         iScheduleTable->setSpan( 1, 1, iNumberOfHours, weekLengthAsDays() );
410
411         iLastRefresh = iCurrentDateTime.time();
412 }
413
414 void ScheduleWidget::refreshMeetings( const QList<Meeting*> &aMeetings )
415 {
416         iMeetings = aMeetings;
417         qDebug() << "Count: " << iMeetings.size();
418         refresh();
419 }
420
421 void ScheduleWidget::setCurrentDateTime( QDateTime aCurrentDateTime )
422 {
423         iCurrentDateTime = aCurrentDateTime;
424
425         if ( iLastRefresh.secsTo( iCurrentDateTime.time() ) > sRefreshIntervalInSeconds )
426         {
427                 // enough time has elapsed since last refresh
428                 refresh();
429         }
430 }
431
432 void ScheduleWidget::showPreviousWeek()
433 {
434         iShownDate = iShownDate.addDays( -7 );
435         refresh();
436         emit shownWeekChanged( iShownDate );
437 }
438
439 void ScheduleWidget::showCurrentWeek()
440 {
441         iShownDate = iCurrentDateTime.date();
442
443         // set weekday to monday
444         iShownDate = iShownDate.addDays( -( iShownDate.dayOfWeek() - 1 ) );
445
446         refresh();
447         emit shownWeekChanged( iShownDate );
448 }
449
450 void ScheduleWidget::showNextWeek()
451 {
452         iShownDate = iShownDate.addDays( 7 );
453         refresh();
454         emit shownWeekChanged( iShownDate );
455 }
456
457 int ScheduleWidget::computeHeaderRow( QTime aTime )
458 {
459         // map given time to correct header row in the schedule table
460         return aTime.hour() - ( iStartHour - 1 );
461 }
462
463 int ScheduleWidget::weekLengthAsDays()
464 {
465         return ( iDaysInSchedule == DisplaySettings::WholeWeekInSchedule ) ? 7 : 5;
466 }
467
468 void ScheduleWidget::resizeEvent( QResizeEvent* /* aEvent */ )
469 {
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 );
475
476         iScheduleTable->setRowHeight( 0, headerRowHeight );
477         for ( int i = 1; i < iScheduleTable->rowCount(); ++i )
478         {
479                 iScheduleTable->setRowHeight( i, rowHeight );
480         }
481
482         iScheduleTable->setColumnWidth( 0, headerColumnWidth );
483         for ( int i = 1; i < iScheduleTable->columnCount(); ++i )
484         {
485                 iScheduleTable->setColumnWidth( i, columnWidth );
486         }
487
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 );
494 }