File indexing completed on 2024-06-09 05:08:37

0001 /*
0002   SPDX-FileCopyrightText: 2008 Bruno Virlet <bruno.virlet@gmail.com>
0003 
0004   SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0
0005 */
0006 
0007 #include "monthitem.h"
0008 #include "helper.h"
0009 #include "monthgraphicsitems.h"
0010 #include "monthscene.h"
0011 #include "monthview.h"
0012 #include "prefs.h"
0013 #include "prefs_base.h" // Ugly, but needed for the Enums
0014 
0015 #include <Akonadi/CalendarUtils>
0016 #include <Akonadi/EntityTreeModel>
0017 #include <Akonadi/IncidenceChanger>
0018 #include <Akonadi/TagCache>
0019 #include <CalendarSupport/KCalPrefs>
0020 #include <CalendarSupport/Utils>
0021 
0022 #include <KCalUtils/IncidenceFormatter>
0023 #include <KCalUtils/RecurrenceActions>
0024 
0025 #include "calendarview_debug.h"
0026 #include <KMessageBox>
0027 
0028 #include <QDrag>
0029 
0030 using namespace EventViews;
0031 using namespace KCalendarCore;
0032 
0033 MonthItem::MonthItem(MonthScene *monthScene)
0034     : mMonthScene(monthScene)
0035 {
0036 }
0037 
0038 MonthItem::~MonthItem()
0039 {
0040     deleteAll();
0041 }
0042 
0043 void MonthItem::deleteAll()
0044 {
0045     qDeleteAll(mMonthGraphicsItemList);
0046     mMonthGraphicsItemList.clear();
0047 }
0048 
0049 QWidget *MonthItem::parentWidget() const
0050 {
0051     return mMonthScene ? mMonthScene->monthView() : nullptr;
0052 }
0053 
0054 void MonthItem::updateMonthGraphicsItems()
0055 {
0056     // Remove all items
0057     qDeleteAll(mMonthGraphicsItemList);
0058     mMonthGraphicsItemList.clear();
0059 
0060     const QDate monthStartDate = startDate();
0061     const QDate monthEndDate = endDate();
0062 
0063     // For each row of the month view, create an item to build the whole
0064     // MonthItem's MonthGraphicsItems.
0065     for (QDate d = mMonthScene->mMonthView->actualStartDateTime().date(); d < mMonthScene->mMonthView->actualEndDateTime().date(); d = d.addDays(7)) {
0066         QDate end = d.addDays(6);
0067 
0068         int span;
0069         QDate start;
0070         if (monthStartDate <= d && monthEndDate >= end) { // MonthItem takes the whole line
0071             span = 6;
0072             start = d;
0073         } else if (monthStartDate >= d && monthEndDate <= end) { // starts and ends on this line
0074             start = monthStartDate;
0075             span = daySpan();
0076         } else if (d <= monthEndDate && monthEndDate <= end) { // MonthItem ends on this line
0077             span = mMonthScene->getLeftSpan(monthEndDate);
0078             start = d;
0079         } else if (d <= monthStartDate && monthStartDate <= end) { // MonthItem begins on this line
0080             span = mMonthScene->getRightSpan(monthStartDate);
0081             start = monthStartDate;
0082         } else { // MonthItem is not on the line
0083             continue;
0084         }
0085 
0086         // A new item needs to be created
0087         auto newItem = new MonthGraphicsItem(this);
0088         mMonthGraphicsItemList << newItem;
0089         newItem->setStartDate(start);
0090         newItem->setDaySpan(span);
0091     }
0092 
0093     if (isMoving() || isResizing()) {
0094         setZValue(100);
0095     } else {
0096         setZValue(0);
0097     }
0098 }
0099 
0100 void MonthItem::beginResize()
0101 {
0102     mOverrideDaySpan = daySpan();
0103     mOverrideStartDate = startDate();
0104     mResizing = true;
0105     setZValue(100);
0106 }
0107 
0108 void MonthItem::endResize()
0109 {
0110     setZValue(0);
0111     mResizing = false; // startDate() and daySpan() return real values again
0112 
0113     if (mOverrideStartDate != startDate() || mOverrideDaySpan != daySpan()) {
0114         finalizeResize(mOverrideStartDate, mOverrideStartDate.addDays(mOverrideDaySpan));
0115     }
0116 }
0117 
0118 void MonthItem::beginMove()
0119 {
0120     mOverrideDaySpan = daySpan();
0121     mOverrideStartDate = startDate();
0122     mMoving = true;
0123     setZValue(100);
0124 }
0125 
0126 void MonthItem::endMove()
0127 {
0128     setZValue(0);
0129     mMoving = false; // startDate() and daySpan() return real values again
0130 
0131     if (mOverrideStartDate != startDate()) {
0132         finalizeMove(mOverrideStartDate);
0133     }
0134 }
0135 
0136 bool MonthItem::resizeBy(int offsetToPreviousDate)
0137 {
0138     bool ret = false;
0139     if (mMonthScene->resizeType() == MonthScene::ResizeLeft) {
0140         if (mOverrideDaySpan - offsetToPreviousDate >= 0) {
0141             mOverrideStartDate = mOverrideStartDate.addDays(offsetToPreviousDate);
0142             mOverrideDaySpan = mOverrideDaySpan - offsetToPreviousDate;
0143             ret = true;
0144         }
0145     } else if (mMonthScene->resizeType() == MonthScene::ResizeRight) {
0146         if (mOverrideDaySpan + offsetToPreviousDate >= 0) {
0147             mOverrideDaySpan = mOverrideDaySpan + offsetToPreviousDate;
0148             ret = true;
0149         }
0150     }
0151 
0152     if (ret) {
0153         updateMonthGraphicsItems();
0154     }
0155     return ret;
0156 }
0157 
0158 void MonthItem::moveBy(int offsetToPreviousDate)
0159 {
0160     mOverrideStartDate = mOverrideStartDate.addDays(offsetToPreviousDate);
0161     updateMonthGraphicsItems();
0162 }
0163 
0164 void MonthItem::moveTo(QDate date)
0165 {
0166     mOverrideStartDate = date;
0167     updateMonthGraphicsItems();
0168 }
0169 
0170 void MonthItem::updateGeometry()
0171 {
0172     for (MonthGraphicsItem *item : std::as_const(mMonthGraphicsItemList)) {
0173         item->updateGeometry();
0174     }
0175 }
0176 
0177 void MonthItem::setZValue(qreal z)
0178 {
0179     for (MonthGraphicsItem *item : std::as_const(mMonthGraphicsItemList)) {
0180         item->setZValue(z);
0181     }
0182 }
0183 
0184 QDate MonthItem::startDate() const
0185 {
0186     if ((isMoving() || isResizing()) && mOverrideStartDate.isValid()) {
0187         return mOverrideStartDate;
0188     }
0189 
0190     return realStartDate();
0191 }
0192 
0193 QDate MonthItem::endDate() const
0194 {
0195     if ((isMoving() || isResizing()) && mOverrideStartDate.isValid()) {
0196         return mOverrideStartDate.addDays(mOverrideDaySpan);
0197     }
0198 
0199     return realEndDate();
0200 }
0201 
0202 int MonthItem::daySpan() const
0203 {
0204     if (isMoving() || isResizing()) {
0205         return mOverrideDaySpan;
0206     }
0207     QDateTime start(startDate().startOfDay());
0208     QDateTime end(endDate().startOfDay());
0209 
0210     if (start.isValid() && end.isValid()) {
0211         return start.daysTo(end);
0212     }
0213 
0214     return 0;
0215 }
0216 
0217 bool MonthItem::greaterThan(const MonthItem *e1, const MonthItem *e2)
0218 {
0219     const int leftDaySpan = e1->daySpan();
0220     const int rightDaySpan = e2->daySpan();
0221     if (leftDaySpan == rightDaySpan) {
0222         const QDate leftStartDate = e1->startDate();
0223         const QDate rightStartDate = e2->startDate();
0224         if (!leftStartDate.isValid() || !rightStartDate.isValid()) {
0225             return false;
0226         }
0227         if (leftStartDate == rightStartDate) {
0228             if (e1->allDay() && !e2->allDay()) {
0229                 return true;
0230             }
0231             if (!e1->allDay() && e2->allDay()) {
0232                 return false;
0233             }
0234             return e1->greaterThanFallback(e2);
0235         } else {
0236             return leftStartDate > rightStartDate;
0237         }
0238     }
0239     return leftDaySpan > rightDaySpan;
0240 }
0241 
0242 bool MonthItem::greaterThanFallback(const MonthItem *other) const
0243 {
0244     const auto h = qobject_cast<const HolidayMonthItem *>(other);
0245 
0246     // If "other" is a holiday, display it first.
0247     return !h;
0248 }
0249 
0250 void MonthItem::updatePosition()
0251 {
0252     if (!startDate().isValid() || !endDate().isValid()) {
0253         return;
0254     }
0255 
0256     int firstFreeSpace = 0;
0257     for (QDate d = startDate(); d <= endDate(); d = d.addDays(1)) {
0258         MonthCell *cell = mMonthScene->mMonthCellMap.value(d);
0259         if (!cell) {
0260             continue; // cell can be null if the item begins outside the month
0261         }
0262         int firstFreeSpaceTmp = cell->firstFreeSpace();
0263         if (firstFreeSpaceTmp > firstFreeSpace) {
0264             firstFreeSpace = firstFreeSpaceTmp;
0265         }
0266     }
0267 
0268     for (QDate d = startDate(); d <= endDate(); d = d.addDays(1)) {
0269         MonthCell *cell = mMonthScene->mMonthCellMap.value(d);
0270         if (!cell) {
0271             continue;
0272         }
0273         cell->addMonthItem(this, firstFreeSpace);
0274     }
0275 
0276     mPosition = firstFreeSpace;
0277 }
0278 
0279 QList<MonthGraphicsItem *> EventViews::MonthItem::monthGraphicsItems() const
0280 {
0281     return mMonthGraphicsItemList;
0282 }
0283 
0284 //-----------------------------------------------------------------
0285 // INCIDENCEMONTHITEM
0286 IncidenceMonthItem::IncidenceMonthItem(MonthScene *monthScene,
0287                                        const Akonadi::CollectionCalendar::Ptr &calendar,
0288                                        const Akonadi::Item &aitem,
0289                                        const KCalendarCore::Incidence::Ptr &incidence,
0290                                        QDate recurStartDate)
0291     : MonthItem(monthScene)
0292     , mCalendar(calendar)
0293     , mIncidence(incidence)
0294     , mAkonadiItemId(aitem.id())
0295 {
0296     mIsEvent = CalendarSupport::hasEvent(aitem);
0297     mIsJournal = CalendarSupport::hasJournal(aitem);
0298     mIsTodo = CalendarSupport::hasTodo(aitem);
0299 
0300     KCalendarCore::Incidence::Ptr inc = mIncidence;
0301     if (inc->customProperty("KABC", "BIRTHDAY") == QLatin1StringView("YES") || inc->customProperty("KABC", "ANNIVERSARY") == QLatin1StringView("YES")) {
0302         const int years = EventViews::yearDiff(inc->dtStart().date(), recurStartDate);
0303         if (years > 0) {
0304             inc = KCalendarCore::Incidence::Ptr(inc->clone());
0305             inc->setReadOnly(false);
0306             inc->setDescription(i18np("%2 1 year", "%2 %1 years", years, i18n("Age:")));
0307             inc->setReadOnly(true);
0308             mIncidence = inc;
0309         }
0310     }
0311 
0312     connect(monthScene, &MonthScene::incidenceSelected, this, &IncidenceMonthItem::updateSelection);
0313 
0314     // first set to 0, because it's used in startDate()
0315     mRecurDayOffset = 0;
0316     const auto incidenceStart = mIncidence->dtStart().toLocalTime().date();
0317     if ((mIncidence->recurs() || mIncidence->recurrenceId().isValid()) && incidenceStart.isValid() && recurStartDate.isValid()) {
0318         mRecurDayOffset = incidenceStart.daysTo(recurStartDate);
0319     }
0320 }
0321 
0322 IncidenceMonthItem::~IncidenceMonthItem() = default;
0323 
0324 bool IncidenceMonthItem::greaterThanFallback(const MonthItem *other) const
0325 {
0326     const auto o = qobject_cast<const IncidenceMonthItem *>(other);
0327     if (!o) {
0328         return MonthItem::greaterThanFallback(other);
0329     }
0330 
0331     if (allDay() != o->allDay()) {
0332         return allDay();
0333     }
0334     const KCalendarCore::Incidence::Ptr otherIncidence = o->mIncidence;
0335 
0336     if (mIncidence->dtStart().time() != otherIncidence->dtStart().time()) {
0337         return mIncidence->dtStart().time() < otherIncidence->dtStart().time();
0338     }
0339 
0340     // as a last resort, compare uids
0341     return mIncidence->uid() < otherIncidence->uid();
0342 }
0343 
0344 QDate IncidenceMonthItem::realStartDate() const
0345 {
0346     if (!mIncidence) {
0347         return {};
0348     }
0349 
0350     const QDateTime dt = mIncidence->dateTime(Incidence::RoleDisplayStart);
0351     const QDate start = dt.toLocalTime().date();
0352 
0353     return start.addDays(mRecurDayOffset);
0354 }
0355 
0356 QDate IncidenceMonthItem::realEndDate() const
0357 {
0358     if (!mIncidence) {
0359         return {};
0360     }
0361 
0362     QDateTime dt = mIncidence->dateTime(KCalendarCore::Incidence::RoleDisplayEnd);
0363     if (!mIncidence->allDay() && dt > mIncidence->dateTime(KCalendarCore::Incidence::RoleDisplayStart)) {
0364         // If dt's time portion is 00:00:00, the incidence ends on the previous day
0365         // unless it also starts at 00:00:00 (a duration of 0).
0366         dt = dt.addMSecs(-1);
0367     }
0368     const QDate end = dt.toLocalTime().date();
0369 
0370     return end.addDays(mRecurDayOffset);
0371 }
0372 
0373 bool IncidenceMonthItem::allDay() const
0374 {
0375     return mIncidence->allDay();
0376 }
0377 
0378 bool IncidenceMonthItem::isMoveable() const
0379 {
0380     return mCalendar->hasRight(Akonadi::Collection::CanChangeItem);
0381 }
0382 
0383 bool IncidenceMonthItem::isResizable() const
0384 {
0385     return mCalendar->hasRight(Akonadi::Collection::CanChangeItem);
0386 }
0387 
0388 void IncidenceMonthItem::finalizeMove(const QDate &newStartDate)
0389 {
0390     Q_ASSERT(isMoveable());
0391 
0392     if (startDate().isValid()) {
0393         if (newStartDate.isValid()) {
0394             updateDates(startDate().daysTo(newStartDate), startDate().daysTo(newStartDate));
0395         } else {
0396             if (QDrag *drag = CalendarSupport::createDrag(akonadiItem(), this)) {
0397                 drag->exec();
0398             }
0399         }
0400     }
0401 }
0402 
0403 void IncidenceMonthItem::finalizeResize(const QDate &newStartDate, const QDate &newEndDate)
0404 {
0405     Q_ASSERT(isResizable());
0406 
0407     if (startDate().isValid() && endDate().isValid() && newStartDate.isValid() && newEndDate.isValid()) {
0408         updateDates(startDate().daysTo(newStartDate), endDate().daysTo(newEndDate));
0409     }
0410 }
0411 
0412 void IncidenceMonthItem::updateDates(int startOffset, int endOffset)
0413 {
0414     Akonadi::IncidenceChanger *changer = monthScene()->incidenceChanger();
0415     if (!changer || (startOffset == 0 && endOffset == 0)) {
0416         qCDebug(CALENDARVIEW_LOG) << changer << startOffset << endOffset;
0417         return;
0418     }
0419 
0420     Akonadi::Item item = akonadiItem();
0421     item.setPayload(mIncidence);
0422     if (mIncidence->recurs()) {
0423         const int res = monthScene()->mMonthView->showMoveRecurDialog(mIncidence, startDate());
0424         switch (res) {
0425         case KCalUtils::RecurrenceActions::AllOccurrences: {
0426             // All occurrences
0427             KCalendarCore::Incidence::Ptr oldIncidence(mIncidence->clone());
0428             setNewDates(mIncidence, startOffset, endOffset);
0429             changer->modifyIncidence(item, oldIncidence);
0430             break;
0431         }
0432         case KCalUtils::RecurrenceActions::SelectedOccurrence: // Just this occurrence
0433         case KCalUtils::RecurrenceActions::FutureOccurrences: { // All future occurrences
0434             const bool thisAndFuture = (res == KCalUtils::RecurrenceActions::FutureOccurrences);
0435             QDateTime occurrenceDate(mIncidence->dtStart());
0436             occurrenceDate.setDate(startDate());
0437             KCalendarCore::Incidence::Ptr newIncidence(KCalendarCore::Calendar::createException(mIncidence, occurrenceDate, thisAndFuture));
0438             if (newIncidence) {
0439                 changer->startAtomicOperation(i18n("Move occurrence(s)"));
0440                 setNewDates(newIncidence, startOffset, endOffset);
0441                 changer->createIncidence(newIncidence, item.parentCollection(), parentWidget());
0442                 changer->endAtomicOperation();
0443             } else {
0444                 KMessageBox::error(parentWidget(),
0445                                    i18n("Unable to add the exception item to the calendar. "
0446                                         "No change will be done."),
0447                                    i18nc("@title:window", "Error Occurred"));
0448             }
0449             break;
0450         }
0451         }
0452     } else { // Doesn't recur
0453         KCalendarCore::Incidence::Ptr oldIncidence(mIncidence->clone());
0454         setNewDates(mIncidence, startOffset, endOffset);
0455         changer->modifyIncidence(item, oldIncidence);
0456     }
0457 }
0458 
0459 void IncidenceMonthItem::updateSelection(const Akonadi::Item &incidence, QDate date)
0460 {
0461     Q_UNUSED(date)
0462     setSelected(incidence == akonadiItem());
0463 }
0464 
0465 QString IncidenceMonthItem::text(bool end) const
0466 {
0467     QString ret = mIncidence->summary();
0468     if (!allDay() && !mIsJournal && monthScene()->monthView()->preferences()->showTimeInMonthView()) {
0469         // Prepend the time str to the text
0470         QString timeStr;
0471         if (mIsTodo) {
0472             KCalendarCore::Todo::Ptr todo = mIncidence.staticCast<Todo>();
0473             timeStr = QLocale().toString(todo->dtDue().toLocalTime().time(), QLocale::ShortFormat);
0474         } else {
0475             if (!end) {
0476                 QTime time;
0477                 if (mIncidence->recurs()) {
0478                     const auto start = mIncidence->dtStart().addDays(mRecurDayOffset).addSecs(-1);
0479                     time = mIncidence->recurrence()->getNextDateTime(start).toLocalTime().time();
0480                 } else {
0481                     time = mIncidence->dtStart().toLocalTime().time();
0482                 }
0483                 timeStr = QLocale().toString(time, QLocale::ShortFormat);
0484             } else {
0485                 KCalendarCore::Event::Ptr event = mIncidence.staticCast<Event>();
0486                 timeStr = QLocale().toString(event->dtEnd().toLocalTime().time(), QLocale::ShortFormat);
0487             }
0488         }
0489         if (!timeStr.isEmpty()) {
0490             if (!end) {
0491                 ret = timeStr + QLatin1Char(' ') + ret;
0492             } else {
0493                 ret = ret + QLatin1Char(' ') + timeStr;
0494             }
0495         }
0496     }
0497 
0498     return ret;
0499 }
0500 
0501 QString IncidenceMonthItem::toolTipText(const QDate &date) const
0502 {
0503     return KCalUtils::IncidenceFormatter::toolTipStr(Akonadi::CalendarUtils::displayName(mCalendar->model(), akonadiItem().parentCollection()),
0504                                                      mIncidence,
0505                                                      date,
0506                                                      true);
0507 }
0508 
0509 QList<QPixmap> IncidenceMonthItem::icons() const
0510 {
0511     QList<QPixmap> ret;
0512 
0513     if (!mIncidence) {
0514         return ret;
0515     }
0516 
0517     bool specialEvent = false;
0518     Akonadi::Item item = akonadiItem();
0519 
0520     const QSet<EventView::ItemIcon> icons = monthScene()->monthView()->preferences()->monthViewIcons();
0521 
0522     QString customIconName;
0523     if (icons.contains(EventViews::EventView::CalendarCustomIcon)) {
0524         const QString iconName = monthScene()->monthView()->iconForItem(item);
0525         if (!iconName.isEmpty() && iconName != QLatin1StringView("view-calendar") && iconName != QLatin1StringView("office-calendar")) {
0526             customIconName = iconName;
0527             ret << QPixmap(cachedSmallIcon(iconName));
0528         }
0529     }
0530 
0531     if (mIsEvent) {
0532         if (mIncidence->customProperty("KABC", "ANNIVERSARY") == QLatin1StringView("YES")) {
0533             specialEvent = true;
0534             ret << monthScene()->anniversaryPixmap();
0535         } else if (mIncidence->customProperty("KABC", "BIRTHDAY") == QLatin1StringView("YES")) {
0536             specialEvent = true;
0537             // Disabling birthday icon because it's the birthday agent's icon
0538             // and we allow to display the agent's icon now.
0539             // ret << monthScene()->birthdayPixmap();
0540         }
0541 
0542         // smartins: Disabling the event Pixmap because:
0543         // 1. Save precious space so we can read the event's title better.
0544         // 2. We don't need a pixmap to tell us an item is an event we
0545         //    only need one to tell us it's not, as month view was designed for events.
0546         // 3. If only to-dos and journals have a pixmap they will be distinguished
0547         //    from event's much easier.
0548 
0549         // ret << monthScene()->eventPixmap();
0550     } else if ((mIsTodo || mIsJournal) && icons.contains(mIsTodo ? EventView::TaskIcon : EventView::JournalIcon)) {
0551         QDateTime occurrenceDateTime = mIncidence->dateTime(Incidence::RoleRecurrenceStart).addDays(mRecurDayOffset);
0552 
0553         const QString incidenceIconName = mIncidence->iconName(occurrenceDateTime);
0554         if (customIconName != incidenceIconName) {
0555             ret << QPixmap(cachedSmallIcon(incidenceIconName));
0556         }
0557     }
0558 
0559     if (icons.contains(EventView::ReadOnlyIcon) && !mCalendar->hasRight(Akonadi::Collection::CanChangeItem) && !specialEvent) {
0560         ret << monthScene()->readonlyPixmap();
0561     }
0562 
0563     /* sorry, this looks too cluttered. disable until we can
0564        make something prettier; no idea at this time -- allen */
0565     if (icons.contains(EventView::ReminderIcon) && mIncidence->hasEnabledAlarms() && !specialEvent) {
0566         ret << monthScene()->alarmPixmap();
0567     }
0568     if (icons.contains(EventView::RecurringIcon) && mIncidence->recurs() && !specialEvent) {
0569         ret << monthScene()->recurPixmap();
0570     }
0571     // TODO: check what to do with Reply
0572 
0573     return ret;
0574 }
0575 
0576 QColor IncidenceMonthItem::catColor() const
0577 {
0578     Q_ASSERT(mIncidence);
0579     const auto &prefs = monthScene()->monthView()->preferences();
0580 
0581     const auto &categories = mIncidence->categories();
0582     if (categories.isEmpty() || !Akonadi::TagCache::instance()->tagColor(categories.first()).isValid()) {
0583         const auto &colorPreference = prefs->monthViewColors();
0584         if (colorPreference == PrefsBase::CategoryOnly) {
0585             return CalendarSupport::KCalPrefs::instance()->unsetCategoryColor();
0586         }
0587         return EventViews::resourceColor(akonadiItem(), prefs);
0588     }
0589     return Akonadi::TagCache::instance()->tagColor(categories.first());
0590 }
0591 
0592 QColor IncidenceMonthItem::bgColor() const
0593 {
0594     const auto &prefs = monthScene()->monthView()->preferences();
0595 
0596     if (!prefs->todosUseCategoryColors() && mIsTodo) {
0597         Todo::Ptr todo = Akonadi::CalendarUtils::todo(akonadiItem());
0598         Q_ASSERT(todo);
0599         if (todo) {
0600             // this is dtDue if there's no dtRecurrence
0601             const auto dtRecurrence = todo->dtRecurrence().toLocalTime().date();
0602             const auto today = QDate::currentDate();
0603             if (startDate() >= dtRecurrence) {
0604                 if (todo->isOverdue() && today > startDate()) {
0605                     return prefs->todoOverdueColor();
0606                 }
0607                 if (today == startDate() && !todo->isCompleted()) {
0608                     return prefs->todoDueTodayColor();
0609                 }
0610             }
0611         }
0612     }
0613 
0614     const auto &colorPreference = prefs->monthViewColors();
0615     const auto bgDisplaysResource = colorPreference == PrefsBase::MonthItemResourceInsideCategoryOutside || colorPreference == PrefsBase::MonthItemResourceOnly;
0616     return bgDisplaysResource ? EventViews::resourceColor(akonadiItem(), prefs) : catColor();
0617 }
0618 
0619 QColor IncidenceMonthItem::frameColor() const
0620 {
0621     const auto &prefs = monthScene()->monthView()->preferences();
0622     const auto frameDisplaysResource =
0623         (prefs->monthViewColors() == PrefsBase::MonthItemResourceOnly || prefs->monthViewColors() == PrefsBase::MonthItemCategoryInsideResourceOutside);
0624     const auto frameColor = frameDisplaysResource ? EventViews::resourceColor(akonadiItem(), prefs) : catColor();
0625     return EventView::itemFrameColor(frameColor, selected());
0626 }
0627 
0628 Akonadi::Item IncidenceMonthItem::akonadiItem() const
0629 {
0630     if (mIncidence) {
0631         return mCalendar->item(mIncidence);
0632     } else {
0633         return {};
0634     }
0635 }
0636 
0637 KCalendarCore::Incidence::Ptr IncidenceMonthItem::incidence() const
0638 {
0639     return mIncidence;
0640 }
0641 
0642 Akonadi::Item::Id IncidenceMonthItem::akonadiItemId() const
0643 {
0644     return mAkonadiItemId;
0645 }
0646 
0647 Akonadi::CollectionCalendar::Ptr IncidenceMonthItem::calendar() const
0648 {
0649     return mCalendar;
0650 }
0651 
0652 void IncidenceMonthItem::setNewDates(const KCalendarCore::Incidence::Ptr &incidence, int startOffset, int endOffset)
0653 {
0654     if (mIsTodo) {
0655         // For to-dos endOffset is ignored because it will always be == to startOffset because we only
0656         // support moving to-dos, not resizing them. There are no multi-day to-dos.
0657         // Lets just call it offset to reduce confusion.
0658         const int offset = startOffset;
0659 
0660         KCalendarCore::Todo::Ptr todo = incidence.staticCast<Todo>();
0661         QDateTime due = todo->dtDue();
0662         QDateTime start = todo->dtStart();
0663         if (due.isValid()) { // Due has priority over start.
0664             // We will only move the due date, unlike events where we move both.
0665             due = due.addDays(offset);
0666             todo->setDtDue(due);
0667 
0668             if (start.isValid() && start > due) {
0669                 // Start can't be bigger than due.
0670                 todo->setDtStart(due);
0671             }
0672         } else if (start.isValid()) {
0673             // So we're displaying a to-do that doesn't have due date, only start...
0674             start = start.addDays(offset);
0675             todo->setDtStart(start);
0676         } else {
0677             // This never happens
0678             qCWarning(CALENDARVIEW_LOG) << "Move what? uid:" << todo->uid() << "; summary=" << todo->summary();
0679         }
0680     } else {
0681         incidence->setDtStart(incidence->dtStart().addDays(startOffset));
0682         if (mIsEvent) {
0683             KCalendarCore::Event::Ptr event = incidence.staticCast<Event>();
0684             event->setDtEnd(event->dtEnd().addDays(endOffset));
0685         }
0686     }
0687 }
0688 
0689 //-----------------------------------------------------------------
0690 // HOLIDAYMONTHITEM
0691 HolidayMonthItem::HolidayMonthItem(MonthScene *monthScene, QDate date, const QString &name)
0692     : HolidayMonthItem(monthScene, date, date, name)
0693 {
0694 }
0695 
0696 HolidayMonthItem::HolidayMonthItem(MonthScene *monthScene, QDate startDate, QDate endDate, const QString &name)
0697     : MonthItem(monthScene)
0698     , mStartDate(startDate)
0699     , mEndDate(endDate)
0700     , mName(name)
0701 {
0702 }
0703 
0704 HolidayMonthItem::~HolidayMonthItem() = default;
0705 
0706 bool HolidayMonthItem::greaterThanFallback(const MonthItem *other) const
0707 {
0708     const auto o = qobject_cast<const HolidayMonthItem *>(other);
0709     // always put holidays on top
0710     return !o;
0711 }
0712 
0713 void HolidayMonthItem::finalizeMove(const QDate &newStartDate)
0714 {
0715     Q_UNUSED(newStartDate)
0716     Q_ASSERT(false);
0717 }
0718 
0719 void HolidayMonthItem::finalizeResize(const QDate &newStartDate, const QDate &newEndDate)
0720 {
0721     Q_UNUSED(newStartDate)
0722     Q_UNUSED(newEndDate)
0723     Q_ASSERT(false);
0724 }
0725 
0726 QList<QPixmap> HolidayMonthItem::icons() const
0727 {
0728     QList<QPixmap> ret;
0729     ret << monthScene()->holidayPixmap();
0730 
0731     return ret;
0732 }
0733 
0734 QColor HolidayMonthItem::bgColor() const
0735 {
0736     // FIXME: Currently, only this value is settable in the options.
0737     // There is a monthHolidaysBackgroundColor() option too. Maybe it would be
0738     // wise to merge those two.
0739     return monthScene()->monthView()->preferences()->agendaHolidaysBackgroundColor();
0740 }
0741 
0742 QColor HolidayMonthItem::frameColor() const
0743 {
0744     return Qt::black;
0745 }
0746 
0747 #include "moc_monthitem.cpp"