File indexing completed on 2024-05-12 05:21:23

0001 /*
0002   This file is part of KOrganizer.
0003 
0004   SPDX-FileCopyrightText: 2001 Eitzenberger Thomas <thomas.eitzenberger@siemens.at>
0005   Parts of the source code have been copied from kdpdatebutton.cpp
0006 
0007   SPDX-FileCopyrightText: 2003 Cornelius Schumacher <schumacher@kde.org>
0008   SPDX-FileCopyrightText: 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
0009 
0010   SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0
0011 */
0012 
0013 #include "kodaymatrix.h"
0014 #include "koglobals.h"
0015 #include "prefs/koprefs.h"
0016 
0017 #include <CalendarSupport/Utils>
0018 
0019 #include <Akonadi/ItemFetchJob>
0020 #include <Akonadi/ItemFetchScope>
0021 
0022 #include <QIcon>
0023 #include <QMenu>
0024 #include <QUrl>
0025 
0026 #include <KLocalizedString>
0027 #include <QMimeData>
0028 #include <QMouseEvent>
0029 #include <QPainter>
0030 #include <QToolTip>
0031 
0032 // ============================================================================
0033 //  K O D A Y M A T R I X
0034 // ============================================================================
0035 
0036 const int KODayMatrix::NOSELECTION = -1000;
0037 const int KODayMatrix::NUMDAYS = 42;
0038 
0039 KODayMatrix::KODayMatrix(QWidget *parent)
0040     : QFrame(parent)
0041     , mStartDate()
0042 {
0043     // initialize dynamic arrays
0044     mDays = new QDate[NUMDAYS];
0045     mDayLabels = new QString[NUMDAYS];
0046 
0047     mTodayMarginWidth = 2;
0048     mSelEnd = mSelStart = NOSELECTION;
0049 
0050     recalculateToday();
0051 
0052     mHighlightEvents = true;
0053     mHighlightTodos = false;
0054     mHighlightJournals = false;
0055 }
0056 
0057 void KODayMatrix::addCalendar(const Akonadi::CollectionCalendar::Ptr &calendar)
0058 {
0059     calendar->registerObserver(this);
0060     mCalendars.push_back(calendar);
0061 
0062     setAcceptDrops(true);
0063     updateIncidences();
0064 }
0065 
0066 void KODayMatrix::removeCalendar(const Akonadi::CollectionCalendar::Ptr &calendar)
0067 {
0068     calendar->unregisterObserver(this);
0069     mCalendars.removeOne(calendar);
0070 
0071     setAcceptDrops(!mCalendars.empty());
0072     updateIncidences();
0073 }
0074 
0075 QColor KODayMatrix::getShadedColor(const QColor &color) const
0076 {
0077     QColor shaded;
0078     int h = 0;
0079     int s = 0;
0080     int v = 0;
0081     color.getHsv(&h, &s, &v);
0082     s = s / 4;
0083     v = 192 + v / 4;
0084     shaded.setHsv(h, s, v);
0085 
0086     return shaded;
0087 }
0088 
0089 KODayMatrix::~KODayMatrix()
0090 {
0091     for (const auto &calendar : mCalendars) {
0092         calendar->unregisterObserver(this);
0093     }
0094 
0095     delete[] mDays;
0096     delete[] mDayLabels;
0097 }
0098 
0099 void KODayMatrix::addSelectedDaysTo(KCalendarCore::DateList &selDays)
0100 {
0101     if (mSelStart == NOSELECTION) {
0102         return;
0103     }
0104 
0105     // cope with selection being out of matrix limits at top (< 0)
0106     int i0 = mSelStart;
0107     if (i0 < 0) {
0108         for (int i = i0; i < 0; ++i) {
0109             selDays.append(mDays[0].addDays(i));
0110         }
0111         i0 = 0;
0112     }
0113 
0114     // cope with selection being out of matrix limits at bottom (> NUMDAYS-1)
0115     if (mSelEnd > NUMDAYS - 1) {
0116         for (int i = i0; i <= NUMDAYS - 1; ++i) {
0117             selDays.append(mDays[i]);
0118         }
0119         for (int i = NUMDAYS; i < mSelEnd; ++i) {
0120             selDays.append(mDays[0].addDays(i));
0121         }
0122     } else {
0123         // apply normal routine to selection being entirely within matrix limits
0124         for (int i = i0; i <= mSelEnd; ++i) {
0125             selDays.append(mDays[i]);
0126         }
0127     }
0128 }
0129 
0130 void KODayMatrix::setSelectedDaysFrom(QDate start, QDate end)
0131 {
0132     if (mStartDate.isValid()) {
0133         mSelStart = mStartDate.daysTo(start);
0134         mSelEnd = mStartDate.daysTo(end);
0135     }
0136 }
0137 
0138 void KODayMatrix::clearSelection()
0139 {
0140     mSelEnd = mSelStart = NOSELECTION;
0141 }
0142 
0143 void KODayMatrix::recalculateToday()
0144 {
0145     if (!mStartDate.isValid()) {
0146         return;
0147     }
0148 
0149     mToday = -1;
0150     for (int i = 0; i < NUMDAYS; ++i) {
0151         mDays[i] = mStartDate.addDays(i);
0152         mDayLabels[i] = QString::number(mDays[i].day());
0153 
0154         // if today is in the currently displayed month, highlight today
0155         if (mDays[i].year() == QDate::currentDate().year() && mDays[i].month() == QDate::currentDate().month()
0156             && mDays[i].day() == QDate::currentDate().day()) {
0157             mToday = i;
0158         }
0159     }
0160 }
0161 
0162 void KODayMatrix::updateView()
0163 {
0164     updateView(mStartDate);
0165 }
0166 
0167 void KODayMatrix::setUpdateNeeded()
0168 {
0169     mPendingChanges = true;
0170 }
0171 
0172 void KODayMatrix::updateView(QDate actdate)
0173 {
0174     if (!actdate.isValid() || NUMDAYS < 1) {
0175         return;
0176     }
0177     // flag to indicate if the starting day of the matrix has changed by this call
0178     bool daychanged = false;
0179 
0180     // if a new startdate is to be set then apply Cornelius's calculation
0181     // of the first day to be shown
0182     if (actdate != mStartDate) {
0183         // reset index of selection according to shift of starting date from
0184         // startdate to actdate.
0185         if (mSelStart != NOSELECTION) {
0186             int tmp = actdate.daysTo(mStartDate);
0187             // shift selection if new one would be visible at least partly !
0188             if (mSelStart + tmp < NUMDAYS && mSelEnd + tmp >= 0) {
0189                 // nested if required for next X display pushed from a different month
0190                 // correction required. otherwise, for month forward and backward,
0191                 // it must be avoided.
0192                 if (mSelStart > NUMDAYS || mSelStart < 0) {
0193                     mSelStart = mSelStart + tmp;
0194                 }
0195                 if (mSelEnd > NUMDAYS || mSelEnd < 0) {
0196                     mSelEnd = mSelEnd + tmp;
0197                 }
0198             }
0199         }
0200 
0201         mStartDate = actdate;
0202         daychanged = true;
0203     }
0204 
0205     if (daychanged) {
0206         recalculateToday();
0207     }
0208 
0209     // The calendar has not changed in the meantime and the selected range
0210     // is still the same so we can save the expensive updateIncidences() call
0211     if (!daychanged && !mPendingChanges) {
0212         return;
0213     }
0214 
0215     // TODO_Recurrence: If we just change the selection, but not the data,
0216     // there's no need to update the whole list of incidences... This is just a
0217     // waste of computational power
0218     updateIncidences();
0219     QMap<QDate, QStringList> holidaysByDate = KOGlobals::self()->holiday(mDays[0], mDays[NUMDAYS - 1]);
0220     for (int i = 0; i < NUMDAYS; ++i) {
0221         // if it is a holy day then draw it red. Sundays are consider holidays, too
0222         QStringList holidays = holidaysByDate[mDays[i]];
0223         QString holiStr;
0224 
0225         if (!holidays.isEmpty()) {
0226             holiStr = holidays.join(i18nc("delimiter for joining holiday names", ","));
0227             if (holiStr.isEmpty()) {
0228                 holiStr = QLatin1StringView("");
0229             }
0230         }
0231         mHolidays[i] = holiStr;
0232     }
0233 }
0234 
0235 void KODayMatrix::updateIncidences()
0236 {
0237     mEvents.clear();
0238 
0239     if (mHighlightEvents) {
0240         updateEvents();
0241     }
0242 
0243     if (mHighlightTodos) {
0244         updateTodos();
0245     }
0246 
0247     if (mHighlightJournals) {
0248         updateJournals();
0249     }
0250 
0251     mPendingChanges = false;
0252 }
0253 
0254 void KODayMatrix::updateJournals()
0255 {
0256     for (const auto &calendar : mCalendars) {
0257         for (const KCalendarCore::Incidence::Ptr &inc : calendar->incidences()) {
0258             Q_ASSERT(inc);
0259             QDate d = inc->dtStart().toLocalTime().date();
0260             if (inc->type() == KCalendarCore::Incidence::TypeJournal && d >= mDays[0] && d <= mDays[NUMDAYS - 1] && !mEvents.contains(d)) {
0261                 mEvents.append(d);
0262             }
0263             if (mEvents.count() == NUMDAYS) {
0264                 // No point in wasting cpu, all days are bold already
0265                 break;
0266             }
0267         }
0268     }
0269 }
0270 
0271 /**
0272  * Although updateTodos() is simpler it has some similarities with updateEvent()
0273  * but don't bother refactoring them so they share code, there's a bigger fish:
0274  * Try to refactor updateTodos(), updateEvent(), updateJournals(), monthview,
0275  * agenda view, timeline view, event list view and todo list view
0276  * all these 9 places have incidence listing code in common, maybe it could go
0277  * to kcal. Ah, and then there's kontact's summary view which still uses
0278  * the old CPU consuming code.
0279  */
0280 void KODayMatrix::updateTodos()
0281 {
0282     QDate d;
0283     for (const auto &calendar : mCalendars) {
0284         for (const KCalendarCore::Todo::Ptr &t : calendar->todos()) {
0285             if (mEvents.count() == NUMDAYS) {
0286                 // No point in wasting cpu, all days are bold already
0287                 break;
0288             }
0289             Q_ASSERT(t);
0290             if (t->hasDueDate()) {
0291                 ushort recurType = t->recurrenceType();
0292 
0293                 if (t->recurs() && !(recurType == KCalendarCore::Recurrence::rDaily && !KOPrefs::instance()->mDailyRecur)
0294                     && !(recurType == KCalendarCore::Recurrence::rWeekly && !KOPrefs::instance()->mWeeklyRecur)) {
0295                     // It's a recurring todo, find out in which days it occurs
0296                     const auto timeDateList =
0297                         t->recurrence()->timesInInterval(QDateTime(mDays[0], {}, Qt::LocalTime), QDateTime(mDays[NUMDAYS - 1], {}, Qt::LocalTime));
0298 
0299                     for (const QDateTime &dt : timeDateList) {
0300                         d = dt.toLocalTime().date();
0301                         if (!mEvents.contains(d)) {
0302                             mEvents.append(d);
0303                         }
0304                     }
0305                 } else {
0306                     d = t->dtDue().toLocalTime().date();
0307                     if (d >= mDays[0] && d <= mDays[NUMDAYS - 1] && !mEvents.contains(d)) {
0308                         mEvents.append(d);
0309                     }
0310                 }
0311             }
0312         }
0313     }
0314 }
0315 
0316 void KODayMatrix::updateEvents()
0317 {
0318     if (mEvents.count() == NUMDAYS) {
0319         mPendingChanges = false;
0320         // No point in wasting cpu, all days are bold already
0321         return;
0322     }
0323 
0324     for (const auto &calendar : mCalendars) {
0325         const auto eventlist = calendar->events(mDays[0], mDays[NUMDAYS - 1], calendar->timeZone());
0326         for (const KCalendarCore::Event::Ptr &event : eventlist) {
0327             if (mEvents.count() == NUMDAYS) {
0328                 // No point in wasting cpu, all days are bold already
0329                 break;
0330             }
0331 
0332             Q_ASSERT(event);
0333             const ushort recurType = event->recurrenceType();
0334             const QDateTime dtStart = event->dtStart().toLocalTime();
0335 
0336             // timed incidences occur in
0337             //   [dtStart(), dtEnd()[. All-day incidences occur in [dtStart(), dtEnd()]
0338             // so we subtract 1 second in the timed case
0339             const int secsToAdd = event->allDay() ? 0 : -1;
0340             const QDateTime dtEnd = event->dtEnd().toLocalTime().addSecs(secsToAdd);
0341 
0342             if (!(recurType == KCalendarCore::Recurrence::rDaily && !KOPrefs::instance()->mDailyRecur)
0343                 && !(recurType == KCalendarCore::Recurrence::rWeekly && !KOPrefs::instance()->mWeeklyRecur)) {
0344                 KCalendarCore::DateTimeList timeDateList;
0345                 const bool isRecurrent = event->recurs();
0346                 const int eventDuration = dtStart.daysTo(dtEnd);
0347 
0348                 if (isRecurrent) {
0349                     // Its a recurring event, find out in which days it occurs
0350                     timeDateList =
0351                         event->recurrence()->timesInInterval(QDateTime(mDays[0], {}, Qt::LocalTime), QDateTime(mDays[NUMDAYS - 1], {}, Qt::LocalTime));
0352                 } else {
0353                     if (dtStart.date() >= mDays[0]) {
0354                         timeDateList.append(dtStart);
0355                     } else {
0356                         // The event starts in another month (not visible))
0357                         timeDateList.append(QDateTime(mDays[0], {}, Qt::LocalTime));
0358                     }
0359                 }
0360 
0361                 for (auto t = timeDateList.begin(); t != timeDateList.end(); ++t) {
0362                     // This could be a multiday event, so iterate from dtStart() to dtEnd()
0363                     QDate d = t->toLocalTime().date();
0364                     int j = 0;
0365 
0366                     QDate occurrenceEnd;
0367                     if (isRecurrent) {
0368                         occurrenceEnd = d.addDays(eventDuration);
0369                     } else {
0370                         occurrenceEnd = dtEnd.date();
0371                     }
0372 
0373                     do {
0374                         mEvents.append(d);
0375                         ++j;
0376                         d = d.addDays(1);
0377                     } while (d <= occurrenceEnd && j < NUMDAYS);
0378                 }
0379             }
0380         }
0381         mPendingChanges = false;
0382     }
0383 }
0384 
0385 const QDate &KODayMatrix::getDate(int offset) const
0386 {
0387     if (offset < 0 || offset > NUMDAYS - 1) {
0388         return mDays[0];
0389     }
0390     return mDays[offset];
0391 }
0392 
0393 QString KODayMatrix::getHolidayLabel(int offset) const
0394 {
0395     if (offset < 0 || offset > NUMDAYS - 1) {
0396         return {};
0397     }
0398     return mHolidays[offset];
0399 }
0400 
0401 int KODayMatrix::getDayIndexFrom(int x, int y) const
0402 {
0403     return 7 * (y / mDaySize.height()) + (KOGlobals::self()->reverseLayout() ? 6 - x / mDaySize.width() : x / mDaySize.width());
0404 }
0405 
0406 void KODayMatrix::calendarIncidenceAdded(const KCalendarCore::Incidence::Ptr &incidence)
0407 {
0408     Q_UNUSED(incidence)
0409     mPendingChanges = true;
0410 }
0411 
0412 void KODayMatrix::calendarIncidenceChanged(const KCalendarCore::Incidence::Ptr &incidence)
0413 {
0414     Q_UNUSED(incidence)
0415     mPendingChanges = true;
0416 }
0417 
0418 void KODayMatrix::calendarIncidenceDeleted(const KCalendarCore::Incidence::Ptr &incidence, const KCalendarCore::Calendar *calendar)
0419 {
0420     Q_UNUSED(incidence)
0421     Q_UNUSED(calendar)
0422     mPendingChanges = true;
0423 }
0424 
0425 void KODayMatrix::setHighlightMode(bool highlightEvents, bool highlightTodos, bool highlightJournals)
0426 {
0427     // don't set mPendingChanges to true if nothing changed
0428     if (highlightTodos != mHighlightTodos || highlightEvents != mHighlightEvents || highlightJournals != mHighlightJournals) {
0429         mHighlightEvents = highlightEvents;
0430         mHighlightTodos = highlightTodos;
0431         mHighlightJournals = highlightJournals;
0432         mPendingChanges = true;
0433     }
0434 }
0435 
0436 void KODayMatrix::resourcesChanged()
0437 {
0438     mPendingChanges = true;
0439 }
0440 
0441 // ----------------------------------------------------------------------------
0442 //  M O U S E   E V E N T   H A N D L I N G
0443 // ----------------------------------------------------------------------------
0444 
0445 bool KODayMatrix::event(QEvent *event)
0446 {
0447     if (KOPrefs::instance()->mEnableToolTips && event->type() == QEvent::ToolTip) {
0448         auto helpEvent = static_cast<QHelpEvent *>(event);
0449 
0450         // calculate which cell of the matrix the mouse is in
0451         QRect sz = frameRect();
0452         int dheight = sz.height() * 7 / 42;
0453         int dwidth = sz.width() / 7;
0454         int row = helpEvent->pos().y() / dheight;
0455         int col = helpEvent->pos().x() / dwidth;
0456 
0457         // show holiday names only
0458         QString tipText = getHolidayLabel(col + row * 7);
0459         if (!tipText.isEmpty()) {
0460             QToolTip::showText(helpEvent->globalPos(), tipText);
0461         } else {
0462             QToolTip::hideText();
0463         }
0464     }
0465     return QWidget::event(event);
0466 }
0467 
0468 void KODayMatrix::mousePressEvent(QMouseEvent *e)
0469 {
0470     mSelStart = getDayIndexFrom(e->position().toPoint().x(), e->position().toPoint().y());
0471     if (e->button() == Qt::RightButton) {
0472         popupMenu(mDays[mSelStart], mDays[mSelEnd]);
0473     } else if (e->button() == Qt::LeftButton) {
0474         if (mSelStart > NUMDAYS - 1) {
0475             mSelStart = NUMDAYS - 1;
0476         }
0477         mSelInit = mSelStart;
0478     }
0479 }
0480 void KODayMatrix::popupMenu(const QDate &date, const QDate &date2)
0481 {
0482     QMenu popup(this);
0483     popup.setTitle(date.toString());
0484     QAction *newEventAction = popup.addAction(QIcon::fromTheme(QStringLiteral("appointment-new")), i18n("New E&vent..."));
0485     QAction *newTodoAction = popup.addAction(QIcon::fromTheme(QStringLiteral("task-new")), i18n("New &To-do..."));
0486     QAction *newJournalAction = popup.addAction(QIcon::fromTheme(QStringLiteral("journal-new")), i18n("New &Journal..."));
0487     QAction *ret = popup.exec(QCursor::pos());
0488     if (ret == newEventAction) {
0489         Q_EMIT newEventSignal(date, date2);
0490     } else if (ret == newTodoAction) {
0491         Q_EMIT newTodoSignal(date);
0492     } else if (ret == newJournalAction) {
0493         Q_EMIT newJournalSignal(date);
0494     }
0495 }
0496 
0497 void KODayMatrix::mouseReleaseEvent(QMouseEvent *e)
0498 {
0499     if (e->button() != Qt::LeftButton) {
0500         return;
0501     }
0502     int tmp = getDayIndexFrom(e->position().toPoint().x(), e->position().toPoint().y());
0503     if (tmp > NUMDAYS - 1) {
0504         tmp = NUMDAYS - 1;
0505     }
0506 
0507     if (mSelInit > tmp) {
0508         mSelEnd = mSelInit;
0509         if (tmp != mSelStart) {
0510             mSelStart = tmp;
0511             update();
0512         }
0513     } else {
0514         mSelStart = mSelInit;
0515 
0516         // repaint only if selection has changed
0517         if (tmp != mSelEnd) {
0518             mSelEnd = tmp;
0519             update();
0520         }
0521     }
0522 
0523     KCalendarCore::DateList daylist;
0524     if (mSelStart < 0) {
0525         mSelStart = 0;
0526     }
0527     daylist.reserve(mSelEnd - mSelStart + 1);
0528     for (int i = mSelStart; i <= mSelEnd; ++i) {
0529         daylist.append(mDays[i]);
0530     }
0531     Q_EMIT selected(static_cast<const KCalendarCore::DateList>(daylist));
0532 }
0533 
0534 void KODayMatrix::mouseMoveEvent(QMouseEvent *e)
0535 {
0536     int tmp = getDayIndexFrom(e->position().toPoint().x(), e->position().toPoint().y());
0537     if (tmp > NUMDAYS - 1) {
0538         tmp = NUMDAYS - 1;
0539     }
0540 
0541     if (mSelInit > tmp) {
0542         mSelEnd = mSelInit;
0543         if (tmp != mSelStart) {
0544             mSelStart = tmp;
0545             update();
0546         }
0547     } else {
0548         mSelStart = mSelInit;
0549 
0550         // repaint only if selection has changed
0551         if (tmp != mSelEnd) {
0552             mSelEnd = tmp;
0553             update();
0554         }
0555     }
0556 }
0557 
0558 // ----------------------------------------------------------------------------
0559 //  D R A G ' N   D R O P   H A N D L I N G
0560 // ----------------------------------------------------------------------------
0561 
0562 //-----------------------------------------------------------------------------
0563 // Drag and Drop handling -- based on the Troll Tech dirview example
0564 
0565 enum { DRAG_COPY = 0, DRAG_MOVE = 1, DRAG_CANCEL = 2 };
0566 
0567 void KODayMatrix::dragEnterEvent(QDragEnterEvent *e)
0568 {
0569     e->acceptProposedAction();
0570     const QMimeData *md = e->mimeData();
0571     if (!CalendarSupport::canDecode(md)) {
0572         e->ignore();
0573         return;
0574     }
0575 
0576     // some visual feedback
0577     //  oldPalette = palette();
0578     //  setPalette(my_HilitePalette);
0579     //  update();
0580 }
0581 
0582 void KODayMatrix::dragMoveEvent(QDragMoveEvent *e)
0583 {
0584     const QMimeData *md = e->mimeData();
0585     if (!CalendarSupport::canDecode(md)) {
0586         e->ignore();
0587         return;
0588     }
0589     e->accept();
0590 }
0591 
0592 void KODayMatrix::dragLeaveEvent(QDragLeaveEvent *dl)
0593 {
0594     Q_UNUSED(dl)
0595     //  setPalette(oldPalette);
0596     //  update();
0597 }
0598 
0599 void KODayMatrix::dropEvent(QDropEvent *e)
0600 {
0601     QList<QUrl> urls = (e->mimeData()->urls());
0602     // qCDebug(KORGANIZER_LOG)<<" urls :"<<urls;
0603     if (urls.isEmpty()) {
0604         e->ignore();
0605         return;
0606     }
0607     // For the moment support 1 url
0608     if (urls.count() >= 1) {
0609         QUrl res = urls.at(0);
0610 
0611         auto job = new Akonadi::ItemFetchJob(Akonadi::Item::fromUrl(res));
0612         job->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
0613         job->fetchScope().fetchFullPayload();
0614         Akonadi::Item::List items;
0615         if (job->exec()) {
0616             items = job->items();
0617         }
0618         bool exist = items.at(0).isValid();
0619         int action = DRAG_CANCEL;
0620         Qt::KeyboardModifiers keyboardModifiers = e->modifiers();
0621         if (keyboardModifiers & Qt::ControlModifier) {
0622             action = DRAG_COPY;
0623         } else if (keyboardModifiers & Qt::ShiftModifier) {
0624             action = DRAG_MOVE;
0625         } else {
0626             QAction *copy = nullptr;
0627             QAction *move = nullptr;
0628             auto menu = new QMenu(this);
0629             if (exist) {
0630                 move = menu->addAction(QIcon::fromTheme(QStringLiteral("edit-paste")), i18n("&Move"));
0631                 if (/*existingEvent*/ true) {
0632                     copy = menu->addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("&Copy"));
0633                 }
0634             } else {
0635                 move = menu->addAction(QIcon::fromTheme(QStringLiteral("list-add")), i18n("&Add"));
0636             }
0637             menu->addSeparator();
0638             /*QAction *cancel =*/
0639             menu->addAction(QIcon::fromTheme(QStringLiteral("process-stop")), i18n("&Cancel"));
0640             QAction *a = menu->exec(QCursor::pos());
0641             if (a == copy) {
0642                 action = DRAG_COPY;
0643             } else if (a == move) {
0644                 action = DRAG_MOVE;
0645             }
0646             delete menu;
0647         }
0648 
0649         if (action == DRAG_COPY || action == DRAG_MOVE) {
0650             e->accept();
0651             int idx = getDayIndexFrom(e->position().toPoint().x(), e->position().toPoint().y());
0652 
0653             if (action == DRAG_COPY) {
0654                 Q_EMIT incidenceDropped(items.at(0), mDays[idx]);
0655             } else if (action == DRAG_MOVE) {
0656                 Q_EMIT incidenceDroppedMove(items.at(0), mDays[idx]);
0657             }
0658         }
0659     }
0660 }
0661 
0662 // ----------------------------------------------------------------------------
0663 //  P A I N T   E V E N T   H A N D L I N G
0664 // ----------------------------------------------------------------------------
0665 
0666 void KODayMatrix::paintEvent(QPaintEvent *)
0667 {
0668     QPainter p;
0669     const QRect rect = frameRect();
0670     const int dayHeight = mDaySize.height();
0671     const int dayWidth = mDaySize.width();
0672     int row;
0673     int column;
0674     const bool isRTL = KOGlobals::self()->reverseLayout();
0675 
0676     QPalette pal = palette();
0677 
0678     p.begin(this);
0679 
0680     // draw background
0681     p.fillRect(0, 0, rect.width(), rect.height(), QBrush(pal.color(QPalette::Base)));
0682 
0683     // draw topleft frame
0684     p.setPen(pal.color(QPalette::Mid));
0685     p.drawRect(0, 0, rect.width() - 1, rect.height() - 1);
0686     // don't paint over borders
0687     p.translate(1, 1);
0688 
0689     // draw selected days with highlighted background color
0690     if (mSelStart != NOSELECTION) {
0691         row = mSelStart / 7;
0692         // fix larger selections starting in the previous month
0693         if (row < 0 && mSelEnd > 0) {
0694             row = 0;
0695         }
0696         column = mSelStart - row * 7;
0697         const QColor selectionColor = KOPrefs::instance()->agendaGridHighlightColor();
0698 
0699         if (row < 6 && row >= 0) {
0700             if (row == mSelEnd / 7) {
0701                 // Single row selection
0702                 p.fillRect(isRTL ? (7 - (mSelEnd - mSelStart + 1) - column) * dayWidth : column * dayWidth,
0703                            row * dayHeight,
0704                            (mSelEnd - mSelStart + 1) * dayWidth,
0705                            dayHeight,
0706                            selectionColor);
0707             } else {
0708                 // draw first row to the right
0709                 p.fillRect(isRTL ? 0 : column * dayWidth, row * dayHeight, (7 - column) * dayWidth, dayHeight, selectionColor);
0710                 // draw full block till last line
0711                 int selectionHeight = mSelEnd / 7 - row;
0712                 if (selectionHeight + row >= 6) {
0713                     selectionHeight = 6 - row;
0714                 }
0715                 if (selectionHeight > 1) {
0716                     p.fillRect(0, (row + 1) * dayHeight, 7 * dayWidth, (selectionHeight - 1) * dayHeight, selectionColor);
0717                 }
0718                 // draw last block from left to mSelEnd
0719                 if (mSelEnd / 7 < 6) {
0720                     int selectionWidth = mSelEnd - 7 * (mSelEnd / 7) + 1;
0721                     p.fillRect(isRTL ? (7 - selectionWidth) * dayWidth : 0,
0722                                (row + selectionHeight) * dayHeight,
0723                                selectionWidth * dayWidth,
0724                                dayHeight,
0725                                selectionColor);
0726                 }
0727             }
0728         }
0729     }
0730 
0731     // iterate over all days in the matrix and draw the day label in appropriate colors
0732     const QColor textColor = pal.color(QPalette::Text);
0733     const QColor textColorShaded = getShadedColor(textColor);
0734     QColor actcol = textColorShaded;
0735     p.setPen(actcol);
0736     QPen tmppen;
0737 
0738     const QList<QDate> workDays = CalendarSupport::workDays(mDays[0], mDays[NUMDAYS - 1]);
0739     for (int i = 0; i < NUMDAYS; ++i) {
0740         row = i / 7;
0741         column = isRTL ? 6 - (i - row * 7) : i - row * 7;
0742 
0743         // if it is the first day of a month switch color from normal to shaded and vice versa
0744         if (mDays[i].day() == 1) {
0745             if (actcol == textColorShaded) {
0746                 actcol = textColor;
0747             } else {
0748                 actcol = textColorShaded;
0749             }
0750             p.setPen(actcol);
0751         }
0752 
0753         // Reset pen color after selected days block
0754         if (i == mSelEnd + 1) {
0755             p.setPen(actcol);
0756         }
0757 
0758         const bool holiday = !workDays.contains(mDays[i]);
0759 
0760         const QColor holidayColorShaded = getShadedColor(KOPrefs::instance()->agendaHolidaysBackgroundColor());
0761 
0762         // if today then draw rectangle around day
0763         if (mToday == i) {
0764             tmppen = p.pen();
0765             QPen todayPen(p.pen());
0766 
0767             todayPen.setWidth(mTodayMarginWidth);
0768             // draw red rectangle for holidays
0769             if (holiday) {
0770                 if (actcol == textColor) {
0771                     todayPen.setColor(KOPrefs::instance()->agendaHolidaysBackgroundColor());
0772                 } else {
0773                     todayPen.setColor(holidayColorShaded);
0774                 }
0775             }
0776             // draw gray rectangle for today if in selection
0777             if (i >= mSelStart && i <= mSelEnd) {
0778                 const QColor grey(QStringLiteral("grey"));
0779                 todayPen.setColor(grey);
0780             }
0781             p.setPen(todayPen);
0782             p.drawRect(column * dayWidth, row * dayHeight, dayWidth, dayHeight);
0783             p.setPen(tmppen);
0784         }
0785 
0786         // if any events are on that day then draw it using a bold font
0787         if (mEvents.contains(mDays[i])) {
0788             QFont myFont = font();
0789             myFont.setBold(true);
0790             p.setFont(myFont);
0791         }
0792 
0793         // if it is a holiday then use the default holiday color
0794         if (holiday) {
0795             if (actcol == textColor) {
0796                 p.setPen(KOPrefs::instance()->agendaHolidaysBackgroundColor());
0797             } else {
0798                 p.setPen(holidayColorShaded);
0799             }
0800         }
0801 
0802         // draw selected days with special color
0803         if (i >= mSelStart && i <= mSelEnd && !holiday) {
0804             p.setPen(Qt::white);
0805         }
0806 
0807         p.drawText(column * dayWidth, row * dayHeight, dayWidth, dayHeight, Qt::AlignHCenter | Qt::AlignVCenter, mDayLabels[i]);
0808 
0809         // reset color to actual color
0810         if (holiday) {
0811             p.setPen(actcol);
0812         }
0813         // reset bold font to plain font
0814         if (mEvents.contains(mDays[i]) > 0) {
0815             QFont myFont = font();
0816             myFont.setBold(false);
0817             p.setFont(myFont);
0818         }
0819     }
0820     p.end();
0821 }
0822 
0823 // ----------------------------------------------------------------------------
0824 //  R E SI Z E   E V E N T   H A N D L I N G
0825 // ----------------------------------------------------------------------------
0826 
0827 void KODayMatrix::resizeEvent(QResizeEvent *)
0828 {
0829     QRect sz = frameRect();
0830     mDaySize.setHeight(sz.height() * 7 / NUMDAYS);
0831     mDaySize.setWidth(sz.width() / 7);
0832 }
0833 
0834 /* static */
0835 QPair<QDate, QDate> KODayMatrix::matrixLimits(QDate month)
0836 {
0837     QDate d(month.year(), month.month(), 1);
0838 
0839     const int dayOfWeek = d.dayOfWeek();
0840     const int weekstart = KOGlobals::self()->firstDayOfWeek();
0841 
0842     d = d.addDays(-(7 + dayOfWeek - weekstart) % 7);
0843 
0844     if (dayOfWeek == weekstart) {
0845         d = d.addDays(-7); // Start on the second line
0846     }
0847 
0848     return qMakePair(d, d.addDays(NUMDAYS - 1));
0849 }
0850 
0851 #include "moc_kodaymatrix.cpp"