diff --git a/src/agenda/agendaitem.cpp b/src/agenda/agendaitem.cpp index 261e5ab..f3a73c0 100644 --- a/src/agenda/agendaitem.cpp +++ b/src/agenda/agendaitem.cpp @@ -1,1350 +1,1350 @@ /* 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(QStringLiteral("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(KDateTime(mOccurrenceDateTime.toLocalTime())); + 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; if (CalendarSupport::hasTodo(mIncidence) && !mEventView->preferences()->todosUseCategoryColors()) { Todo::Ptr todo = CalendarSupport::todo(mIncidence); Q_ASSERT(todo); const QDate dueDate = todo->dtDue().toLocalZone().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.light(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).toLocalZone().time(), QLocale::ShortFormat); if (CalendarSupport::hasEvent(mIncidence)) { longH = i18n("%1 - %2", shortH, QLocale().toString(mIncidence->dateTime(KCalCore::Incidence::RoleEnd).toLocalZone().time(), QLocale::ShortFormat)); } else { longH = shortH; } } else if (!mMultiItemInfo->mFirstMultiItem) { shortH = QLocale().toString(mIncidence->dtStart().toLocalZone().time(), QLocale::ShortFormat); longH = shortH; } else { shortH = QLocale().toString(mIncidence->dateTime(KCalCore::Incidence::RoleEnd).toLocalZone().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().toLocalZone().date()), QLocale().toString(mIncidence->dateTime(KCalCore::Incidence::RoleEnd).toLocalZone().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; } QRect r = rect; r.adjust(0, 0, 1, 1); p->save(); QPainterPath path; bool shrinkWidth = r.width() < 16; bool shrinkHeight = r.height() < 16; qreal rnd = 2.1; int sw = shrinkWidth ? 10 : 11; int sh = shrinkHeight ? 10 : 11; QRectF tr(r.x() + r.width() - sw - rnd, r.y() + rnd, sw, sh); QRectF tl(r.x() + rnd, r.y() + rnd, sw, sh); QRectF bl(r.x() + rnd, r.y() + r.height() - sh - 1 - rnd, sw, sh); QRectF br(r.x() + r.width() - sw - rnd, r.y() + r.height() - sh - 1 - rnd, sw, sh); if (roundTop) { path.moveTo(tr.topRight()); path.arcTo(tr, 0.0, 90.0); path.lineTo(tl.topRight()); path.arcTo(tl, 90.0, 90.0); } else { path.moveTo(tr.topRight()); path.lineTo(tl.topLeft()); } if (roundBottom) { path.lineTo(bl.topLeft()); path.arcTo(bl, 180.0, 90.0); path.lineTo(br.bottomLeft()); path.arcTo(br, 270.0, 90.0); } else { path.lineTo(bl.bottomLeft()); path.lineTo(br.bottomRight()); } path.closeSubpath(); // header if (!frame) { QLinearGradient gradient(QPointF(r.x(), r.y()), QPointF(r.x(), r.height())); if (selected) { QColor top = bgColor.dark(250); top.setAlpha(40); gradient.setColorAt(0, top); gradient.setColorAt(1, QColor(255, 255, 255, 30)); } else { gradient.setColorAt(0, QColor(255, 255, 255, 90)); gradient.setColorAt(1, QColor(0, 0, 0, 10)); } p->setBrush(bgColor); p->setPen(Qt::NoPen); p->drawPath(path); p->setBrush(gradient); p->setPen(Qt::NoPen); p->drawPath(path); QPixmap separator; QString key(QStringLiteral("ko_hsep")); if (!QPixmapCache::find(key, separator)) { separator = QPixmap(QStringLiteral(":/headerSeparator.png")); QPixmapCache::insert(key, separator); } p->fillRect(QRect(r.x() + 3, r.y() + r.height() - 2, r.x() + r.width() - 4, 2), QBrush(separator)); p->restore(); return; } QLinearGradient gradient(QPointF(r.x(), r.y()), QPointF(r.x(), r.height())); if (r.height() > 50) { if (mIncidence->allDay() && mIncidence->dtStart() == mIncidence->dateTime(KCalCore::Incidence::RoleEnd) && CalendarSupport::hasEvent(mIncidence)) { gradient.setColorAt(0, bgColor.light(130)); qreal t = 1.0 - (r.height() - 18.0) / r.height(); gradient.setColorAt(t, bgColor.light(115)); qreal b = (r.height() - 20.0) / r.height(); gradient.setColorAt(b, bgColor); } else { gradient.setColorAt(0, bgColor.light(115)); qreal b = (r.height() - 20.0) / r.height(); gradient.setColorAt(b, bgColor); } gradient.setColorAt(1, bgColor.dark(110)); } else { if (mIncidence->allDay() && mIncidence->dtStart() == mIncidence->dateTime(KCalCore::Incidence::RoleEnd) && !CalendarSupport::hasTodo(mIncidence)) { gradient.setColorAt(0, bgColor.light(130)); gradient.setColorAt(0.35, bgColor.light(115)); gradient.setColorAt(0.65, bgColor); } else { gradient.setColorAt(0, bgColor.light(115)); gradient.setColorAt(0.65, bgColor); } gradient.setColorAt(1, bgColor.dark(110)); } p->setBrush(gradient); p->setPen(Qt::NoPen); p->drawPath(path); p->setRenderHint(QPainter::Antialiasing, false); if (r.width() - 16 > 0) { QPixmap topLines; QString key(QStringLiteral("ko_t")); if (!QPixmapCache::find(key, topLines)) { topLines = QPixmap(QStringLiteral(":/topLines.png")); QPixmapCache::insert(key, topLines); } p->setBrushOrigin(r.x() + 8, r.y()); p->fillRect(QRect(r.x() + 8, r.y(), r.width() - 16, 5), QBrush(topLines)); QPixmap bottomLines; key = QStringLiteral("ko_b"); if (!QPixmapCache::find(key, bottomLines)) { bottomLines = QPixmap(QStringLiteral(":/bottomLines.png")); QPixmapCache::insert(key, bottomLines); } p->setBrushOrigin(r.x() + 8, r.y() + r.height() - 6); p->fillRect(QRect(r.x() + 8, r.y() + r.height() - 6, r.width() - 16, 6), QBrush(bottomLines)); } if (r.height() - 16 > 0) { QPixmap leftLines; QString key(QStringLiteral("ko_l")); if (!QPixmapCache::find(key, leftLines)) { leftLines = QPixmap(QStringLiteral(":/leftLines.png")); QPixmapCache::insert(key, leftLines); } p->setBrushOrigin(r.x(), r.y() + 8); p->fillRect(QRect(r.x(), r.y() + 8, 5, r.height() - 16), QBrush(leftLines)); QPixmap rightLines; key = QStringLiteral("ko_r"); if (!QPixmapCache::find(key, rightLines)) { rightLines = QPixmap(QStringLiteral(":/rightLines.png")); QPixmapCache::insert(key, rightLines); } p->setBrushOrigin(r.x() + r.width() - 5, r.y() + 8); p->fillRect(QRect(r.x() + r.width() - 5, r.y() + 8, 5, r.height() - 16), QBrush(rightLines)); } // don't overlap the edges int lw = shrinkWidth ? r.width() / 2 : 8; int rw = shrinkWidth ? r.width() - lw : 8; int th = shrinkHeight ? r.height() / 2 : 8; int bh = shrinkHeight ? r.height() - th : 8; // keep the bottom round for items which ending at 00:15 if (shrinkHeight && !roundTop && roundBottom && r.height() > 3) { bh += th - 3; th = 3; } QPixmap topLeft; QString key = roundTop ? QStringLiteral("ko_tl") : QStringLiteral("ko_rtl"); if (!QPixmapCache::find(key, topLeft)) { topLeft = roundTop ? QPixmap(QStringLiteral(":/roundTopLeft.png")) : QPixmap(QStringLiteral(":/rectangularTopLeft.png")); QPixmapCache::insert(key, topLeft); } p->drawPixmap(r.x(), r.y(), topLeft, 0, 0, lw, th); QPixmap topRight; key = roundTop ? QStringLiteral("ko_tr") : QStringLiteral("ko_rtr"); if (!QPixmapCache::find(key, topRight)) { topRight = roundTop ? QPixmap(QStringLiteral(":/roundTopRight.png")) : QPixmap(QStringLiteral(":/rectangularTopRight.png")); QPixmapCache::insert(key, topRight); } p->drawPixmap(r.x() + r.width() - rw, r.y(), topRight, 8 - rw, 0, rw, th); QPixmap bottomLeft; key = roundBottom ? QStringLiteral("ko_bl") : QStringLiteral("ko_rbl"); if (!QPixmapCache::find(key, bottomLeft)) { bottomLeft = roundBottom ? QPixmap(QStringLiteral(":/roundBottomLeft.png")) : QPixmap(QStringLiteral(":/rectangularBottomLeft.png")); QPixmapCache::insert(key, bottomLeft); } p->drawPixmap(r.x(), r.y() + r.height() - bh, bottomLeft, 0, 8 - bh, lw, bh); QPixmap bottomRight; key = roundBottom ? QStringLiteral("ko_br") : QStringLiteral("ko_rbr"); if (!QPixmapCache::find(key, bottomRight)) { bottomRight = roundBottom ? QPixmap(QStringLiteral(":/roundBottomRight.png")) : QPixmap(QStringLiteral(":/rectangularBottomRight.png")); QPixmapCache::insert(key, bottomRight); } p->drawPixmap(r.x() + r.width() - rw, r.y() + r.height() - bh, bottomRight, 8 - rw, 8 - bh, rw, 8); p->restore(); } 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); }