File indexing completed on 2024-11-17 04:42:35
0001 /* 0002 SPDX-FileCopyrightText: 2000, 2001, 2003 Cornelius Schumacher <schumacher@kde.org> 0003 SPDX-FileCopyrightText: 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0 0006 */ 0007 #include "agendaitem.h" 0008 #include "eventview.h" 0009 #include "helper.h" 0010 0011 #include "prefs.h" 0012 #include "prefs_base.h" // for enums 0013 0014 #include <CalendarSupport/KCalPrefs> 0015 #include <CalendarSupport/Utils> 0016 0017 #include <Akonadi/TagCache> 0018 0019 #include <KContacts/VCardDrag> 0020 0021 #include <KCalUtils/ICalDrag> 0022 #include <KCalUtils/IncidenceFormatter> 0023 #include <KCalUtils/VCalDrag> 0024 0025 #include <KEmailAddress> 0026 0027 #include <KLocalizedString> 0028 #include <KMessageBox> 0029 #include <KWordWrap> 0030 0031 #include <QDragEnterEvent> 0032 #include <QLocale> 0033 #include <QMimeData> 0034 #include <QPainter> 0035 #include <QPainterPath> 0036 #include <QPixmapCache> 0037 #include <QToolTip> 0038 0039 using namespace KCalendarCore; 0040 using namespace EventViews; 0041 0042 //----------------------------------------------------------------------------- 0043 0044 QPixmap *AgendaItem::alarmPxmp = nullptr; 0045 QPixmap *AgendaItem::recurPxmp = nullptr; 0046 QPixmap *AgendaItem::readonlyPxmp = nullptr; 0047 QPixmap *AgendaItem::replyPxmp = nullptr; 0048 QPixmap *AgendaItem::groupPxmp = nullptr; 0049 QPixmap *AgendaItem::groupPxmpTent = nullptr; 0050 QPixmap *AgendaItem::organizerPxmp = nullptr; 0051 QPixmap *AgendaItem::eventPxmp = nullptr; 0052 0053 //----------------------------------------------------------------------------- 0054 0055 AgendaItem::AgendaItem(EventView *eventView, 0056 const MultiViewCalendar::Ptr &calendar, 0057 const KCalendarCore::Incidence::Ptr &item, 0058 int itemPos, 0059 int itemCount, 0060 const QDateTime &qd, 0061 bool isSelected, 0062 QWidget *parent) 0063 : QWidget(parent) 0064 , mEventView(eventView) 0065 , mCalendar(calendar) 0066 , mIncidence(item) 0067 , mOccurrenceDateTime(qd) 0068 , mSelected(isSelected) 0069 , mSpecialEvent(false) 0070 { 0071 if (!mIncidence) { 0072 mValid = false; 0073 return; 0074 } 0075 0076 mIncidence = Incidence::Ptr(mIncidence->clone()); 0077 if (mIncidence->customProperty("KABC", "BIRTHDAY") == QLatin1StringView("YES") 0078 || mIncidence->customProperty("KABC", "ANNIVERSARY") == QLatin1StringView("YES")) { 0079 const int years = EventViews::yearDiff(mIncidence->dtStart().date(), qd.toLocalTime().date()); 0080 if (years > 0) { 0081 mIncidence->setReadOnly(false); 0082 mIncidence->setSummary(i18np("%2 (1 year)", "%2 (%1 years)", years, mIncidence->summary())); 0083 mIncidence->setReadOnly(true); 0084 mCloned = true; 0085 } 0086 } 0087 0088 mLabelText = mIncidence->summary(); 0089 mIconAlarm = false; 0090 mIconRecur = false; 0091 mIconReadonly = false; 0092 mIconReply = false; 0093 mIconGroup = false; 0094 mIconGroupTent = false; 0095 mIconOrganizer = false; 0096 mMultiItemInfo = nullptr; 0097 mStartMoveInfo = nullptr; 0098 0099 mItemPos = itemPos; 0100 mItemCount = itemCount; 0101 0102 QPalette pal = palette(); 0103 pal.setColor(QPalette::Window, Qt::transparent); 0104 setPalette(pal); 0105 0106 setCellXY(0, 0, 1); 0107 setCellXRight(0); 0108 setMouseTracking(true); 0109 mResourceColor = QColor(); 0110 updateIcons(); 0111 0112 setAcceptDrops(true); 0113 } 0114 0115 AgendaItem::~AgendaItem() = default; 0116 0117 void AgendaItem::updateIcons() 0118 { 0119 if (!mValid) { 0120 return; 0121 } 0122 mIconReadonly = mIncidence->isReadOnly(); 0123 mIconRecur = mIncidence->recurs() || mIncidence->hasRecurrenceId(); 0124 mIconAlarm = mIncidence->hasEnabledAlarms(); 0125 if (mIncidence->attendeeCount() > 1) { 0126 if (mEventView->kcalPreferences()->thatIsMe(mIncidence->organizer().email())) { 0127 mIconReply = false; 0128 mIconGroup = false; 0129 mIconGroupTent = false; 0130 mIconOrganizer = true; 0131 } else { 0132 KCalendarCore::Attendee me = mIncidence->attendeeByMails(mEventView->kcalPreferences()->allEmails()); 0133 0134 if (!me.isNull()) { 0135 if (me.status() == KCalendarCore::Attendee::NeedsAction && me.RSVP()) { 0136 mIconReply = true; 0137 mIconGroup = false; 0138 mIconGroupTent = false; 0139 mIconOrganizer = false; 0140 } else if (me.status() == KCalendarCore::Attendee::Tentative) { 0141 mIconReply = false; 0142 mIconGroup = false; 0143 mIconGroupTent = true; 0144 mIconOrganizer = false; 0145 } else { 0146 mIconReply = false; 0147 mIconGroup = true; 0148 mIconGroupTent = false; 0149 mIconOrganizer = false; 0150 } 0151 } else { 0152 mIconReply = false; 0153 mIconGroup = true; 0154 mIconGroupTent = false; 0155 mIconOrganizer = false; 0156 } 0157 } 0158 } 0159 update(); 0160 } 0161 0162 void AgendaItem::select(bool selected) 0163 { 0164 if (mSelected != selected) { 0165 mSelected = selected; 0166 update(); 0167 } 0168 } 0169 0170 bool AgendaItem::dissociateFromMultiItem() 0171 { 0172 if (!isMultiItem()) { 0173 return false; 0174 } 0175 0176 AgendaItem::QPtr firstItem = firstMultiItem(); 0177 if (firstItem == this) { 0178 firstItem = nextMultiItem(); 0179 } 0180 0181 AgendaItem::QPtr lastItem = lastMultiItem(); 0182 if (lastItem == this) { 0183 lastItem = prevMultiItem(); 0184 } 0185 0186 AgendaItem::QPtr prevItem = prevMultiItem(); 0187 AgendaItem::QPtr nextItem = nextMultiItem(); 0188 0189 if (prevItem) { 0190 prevItem->setMultiItem(firstItem, prevItem->prevMultiItem(), nextItem, lastItem); 0191 } 0192 if (nextItem) { 0193 nextItem->setMultiItem(firstItem, prevItem, nextItem->prevMultiItem(), lastItem); 0194 } 0195 delete mMultiItemInfo; 0196 mMultiItemInfo = nullptr; 0197 return true; 0198 } 0199 0200 void AgendaItem::setIncidence(const KCalendarCore::Incidence::Ptr &incidence) 0201 { 0202 mValid = false; 0203 if (incidence) { 0204 mValid = true; 0205 mIncidence = incidence; 0206 mLabelText = mIncidence->summary(); 0207 updateIcons(); 0208 } 0209 } 0210 0211 /* 0212 Return height of item in units of agenda cells 0213 */ 0214 int AgendaItem::cellHeight() const 0215 { 0216 return mCellYBottom - mCellYTop + 1; 0217 } 0218 0219 /* 0220 Return height of item in units of agenda cells 0221 */ 0222 int AgendaItem::cellWidth() const 0223 { 0224 return mCellXRight - mCellXLeft + 1; 0225 } 0226 0227 void AgendaItem::setOccurrenceDateTime(const QDateTime &qd) 0228 { 0229 mOccurrenceDateTime = qd; 0230 } 0231 0232 QDate AgendaItem::occurrenceDate() const 0233 { 0234 return mOccurrenceDateTime.toLocalTime().date(); 0235 } 0236 0237 void AgendaItem::setCellXY(int X, int YTop, int YBottom) 0238 { 0239 mCellXLeft = X; 0240 mCellYTop = YTop; 0241 mCellYBottom = YBottom; 0242 } 0243 0244 void AgendaItem::setCellXRight(int XRight) 0245 { 0246 mCellXRight = XRight; 0247 } 0248 0249 void AgendaItem::setCellX(int XLeft, int XRight) 0250 { 0251 mCellXLeft = XLeft; 0252 mCellXRight = XRight; 0253 } 0254 0255 void AgendaItem::setCellY(int YTop, int YBottom) 0256 { 0257 mCellYTop = YTop; 0258 mCellYBottom = YBottom; 0259 } 0260 0261 void AgendaItem::setMultiItem(const AgendaItem::QPtr &first, const AgendaItem::QPtr &prev, const AgendaItem::QPtr &next, const AgendaItem::QPtr &last) 0262 { 0263 if (!mMultiItemInfo) { 0264 mMultiItemInfo = new MultiItemInfo; 0265 } 0266 mMultiItemInfo->mFirstMultiItem = first; 0267 mMultiItemInfo->mPrevMultiItem = prev; 0268 mMultiItemInfo->mNextMultiItem = next; 0269 mMultiItemInfo->mLastMultiItem = last; 0270 } 0271 0272 bool AgendaItem::isMultiItem() const 0273 { 0274 return mMultiItemInfo; 0275 } 0276 0277 AgendaItem::QPtr AgendaItem::prependMoveItem(const AgendaItem::QPtr &e) 0278 { 0279 if (!e) { 0280 return nullptr; 0281 } 0282 0283 AgendaItem::QPtr first = nullptr; 0284 AgendaItem::QPtr last = nullptr; 0285 if (isMultiItem()) { 0286 first = mMultiItemInfo->mFirstMultiItem; 0287 last = mMultiItemInfo->mLastMultiItem; 0288 } 0289 if (!first) { 0290 first = this; 0291 } 0292 if (!last) { 0293 last = this; 0294 } 0295 0296 e->setMultiItem(nullptr, nullptr, first, last); 0297 first->setMultiItem(e, e, first->nextMultiItem(), first->lastMultiItem()); 0298 0299 AgendaItem::QPtr tmp = first->nextMultiItem(); 0300 while (tmp) { 0301 tmp->setMultiItem(e, tmp->prevMultiItem(), tmp->nextMultiItem(), tmp->lastMultiItem()); 0302 tmp = tmp->nextMultiItem(); 0303 } 0304 0305 if (mStartMoveInfo && !e->moveInfo()) { 0306 e->mStartMoveInfo = new MultiItemInfo(*mStartMoveInfo); 0307 // e->moveInfo()->mFirstMultiItem = moveInfo()->mFirstMultiItem; 0308 // e->moveInfo()->mLastMultiItem = moveInfo()->mLastMultiItem; 0309 e->moveInfo()->mPrevMultiItem = nullptr; 0310 e->moveInfo()->mNextMultiItem = first; 0311 } 0312 0313 if (first && first->moveInfo()) { 0314 first->moveInfo()->mPrevMultiItem = e; 0315 } 0316 return e; 0317 } 0318 0319 AgendaItem::QPtr AgendaItem::appendMoveItem(const AgendaItem::QPtr &e) 0320 { 0321 if (!e) { 0322 return nullptr; 0323 } 0324 0325 AgendaItem::QPtr first = nullptr; 0326 AgendaItem::QPtr last = nullptr; 0327 if (isMultiItem()) { 0328 first = mMultiItemInfo->mFirstMultiItem; 0329 last = mMultiItemInfo->mLastMultiItem; 0330 } 0331 if (!first) { 0332 first = this; 0333 } 0334 if (!last) { 0335 last = this; 0336 } 0337 0338 e->setMultiItem(first, last, nullptr, nullptr); 0339 AgendaItem::QPtr tmp = first; 0340 0341 while (tmp) { 0342 tmp->setMultiItem(tmp->firstMultiItem(), tmp->prevMultiItem(), tmp->nextMultiItem(), e); 0343 tmp = tmp->nextMultiItem(); 0344 } 0345 last->setMultiItem(last->firstMultiItem(), last->prevMultiItem(), e, e); 0346 0347 if (mStartMoveInfo && !e->moveInfo()) { 0348 e->mStartMoveInfo = new MultiItemInfo(*mStartMoveInfo); 0349 // e->moveInfo()->mFirstMultiItem = moveInfo()->mFirstMultiItem; 0350 // e->moveInfo()->mLastMultiItem = moveInfo()->mLastMultiItem; 0351 e->moveInfo()->mPrevMultiItem = last; 0352 e->moveInfo()->mNextMultiItem = nullptr; 0353 } 0354 if (last && last->moveInfo()) { 0355 last->moveInfo()->mNextMultiItem = e; 0356 } 0357 return e; 0358 } 0359 0360 AgendaItem::QPtr AgendaItem::removeMoveItem(const AgendaItem::QPtr &e) 0361 { 0362 if (isMultiItem()) { 0363 AgendaItem::QPtr first = mMultiItemInfo->mFirstMultiItem; 0364 AgendaItem::QPtr next; 0365 AgendaItem::QPtr prev; 0366 AgendaItem::QPtr last = mMultiItemInfo->mLastMultiItem; 0367 if (!first) { 0368 first = this; 0369 } 0370 if (!last) { 0371 last = this; 0372 } 0373 if (first == e) { 0374 first = first->nextMultiItem(); 0375 first->setMultiItem(nullptr, nullptr, first->nextMultiItem(), first->lastMultiItem()); 0376 } 0377 if (last == e) { 0378 last = last->prevMultiItem(); 0379 last->setMultiItem(last->firstMultiItem(), last->prevMultiItem(), nullptr, nullptr); 0380 } 0381 0382 AgendaItem::QPtr tmp = first; 0383 if (first == last) { 0384 delete mMultiItemInfo; 0385 tmp = nullptr; 0386 mMultiItemInfo = nullptr; 0387 } 0388 while (tmp) { 0389 next = tmp->nextMultiItem(); 0390 prev = tmp->prevMultiItem(); 0391 if (e == next) { 0392 next = next->nextMultiItem(); 0393 } 0394 if (e == prev) { 0395 prev = prev->prevMultiItem(); 0396 } 0397 tmp->setMultiItem((tmp == first) ? nullptr : first, (tmp == prev) ? nullptr : prev, (tmp == next) ? nullptr : next, (tmp == last) ? nullptr : last); 0398 tmp = tmp->nextMultiItem(); 0399 } 0400 } 0401 0402 return e; 0403 } 0404 0405 void AgendaItem::startMove() 0406 { 0407 AgendaItem::QPtr first = this; 0408 if (isMultiItem() && mMultiItemInfo->mFirstMultiItem) { 0409 first = mMultiItemInfo->mFirstMultiItem; 0410 } 0411 first->startMovePrivate(); 0412 } 0413 0414 void AgendaItem::startMovePrivate() 0415 { 0416 mStartMoveInfo = new MultiItemInfo; 0417 mStartMoveInfo->mStartCellXLeft = mCellXLeft; 0418 mStartMoveInfo->mStartCellXRight = mCellXRight; 0419 mStartMoveInfo->mStartCellYTop = mCellYTop; 0420 mStartMoveInfo->mStartCellYBottom = mCellYBottom; 0421 if (mMultiItemInfo) { 0422 mStartMoveInfo->mFirstMultiItem = mMultiItemInfo->mFirstMultiItem; 0423 mStartMoveInfo->mLastMultiItem = mMultiItemInfo->mLastMultiItem; 0424 mStartMoveInfo->mPrevMultiItem = mMultiItemInfo->mPrevMultiItem; 0425 mStartMoveInfo->mNextMultiItem = mMultiItemInfo->mNextMultiItem; 0426 } else { 0427 mStartMoveInfo->mFirstMultiItem = nullptr; 0428 mStartMoveInfo->mLastMultiItem = nullptr; 0429 mStartMoveInfo->mPrevMultiItem = nullptr; 0430 mStartMoveInfo->mNextMultiItem = nullptr; 0431 } 0432 if (isMultiItem() && mMultiItemInfo->mNextMultiItem) { 0433 mMultiItemInfo->mNextMultiItem->startMovePrivate(); 0434 } 0435 } 0436 0437 void AgendaItem::resetMove() 0438 { 0439 if (mStartMoveInfo) { 0440 if (mStartMoveInfo->mFirstMultiItem) { 0441 mStartMoveInfo->mFirstMultiItem->resetMovePrivate(); 0442 } else { 0443 resetMovePrivate(); 0444 } 0445 } 0446 } 0447 0448 void AgendaItem::resetMovePrivate() 0449 { 0450 if (mStartMoveInfo) { 0451 mCellXLeft = mStartMoveInfo->mStartCellXLeft; 0452 mCellXRight = mStartMoveInfo->mStartCellXRight; 0453 mCellYTop = mStartMoveInfo->mStartCellYTop; 0454 mCellYBottom = mStartMoveInfo->mStartCellYBottom; 0455 0456 // if we don't have mMultiItemInfo, the item didn't span two days before, 0457 // and wasn't moved over midnight, either, so we don't have to reset 0458 // anything. Otherwise, restore from mMoveItemInfo 0459 if (mMultiItemInfo) { 0460 // It was already a multi-day info 0461 mMultiItemInfo->mFirstMultiItem = mStartMoveInfo->mFirstMultiItem; 0462 mMultiItemInfo->mPrevMultiItem = mStartMoveInfo->mPrevMultiItem; 0463 mMultiItemInfo->mNextMultiItem = mStartMoveInfo->mNextMultiItem; 0464 mMultiItemInfo->mLastMultiItem = mStartMoveInfo->mLastMultiItem; 0465 0466 if (!mStartMoveInfo->mFirstMultiItem) { 0467 // This was the first multi-item when the move started, delete all previous 0468 AgendaItem::QPtr toDel = mStartMoveInfo->mPrevMultiItem; 0469 AgendaItem::QPtr nowDel = nullptr; 0470 while (toDel) { 0471 nowDel = toDel; 0472 if (nowDel->moveInfo()) { 0473 toDel = nowDel->moveInfo()->mPrevMultiItem; 0474 } 0475 Q_EMIT removeAgendaItem(nowDel); 0476 } 0477 mMultiItemInfo->mFirstMultiItem = nullptr; 0478 mMultiItemInfo->mPrevMultiItem = nullptr; 0479 } 0480 if (!mStartMoveInfo->mLastMultiItem) { 0481 // This was the last multi-item when the move started, delete all next 0482 AgendaItem::QPtr toDel = mStartMoveInfo->mNextMultiItem; 0483 AgendaItem::QPtr nowDel = nullptr; 0484 while (toDel) { 0485 nowDel = toDel; 0486 if (nowDel->moveInfo()) { 0487 toDel = nowDel->moveInfo()->mNextMultiItem; 0488 } 0489 Q_EMIT removeAgendaItem(nowDel); 0490 } 0491 mMultiItemInfo->mLastMultiItem = nullptr; 0492 mMultiItemInfo->mNextMultiItem = nullptr; 0493 } 0494 0495 if (mStartMoveInfo->mFirstMultiItem == nullptr && mStartMoveInfo->mLastMultiItem == nullptr) { 0496 // it was a single-day event before we started the move. 0497 delete mMultiItemInfo; 0498 mMultiItemInfo = nullptr; 0499 } 0500 } 0501 delete mStartMoveInfo; 0502 mStartMoveInfo = nullptr; 0503 } 0504 Q_EMIT showAgendaItem(this); 0505 if (nextMultiItem()) { 0506 nextMultiItem()->resetMovePrivate(); 0507 } 0508 } 0509 0510 void AgendaItem::endMove() 0511 { 0512 AgendaItem::QPtr first = firstMultiItem(); 0513 if (!first) { 0514 first = this; 0515 } 0516 first->endMovePrivate(); 0517 } 0518 0519 void AgendaItem::endMovePrivate() 0520 { 0521 if (mStartMoveInfo) { 0522 // if first, delete all previous 0523 if (!firstMultiItem() || firstMultiItem() == this) { 0524 AgendaItem::QPtr toDel = mStartMoveInfo->mPrevMultiItem; 0525 AgendaItem::QPtr nowDel = nullptr; 0526 while (toDel) { 0527 nowDel = toDel; 0528 if (nowDel->moveInfo()) { 0529 toDel = nowDel->moveInfo()->mPrevMultiItem; 0530 } 0531 Q_EMIT removeAgendaItem(nowDel); 0532 } 0533 } 0534 // if last, delete all next 0535 if (!lastMultiItem() || lastMultiItem() == this) { 0536 AgendaItem::QPtr toDel = mStartMoveInfo->mNextMultiItem; 0537 AgendaItem::QPtr nowDel = nullptr; 0538 while (toDel) { 0539 nowDel = toDel; 0540 if (nowDel->moveInfo()) { 0541 toDel = nowDel->moveInfo()->mNextMultiItem; 0542 } 0543 Q_EMIT removeAgendaItem(nowDel); 0544 } 0545 } 0546 // also delete the moving info 0547 delete mStartMoveInfo; 0548 mStartMoveInfo = nullptr; 0549 if (nextMultiItem()) { 0550 nextMultiItem()->endMovePrivate(); 0551 } 0552 } 0553 } 0554 0555 void AgendaItem::moveRelative(int dx, int dy) 0556 { 0557 int newXLeft = cellXLeft() + dx; 0558 int newXRight = cellXRight() + dx; 0559 int newYTop = cellYTop() + dy; 0560 int newYBottom = cellYBottom() + dy; 0561 setCellXY(newXLeft, newYTop, newYBottom); 0562 setCellXRight(newXRight); 0563 } 0564 0565 void AgendaItem::expandTop(int dy, const bool allowOverLimit) 0566 { 0567 int newYTop = cellYTop() + dy; 0568 int newYBottom = cellYBottom(); 0569 if (newYTop > newYBottom && !allowOverLimit) { 0570 newYTop = newYBottom; 0571 } 0572 setCellY(newYTop, newYBottom); 0573 } 0574 0575 void AgendaItem::expandBottom(int dy) 0576 { 0577 int newYTop = cellYTop(); 0578 int newYBottom = cellYBottom() + dy; 0579 if (newYBottom < newYTop) { 0580 newYBottom = newYTop; 0581 } 0582 setCellY(newYTop, newYBottom); 0583 } 0584 0585 void AgendaItem::expandLeft(int dx) 0586 { 0587 int newXLeft = cellXLeft() + dx; 0588 int newXRight = cellXRight(); 0589 if (newXLeft > newXRight) { 0590 newXLeft = newXRight; 0591 } 0592 setCellX(newXLeft, newXRight); 0593 } 0594 0595 void AgendaItem::expandRight(int dx) 0596 { 0597 int newXLeft = cellXLeft(); 0598 int newXRight = cellXRight() + dx; 0599 if (newXRight < newXLeft) { 0600 newXRight = newXLeft; 0601 } 0602 setCellX(newXLeft, newXRight); 0603 } 0604 0605 void AgendaItem::dragEnterEvent(QDragEnterEvent *e) 0606 { 0607 const QMimeData *md = e->mimeData(); 0608 if (KCalUtils::ICalDrag::canDecode(md) || KCalUtils::VCalDrag::canDecode(md)) { 0609 // TODO: Allow dragging events/todos onto other events to create a relation 0610 e->ignore(); 0611 return; 0612 } 0613 if (KContacts::VCardDrag::canDecode(md) || md->hasText()) { 0614 e->accept(); 0615 } else { 0616 e->ignore(); 0617 } 0618 } 0619 0620 void AgendaItem::addAttendee(const QString &newAttendee) 0621 { 0622 if (!mValid) { 0623 return; 0624 } 0625 0626 QString name; 0627 QString email; 0628 KEmailAddress::extractEmailAddressAndName(newAttendee, email, name); 0629 if (!(name.isEmpty() && email.isEmpty())) { 0630 mIncidence->addAttendee(KCalendarCore::Attendee(name, email)); 0631 KMessageBox::information(this, 0632 i18n("Attendee \"%1\" added to the calendar item \"%2\"", KEmailAddress::normalizedAddress(name, email, QString()), text()), 0633 i18nc("@title:window", "Attendee added"), 0634 QStringLiteral("AttendeeDroppedAdded")); 0635 } 0636 } 0637 0638 void AgendaItem::dropEvent(QDropEvent *e) 0639 { 0640 // TODO: Organize this better: First check for attachment 0641 // (not only file, also any other url!), then if it's a vcard, 0642 // otherwise check for attendees, then if the data is binary, 0643 // add a binary attachment. 0644 if (!mValid) { 0645 return; 0646 } 0647 0648 const QMimeData *md = e->mimeData(); 0649 0650 bool decoded = md->hasText(); 0651 QString text = md->text(); 0652 if (decoded && text.startsWith(QLatin1StringView("file:"))) { 0653 mIncidence->addAttachment(KCalendarCore::Attachment(text)); 0654 return; 0655 } 0656 0657 KContacts::Addressee::List list; 0658 0659 if (KContacts::VCardDrag::fromMimeData(md, list)) { 0660 for (const KContacts::Addressee &addressee : std::as_const(list)) { 0661 QString em(addressee.fullEmail()); 0662 if (em.isEmpty()) { 0663 em = addressee.realName(); 0664 } 0665 addAttendee(em); 0666 } 0667 } 0668 } 0669 0670 QList<AgendaItem::QPtr> &AgendaItem::conflictItems() 0671 { 0672 return mConflictItems; 0673 } 0674 0675 void AgendaItem::setConflictItems(const QList<AgendaItem::QPtr> &ci) 0676 { 0677 mConflictItems = ci; 0678 for (QList<AgendaItem::QPtr>::iterator it = mConflictItems.begin(), end(mConflictItems.end()); it != end; ++it) { 0679 (*it)->addConflictItem(this); 0680 } 0681 } 0682 0683 void AgendaItem::addConflictItem(const AgendaItem::QPtr &ci) 0684 { 0685 if (!mConflictItems.contains(ci)) { 0686 mConflictItems.append(ci); 0687 } 0688 } 0689 0690 QString AgendaItem::label() const 0691 { 0692 return mLabelText; 0693 } 0694 0695 bool AgendaItem::overlaps(CellItem *o) const 0696 { 0697 AgendaItem::QPtr other = static_cast<AgendaItem *>(o); 0698 0699 if (cellXLeft() <= other->cellXRight() && cellXRight() >= other->cellXLeft()) { 0700 if ((cellYTop() <= other->cellYBottom()) && (cellYBottom() >= other->cellYTop())) { 0701 return true; 0702 } 0703 } 0704 0705 return false; 0706 } 0707 0708 static void conditionalPaint(QPainter *p, bool condition, int &x, int y, int ft, const QPixmap &pxmp) 0709 { 0710 if (condition) { 0711 p->drawPixmap(x, y, pxmp); 0712 x += pxmp.width() + ft; 0713 } 0714 } 0715 0716 void AgendaItem::paintIcon(QPainter *p, int &x, int y, int ft) 0717 { 0718 QString iconName; 0719 if (mIncidence->customProperty("KABC", "ANNIVERSARY") == QLatin1StringView("YES")) { 0720 mSpecialEvent = true; 0721 iconName = QStringLiteral("view-calendar-wedding-anniversary"); 0722 } else if (mIncidence->customProperty("KABC", "BIRTHDAY") == QLatin1StringView("YES")) { 0723 mSpecialEvent = true; 0724 // We don't draw icon. The icon is drawn already, because it's the Akonadi::Collection's icon 0725 } 0726 0727 conditionalPaint(p, !iconName.isEmpty(), x, y, ft, cachedSmallIcon(iconName)); 0728 } 0729 0730 void AgendaItem::paintIcons(QPainter *p, int &x, int y, int ft) 0731 { 0732 if (!mEventView->preferences()->enableAgendaItemIcons()) { 0733 return; 0734 } 0735 0736 paintIcon(p, x, y, ft); 0737 0738 QSet<EventView::ItemIcon> icons = mEventView->preferences()->agendaViewIcons(); 0739 0740 if (icons.contains(EventViews::EventView::CalendarCustomIcon)) { 0741 const QString iconName = mCalendar->iconForIncidence(mIncidence); 0742 if (!iconName.isEmpty() && iconName != QLatin1StringView("view-calendar") && iconName != QLatin1StringView("office-calendar")) { 0743 conditionalPaint(p, true, x, y, ft, QIcon::fromTheme(iconName).pixmap(16, 16)); 0744 } 0745 } 0746 0747 const bool isTodo = mIncidence && mIncidence->type() == Incidence::TypeTodo; 0748 0749 if (isTodo && icons.contains(EventViews::EventView::TaskIcon)) { 0750 const QString iconName = mIncidence->iconName(mOccurrenceDateTime.toLocalTime()); 0751 conditionalPaint(p, !mSpecialEvent, x, y, ft, QIcon::fromTheme(iconName).pixmap(16, 16)); 0752 } 0753 0754 if (icons.contains(EventView::RecurringIcon)) { 0755 conditionalPaint(p, mIconRecur && !mSpecialEvent, x, y, ft, *recurPxmp); 0756 } 0757 0758 if (icons.contains(EventView::ReminderIcon)) { 0759 conditionalPaint(p, mIconAlarm && !mSpecialEvent, x, y, ft, *alarmPxmp); 0760 } 0761 0762 if (icons.contains(EventView::ReadOnlyIcon)) { 0763 conditionalPaint(p, mIconReadonly && !mSpecialEvent, x, y, ft, *readonlyPxmp); 0764 } 0765 0766 if (icons.contains(EventView::ReplyIcon)) { 0767 conditionalPaint(p, mIconReply, x, y, ft, *replyPxmp); 0768 } 0769 0770 if (icons.contains(EventView::AttendingIcon)) { 0771 conditionalPaint(p, mIconGroup, x, y, ft, *groupPxmp); 0772 } 0773 0774 if (icons.contains(EventView::TentativeIcon)) { 0775 conditionalPaint(p, mIconGroupTent, x, y, ft, *groupPxmpTent); 0776 } 0777 0778 if (icons.contains(EventView::OrganizerIcon)) { 0779 conditionalPaint(p, mIconOrganizer, x, y, ft, *organizerPxmp); 0780 } 0781 } 0782 0783 void AgendaItem::paintEvent(QPaintEvent *ev) 0784 { 0785 if (!mValid) { 0786 return; 0787 } 0788 0789 QRect visRect = visibleRegion().boundingRect(); 0790 // when scrolling horizontally in the side-by-side view, the repainted area is clipped 0791 // to the newly visible area, which is a problem since the content changes when visRect 0792 // changes, so repaint the full item in that case 0793 if (ev->rect() != visRect && visRect.isValid() && ev->rect().isValid()) { 0794 update(visRect); 0795 return; 0796 } 0797 0798 QPainter p(this); 0799 p.setRenderHint(QPainter::Antialiasing); 0800 const int fmargin = 0; // frame margin 0801 const int ft = 1; // frame thickness for layout, see drawRoundedRect(), 0802 // keep multiple of 2 0803 const int margin = 5 + ft + fmargin; // frame + space between frame and content 0804 0805 // General idea is to always show the icons (even in the all-day events). 0806 // This creates a consistent feeling for the user when the view mode 0807 // changes and therefore the available width changes. 0808 // Also look at #17984 0809 0810 if (!alarmPxmp) { 0811 alarmPxmp = new QPixmap(QIcon::fromTheme(QStringLiteral("task-reminder")).pixmap(16, 16)); 0812 recurPxmp = new QPixmap(QIcon::fromTheme(QStringLiteral("appointment-recurring")).pixmap(16, 16)); 0813 readonlyPxmp = new QPixmap(QIcon::fromTheme(QStringLiteral("object-locked")).pixmap(16, 16)); 0814 replyPxmp = new QPixmap(QIcon::fromTheme(QStringLiteral("mail-reply-sender")).pixmap(16, 16)); 0815 groupPxmp = new QPixmap(QIcon::fromTheme(QStringLiteral("meeting-attending")).pixmap(16, 16)); 0816 groupPxmpTent = new QPixmap(QIcon::fromTheme(QStringLiteral("meeting-attending-tentative")).pixmap(16, 16)); 0817 organizerPxmp = new QPixmap(QIcon::fromTheme(QStringLiteral("meeting-organizer")).pixmap(16, 16)); 0818 } 0819 0820 const auto categoryColor = getCategoryColor(); 0821 const auto resourceColor = mResourceColor.isValid() ? mResourceColor : categoryColor; 0822 const auto frameColor = getFrameColor(resourceColor, categoryColor); 0823 const auto bgBaseColor = getBackgroundColor(resourceColor, categoryColor); 0824 const auto bgColor = mSelected ? bgBaseColor.lighter(EventView::BRIGHTNESS_FACTOR) : bgBaseColor; 0825 const auto textColor = EventViews::getTextColor(bgColor); 0826 0827 p.setPen(textColor); 0828 0829 p.setFont(mEventView->preferences()->agendaViewFont()); 0830 QFontMetrics fm = p.fontMetrics(); 0831 0832 const int singleLineHeight = fm.boundingRect(mLabelText).height(); 0833 0834 const bool roundTop = !prevMultiItem(); 0835 const bool roundBottom = !nextMultiItem(); 0836 0837 drawRoundedRect(&p, 0838 QRect(fmargin, fmargin, width() - fmargin * 2, height() - fmargin * 2), 0839 mSelected, 0840 bgColor, 0841 frameColor, 0842 true, 0843 ft, 0844 roundTop, 0845 roundBottom); 0846 0847 // calculate the height of the full version (case 4) to test whether it is 0848 // possible 0849 0850 QString shortH; 0851 QString longH; 0852 if (!isMultiItem()) { 0853 shortH = QLocale().toString(mIncidence->dateTime(KCalendarCore::Incidence::RoleDisplayStart).toLocalTime().time(), QLocale::ShortFormat); 0854 0855 if (CalendarSupport::hasEvent(mIncidence)) { 0856 longH = 0857 i18n("%1 - %2", shortH, QLocale().toString(mIncidence->dateTime(KCalendarCore::Incidence::RoleEnd).toLocalTime().time(), QLocale::ShortFormat)); 0858 } else { 0859 longH = shortH; 0860 } 0861 } else if (!mMultiItemInfo->mFirstMultiItem) { 0862 shortH = QLocale().toString(mIncidence->dtStart().toLocalTime().time(), QLocale::ShortFormat); 0863 longH = shortH; 0864 } else { 0865 shortH = QLocale().toString(mIncidence->dateTime(KCalendarCore::Incidence::RoleEnd).toLocalTime().time(), QLocale::ShortFormat); 0866 longH = i18n("- %1", shortH); 0867 } 0868 0869 KWordWrap ww = KWordWrap::formatText(fm, QRect(0, 0, width() - (2 * margin), -1), 0, mLabelText); 0870 int th = ww.boundingRect().height(); 0871 0872 int hlHeight = 0873 qMax(fm.boundingRect(longH).height(), 0874 qMax(alarmPxmp->height(), 0875 qMax(recurPxmp->height(), qMax(readonlyPxmp->height(), qMax(replyPxmp->height(), qMax(groupPxmp->height(), organizerPxmp->height())))))); 0876 0877 const bool completelyRenderable = th < (height() - 2 * ft - 2 - hlHeight); 0878 0879 // case 1: do not draw text when not even a single line fits 0880 // Don't do this any more, always try to print out the text. 0881 // Even if it's just a few pixel, one can still guess the whole 0882 // text from just four pixels' height! 0883 if ( //( singleLineHeight > height() - 4 ) || 0884 (width() < 16)) { 0885 int x = qRound((width() - 16) / 2.0); 0886 paintIcon(&p, x /*by-ref*/, margin, ft); 0887 return; 0888 } 0889 0890 // case 2: draw a single line when no more space 0891 if ((2 * singleLineHeight) > (height() - 2 * margin)) { 0892 int x = margin; 0893 int txtWidth; 0894 0895 if (mIncidence->allDay()) { 0896 x += visRect.left(); 0897 const int y = qRound((height() - 16) / 2.0); 0898 paintIcons(&p, x, y, ft); 0899 txtWidth = visRect.right() - margin - x; 0900 } else { 0901 const int y = qRound((height() - 16) / 2.0); 0902 paintIcons(&p, x, y, ft); 0903 txtWidth = width() - margin - x; 0904 } 0905 0906 const int y = ((height() - singleLineHeight) / 2) + fm.ascent(); 0907 KWordWrap::drawFadeoutText(&p, x, y, txtWidth, mLabelText); 0908 return; 0909 } 0910 0911 // case 3: enough for 2-5 lines, but not for the header. 0912 // Also used for the middle days in multi-events 0913 if (((!completelyRenderable) && ((height() - (2 * margin)) <= (5 * singleLineHeight))) 0914 || (isMultiItem() && mMultiItemInfo->mNextMultiItem && mMultiItemInfo->mFirstMultiItem)) { 0915 int x = margin; 0916 int txtWidth; 0917 0918 if (mIncidence->allDay()) { 0919 x += visRect.left(); 0920 paintIcons(&p, x, margin, ft); 0921 txtWidth = visRect.right() - margin - x; 0922 } else { 0923 paintIcons(&p, x, margin, ft); 0924 txtWidth = width() - margin - x; 0925 } 0926 0927 ww = KWordWrap::formatText(fm, QRect(0, 0, txtWidth, (height() - (2 * margin))), 0, mLabelText); 0928 0929 ww.drawText(&p, x, margin, Qt::AlignHCenter | KWordWrap::FadeOut); 0930 return; 0931 } 0932 0933 // case 4: paint everything, with header: 0934 // consists of (vertically) ft + headline&icons + ft + text + margin 0935 int y = 2 * ft + hlHeight; 0936 if (completelyRenderable) { 0937 y += (height() - (2 * ft) - margin - hlHeight - th) / 2; 0938 } 0939 0940 int x = margin; 0941 int txtWidth; 0942 int hTxtWidth; 0943 int eventX; 0944 0945 if (mIncidence->allDay()) { 0946 shortH.clear(); 0947 longH.clear(); 0948 0949 if (const KCalendarCore::Event::Ptr event = CalendarSupport::event(mIncidence)) { 0950 if (event->isMultiDay(QTimeZone::systemTimeZone())) { 0951 // multi-day, all-day event 0952 shortH = i18n("%1 - %2", 0953 QLocale().toString(mIncidence->dtStart().toLocalTime().date()), 0954 QLocale().toString(mIncidence->dateTime(KCalendarCore::Incidence::RoleEnd).toLocalTime().date())); 0955 longH = shortH; 0956 0957 // paint headline 0958 drawRoundedRect(&p, 0959 QRect(fmargin, fmargin, width() - fmargin * 2, -fmargin * 2 + margin + hlHeight), 0960 mSelected, 0961 frameColor, 0962 frameColor, 0963 false, 0964 ft, 0965 roundTop, 0966 false); 0967 } else { 0968 // single-day, all-day event 0969 0970 // paint headline 0971 drawRoundedRect(&p, 0972 QRect(fmargin, fmargin, width() - fmargin * 2, -fmargin * 2 + margin + hlHeight), 0973 mSelected, 0974 frameColor, 0975 frameColor, 0976 false, 0977 ft, 0978 roundTop, 0979 false); 0980 } 0981 } else { 0982 // to-do 0983 0984 // paint headline 0985 drawRoundedRect(&p, 0986 QRect(fmargin, fmargin, width() - fmargin * 2, -fmargin * 2 + margin + hlHeight), 0987 mSelected, 0988 frameColor, 0989 frameColor, 0990 false, 0991 ft, 0992 roundTop, 0993 false); 0994 } 0995 0996 x += visRect.left(); 0997 eventX = x; 0998 txtWidth = visRect.right() - margin - x; 0999 paintIcons(&p, x, margin / 2, ft); 1000 hTxtWidth = visRect.right() - margin - x; 1001 } else { 1002 // paint headline 1003 drawRoundedRect(&p, 1004 QRect(fmargin, fmargin, width() - fmargin * 2, -fmargin * 2 + margin + hlHeight), 1005 mSelected, 1006 frameColor, 1007 frameColor, 1008 false, 1009 ft, 1010 roundTop, 1011 false); 1012 1013 txtWidth = width() - margin - x; 1014 eventX = x; 1015 paintIcons(&p, x, margin / 2, ft); 1016 hTxtWidth = width() - margin - x; 1017 } 1018 1019 QString headline; 1020 int hw = fm.boundingRect(longH).width(); 1021 if (hw > hTxtWidth) { 1022 headline = shortH; 1023 hw = fm.boundingRect(shortH).width(); 1024 if (hw < txtWidth) { 1025 x += (hTxtWidth - hw) / 2; 1026 } 1027 } else { 1028 headline = longH; 1029 x += (hTxtWidth - hw) / 2; 1030 } 1031 p.setBackground(QBrush(frameColor)); 1032 p.setPen(EventViews::getTextColor(frameColor)); 1033 KWordWrap::drawFadeoutText(&p, x, (margin + hlHeight + fm.ascent()) / 2 - 2, hTxtWidth, headline); 1034 1035 // draw event text 1036 ww = KWordWrap::formatText(fm, QRect(0, 0, txtWidth, height() - margin - y), 0, mLabelText); 1037 1038 p.setBackground(QBrush(bgColor)); 1039 p.setPen(textColor); 1040 QString ws = ww.wrappedString(); 1041 if (QStringView(ws).left(ws.length() - 1).indexOf(QLatin1Char('\n')) >= 0) { 1042 ww.drawText(&p, eventX, y, Qt::AlignLeft | KWordWrap::FadeOut); 1043 } else { 1044 ww.drawText(&p, eventX + (txtWidth - ww.boundingRect().width() - 2 * margin) / 2, y, Qt::AlignHCenter | KWordWrap::FadeOut); 1045 } 1046 } 1047 1048 void AgendaItem::drawRoundedRect(QPainter *p, 1049 QRect rect, 1050 bool selected, 1051 const QColor &bgColor, 1052 const QColor &frameColor, 1053 bool frame, 1054 int ft, 1055 bool roundTop, 1056 bool roundBottom) 1057 { 1058 Q_UNUSED(ft) 1059 if (!mValid) { 1060 return; 1061 } 1062 1063 QPainterPath path; 1064 1065 const int RECT_MARGIN = 2; 1066 const int RADIUS = 2; // absolute radius 1067 1068 const QRect rectWithMargin(rect.x() + RECT_MARGIN, rect.y() + RECT_MARGIN, rect.width() - 2 * RECT_MARGIN, rect.height() - 2 * RECT_MARGIN); 1069 1070 const QPoint pointLeftTop(rectWithMargin.x(), rectWithMargin.y()); 1071 const QPoint pointRightTop(rectWithMargin.x() + rectWithMargin.width(), rectWithMargin.y()); 1072 const QPoint pointLeftBottom(rectWithMargin.x(), rectWithMargin.y() + rectWithMargin.height()); 1073 const QPoint pointRightBottom(rectWithMargin.x() + rectWithMargin.width(), rectWithMargin.y() + rectWithMargin.height()); 1074 1075 if (!roundTop && !roundBottom) { 1076 path.addRect(rectWithMargin); 1077 } else if (roundTop && roundBottom) { 1078 path.addRoundedRect(rectWithMargin, RADIUS, RADIUS, Qt::AbsoluteSize); 1079 } else if (roundTop) { 1080 path.moveTo(pointRightBottom); 1081 path.lineTo(pointLeftBottom); 1082 path.lineTo(QPoint(pointLeftTop.x(), pointLeftTop.y() + RADIUS)); 1083 path.quadTo(pointLeftTop, QPoint(pointLeftTop.x() + RADIUS, pointLeftTop.y())); 1084 path.lineTo(QPoint(pointRightTop.x() - RADIUS, pointRightTop.y())); 1085 path.quadTo(pointRightTop, QPoint(pointRightTop.x(), pointRightTop.y() + RADIUS)); 1086 path.lineTo(pointRightBottom); 1087 } else if (roundBottom) { 1088 path.moveTo(pointRightTop); 1089 path.lineTo(QPoint(pointRightBottom.x(), pointRightBottom.y() - RADIUS)); 1090 path.quadTo(pointRightBottom, QPoint(pointRightBottom.x() - RADIUS, pointRightBottom.y())); 1091 path.lineTo(QPoint(pointLeftBottom.x() + RADIUS, pointLeftBottom.y())); 1092 path.quadTo(pointLeftBottom, QPoint(pointLeftBottom.x(), pointLeftBottom.y() - RADIUS)); 1093 path.lineTo(pointLeftTop); 1094 path.lineTo(pointRightTop); 1095 } 1096 1097 path.closeSubpath(); 1098 p->save(); 1099 p->setRenderHint(QPainter::Antialiasing, false); 1100 const QPen border(frameColor, 1.0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin); 1101 p->setPen(border); 1102 1103 // header 1104 if (!frame) { 1105 QBrush brushSolid(Qt::SolidPattern); 1106 1107 QColor top = bgColor.darker(250); 1108 top.setAlpha(selected ? 40 : 60); 1109 brushSolid.setColor(top); 1110 1111 p->setBrush(bgColor); 1112 p->drawPath(path); 1113 1114 p->setBrush(brushSolid); 1115 p->drawPath(path); 1116 p->restore(); 1117 1118 return; 1119 } 1120 1121 p->setBrush(bgColor); 1122 p->drawPath(path); 1123 p->restore(); 1124 } 1125 1126 QColor AgendaItem::getCategoryColor() const 1127 { 1128 const QStringList &categories = mIncidence->categories(); 1129 if (categories.isEmpty() || !Akonadi::TagCache::instance()->tagColor(categories.first()).isValid()) { 1130 const auto colorPreference = mEventView->preferences()->agendaViewColors(); 1131 if (colorPreference == PrefsBase::CategoryOnly || !mResourceColor.isValid()) { 1132 return CalendarSupport::KCalPrefs::instance()->unsetCategoryColor(); 1133 } 1134 return mResourceColor; 1135 } 1136 return Akonadi::TagCache::instance()->tagColor(categories.first()); 1137 } 1138 1139 QColor AgendaItem::getFrameColor(const QColor &resourceColor, const QColor &categoryColor) const 1140 { 1141 const auto colorPreference = mEventView->preferences()->agendaViewColors(); 1142 const bool frameDisplaysCategory = (colorPreference == PrefsBase::CategoryOnly || colorPreference == PrefsBase::ResourceInsideCategoryOutside); 1143 return frameDisplaysCategory ? categoryColor : resourceColor; 1144 } 1145 1146 QColor AgendaItem::getBackgroundColor(const QColor &resourceColor, const QColor &categoryColor) const 1147 { 1148 if (CalendarSupport::hasTodo(mIncidence) && !mEventView->preferences()->todosUseCategoryColors()) { 1149 Todo::Ptr todo = CalendarSupport::todo(mIncidence); 1150 Q_ASSERT(todo); 1151 const QDate dueDate = todo->dtDue().toLocalTime().date(); 1152 const QDate today = QDate::currentDate(); 1153 const QDate occurrenceDate = this->occurrenceDate(); 1154 if (todo->isOverdue() && today >= occurrenceDate) { 1155 return mEventView->preferences()->todoOverdueColor(); 1156 } else if (dueDate == today && dueDate == occurrenceDate && !todo->isCompleted()) { 1157 return mEventView->preferences()->todoDueTodayColor(); 1158 } 1159 } 1160 const auto colorPreference = mEventView->preferences()->agendaViewColors(); 1161 const bool bgDisplaysCategory = (colorPreference == PrefsBase::CategoryOnly || colorPreference == PrefsBase::CategoryInsideResourceOutside); 1162 return bgDisplaysCategory ? categoryColor : resourceColor; 1163 } 1164 1165 bool AgendaItem::eventFilter(QObject *obj, QEvent *event) 1166 { 1167 if (event->type() == QEvent::Paint) { 1168 return mValid; 1169 } else { 1170 // standard event processing 1171 return QObject::eventFilter(obj, event); 1172 } 1173 } 1174 1175 bool AgendaItem::event(QEvent *event) 1176 { 1177 if (event->type() == QEvent::ToolTip) { 1178 if (!mEventView->preferences()->enableToolTips()) { 1179 return true; 1180 } else if (mValid) { 1181 auto helpEvent = static_cast<QHelpEvent *>(event); 1182 QToolTip::showText(helpEvent->globalPos(), 1183 KCalUtils::IncidenceFormatter::toolTipStr(mCalendar->displayName(mIncidence), mIncidence, occurrenceDate(), true), 1184 this); 1185 } 1186 } 1187 return QWidget::event(event); 1188 } 1189 1190 #include "moc_agendaitem.cpp"