diff --git a/src/agenda/agendaitem.cpp b/src/agenda/agendaitem.cpp index 73a9bc4..7235b5c 100644 --- a/src/agenda/agendaitem.cpp +++ b/src/agenda/agendaitem.cpp @@ -1,1233 +1,1203 @@ /* Copyright (c) 2000,2001,2003 Cornelius Schumacher Copyright (C) 2003-2004 Reinhold Kainhofer This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "agendaitem.h" #include "eventview.h" #include "viewcalendar.h" #include "helper.h" #include "prefs.h" #include "prefs_base.h" // for enums #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KCalCore; using namespace EventViews; //----------------------------------------------------------------------------- QPixmap *AgendaItem::alarmPxmp = nullptr; QPixmap *AgendaItem::recurPxmp = nullptr; QPixmap *AgendaItem::readonlyPxmp = nullptr; QPixmap *AgendaItem::replyPxmp = nullptr; QPixmap *AgendaItem::groupPxmp = nullptr; QPixmap *AgendaItem::groupPxmpTent = nullptr; QPixmap *AgendaItem::organizerPxmp = nullptr; QPixmap *AgendaItem::eventPxmp = nullptr; //----------------------------------------------------------------------------- AgendaItem::AgendaItem(EventView *eventView, const MultiViewCalendar::Ptr &calendar, const KCalCore::Incidence::Ptr &item, int itemPos, int itemCount, const QDateTime &qd, bool isSelected, QWidget *parent) : QWidget(parent) , mEventView(eventView) , mCalendar(calendar) , mIncidence(item) , mOccurrenceDateTime(qd) , mValid(true) , mCloned(false) , mSelected(isSelected) , mSpecialEvent(false) { if (!mIncidence) { mValid = false; return; } mIncidence = Incidence::Ptr(mIncidence->clone()); if (mIncidence->customProperty("KABC", "BIRTHDAY") == QLatin1String("YES") || mIncidence->customProperty("KABC", "ANNIVERSARY") == QLatin1String("YES")) { const int years = EventViews::yearDiff(mIncidence->dtStart().date(), qd.toLocalTime().date()); if (years > 0) { mIncidence->setReadOnly(false); mIncidence->setSummary(i18np("%2 (1 year)", "%2 (%1 years)", years, mIncidence->summary())); mIncidence->setReadOnly(true); mCloned = true; } } mLabelText = mIncidence->summary(); mIconAlarm = false; mIconRecur = false; mIconReadonly = false; mIconReply = false; mIconGroup = false; mIconGroupTent = false; mIconOrganizer = false; mMultiItemInfo = nullptr; mStartMoveInfo = nullptr; mItemPos = itemPos; mItemCount = itemCount; QPalette pal = palette(); pal.setColor(QPalette::Window, Qt::transparent); setPalette(pal); setCellXY(0, 0, 1); setCellXRight(0); setMouseTracking(true); mResourceColor = QColor(); updateIcons(); setAcceptDrops(true); } AgendaItem::~AgendaItem() { } void AgendaItem::updateIcons() { if (!mValid) { return; } mIconReadonly = mIncidence->isReadOnly(); mIconRecur = mIncidence->recurs() || mIncidence->hasRecurrenceId(); mIconAlarm = mIncidence->hasEnabledAlarms(); if (mIncidence->attendeeCount() > 1) { if (mEventView->kcalPreferences()->thatIsMe(mIncidence->organizer()->email())) { mIconReply = false; mIconGroup = false; mIconGroupTent = false; mIconOrganizer = true; } else { KCalCore::Attendee::Ptr me = mIncidence->attendeeByMails(mEventView->kcalPreferences()->allEmails()); if (me) { if (me->status() == KCalCore::Attendee::NeedsAction && me->RSVP()) { mIconReply = true; mIconGroup = false; mIconGroupTent = false; mIconOrganizer = false; } else if (me->status() == KCalCore::Attendee::Tentative) { mIconReply = false; mIconGroup = false; mIconGroupTent = true; mIconOrganizer = false; } else { mIconReply = false; mIconGroup = true; mIconGroupTent = false; mIconOrganizer = false; } } else { mIconReply = false; mIconGroup = true; mIconGroupTent = false; mIconOrganizer = false; } } } update(); } void AgendaItem::select(bool selected) { if (mSelected != selected) { mSelected = selected; update(); } } bool AgendaItem::dissociateFromMultiItem() { if (!isMultiItem()) { return false; } AgendaItem::QPtr firstItem = firstMultiItem(); if (firstItem == this) { firstItem = nextMultiItem(); } AgendaItem::QPtr lastItem = lastMultiItem(); if (lastItem == this) { lastItem = prevMultiItem(); } AgendaItem::QPtr prevItem = prevMultiItem(); AgendaItem::QPtr nextItem = nextMultiItem(); if (prevItem) { prevItem->setMultiItem(firstItem, prevItem->prevMultiItem(), nextItem, lastItem); } if (nextItem) { nextItem->setMultiItem(firstItem, prevItem, nextItem->prevMultiItem(), lastItem); } delete mMultiItemInfo; mMultiItemInfo = nullptr; return true; } void AgendaItem::setIncidence(const KCalCore::Incidence::Ptr &incidence) { mValid = false; if (incidence) { mValid = true; mIncidence = incidence; mLabelText = mIncidence->summary(); updateIcons(); } } /* Return height of item in units of agenda cells */ int AgendaItem::cellHeight() const { return mCellYBottom - mCellYTop + 1; } /* Return height of item in units of agenda cells */ int AgendaItem::cellWidth() const { return mCellXRight - mCellXLeft + 1; } void AgendaItem::setOccurrenceDateTime(const QDateTime &qd) { mOccurrenceDateTime = qd; } QDate AgendaItem::occurrenceDate() const { return mOccurrenceDateTime.toLocalTime().date(); } void AgendaItem::setCellXY(int X, int YTop, int YBottom) { mCellXLeft = X; mCellYTop = YTop; mCellYBottom = YBottom; } void AgendaItem::setCellXRight(int XRight) { mCellXRight = XRight; } void AgendaItem::setCellX(int XLeft, int XRight) { mCellXLeft = XLeft; mCellXRight = XRight; } void AgendaItem::setCellY(int YTop, int YBottom) { mCellYTop = YTop; mCellYBottom = YBottom; } void AgendaItem::setMultiItem(const AgendaItem::QPtr &first, const AgendaItem::QPtr &prev, const AgendaItem::QPtr &next, const AgendaItem::QPtr &last) { if (!mMultiItemInfo) { mMultiItemInfo = new MultiItemInfo; } mMultiItemInfo->mFirstMultiItem = first; mMultiItemInfo->mPrevMultiItem = prev; mMultiItemInfo->mNextMultiItem = next; mMultiItemInfo->mLastMultiItem = last; } bool AgendaItem::isMultiItem() const { return mMultiItemInfo; } AgendaItem::QPtr AgendaItem::prependMoveItem(const AgendaItem::QPtr &e) { if (!e) { return nullptr; } AgendaItem::QPtr first = nullptr, last = nullptr; if (isMultiItem()) { first = mMultiItemInfo->mFirstMultiItem; last = mMultiItemInfo->mLastMultiItem; } if (!first) { first = this; } if (!last) { last = this; } e->setMultiItem(nullptr, nullptr, first, last); first->setMultiItem(e, e, first->nextMultiItem(), first->lastMultiItem()); AgendaItem::QPtr tmp = first->nextMultiItem(); while (tmp) { tmp->setMultiItem(e, tmp->prevMultiItem(), tmp->nextMultiItem(), tmp->lastMultiItem()); tmp = tmp->nextMultiItem(); } if (mStartMoveInfo && !e->moveInfo()) { e->mStartMoveInfo = new MultiItemInfo(*mStartMoveInfo); // e->moveInfo()->mFirstMultiItem = moveInfo()->mFirstMultiItem; // e->moveInfo()->mLastMultiItem = moveInfo()->mLastMultiItem; e->moveInfo()->mPrevMultiItem = nullptr; e->moveInfo()->mNextMultiItem = first; } if (first && first->moveInfo()) { first->moveInfo()->mPrevMultiItem = e; } return e; } AgendaItem::QPtr AgendaItem::appendMoveItem(const AgendaItem::QPtr &e) { if (!e) { return nullptr; } AgendaItem::QPtr first = nullptr, last = nullptr; if (isMultiItem()) { first = mMultiItemInfo->mFirstMultiItem; last = mMultiItemInfo->mLastMultiItem; } if (!first) { first = this; } if (!last) { last = this; } e->setMultiItem(first, last, nullptr, nullptr); AgendaItem::QPtr tmp = first; while (tmp) { tmp->setMultiItem(tmp->firstMultiItem(), tmp->prevMultiItem(), tmp->nextMultiItem(), e); tmp = tmp->nextMultiItem(); } last->setMultiItem(last->firstMultiItem(), last->prevMultiItem(), e, e); if (mStartMoveInfo && !e->moveInfo()) { e->mStartMoveInfo = new MultiItemInfo(*mStartMoveInfo); // e->moveInfo()->mFirstMultiItem = moveInfo()->mFirstMultiItem; // e->moveInfo()->mLastMultiItem = moveInfo()->mLastMultiItem; e->moveInfo()->mPrevMultiItem = last; e->moveInfo()->mNextMultiItem = nullptr; } if (last && last->moveInfo()) { last->moveInfo()->mNextMultiItem = e; } return e; } AgendaItem::QPtr AgendaItem::removeMoveItem(const AgendaItem::QPtr &e) { if (isMultiItem()) { AgendaItem::QPtr first = mMultiItemInfo->mFirstMultiItem; AgendaItem::QPtr next, prev; AgendaItem::QPtr last = mMultiItemInfo->mLastMultiItem; if (!first) { first = this; } if (!last) { last = this; } if (first == e) { first = first->nextMultiItem(); first->setMultiItem(nullptr, nullptr, first->nextMultiItem(), first->lastMultiItem()); } if (last == e) { last = last->prevMultiItem(); last->setMultiItem(last->firstMultiItem(), last->prevMultiItem(), nullptr, nullptr); } AgendaItem::QPtr tmp = first; if (first == last) { delete mMultiItemInfo; tmp = nullptr; mMultiItemInfo = nullptr; } while (tmp) { next = tmp->nextMultiItem(); prev = tmp->prevMultiItem(); if (e == next) { next = next->nextMultiItem(); } if (e == prev) { prev = prev->prevMultiItem(); } tmp->setMultiItem((tmp == first) ? nullptr : first, (tmp == prev) ? nullptr : prev, (tmp == next) ? nullptr : next, (tmp == last) ? nullptr : last); tmp = tmp->nextMultiItem(); } } return e; } void AgendaItem::startMove() { AgendaItem::QPtr first = this; if (isMultiItem() && mMultiItemInfo->mFirstMultiItem) { first = mMultiItemInfo->mFirstMultiItem; } first->startMovePrivate(); } void AgendaItem::startMovePrivate() { mStartMoveInfo = new MultiItemInfo; mStartMoveInfo->mStartCellXLeft = mCellXLeft; mStartMoveInfo->mStartCellXRight = mCellXRight; mStartMoveInfo->mStartCellYTop = mCellYTop; mStartMoveInfo->mStartCellYBottom = mCellYBottom; if (mMultiItemInfo) { mStartMoveInfo->mFirstMultiItem = mMultiItemInfo->mFirstMultiItem; mStartMoveInfo->mLastMultiItem = mMultiItemInfo->mLastMultiItem; mStartMoveInfo->mPrevMultiItem = mMultiItemInfo->mPrevMultiItem; mStartMoveInfo->mNextMultiItem = mMultiItemInfo->mNextMultiItem; } else { mStartMoveInfo->mFirstMultiItem = nullptr; mStartMoveInfo->mLastMultiItem = nullptr; mStartMoveInfo->mPrevMultiItem = nullptr; mStartMoveInfo->mNextMultiItem = nullptr; } if (isMultiItem() && mMultiItemInfo->mNextMultiItem) { mMultiItemInfo->mNextMultiItem->startMovePrivate(); } } void AgendaItem::resetMove() { if (mStartMoveInfo) { if (mStartMoveInfo->mFirstMultiItem) { mStartMoveInfo->mFirstMultiItem->resetMovePrivate(); } else { resetMovePrivate(); } } } void AgendaItem::resetMovePrivate() { if (mStartMoveInfo) { mCellXLeft = mStartMoveInfo->mStartCellXLeft; mCellXRight = mStartMoveInfo->mStartCellXRight; mCellYTop = mStartMoveInfo->mStartCellYTop; mCellYBottom = mStartMoveInfo->mStartCellYBottom; // if we don't have mMultiItemInfo, the item didn't span two days before, // and wasn't moved over midnight, either, so we don't have to reset // anything. Otherwise, restore from mMoveItemInfo if (mMultiItemInfo) { // It was already a multi-day info mMultiItemInfo->mFirstMultiItem = mStartMoveInfo->mFirstMultiItem; mMultiItemInfo->mPrevMultiItem = mStartMoveInfo->mPrevMultiItem; mMultiItemInfo->mNextMultiItem = mStartMoveInfo->mNextMultiItem; mMultiItemInfo->mLastMultiItem = mStartMoveInfo->mLastMultiItem; if (!mStartMoveInfo->mFirstMultiItem) { // This was the first multi-item when the move started, delete all previous AgendaItem::QPtr toDel = mStartMoveInfo->mPrevMultiItem; AgendaItem::QPtr nowDel = nullptr; while (toDel) { nowDel = toDel; if (nowDel->moveInfo()) { toDel = nowDel->moveInfo()->mPrevMultiItem; } Q_EMIT removeAgendaItem(nowDel); } mMultiItemInfo->mFirstMultiItem = nullptr; mMultiItemInfo->mPrevMultiItem = nullptr; } if (!mStartMoveInfo->mLastMultiItem) { // This was the last multi-item when the move started, delete all next AgendaItem::QPtr toDel = mStartMoveInfo->mNextMultiItem; AgendaItem::QPtr nowDel = nullptr; while (toDel) { nowDel = toDel; if (nowDel->moveInfo()) { toDel = nowDel->moveInfo()->mNextMultiItem; } Q_EMIT removeAgendaItem(nowDel); } mMultiItemInfo->mLastMultiItem = nullptr; mMultiItemInfo->mNextMultiItem = nullptr; } if (mStartMoveInfo->mFirstMultiItem == nullptr && mStartMoveInfo->mLastMultiItem == nullptr) { // it was a single-day event before we started the move. delete mMultiItemInfo; mMultiItemInfo = nullptr; } } delete mStartMoveInfo; mStartMoveInfo = nullptr; } Q_EMIT showAgendaItem(this); if (nextMultiItem()) { nextMultiItem()->resetMovePrivate(); } } void AgendaItem::endMove() { AgendaItem::QPtr first = firstMultiItem(); if (!first) { first = this; } first->endMovePrivate(); } void AgendaItem::endMovePrivate() { if (mStartMoveInfo) { // if first, delete all previous if (!firstMultiItem() || firstMultiItem() == this) { AgendaItem::QPtr toDel = mStartMoveInfo->mPrevMultiItem; AgendaItem::QPtr nowDel = nullptr; while (toDel) { nowDel = toDel; if (nowDel->moveInfo()) { toDel = nowDel->moveInfo()->mPrevMultiItem; } Q_EMIT removeAgendaItem(nowDel); } } // if last, delete all next if (!lastMultiItem() || lastMultiItem() == this) { AgendaItem::QPtr toDel = mStartMoveInfo->mNextMultiItem; AgendaItem::QPtr nowDel = nullptr; while (toDel) { nowDel = toDel; if (nowDel->moveInfo()) { toDel = nowDel->moveInfo()->mNextMultiItem; } Q_EMIT removeAgendaItem(nowDel); } } // also delete the moving info delete mStartMoveInfo; mStartMoveInfo = nullptr; if (nextMultiItem()) { nextMultiItem()->endMovePrivate(); } } } void AgendaItem::moveRelative(int dx, int dy) { int newXLeft = cellXLeft() + dx; int newXRight = cellXRight() + dx; int newYTop = cellYTop() + dy; int newYBottom = cellYBottom() + dy; setCellXY(newXLeft, newYTop, newYBottom); setCellXRight(newXRight); } void AgendaItem::expandTop(int dy, const bool allowOverLimit) { int newYTop = cellYTop() + dy; int newYBottom = cellYBottom(); if (newYTop > newYBottom && !allowOverLimit) { newYTop = newYBottom; } setCellY(newYTop, newYBottom); } void AgendaItem::expandBottom(int dy) { int newYTop = cellYTop(); int newYBottom = cellYBottom() + dy; if (newYBottom < newYTop) { newYBottom = newYTop; } setCellY(newYTop, newYBottom); } void AgendaItem::expandLeft(int dx) { int newXLeft = cellXLeft() + dx; int newXRight = cellXRight(); if (newXLeft > newXRight) { newXLeft = newXRight; } setCellX(newXLeft, newXRight); } void AgendaItem::expandRight(int dx) { int newXLeft = cellXLeft(); int newXRight = cellXRight() + dx; if (newXRight < newXLeft) { newXRight = newXLeft; } setCellX(newXLeft, newXRight); } void AgendaItem::dragEnterEvent(QDragEnterEvent *e) { const QMimeData *md = e->mimeData(); if (KCalUtils::ICalDrag::canDecode(md) || KCalUtils::VCalDrag::canDecode(md)) { // TODO: Allow dragging events/todos onto other events to create a relation e->ignore(); return; } if (KContacts::VCardDrag::canDecode(md) || md->hasText()) { e->accept(); } else { e->ignore(); } } void AgendaItem::addAttendee(const QString &newAttendee) { if (!mValid) { return; } QString name, email; KEmailAddress::extractEmailAddressAndName(newAttendee, email, name); if (!(name.isEmpty() && email.isEmpty())) { mIncidence->addAttendee(KCalCore::Attendee::Ptr(new KCalCore::Attendee(name, email))); KMessageBox::information( this, i18n("Attendee \"%1\" added to the calendar item \"%2\"", KEmailAddress::normalizedAddress(name, email, QString()), text()), i18n("Attendee added"), QStringLiteral("AttendeeDroppedAdded")); } } void AgendaItem::dropEvent(QDropEvent *e) { // TODO: Organize this better: First check for attachment // (not only file, also any other url!), then if it's a vcard, // otherwise check for attendees, then if the data is binary, // add a binary attachment. if (!mValid) { return; } const QMimeData *md = e->mimeData(); bool decoded = md->hasText(); QString text = md->text(); if (decoded && text.startsWith(QLatin1String("file:"))) { mIncidence->addAttachment(KCalCore::Attachment::Ptr(new KCalCore::Attachment(text))); return; } KContacts::Addressee::List list; if (KContacts::VCardDrag::fromMimeData(md, list)) { for (const KContacts::Addressee &addressee : qAsConst(list)) { QString em(addressee.fullEmail()); if (em.isEmpty()) { em = addressee.realName(); } addAttendee(em); } } } QList &AgendaItem::conflictItems() { return mConflictItems; } void AgendaItem::setConflictItems(const QList &ci) { mConflictItems = ci; for (QList::iterator it = mConflictItems.begin(), end(mConflictItems.end()); it != end; ++it) { (*it)->addConflictItem(this); } } void AgendaItem::addConflictItem(const AgendaItem::QPtr &ci) { if (!mConflictItems.contains(ci)) { mConflictItems.append(ci); } } QString AgendaItem::label() const { return mLabelText; } bool AgendaItem::overlaps(CellItem *o) const { AgendaItem::QPtr other = static_cast(o); if (cellXLeft() <= other->cellXRight() && cellXRight() >= other->cellXLeft()) { if ((cellYTop() <= other->cellYBottom()) && (cellYBottom() >= other->cellYTop())) { return true; } } return false; } static void conditionalPaint(QPainter *p, bool condition, int &x, int y, int ft, const QPixmap &pxmp) { if (condition) { p->drawPixmap(x, y, pxmp); x += pxmp.width() + ft; } } void AgendaItem::paintIcon(QPainter *p, int &x, int y, int ft) { QString iconName; if (mIncidence->customProperty("KABC", "ANNIVERSARY") == QLatin1String("YES")) { mSpecialEvent = true; iconName = QStringLiteral("view-calendar-wedding-anniversary"); } else if (mIncidence->customProperty("KABC", "BIRTHDAY") == QLatin1String("YES")) { mSpecialEvent = true; // We don't draw icon. The icon is drawn already, because it's the Akonadi::Collection's icon } conditionalPaint(p, !iconName.isEmpty(), x, y, ft, cachedSmallIcon(iconName)); } void AgendaItem::paintIcons(QPainter *p, int &x, int y, int ft) { if (!mEventView->preferences()->enableAgendaItemIcons()) { return; } paintIcon(p, x, y, ft); QSet icons = mEventView->preferences()->agendaViewIcons(); if (icons.contains(EventViews::EventView::CalendarCustomIcon)) { const QString iconName = mCalendar->iconForIncidence(mIncidence); if (!iconName.isEmpty() && iconName != QLatin1String("view-calendar") && iconName != QLatin1String("office-calendar")) { conditionalPaint(p, true, x, y, ft, QIcon::fromTheme(iconName).pixmap(16, 16)); } } const bool isTodo = mIncidence && mIncidence->type() == Incidence::TypeTodo; if (isTodo && icons.contains(EventViews::EventView::TaskIcon)) { const QString iconName = mIncidence->iconName(mOccurrenceDateTime.toLocalTime()); conditionalPaint(p, !mSpecialEvent, x, y, ft, QIcon::fromTheme(iconName).pixmap(16, 16)); } if (icons.contains(EventView::RecurringIcon)) { conditionalPaint(p, mIconRecur && !mSpecialEvent, x, y, ft, *recurPxmp); } if (icons.contains(EventView::ReminderIcon)) { conditionalPaint(p, mIconAlarm && !mSpecialEvent, x, y, ft, *alarmPxmp); } if (icons.contains(EventView::ReadOnlyIcon)) { conditionalPaint(p, mIconReadonly && !mSpecialEvent, x, y, ft, *readonlyPxmp); } if (icons.contains(EventView::ReplyIcon)) { conditionalPaint(p, mIconReply, x, y, ft, *replyPxmp); } if (icons.contains(EventView::AttendingIcon)) { conditionalPaint(p, mIconGroup, x, y, ft, *groupPxmp); } if (icons.contains(EventView::TentativeIcon)) { conditionalPaint(p, mIconGroupTent, x, y, ft, *groupPxmpTent); } if (icons.contains(EventView::OrganizerIcon)) { conditionalPaint(p, mIconOrganizer, x, y, ft, *organizerPxmp); } } void AgendaItem::paintEvent(QPaintEvent *ev) { if (!mValid) { return; } QRect visRect = visibleRegion().boundingRect(); // when scrolling horizontally in the side-by-side view, the repainted area is clipped // to the newly visible area, which is a problem since the content changes when visRect // changes, so repaint the full item in that case if (ev->rect() != visRect && visRect.isValid() && ev->rect().isValid()) { update(visRect); return; } QPainter p(this); p.setRenderHint(QPainter::Antialiasing); const int fmargin = 0; // frame margin const int ft = 1; // frame thickness for layout, see drawRoundedRect(), // keep multiple of 2 const int margin = 5 + ft + fmargin; // frame + space between frame and content // General idea is to always show the icons (even in the all-day events). // This creates a consistent feeling for the user when the view mode // changes and therefore the available width changes. // Also look at #17984 if (!alarmPxmp) { alarmPxmp = new QPixmap(QIcon::fromTheme(QStringLiteral("task-reminder")).pixmap(16, 16)); recurPxmp = new QPixmap(QIcon::fromTheme(QStringLiteral("appointment-recurring")).pixmap(16, 16)); readonlyPxmp = new QPixmap(QIcon::fromTheme(QStringLiteral("object-locked")).pixmap(16, 16)); replyPxmp = new QPixmap(QIcon::fromTheme(QStringLiteral("mail-reply-sender")).pixmap(16, 16)); groupPxmp = new QPixmap(QIcon::fromTheme(QStringLiteral("meeting-attending")).pixmap(16, 16)); groupPxmpTent = new QPixmap(QIcon::fromTheme(QStringLiteral( "meeting-attending-tentative")).pixmap(16, 16)); organizerPxmp = new QPixmap(QIcon::fromTheme(QStringLiteral("meeting-organizer")).pixmap(16, 16)); } - QColor bgColor; + const auto categoryColor = getCategoryColor(); + const auto resourceColor = mResourceColor.isValid() ? mResourceColor : categoryColor; + const auto frameColor = getFrameColor(resourceColor, categoryColor); + const auto bgBaseColor = getBackgroundColor(resourceColor, categoryColor); + const auto bgColor = mSelected ? bgBaseColor.lighter(EventView::BRIGHTNESS_FACTOR) : bgBaseColor; + const auto textColor = EventViews::getTextColor(bgColor); - if (CalendarSupport::hasTodo(mIncidence) - && !mEventView->preferences()->todosUseCategoryColors()) { - Todo::Ptr todo = CalendarSupport::todo(mIncidence); - Q_ASSERT(todo); - const QDate dueDate = todo->dtDue().toLocalTime().date(); - const QDate today = QDate::currentDate(); - const QDate occurrenceDate = this->occurrenceDate(); - if (todo->isOverdue() && today >= occurrenceDate) { - bgColor = mEventView->preferences()->todoOverdueColor(); - } else if (dueDate == today && dueDate == occurrenceDate) { - bgColor = mEventView->preferences()->todoDueTodayColor(); - } - } - - QColor categoryColor; - const QStringList categories = mIncidence->categories(); - QString cat; - if (!categories.isEmpty()) { - cat = categories.first(); - } - - categoryColor = cat.isEmpty() ? CalendarSupport::KCalPrefs::instance()->unsetCategoryColor() - : CalendarSupport::KCalPrefs::instance()->categoryColor(cat); - - QColor resourceColor = mResourceColor; - if (!resourceColor.isValid()) { - resourceColor = categoryColor; - } - - QColor frameColor; - // TODO PrefsBase enums should probably be redefined in Prefs - if (mEventView->preferences()->agendaViewColors() == PrefsBase::ResourceOnly - || mEventView->preferences()->agendaViewColors() - == PrefsBase::CategoryInsideResourceOutside) { - frameColor = bgColor.isValid() ? bgColor : resourceColor; - } else { - frameColor = bgColor.isValid() ? bgColor : categoryColor; - } - - if (!bgColor.isValid()) { - if (mEventView->preferences()->agendaViewColors() == PrefsBase::ResourceOnly - || mEventView->preferences()->agendaViewColors() - == PrefsBase::ResourceInsideCategoryOutside) { - bgColor = resourceColor; - } else { - bgColor = categoryColor; - } - } - - if (cat.isEmpty() - && mEventView->preferences()->agendaViewColors() - == PrefsBase::ResourceInsideCategoryOutside) { - frameColor = bgColor; - } - - if (cat.isEmpty() - && mEventView->preferences()->agendaViewColors() - == PrefsBase::CategoryInsideResourceOutside) { - bgColor = frameColor; - } - - frameColor = EventView::itemFrameColor(frameColor, mSelected); - - if (!CalendarSupport::KCalPrefs::instance()->hasCategoryColor(cat)) { - categoryColor = resourceColor; - } - - if (!bgColor.isValid()) { - bgColor = categoryColor; - } - - if (mSelected) { - bgColor = bgColor.lighter(EventView::BRIGHTNESS_FACTOR); - } - - const QColor textColor = EventViews::getTextColor(bgColor); p.setPen(textColor); p.setFont(mEventView->preferences()->agendaViewFont()); QFontMetrics fm = p.fontMetrics(); const int singleLineHeight = fm.boundingRect(mLabelText).height(); const bool roundTop = !prevMultiItem(); const bool roundBottom = !nextMultiItem(); drawRoundedRect(&p, QRect(fmargin, fmargin, width() - fmargin * 2, height() - fmargin * 2), mSelected, bgColor, true, ft, roundTop, roundBottom); // calculate the height of the full version (case 4) to test whether it is // possible QString shortH; QString longH; if (!isMultiItem()) { shortH = QLocale().toString(mIncidence->dateTime( KCalCore::Incidence::RoleDisplayStart).toLocalTime().time(), QLocale::ShortFormat); if (CalendarSupport::hasEvent(mIncidence)) { longH = i18n("%1 - %2", shortH, QLocale().toString(mIncidence->dateTime(KCalCore::Incidence::RoleEnd). toLocalTime().time(), QLocale::ShortFormat)); } else { longH = shortH; } } else if (!mMultiItemInfo->mFirstMultiItem) { shortH = QLocale().toString(mIncidence->dtStart().toLocalTime().time(), QLocale::ShortFormat); longH = shortH; } else { shortH = QLocale().toString(mIncidence->dateTime( KCalCore::Incidence::RoleEnd).toLocalTime().time(), QLocale::ShortFormat); longH = i18n("- %1", shortH); } KWordWrap ww = KWordWrap::formatText( fm, QRect(0, 0, width() - (2 * margin), -1), 0, mLabelText); int th = ww.boundingRect().height(); int hlHeight = qMax(fm.boundingRect(longH).height(), qMax(alarmPxmp->height(), qMax(recurPxmp->height(), qMax(readonlyPxmp->height(), qMax(replyPxmp->height(), qMax(groupPxmp->height(), organizerPxmp->height())))))); const bool completelyRenderable = th < (height() - 2 * ft - 2 - hlHeight); // case 1: do not draw text when not even a single line fits // Don't do this any more, always try to print out the text. // Even if it's just a few pixel, one can still guess the whole // text from just four pixels' height! if ( //( singleLineHeight > height() - 4 ) || (width() < 16)) { int x = qRound((width() - 16) / 2.0); paintIcon(&p, x /*by-ref*/, margin, ft); return; } // case 2: draw a single line when no more space if ((2 * singleLineHeight) > (height() - 2 * margin)) { int x = margin, txtWidth; if (mIncidence->allDay()) { x += visRect.left(); const int y = qRound((height() - 16) / 2.0); paintIcons(&p, x, y, ft); txtWidth = visRect.right() - margin - x; } else { const int y = qRound((height() - 16) / 2.0); paintIcons(&p, x, y, ft); txtWidth = width() - margin - x; } const int y = ((height() - singleLineHeight) / 2) + fm.ascent(); KWordWrap::drawFadeoutText(&p, x, y, txtWidth, mLabelText); return; } // case 3: enough for 2-5 lines, but not for the header. // Also used for the middle days in multi-events if (((!completelyRenderable) && ((height() - (2 * margin)) <= (5 * singleLineHeight))) || (isMultiItem() && mMultiItemInfo->mNextMultiItem && mMultiItemInfo->mFirstMultiItem)) { int x = margin, txtWidth; if (mIncidence->allDay()) { x += visRect.left(); paintIcons(&p, x, margin, ft); txtWidth = visRect.right() - margin - x; } else { paintIcons(&p, x, margin, ft); txtWidth = width() - margin - x; } ww = KWordWrap::formatText( fm, QRect(0, 0, txtWidth, (height() - (2 * margin))), 0, mLabelText); ww.drawText(&p, x, margin, Qt::AlignHCenter | KWordWrap::FadeOut); return; } // case 4: paint everything, with header: // consists of (vertically) ft + headline&icons + ft + text + margin int y = 2 * ft + hlHeight; if (completelyRenderable) { y += (height() - (2 * ft) - margin - hlHeight - th) / 2; } int x = margin, txtWidth, hTxtWidth, eventX; if (mIncidence->allDay()) { shortH.clear(); longH.clear(); if (const KCalCore::Event::Ptr event = CalendarSupport::event(mIncidence)) { if (event->isMultiDay(QTimeZone::systemTimeZone())) { // multi-day, all-day event shortH = i18n("%1 - %2", QLocale().toString(mIncidence->dtStart().toLocalTime().date()), QLocale().toString(mIncidence->dateTime(KCalCore::Incidence::RoleEnd). toLocalTime().date())); longH = shortH; // paint headline drawRoundedRect( &p, QRect(fmargin, fmargin, width() - fmargin * 2, -fmargin * 2 + margin + hlHeight), mSelected, frameColor, false, ft, roundTop, false); } else { // single-day, all-day event // paint headline drawRoundedRect( &p, QRect(fmargin, fmargin, width() - fmargin * 2, -fmargin * 2 + margin + hlHeight), mSelected, frameColor, false, ft, roundTop, false); } } else { // to-do // paint headline drawRoundedRect( &p, QRect(fmargin, fmargin, width() - fmargin * 2, -fmargin * 2 + margin + hlHeight), mSelected, frameColor, false, ft, roundTop, false); } x += visRect.left(); eventX = x; txtWidth = visRect.right() - margin - x; paintIcons(&p, x, margin / 2, ft); hTxtWidth = visRect.right() - margin - x; } else { // paint headline drawRoundedRect( &p, QRect(fmargin, fmargin, width() - fmargin * 2, -fmargin * 2 + margin + hlHeight), mSelected, frameColor, false, ft, roundTop, false); txtWidth = width() - margin - x; eventX = x; paintIcons(&p, x, margin / 2, ft); hTxtWidth = width() - margin - x; } QString headline; int hw = fm.boundingRect(longH).width(); if (hw > hTxtWidth) { headline = shortH; hw = fm.boundingRect(shortH).width(); if (hw < txtWidth) { x += (hTxtWidth - hw) / 2; } } else { headline = longH; x += (hTxtWidth - hw) / 2; } p.setBackground(QBrush(frameColor)); p.setPen(EventViews::getTextColor(frameColor)); KWordWrap::drawFadeoutText(&p, x, (margin + hlHeight + fm.ascent()) / 2 - 2, hTxtWidth, headline); // draw event text ww = KWordWrap::formatText( fm, QRect(0, 0, txtWidth, height() - margin - y), 0, mLabelText); p.setBackground(QBrush(bgColor)); p.setPen(textColor); QString ws = ww.wrappedString(); if (ws.leftRef(ws.length() - 1).indexOf(QLatin1Char('\n')) >= 0) { ww.drawText(&p, eventX, y, Qt::AlignLeft | KWordWrap::FadeOut); } else { ww.drawText(&p, eventX + (txtWidth - ww.boundingRect().width() - 2 * margin) / 2, y, Qt::AlignHCenter | KWordWrap::FadeOut); } } void AgendaItem::drawRoundedRect(QPainter *p, const QRect &rect, bool selected, const QColor &bgColor, bool frame, int ft, bool roundTop, bool roundBottom) { Q_UNUSED(ft); if (!mValid) { return; } QPainterPath path; const int RECT_MARGIN = 2; const int RADIUS = 2; // absolute radius const QRect rectWithMargin(rect.x() + RECT_MARGIN, rect.y() + RECT_MARGIN, rect.width() - 2 * RECT_MARGIN, rect.height() - 2 * RECT_MARGIN); const QPoint pointLeftTop(rectWithMargin.x(), rectWithMargin.y()); const QPoint pointRightTop(rectWithMargin.x() + rectWithMargin.width(), rectWithMargin.y()); const QPoint pointLeftBottom(rectWithMargin.x(), rectWithMargin.y() + rectWithMargin.height()); const QPoint pointRightBottom(rectWithMargin.x() + rectWithMargin.width(), rectWithMargin.y() + rectWithMargin.height()); if (!roundTop && !roundBottom) { path.addRect(rectWithMargin); } else if (roundTop && roundBottom) { path.addRoundedRect(rectWithMargin, RADIUS, RADIUS, Qt::AbsoluteSize); } else if (roundTop) { path.moveTo(pointRightBottom); path.lineTo(pointLeftBottom); path.lineTo(QPoint(pointLeftTop.x(), pointLeftTop.y() + RADIUS)); path.quadTo(pointLeftTop, QPoint(pointLeftTop.x() + RADIUS, pointLeftTop.y())); path.lineTo(QPoint(pointRightTop.x() - RADIUS, pointRightTop.y())); path.quadTo(pointRightTop, QPoint(pointRightTop.x(), pointRightTop.y() + RADIUS)); path.lineTo(pointRightBottom); } else if (roundBottom) { path.moveTo(pointRightTop); path.lineTo(QPoint(pointRightBottom.x(), pointRightBottom.y() - RADIUS)); path.quadTo(pointRightBottom, QPoint(pointRightBottom.x() - RADIUS, pointRightBottom.y())); path.lineTo(QPoint(pointLeftBottom.x() + RADIUS, pointLeftBottom.y())); path.quadTo(pointLeftBottom, QPoint(pointLeftBottom.x(), pointLeftBottom.y() - RADIUS)); path.lineTo(pointLeftTop); path.lineTo(pointRightTop); } path.closeSubpath(); p->save(); p->setRenderHint(QPainter::Antialiasing, false); const QPen border(QBrush(QColor(200, 200, 200, 255)),1.0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin); p->setPen(border); // header if (!frame) { QBrush brushSolid(Qt::SolidPattern); if (selected) { QColor top = bgColor.darker(250); top.setAlpha(20); brushSolid.setColor(top); } else { brushSolid.setColor(QColor(255, 255, 255, 10)); } p->setBrush(bgColor); p->drawPath(path); p->setBrush(brushSolid); p->drawPath(path); p->restore(); return; } p->setBrush(bgColor); p->drawPath(path); p->restore(); } +QColor AgendaItem::getCategoryColor() const +{ + const QStringList &categories = mIncidence->categories(); + if (categories.isEmpty() || !CalendarSupport::KCalPrefs::instance()->hasCategoryColor(categories.first())) { + const auto colorPreference = mEventView->preferences()->agendaViewColors(); + if (colorPreference == PrefsBase::CategoryOnly || !mResourceColor.isValid()) { + return CalendarSupport::KCalPrefs::instance()->unsetCategoryColor(); + } + return mResourceColor; + } + return CalendarSupport::KCalPrefs::instance()->categoryColor(categories.first()); +} + +QColor AgendaItem::getFrameColor(const QColor &resourceColor, const QColor &categoryColor) const +{ + const auto colorPreference = mEventView->preferences()->agendaViewColors(); + const bool frameDisplaysCategory = (colorPreference == PrefsBase::CategoryOnly + || colorPreference == PrefsBase::ResourceInsideCategoryOutside); + return frameDisplaysCategory ? categoryColor : resourceColor; +} + +QColor AgendaItem::getBackgroundColor(const QColor &resourceColor, const QColor &categoryColor) const +{ + if (CalendarSupport::hasTodo(mIncidence) && !mEventView->preferences()->todosUseCategoryColors()) { + Todo::Ptr todo = CalendarSupport::todo(mIncidence); + Q_ASSERT(todo); + const QDate dueDate = todo->dtDue().toLocalTime().date(); + const QDate today = QDate::currentDate(); + const QDate occurrenceDate = this->occurrenceDate(); + if (todo->isOverdue() && today >= occurrenceDate) { + return mEventView->preferences()->todoOverdueColor(); + } else if (dueDate == today && dueDate == occurrenceDate) { + return mEventView->preferences()->todoDueTodayColor(); + } + } + const auto colorPreference = mEventView->preferences()->agendaViewColors(); + const bool bgDisplaysCategory = (colorPreference == PrefsBase::CategoryOnly + || colorPreference == PrefsBase::CategoryInsideResourceOutside); + return bgDisplaysCategory ? categoryColor : resourceColor; +} + bool AgendaItem::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::Paint) { return mValid; } else { // standard event processing return QObject::eventFilter(obj, event); } } bool AgendaItem::event(QEvent *event) { if (event->type() == QEvent::ToolTip) { if (!mEventView->preferences()->enableToolTips()) { return true; } else if (mValid) { QHelpEvent *helpEvent = static_cast(event); QToolTip::showText( helpEvent->globalPos(), KCalUtils::IncidenceFormatter::toolTipStr( mCalendar->displayName(mIncidence), mIncidence, occurrenceDate(), true), this); } } return QWidget::event(event); } diff --git a/src/agenda/agendaitem.h b/src/agenda/agendaitem.h index 06150b5..6e5bc9f 100644 --- a/src/agenda/agendaitem.h +++ b/src/agenda/agendaitem.h @@ -1,320 +1,324 @@ /* Copyright (c) 2000,2001,2003 Cornelius Schumacher Copyright (C) 2003-2004 Reinhold Kainhofer This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef EVENTVIEWS_AGENDAITEM_H #define EVENTVIEWS_AGENDAITEM_H #include "viewcalendar.h" #include #include #include #include #include #include namespace EventViews { class AgendaItem; class EventView; struct MultiItemInfo { int mStartCellXLeft, mStartCellXRight; int mStartCellYTop, mStartCellYBottom; QPointer mFirstMultiItem; QPointer mPrevMultiItem; QPointer mNextMultiItem; QPointer mLastMultiItem; }; /** @class AgendaItem @brief This class describes the widgets that represent the various calendar items in the agenda view The AgendaItem has to make sure that it receives all mouse events, which are to be used for dragging and resizing. That means it has to be installed as event filter for its children, if it has children, and it has to pass mouse events from the children to itself. See eventFilter(). Some comments on the movement of multi-day items: Basically, the agenda items are arranged in two implicit double-linked lists. The mMultiItemInfo works like before to describe the currently viewed multi-item. When moving, new events might need to be added to the beginning or the end of the multi-item sequence, or events might need to be hidden. I cannot just delete this items, since I have to restore/show them if the move is reset (i.e. if a drag started). So internally, I keep another doubly-linked list which is longer than the one defined by mMultiItemInfo, but includes the multi-item sequence, too. The mStartMoveInfo stores the first and last item of the multi-item sequence when the move started. The prev and next members of mStartMoveInfo are used for that longer sequence including all (shown and hidden) items. */ class AgendaItem : public QWidget, public CalendarSupport::CellItem { Q_OBJECT public: typedef QPointer QPtr; typedef QList List; AgendaItem(EventView *eventView, const MultiViewCalendar::Ptr &calendar, const KCalCore::Incidence::Ptr &incidence, int itemPos, int itemCount, const QDateTime &qd, bool isSelected, QWidget *parent); ~AgendaItem() override; Q_REQUIRED_RESULT int cellXLeft() const { return mCellXLeft; } Q_REQUIRED_RESULT int cellXRight() const { return mCellXRight; } Q_REQUIRED_RESULT int cellYTop() const { return mCellYTop; } Q_REQUIRED_RESULT int cellYBottom() const { return mCellYBottom; } Q_REQUIRED_RESULT int cellHeight() const; Q_REQUIRED_RESULT int cellWidth() const; Q_REQUIRED_RESULT int itemPos() const { return mItemPos; } Q_REQUIRED_RESULT int itemCount() const { return mItemCount; } void setCellXY(int X, int YTop, int YBottom); void setCellY(int YTop, int YBottom); void setCellX(int XLeft, int XRight); void setCellXRight(int XRight); /** Start movement */ void startMove(); /** Reset to original values */ void resetMove(); /** End the movement (i.e. clean up) */ void endMove(); void moveRelative(int dx, int dy); /** * Expands the item's top. * * @param dy delta y, number of units to be added to mCellYTop * @param allowOverLimit If false, the new mCellYTop can't be bigger than * mCellYBottom, instead, it gets mCellYBottom's value. * If true, @p dy is always added, regardless if mCellYTop * becomes bigger than mCellYBottom, this is useful when * moving items because it guarantees expandTop and the * following expandBottom call add the same value. */ void expandTop(int dy, const bool allowOverLimit = false); void expandBottom(int dy); void expandLeft(int dx); void expandRight(int dx); Q_REQUIRED_RESULT bool isMultiItem() const; AgendaItem::QPtr prevMoveItem() const { return (mStartMoveInfo) ? (mStartMoveInfo->mPrevMultiItem) : nullptr; } AgendaItem::QPtr nextMoveItem() const { return (mStartMoveInfo) ? (mStartMoveInfo->mNextMultiItem) : nullptr; } MultiItemInfo *moveInfo() const { return mStartMoveInfo; } void setMultiItem(const AgendaItem::QPtr &first, const AgendaItem::QPtr &prev, const AgendaItem::QPtr &next, const AgendaItem::QPtr &last); AgendaItem::QPtr prependMoveItem(const AgendaItem::QPtr &); AgendaItem::QPtr appendMoveItem(const AgendaItem::QPtr &); AgendaItem::QPtr removeMoveItem(const AgendaItem::QPtr &); AgendaItem::QPtr firstMultiItem() const { return (mMultiItemInfo) ? (mMultiItemInfo->mFirstMultiItem) : nullptr; } AgendaItem::QPtr prevMultiItem() const { return (mMultiItemInfo) ? (mMultiItemInfo->mPrevMultiItem) : nullptr; } AgendaItem::QPtr nextMultiItem() const { return (mMultiItemInfo) ? (mMultiItemInfo->mNextMultiItem) : nullptr; } AgendaItem::QPtr lastMultiItem() const { return (mMultiItemInfo) ? (mMultiItemInfo->mLastMultiItem) : nullptr; } Q_REQUIRED_RESULT bool dissociateFromMultiItem(); void setIncidence(const KCalCore::Incidence::Ptr &incidence); const KCalCore::Incidence::Ptr &incidence() const { return mIncidence; } Q_REQUIRED_RESULT QDateTime occurrenceDateTime() const { return mOccurrenceDateTime; } Q_REQUIRED_RESULT QDate occurrenceDate() const; // /** Update the date of this item's occurrence (not in the event) */ void setOccurrenceDateTime(const QDateTime &qd); void setText(const QString &text) { mLabelText = text; } Q_REQUIRED_RESULT QString text() const { return mLabelText; } QList &conflictItems(); void setConflictItems(const QList &); void addConflictItem(const AgendaItem::QPtr &ci); QString label() const override; /** Tells whether this item overlaps item @p o */ bool overlaps(CellItem *o) const override; void setResourceColor(const QColor &color) { mResourceColor = color; } Q_REQUIRED_RESULT QColor resourceColor() const { return mResourceColor; } Q_SIGNALS: void removeAgendaItem(const AgendaItem::QPtr &); void showAgendaItem(const AgendaItem::QPtr &); public Q_SLOTS: void updateIcons(); void select(bool selected = true); void addAttendee(const QString &); protected: bool eventFilter(QObject *obj, QEvent *event) override; bool event(QEvent *event) override; void dragEnterEvent(QDragEnterEvent *e) override; void dropEvent(QDropEvent *e) override; void paintEvent(QPaintEvent *e) override; /** private movement functions. startMove needs to be called of only one of * the multitems. it will then loop through the whole series using * startMovePrivate. Same for resetMove and endMove */ void startMovePrivate(); void resetMovePrivate(); void endMovePrivate(); // Variables to remember start position MultiItemInfo *mStartMoveInfo = nullptr; //Color of the resource QColor mResourceColor; private: void paintIcon(QPainter *p, int &x, int y, int ft); // paint all visible icons void paintIcons(QPainter *p, int &x, int y, int ft); void drawRoundedRect(QPainter *p, const QRect &rect, bool selected, const QColor &bgcolor, bool frame, int ft, bool roundTop, bool roundBottom); + Q_REQUIRED_RESULT QColor getCategoryColor() const; + Q_REQUIRED_RESULT QColor getFrameColor(const QColor &resourceColor, const QColor &categoryColor) const; + Q_REQUIRED_RESULT QColor getBackgroundColor(const QColor &resourceColor, const QColor &categoryColor) const; + int mCellXLeft, mCellXRight; int mCellYTop, mCellYBottom; EventView *mEventView = nullptr; MultiViewCalendar::Ptr mCalendar; KCalCore::Incidence::Ptr mIncidence; QDateTime mOccurrenceDateTime; bool mValid; bool mCloned; QString mLabelText; bool mSelected; bool mIconAlarm, mIconRecur, mIconReadonly; bool mIconReply, mIconGroup, mIconGroupTent; bool mIconOrganizer, mSpecialEvent; // For incidences that expand through more than 1 day // Will be 1 for single day incidences int mItemPos; int mItemCount; // Multi item pointers MultiItemInfo *mMultiItemInfo = nullptr; QList mConflictItems; static QPixmap *alarmPxmp; static QPixmap *recurPxmp; static QPixmap *readonlyPxmp; static QPixmap *replyPxmp; static QPixmap *groupPxmp; static QPixmap *groupPxmpTent; static QPixmap *organizerPxmp; static QPixmap *eventPxmp; static QPixmap *todoPxmp; static QPixmap *completedPxmp; }; } #endif diff --git a/src/month/monthitem.cpp b/src/month/monthitem.cpp index a7ba3b5..db041e3 100644 --- a/src/month/monthitem.cpp +++ b/src/month/monthitem.cpp @@ -1,780 +1,766 @@ /* Copyright (c) 2008 Bruno Virlet This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "monthitem.h" #include "helper.h" #include "monthgraphicsitems.h" #include "monthscene.h" #include "monthview.h" #include "prefs.h" #include "prefs_base.h" // Ugly, but needed for the Enums #include #include #include #include #include #include #include "calendarview_debug.h" using namespace EventViews; using namespace KCalCore; MonthItem::MonthItem(MonthScene *monthScene) : mMonthScene(monthScene) , mSelected(false) , mMoving(false) , mResizing(false) { } MonthItem::~MonthItem() { deleteAll(); } void MonthItem::deleteAll() { qDeleteAll(mMonthGraphicsItemList); mMonthGraphicsItemList.clear(); } QWidget *MonthItem::parentWidget() const { return mMonthScene ? mMonthScene->monthView() : nullptr; } void MonthItem::updateMonthGraphicsItems() { // Remove all items qDeleteAll(mMonthGraphicsItemList); mMonthGraphicsItemList.clear(); const QDate monthStartDate = startDate(); const QDate monthEndDate = endDate(); // For each row of the month view, create an item to build the whole // MonthItem's MonthGraphicsItems. for (QDate d = mMonthScene->mMonthView->actualStartDateTime().date(); d < mMonthScene->mMonthView->actualEndDateTime().date(); d = d.addDays(7)) { QDate end = d.addDays(6); int span; QDate start; if (monthStartDate <= d && monthEndDate >= end) { // MonthItem takes the whole line span = 6; start = d; } else if (monthStartDate >= d && monthEndDate <= end) { // starts and ends on this line start = monthStartDate; span = daySpan(); } else if (d <= monthEndDate && monthEndDate <= end) { // MonthItem ends on this line span = mMonthScene->getLeftSpan(monthEndDate); start = d; } else if (d <= monthStartDate && monthStartDate <= end) { // MonthItem begins on this line span = mMonthScene->getRightSpan(monthStartDate); start = monthStartDate; } else { // MonthItem is not on the line continue; } // A new item needs to be created MonthGraphicsItem *newItem = new MonthGraphicsItem(this); mMonthGraphicsItemList << newItem; newItem->setStartDate(start); newItem->setDaySpan(span); } if (isMoving() || isResizing()) { setZValue(100); } else { setZValue(0); } } void MonthItem::beginResize() { mOverrideDaySpan = daySpan(); mOverrideStartDate = startDate(); mResizing = true; setZValue(100); } void MonthItem::endResize() { setZValue(0); mResizing = false; // startDate() and daySpan() return real values again if (mOverrideStartDate != startDate() || mOverrideDaySpan != daySpan()) { finalizeResize(mOverrideStartDate, mOverrideStartDate.addDays(mOverrideDaySpan)); } } void MonthItem::beginMove() { mOverrideDaySpan = daySpan(); mOverrideStartDate = startDate(); mMoving = true; setZValue(100); } void MonthItem::endMove() { setZValue(0); mMoving = false; // startDate() and daySpan() return real values again if (mOverrideStartDate != startDate()) { finalizeMove(mOverrideStartDate); } } bool MonthItem::resizeBy(int offsetToPreviousDate) { bool ret = false; if (mMonthScene->resizeType() == MonthScene::ResizeLeft) { if (mOverrideDaySpan - offsetToPreviousDate >= 0) { mOverrideStartDate = mOverrideStartDate.addDays(offsetToPreviousDate); mOverrideDaySpan = mOverrideDaySpan - offsetToPreviousDate; ret = true; } } else if (mMonthScene->resizeType() == MonthScene::ResizeRight) { if (mOverrideDaySpan + offsetToPreviousDate >= 0) { mOverrideDaySpan = mOverrideDaySpan + offsetToPreviousDate; ret = true; } } if (ret) { updateMonthGraphicsItems(); } return ret; } void MonthItem::moveBy(int offsetToPreviousDate) { mOverrideStartDate = mOverrideStartDate.addDays(offsetToPreviousDate); updateMonthGraphicsItems(); } void MonthItem::updateGeometry() { foreach (MonthGraphicsItem *item, mMonthGraphicsItemList) { item->updateGeometry(); } } void MonthItem::setZValue(qreal z) { foreach (MonthGraphicsItem *item, mMonthGraphicsItemList) { item->setZValue(z); } } QDate MonthItem::startDate() const { if (isMoving() || isResizing()) { return mOverrideStartDate; } return realStartDate(); } QDate MonthItem::endDate() const { if (isMoving() || isResizing()) { return mOverrideStartDate.addDays(mOverrideDaySpan); } return realEndDate(); } int MonthItem::daySpan() const { if (isMoving() || isResizing()) { return mOverrideDaySpan; } QDateTime start(startDate()); QDateTime end(endDate()); if (start.isValid() && end.isValid()) { return start.daysTo(end); } return 0; } bool MonthItem::greaterThan(const MonthItem *e1, const MonthItem *e2) { const QDate leftStartDate = e1->startDate(); const QDate rightStartDate = e2->startDate(); if (!leftStartDate.isValid() || !rightStartDate.isValid()) { return false; } if (leftStartDate == rightStartDate) { const int leftDaySpan = e1->daySpan(); const int rightDaySpan = e2->daySpan(); if (leftDaySpan == rightDaySpan) { if (e1->allDay() && !e2->allDay()) { return true; } if (!e1->allDay() && e2->allDay()) { return false; } return e1->greaterThanFallback(e2); } else { return leftDaySpan > rightDaySpan; } } return leftStartDate < rightStartDate; } bool MonthItem::greaterThanFallback(const MonthItem *other) const { const HolidayMonthItem *h = qobject_cast(other); // If "other" is a holiday, display it first. return !h; } void MonthItem::updatePosition() { if (!startDate().isValid() || !endDate().isValid()) { return; } int firstFreeSpace = 0; for (QDate d = startDate(); d <= endDate(); d = d.addDays(1)) { MonthCell *cell = mMonthScene->mMonthCellMap.value(d); if (!cell) { continue; // cell can be null if the item begins outside the month } int firstFreeSpaceTmp = cell->firstFreeSpace(); if (firstFreeSpaceTmp > firstFreeSpace) { firstFreeSpace = firstFreeSpaceTmp; } } for (QDate d = startDate(); d <= endDate(); d = d.addDays(1)) { MonthCell *cell = mMonthScene->mMonthCellMap.value(d); if (!cell) { continue; } cell->addMonthItem(this, firstFreeSpace); } mPosition = firstFreeSpace; } QList EventViews::MonthItem::monthGraphicsItems() const { return mMonthGraphicsItemList; } //----------------------------------------------------------------- // INCIDENCEMONTHITEM IncidenceMonthItem::IncidenceMonthItem(MonthScene *monthScene, const Akonadi::ETMCalendar::Ptr &calendar, const Akonadi::Item &aitem, const KCalCore::Incidence::Ptr &incidence, const QDate &recurStartDate) : MonthItem(monthScene) , mCalendar(calendar) , mIncidence(incidence) , mAkonadiItemId(aitem.id()) { mIsEvent = CalendarSupport::hasEvent(aitem); mIsJournal = CalendarSupport::hasJournal(aitem); mIsTodo = CalendarSupport::hasTodo(aitem); KCalCore::Incidence::Ptr inc = mIncidence; if (inc->customProperty("KABC", "BIRTHDAY") == QLatin1String("YES") || inc->customProperty("KABC", "ANNIVERSARY") == QLatin1String("YES")) { const int years = EventViews::yearDiff(inc->dtStart().date(), recurStartDate); if (years > 0) { inc = KCalCore::Incidence::Ptr(inc->clone()); inc->setReadOnly(false); inc->setDescription(i18np("%2 1 year", "%2 %1 years", years, i18n("Age:"))); inc->setReadOnly(true); mIncidence = inc; } } connect(monthScene, &MonthScene::incidenceSelected, this, &IncidenceMonthItem::updateSelection); // first set to 0, because it's used in startDate() mRecurDayOffset = 0; if ((mIncidence->recurs() || mIncidence->recurrenceId().isValid()) && startDate().isValid() && recurStartDate.isValid()) { mRecurDayOffset = startDate().daysTo(recurStartDate); } } IncidenceMonthItem::~IncidenceMonthItem() { } bool IncidenceMonthItem::greaterThanFallback(const MonthItem *other) const { const IncidenceMonthItem *o = qobject_cast(other); if (!o) { return MonthItem::greaterThanFallback(other); } if (allDay() != o->allDay()) { return allDay(); } const KCalCore::Incidence::Ptr otherIncidence = o->mIncidence; if (mIncidence->dtStart().time() != otherIncidence->dtStart().time()) { return mIncidence->dtStart().time() < otherIncidence->dtStart().time(); } // as a last resort, compare uids return mIncidence->uid() < otherIncidence->uid(); } QDate IncidenceMonthItem::realStartDate() const { if (!mIncidence) { return QDate(); } const QDateTime dt = mIncidence->dateTime(Incidence::RoleDisplayStart); const QDate start = dt.toLocalTime().date(); return start.addDays(mRecurDayOffset); } QDate IncidenceMonthItem::realEndDate() const { if (!mIncidence) { return QDate(); } const QDateTime dt = mIncidence->dateTime(KCalCore::Incidence::RoleDisplayEnd); const QDate end = dt.toLocalTime().date(); return end.addDays(mRecurDayOffset); } bool IncidenceMonthItem::allDay() const { return mIncidence->allDay(); } bool IncidenceMonthItem::isMoveable() const { return monthScene()->mMonthView->calendar()->hasRight(akonadiItem(), Akonadi::Collection::CanChangeItem); } bool IncidenceMonthItem::isResizable() const { return mIsEvent && monthScene()->mMonthView->calendar()->hasRight( akonadiItem(), Akonadi::Collection::CanChangeItem); } void IncidenceMonthItem::finalizeMove(const QDate &newStartDate) { Q_ASSERT(isMoveable()); if (startDate().isValid() && newStartDate.isValid()) { updateDates(startDate().daysTo(newStartDate), startDate().daysTo(newStartDate)); } } void IncidenceMonthItem::finalizeResize(const QDate &newStartDate, const QDate &newEndDate) { Q_ASSERT(isResizable()); if (startDate().isValid() && endDate().isValid() && newStartDate.isValid() && newEndDate.isValid()) { updateDates(startDate().daysTo(newStartDate), endDate().daysTo(newEndDate)); } } void IncidenceMonthItem::updateDates(int startOffset, int endOffset) { Akonadi::IncidenceChanger *changer = monthScene()->incidenceChanger(); if (!changer || (startOffset == 0 && endOffset == 0)) { qCDebug(CALENDARVIEW_LOG) << changer << startOffset << endOffset; return; } Akonadi::Item item = akonadiItem(); item.setPayload(mIncidence); if (mIncidence->recurs()) { const int res = monthScene()->mMonthView->showMoveRecurDialog(mIncidence, startDate()); switch (res) { case KCalUtils::RecurrenceActions::AllOccurrences: { // All occurrences KCalCore::Incidence::Ptr oldIncidence(mIncidence->clone()); setNewDates(mIncidence, startOffset, endOffset); changer->modifyIncidence(item, oldIncidence); break; } case KCalUtils::RecurrenceActions::SelectedOccurrence: // Just this occurrence case KCalUtils::RecurrenceActions::FutureOccurrences: { // All future occurrences const bool thisAndFuture = (res == KCalUtils::RecurrenceActions::FutureOccurrences); QDateTime occurrenceDate(mIncidence->dtStart()); occurrenceDate.setDate(startDate()); KCalCore::Incidence::Ptr newIncidence(KCalCore::Calendar::createException( mIncidence, occurrenceDate, thisAndFuture)); if (newIncidence) { changer->startAtomicOperation(i18n("Move occurrence(s)")); setNewDates(newIncidence, startOffset, endOffset); changer->createIncidence(newIncidence, item.parentCollection(), parentWidget()); changer->endAtomicOperation(); } else { KMessageBox::sorry( parentWidget(), i18n("Unable to add the exception item to the calendar. " "No change will be done."), i18n("Error Occurred")); } break; } } } else { // Doesn't recur KCalCore::Incidence::Ptr oldIncidence(mIncidence->clone()); setNewDates(mIncidence, startOffset, endOffset); changer->modifyIncidence(item, oldIncidence); } } void IncidenceMonthItem::updateSelection(const Akonadi::Item &incidence, const QDate &date) { Q_UNUSED(date); setSelected(incidence == akonadiItem()); } QString IncidenceMonthItem::text(bool end) const { QString ret = mIncidence->summary(); if (!allDay() && !mIsJournal && monthScene()->monthView()->preferences()->showTimeInMonthView()) { // Prepend the time str to the text QString timeStr; if (mIsTodo) { KCalCore::Todo::Ptr todo = mIncidence.staticCast(); timeStr = QLocale().toString(todo->dtDue().toLocalTime().time(), QLocale::ShortFormat); } else { if (!end) { QTime time; if (mIncidence->recurs()) { const auto start = mIncidence->dtStart().addDays(mRecurDayOffset).addSecs(-1); time = mIncidence->recurrence()->getNextDateTime(start).toLocalTime().time(); } else { time = mIncidence->dtStart().toLocalTime().time(); } timeStr = QLocale().toString(time, QLocale::ShortFormat); } else { KCalCore::Event::Ptr event = mIncidence.staticCast(); timeStr = QLocale().toString( event->dtEnd().toLocalTime().time(), QLocale::ShortFormat); } } if (!timeStr.isEmpty()) { if (!end) { ret = timeStr + QLatin1Char(' ') + ret; } else { ret = ret + QLatin1Char(' ') + timeStr; } } } return ret; } QString IncidenceMonthItem::toolTipText(const QDate &date) const { return KCalUtils::IncidenceFormatter::toolTipStr( CalendarSupport::displayName(mCalendar.data(), akonadiItem().parentCollection()), mIncidence, date, true); } QVector IncidenceMonthItem::icons() const { QVector ret; if (!mIncidence) { return ret; } bool specialEvent = false; Akonadi::Item item = akonadiItem(); const QSet icons = monthScene()->monthView()->preferences()->monthViewIcons(); QString customIconName; if (icons.contains(EventViews::EventView::CalendarCustomIcon)) { const QString iconName = monthScene()->monthView()->iconForItem(item); if (!iconName.isEmpty() && iconName != QLatin1String("view-calendar") && iconName != QLatin1String("office-calendar")) { customIconName = iconName; ret << QPixmap(cachedSmallIcon(iconName)); } } if (mIsEvent) { if (mIncidence->customProperty("KABC", "ANNIVERSARY") == QLatin1String("YES")) { specialEvent = true; ret << monthScene()->anniversaryPixmap(); } else if (mIncidence->customProperty("KABC", "BIRTHDAY") == QLatin1String("YES")) { specialEvent = true; // Disabling birthday icon because it's the birthday agent's icon // and we allow to display the agent's icon now. //ret << monthScene()->birthdayPixmap(); } // smartins: Disabling the event Pixmap because: // 1. Save precious space so we can read the event's title better. // 2. We don't need a pixmap to tell us an item is an event we // only need one to tell us it's not, as month view was designed for events. // 3. If only to-dos and journals have a pixmap they will be distinguished // from event's much easier. // ret << monthScene()->eventPixmap(); } else if ((mIsTodo || mIsJournal) && icons.contains(mIsTodo ? EventView::TaskIcon : EventView::JournalIcon)) { QDateTime occurrenceDateTime = mIncidence->dateTime(Incidence::RoleRecurrenceStart); occurrenceDateTime.setDate(realStartDate()); const QString incidenceIconName = mIncidence->iconName(occurrenceDateTime); if (customIconName != incidenceIconName) { ret << QPixmap(cachedSmallIcon(incidenceIconName)); } } if (icons.contains(EventView::ReadOnlyIcon) && !monthScene()->mMonthView->calendar()->hasRight(item, Akonadi::Collection::CanChangeItem) && !specialEvent) { ret << monthScene()->readonlyPixmap(); } /* sorry, this looks too cluttered. disable until we can make something prettier; no idea at this time -- allen */ if (icons.contains(EventView::ReminderIcon) && mIncidence->hasEnabledAlarms() && !specialEvent) { ret << monthScene()->alarmPixmap(); } if (icons.contains(EventView::RecurringIcon) && mIncidence->recurs() && !specialEvent) { ret << monthScene()->recurPixmap(); } //TODO: check what to do with Reply return ret; } QColor IncidenceMonthItem::catColor() const { Q_ASSERT(mIncidence); - const QStringList categories = mIncidence->categories(); - QString cat; - if (!categories.isEmpty()) { - cat = categories.at(0); - } + const auto &prefs = monthScene()->monthView()->preferences(); - return cat.isEmpty() ? CalendarSupport::KCalPrefs::instance()->unsetCategoryColor() - : CalendarSupport::KCalPrefs::instance()->categoryColor(cat); + const auto &categories = mIncidence->categories(); + if (categories.isEmpty() || !CalendarSupport::KCalPrefs::instance()->hasCategoryColor(categories.first())) { + const auto &colorPreference = prefs->monthViewColors(); + if (colorPreference == PrefsBase::CategoryOnly) { + return CalendarSupport::KCalPrefs::instance()->unsetCategoryColor(); + } + return EventViews::resourceColor(akonadiItem(), prefs); + } + return CalendarSupport::KCalPrefs::instance()->categoryColor(categories.first()); } QColor IncidenceMonthItem::bgColor() const { - QColor bgColor = QColor(); // Default invalid color; + const auto &prefs = monthScene()->monthView()->preferences(); - PrefsPtr prefs = monthScene()->monthView()->preferences(); - if (mIsTodo && !prefs->todosUseCategoryColors()) { + if (!prefs->todosUseCategoryColors() && mIsTodo) { Todo::Ptr todo = CalendarSupport::todo(akonadiItem()); Q_ASSERT(todo); if (todo) { - const QDate dtRecurrence // this is dtDue if there's no dtRecurrence - = todo->dtRecurrence().toLocalTime().date(); - const QDate today = QDate::currentDate(); - if (todo->isOverdue() && today > startDate() && startDate() >= dtRecurrence) { - bgColor = prefs->todoOverdueColor(); - } else if (today == startDate() && !todo->isCompleted() - && startDate() >= dtRecurrence) { - bgColor = prefs->todoDueTodayColor(); + // this is dtDue if there's no dtRecurrence + const auto dtRecurrence = todo->dtRecurrence().toLocalTime().date(); + const auto today = QDate::currentDate(); + if (startDate() >= dtRecurrence) { + if (todo->isOverdue() && today > startDate()) { + return prefs->todoOverdueColor(); + } + if (today == startDate() && !todo->isCompleted()) { + return prefs->todoDueTodayColor(); + } } } } - if (!bgColor.isValid()) { - if (prefs->monthViewColors() == PrefsBase::MonthItemResourceOnly - || prefs->monthViewColors() == PrefsBase::MonthItemResourceInsideCategoryOutside) { - bgColor = EventViews::resourceColor(akonadiItem(), prefs); - } else { - bgColor = catColor(); - } - } - - if (!bgColor.isValid()) { - bgColor = Qt::white; - } - - return bgColor; + const auto &colorPreference = prefs->monthViewColors(); + const auto bgDisplaysResource = colorPreference == PrefsBase::MonthItemResourceInsideCategoryOutside + || colorPreference == PrefsBase::MonthItemResourceOnly; + return bgDisplaysResource ? EventViews::resourceColor(akonadiItem(), prefs) : catColor(); } QColor IncidenceMonthItem::frameColor() const { - QColor frameColor; - - PrefsPtr prefs = monthScene()->monthView()->preferences(); - if (prefs->monthViewColors() == PrefsBase::MonthItemResourceOnly - || prefs->monthViewColors() == PrefsBase::MonthItemCategoryInsideResourceOutside - || (mIncidence->categories().isEmpty() && prefs->monthViewColors() - == PrefsBase::MonthItemResourceInsideCategoryOutside)) { - Q_ASSERT(mIncidence); - frameColor = EventViews::resourceColor(akonadiItem(), prefs); - } else { - frameColor = catColor(); - } - + const auto &prefs = monthScene()->monthView()->preferences(); + const auto frameDisplaysResource = + (prefs->monthViewColors() == PrefsBase::MonthItemResourceOnly + || prefs->monthViewColors() == PrefsBase::MonthItemCategoryInsideResourceOutside); + const auto frameColor = frameDisplaysResource ? EventViews::resourceColor(akonadiItem(), prefs) : catColor(); return EventView::itemFrameColor(frameColor, selected()); } Akonadi::Item IncidenceMonthItem::akonadiItem() const { if (mIncidence) { return monthScene()->mMonthView->calendar()->item(mIncidence); } else { return Akonadi::Item(); } } KCalCore::Incidence::Ptr IncidenceMonthItem::incidence() const { return mIncidence; } Akonadi::Item::Id IncidenceMonthItem::akonadiItemId() const { return mAkonadiItemId; } void IncidenceMonthItem::setNewDates(const KCalCore::Incidence::Ptr &incidence, int startOffset, int endOffset) { if (mIsTodo) { // For to-dos endOffset is ignored because it will always be == to startOffset because we only // support moving to-dos, not resizing them. There are no multi-day to-dos. // Lets just call it offset to reduce confusion. const int offset = startOffset; KCalCore::Todo::Ptr todo = incidence.staticCast(); QDateTime due = todo->dtDue(); QDateTime start = todo->dtStart(); if (due.isValid()) { // Due has priority over start. // We will only move the due date, unlike events where we move both. due = due.addDays(offset); todo->setDtDue(due); if (start.isValid() && start > due) { // Start can't be bigger than due. todo->setDtStart(due); } } else if (start.isValid()) { // So we're displaying a to-do that doesn't have due date, only start... start = start.addDays(offset); todo->setDtStart(start); } else { // This never happens qCWarning(CALENDARVIEW_LOG) << "Move what? uid:" << todo->uid() << "; summary=" << todo->summary(); } } else { incidence->setDtStart(incidence->dtStart().addDays(startOffset)); if (mIsEvent) { KCalCore::Event::Ptr event = incidence.staticCast(); event->setDtEnd(event->dtEnd().addDays(endOffset)); } } } //----------------------------------------------------------------- // HOLIDAYMONTHITEM HolidayMonthItem::HolidayMonthItem(MonthScene *monthScene, const QDate &date, const QString &name) : MonthItem(monthScene) , mDate(date) , mName(name) { } HolidayMonthItem::~HolidayMonthItem() { } bool HolidayMonthItem::greaterThanFallback(const MonthItem *other) const { const HolidayMonthItem *o = qobject_cast(other); if (o) { return MonthItem::greaterThanFallback(other); } // always put holidays on top return false; } void HolidayMonthItem::finalizeMove(const QDate &newStartDate) { Q_UNUSED(newStartDate); Q_ASSERT(false); } void HolidayMonthItem::finalizeResize(const QDate &newStartDate, const QDate &newEndDate) { Q_UNUSED(newStartDate); Q_UNUSED(newEndDate); Q_ASSERT(false); } QVector HolidayMonthItem::icons() const { QVector ret; ret << monthScene()->holidayPixmap(); return ret; } QColor HolidayMonthItem::bgColor() const { // FIXME: Currently, only this value is settable in the options. // There is a monthHolidaysBackgroundColor() option too. Maybe it would be // wise to merge those two. return monthScene()->monthView()->preferences()->agendaHolidaysBackgroundColor(); } QColor HolidayMonthItem::frameColor() const { return Qt::black; } diff --git a/src/month/monthitem.h b/src/month/monthitem.h index bb3645f..03d91ca 100644 --- a/src/month/monthitem.h +++ b/src/month/monthitem.h @@ -1,404 +1,404 @@ /* Copyright (c) 2008 Bruno Virlet This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef EVENTVIEWS_MONTHITEM_H #define EVENTVIEWS_MONTHITEM_H #include "eventviews_export.h" #include #include #include #include #include namespace EventViews { class MonthGraphicsItem; class MonthScene; /** * A month item manages different MonthGraphicsItems. */ class EVENTVIEWS_EXPORT MonthItem : public QObject { Q_OBJECT public: explicit MonthItem(MonthScene *monthWidget); ~MonthItem() override; QWidget *parentWidget() const; /** Compares two events The month view displays a list of items. When loading (which occurs each time there is a change), the items are sorted : - smallest date - bigger span - floating - finally, time in the day */ static bool greaterThan(const MonthItem *e1, const MonthItem *e2); /** Compare this event with a second one, if the former function is not able to sort them. */ virtual bool greaterThanFallback(const MonthItem *other) const; /** The start date of the incidence, generally realStartDate. But it reflect changes, even during move. */ Q_REQUIRED_RESULT QDate startDate() const; /** The end date of the incidence, generally realEndDate. But it reflect changes, even during move. */ Q_REQUIRED_RESULT QDate endDate() const; /** The number of days this item spans. */ Q_REQUIRED_RESULT int daySpan() const; /** This is the real start date, usually the start date of the incidence. */ virtual QDate realStartDate() const = 0; /** This is the real end date, usually the end date of the incidence. */ virtual QDate realEndDate() const = 0; /** True if this item last all the day. */ virtual bool allDay() const = 0; /** Updates geometry of all MonthGraphicsItems. */ void updateGeometry(); /** Find the lowest possible position for this item. The position of an item in a cell is it's vertical position. This is used to avoid overlapping of items. An item keeps the same position in every cell it crosses. The position is measured from top to bottom. */ void updatePosition(); /** Returns true if this item is selected. */ Q_REQUIRED_RESULT bool selected() const { return mSelected; } /** Returns the position of the item ( > 0 ). */ Q_REQUIRED_RESULT int position() const { return mPosition; } /** Returns the associated month scene to this item. */ MonthScene *monthScene() const { return mMonthScene; } /** Begin a move. */ void beginMove(); /** End a move. */ void endMove(); /** Begin a resize. */ void beginResize(); /** End a resize. */ void endResize(); /** Called during move to move the item a bit, relative to the previous move step. */ void moveBy(int offsetFromPreviousDate); /** Called during resize to resize the item a bit, relative to the previous resize step. */ bool resizeBy(int offsetFromPreviousDate); /** Returns true if the item is being moved. */ Q_REQUIRED_RESULT bool isMoving() const { return mMoving; } /** Returns true if the item is being resized. */ Q_REQUIRED_RESULT bool isResizing() const { return mResizing; } /** Returns true if the item can be moved. */ virtual bool isMoveable() const = 0; /** Returns true if the item can be resized. */ virtual bool isResizable() const = 0; /** Deletes all MonthGraphicsItem this item handles. Clear the list. */ void deleteAll(); /** Update the monthgraphicsitems This basically deletes and rebuild all the MonthGraphicsItems but tries to do it wisely: - If there is a moving item, it won't be deleted because then the new item won't receive anymore the MouseMove events. - If there is an item on a line where the new state needs an item, it is used and not deleted. This will avoid flickers. */ void updateMonthGraphicsItems(); /** Sets the selection state of this item. */ void setSelected(bool selected) { mSelected = selected; } // METHODS NEEDED TO PAINT ITEMS /** Returns the text to draw in an item. @param end True if the text at the end of an item should be returned. */ virtual QString text(bool end) const = 0; /** Returns the text for the tooltip of the item */ virtual QString toolTipText(const QDate &date) const = 0; /** Returns the background color of the item. */ - virtual QColor bgColor() const = 0; + virtual Q_REQUIRED_RESULT QColor bgColor() const = 0; /** Returns the frame color of the item. */ - virtual QColor frameColor() const = 0; + virtual Q_REQUIRED_RESULT QColor frameColor() const = 0; /** Returns a list of pixmaps to draw next to the items. */ virtual QVector icons() const = 0; QList monthGraphicsItems() const; protected: /** Called after a move operation. */ virtual void finalizeMove(const QDate &newStartDate) = 0; /** Called after a resize operation. */ virtual void finalizeResize(const QDate &newStartDate, const QDate &newEndDate) = 0; private: /** Sets the value of all MonthGraphicsItem to @param z. */ void setZValue(qreal z); QList mMonthGraphicsItemList; MonthScene *mMonthScene = nullptr; bool mSelected; bool mMoving; // during move bool mResizing; // during resize QDate mOverrideStartDate; int mOverrideDaySpan; int mPosition; }; class EVENTVIEWS_EXPORT IncidenceMonthItem : public MonthItem { Q_OBJECT public: IncidenceMonthItem(MonthScene *monthScene, const Akonadi::ETMCalendar::Ptr &calendar, const Akonadi::Item &item, const KCalCore::Incidence::Ptr &incidence, const QDate &recurStartDate = QDate()); ~IncidenceMonthItem() override; KCalCore::Incidence::Ptr incidence() const; Akonadi::Item akonadiItem() const; Akonadi::Item::Id akonadiItemId() const; bool greaterThanFallback(const MonthItem *other) const override; QDate realStartDate() const override; QDate realEndDate() const override; bool allDay() const override; bool isMoveable() const override; bool isResizable() const override; QString text(bool end) const override; QString toolTipText(const QDate &date) const override; QColor bgColor() const override; QColor frameColor() const override; QVector icons() const override; protected: void finalizeMove(const QDate &newStartDate) override; virtual void finalizeResize(const QDate &newStartDate, const QDate &newEndDate) override; protected Q_SLOTS: /** Update the selected state of this item. If will be selected if incidence is the incidence managed by this item. Else it will be deselected. */ void updateSelection(const Akonadi::Item &incidence, const QDate &date); private: void updateDates(int startOffset, int endOffset); void setNewDates(const KCalCore::Incidence::Ptr &incidence, int startOffset, int endOffset); /** Returns the category color for this incidence. */ QColor catColor() const; Akonadi::ETMCalendar::Ptr mCalendar; KCalCore::Incidence::Ptr mIncidence; Akonadi::Item::Id mAkonadiItemId; int mRecurDayOffset; bool mIsEvent, mIsTodo, mIsJournal; }; class EVENTVIEWS_EXPORT HolidayMonthItem : public MonthItem { Q_OBJECT public: HolidayMonthItem(MonthScene *monthScene, const QDate &date, const QString &name); ~HolidayMonthItem() override; bool greaterThanFallback(const MonthItem *other) const override; QDate realStartDate() const override { return mDate; } QDate realEndDate() const override { return mDate; } bool allDay() const override { return true; } bool isMoveable() const override { return false; } bool isResizable() const override { return false; } QString text(bool end) const override { Q_UNUSED(end); return mName; } QString toolTipText(const QDate &) const override { return mName; } QColor bgColor() const override; QColor frameColor() const override; QVector icons() const override; protected: void finalizeMove(const QDate &newStartDate) override; virtual void finalizeResize(const QDate &newStartDate, const QDate &newEndDate) override; private: QDate mDate; QString mName; }; } #endif