diff --git a/CMakeLists.txt b/CMakeLists.txt index ef59561..38829c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,101 +1,101 @@ cmake_minimum_required(VERSION 3.5) set(PIM_VERSION "5.11.40") project(eventviews VERSION ${PIM_VERSION}) # ECM setup set(KF5_MIN_VERSION "5.59.0") find_package(ECM ${KF5_MIN_VERSION} CONFIG REQUIRED) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) set(LIBRARY_NAMELINK) if(POLICY CMP0053) cmake_policy(SET CMP0053 NEW) endif() include(GenerateExportHeader) include(ECMSetupVersion) include(ECMGenerateHeaders) include(ECMGeneratePriFile) include(ECMSetupVersion) include(FeatureSummary) include(KDEInstallDirs) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(ECMQtDeclareLoggingCategory) include(ECMAddTests) set(EVENTVIEW_LIB_VERSION ${PIM_VERSION}) set(CALENDARUTILS_LIB_VERSION "5.11.40") set(AKONADI_LIB_VERSION "5.11.40") set(QT_REQUIRED_VERSION "5.10.0") -set(KCALENDARCORE_LIB_VERSION "5.11.42") +set(KCALENDARCORE_LIB_VERSION "5.11.44") set(AKONADICALENDAR_LIB_VERSION "5.11.40") set(KMIME_LIB_VERSION "5.11.40") set(LIBKDEPIM_LIB_VERSION "5.11.40") set(CALENDARSUPPORT_LIB_VERSION "5.11.40") find_package(KF5Akonadi ${AKONADI_LIB_VERSION} CONFIG REQUIRED) find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED Widgets UiTools) find_package(KF5I18n ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5Codecs ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5GuiAddons ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5IconThemes ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5Service ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5Completion ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5Holidays ${KF5_MIN_VERSION} CONFIG REQUIRED) set(KDIAGRAM_LIB_VERSION "1.4.0") find_package(KGantt ${KDIAGRAM_LIB_VERSION} CONFIG REQUIRED) find_package(KF5LibkdepimAkonadi ${LIBKDEPIM_LIB_VERSION} CONFIG REQUIRED) find_package(KF5CalendarUtils ${CALENDARUTILS_LIB_VERSION} CONFIG REQUIRED) find_package(KF5CalendarCore ${KCALENDARCORE_LIB_VERSION} CONFIG REQUIRED) find_package(KF5CalendarSupport ${CALENDARSUPPORT_LIB_VERSION} CONFIG REQUIRED) find_package(KF5AkonadiCalendar ${AKONADICALENDAR_LIB_VERSION} CONFIG REQUIRED) find_package(KF5Mime ${KMIME_LIB_VERSION} CONFIG REQUIRED) ecm_setup_version(PROJECT VARIABLE_PREFIX EVENTVIEWS VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/eventviews_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5EventViewsConfigVersion.cmake" SOVERSION 5 ) ########### Targets ########### add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x060000) add_definitions(-DQT_NO_FOREACH) ########### CMake Config Files ########### set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF5EventViews") configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/KF5EventViewsConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KF5EventViewsConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KF5EventViewsConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KF5EventViewsConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) install(EXPORT KF5EventViewsTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KF5EventViewsTargets.cmake NAMESPACE KF5::) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/eventviews_version.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5} COMPONENT Devel ) add_subdirectory(src) if(BUILD_TESTING) find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED Test) add_subdirectory(tests) endif() install(FILES eventviews.renamecategories eventviews.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/src/agenda/agendaitem.cpp b/src/agenda/agendaitem.cpp index 4f4731d..bc5c7f8 100644 --- a/src/agenda/agendaitem.cpp +++ b/src/agenda/agendaitem.cpp @@ -1,1199 +1,1198 @@ /* 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()); + KCalCore::Attendee me = mIncidence->attendeeByMails(mEventView->kcalPreferences()->allEmails()); - if (me) { - if (me->status() == KCalCore::Attendee::NeedsAction && me->RSVP()) { + if (!me.isNull()) { + if (me.status() == KCalCore::Attendee::NeedsAction && me.RSVP()) { mIconReply = true; mIconGroup = false; mIconGroupTent = false; mIconOrganizer = false; - } else if (me->status() == KCalCore::Attendee::Tentative) { + } 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))); + mIncidence->addAttendee(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)); } 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); 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); QColor top = bgColor.darker(250); top.setAlpha(selected ? 40 : 60); brushSolid.setColor(top); 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/eventview.cpp b/src/eventview.cpp index ea70270..be6a1e8 100644 --- a/src/eventview.cpp +++ b/src/eventview.cpp @@ -1,680 +1,680 @@ /* Copyright (c) 2000,2001 Cornelius Schumacher Copyright (C) 2003-2004 Reinhold Kainhofer Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Author: Kevin Krammer, krake@kdab.com Author: Sergio Martins, sergio.martins@kdab.com 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 "eventview_p.h" #include "prefs.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "calendarview_debug.h" #include #include #include using namespace KCalCore; using namespace EventViews; using namespace Akonadi; CalendarSupport::CollectionSelection *EventViewPrivate::sGlobalCollectionSelection = nullptr; /* static */ void EventView::setGlobalCollectionSelection(CalendarSupport::CollectionSelection *s) { EventViewPrivate::sGlobalCollectionSelection = s; } EventView::EventView(QWidget *parent) : QWidget(parent) , d_ptr(new EventViewPrivate()) { QByteArray cname = metaObject()->className(); cname.replace(':', '_'); d_ptr->identifier = cname + '_' + KRandom::randomString(8).toLatin1(); //AKONADI_PORT review: the FocusLineEdit in the editor emits focusReceivedSignal(), //which triggered finishTypeAhead. But the global focus widget in QApplication is //changed later, thus subsequent keyevents still went to this view, triggering another //editor, for each keypress. //Thus, listen to the global focusChanged() signal (seen in Qt 4.6-stable-patched 20091112 -Frank) connect(qobject_cast(QApplication::instance()), &QApplication::focusChanged, this, &EventView::focusChanged); d_ptr->setUpModels(); } EventView::~EventView() { delete d_ptr; } void EventView::defaultAction(const Akonadi::Item &aitem) { qCDebug(CALENDARVIEW_LOG); const Incidence::Ptr incidence = CalendarSupport::incidence(aitem); if (!incidence) { return; } qCDebug(CALENDARVIEW_LOG) << " type:" << int(incidence->type()); if (incidence->isReadOnly()) { Q_EMIT showIncidenceSignal(aitem); } else { Q_EMIT editIncidenceSignal(aitem); } } void EventView::setHolidayRegions(const QStringList ®ions) { Q_D(EventView); qDeleteAll(d->mHolidayRegions); d->mHolidayRegions.clear(); for (const QString ®ionStr : regions) { KHolidays::HolidayRegion *region = new KHolidays::HolidayRegion(regionStr); if (region->isValid()) { d->mHolidayRegions.append(region); } else { delete region; } } } int EventView::showMoveRecurDialog(const Incidence::Ptr &inc, const QDate &date) { QDateTime dateTime(date, {}, Qt::LocalTime); int availableOccurrences = KCalUtils::RecurrenceActions::availableOccurrences(inc, dateTime); const QString caption = i18nc("@title:window", "Changing Recurring Item"); KGuiItem itemFuture(i18n("Also &Future Items")); KGuiItem itemSelected(i18n("Only &This Item")); KGuiItem itemAll(i18n("&All Occurrences")); switch (availableOccurrences) { case KCalUtils::RecurrenceActions::NoOccurrence: return KCalUtils::RecurrenceActions::NoOccurrence; case KCalUtils::RecurrenceActions::SelectedOccurrence: return KCalUtils::RecurrenceActions::SelectedOccurrence; case KCalUtils::RecurrenceActions::AllOccurrences: Q_ASSERT(availableOccurrences & KCalUtils::RecurrenceActions::SelectedOccurrence); // if there are all kinds of ooccurrences (i.e. past present and future) the user might // want the option only apply to current and future occurrences, leaving the past ones // provide a third choice for that ("Also future") if (availableOccurrences == KCalUtils::RecurrenceActions::AllOccurrences) { const QString message = i18n("The item you are trying to change is a recurring item. " "Should the changes be applied only to this single occurrence, " "also to future items, or to all items in the recurrence?"); return KCalUtils::RecurrenceActions::questionSelectedFutureAllCancel( message, caption, itemSelected, itemFuture, itemAll, this); } Q_FALLTHROUGH(); default: Q_ASSERT(availableOccurrences & KCalUtils::RecurrenceActions::SelectedOccurrence); // selected occurrence and either past or future occurrences const QString message = i18n("The item you are trying to change is a recurring item. " "Should the changes be applied only to this single occurrence " "or to all items in the recurrence?"); return KCalUtils::RecurrenceActions::questionSelectedAllCancel( message, caption, itemSelected, itemAll, this); break; } return KCalUtils::RecurrenceActions::NoOccurrence; } void EventView::setCalendar(const Akonadi::ETMCalendar::Ptr &calendar) { Q_D(EventView); if (d->calendar != calendar) { if (d->calendar) { disconnect(d->calendar.data()); } d->calendar = calendar; if (calendar) { if (d->collectionSelectionModel) { d->collectionSelectionModel->setSourceModel(calendar->model()); } connect(calendar.data(), &ETMCalendar::collectionChanged, this, &EventView::onCollectionChanged); } } } Akonadi::ETMCalendar::Ptr EventView::calendar() const { Q_D(const EventView); return d->calendar; } void EventView::setPreferences(const PrefsPtr &preferences) { Q_D(EventView); if (d->mPrefs != preferences) { if (preferences) { d->mPrefs = preferences; } else { d->mPrefs = PrefsPtr(new Prefs()); } updateConfig(); } } void EventView::setKCalPreferences(const KCalPrefsPtr &preferences) { Q_D(EventView); if (d->mKCalPrefs != preferences) { if (preferences) { d->mKCalPrefs = preferences; } else { d->mKCalPrefs = KCalPrefsPtr(new CalendarSupport::KCalPrefs()); } updateConfig(); } } PrefsPtr EventView::preferences() const { Q_D(const EventView); return d->mPrefs; } KCalPrefsPtr EventView::kcalPreferences() const { Q_D(const EventView); return d->mKCalPrefs; } void EventView::dayPassed(const QDate &) { updateView(); } void EventView::setIncidenceChanger(Akonadi::IncidenceChanger *changer) { Q_D(EventView); d->mChanger = changer; } void EventView::flushView() { } EventView *EventView::viewAt(const QPoint &) { return this; } void EventView::updateConfig() { } QDateTime EventView::selectionStart() const { return QDateTime(); } QDateTime EventView::selectionEnd() const { return QDateTime(); } bool EventView::dateRangeSelectionEnabled() const { Q_D(const EventView); return d->mDateRangeSelectionEnabled; } void EventView::setDateRangeSelectionEnabled(bool enable) { Q_D(EventView); d->mDateRangeSelectionEnabled = enable; } bool EventView::supportsZoom() const { return false; } bool EventView::hasConfigurationDialog() const { return false; } void EventView::setDateRange(const QDateTime &start, const QDateTime &end, const QDate &preferredMonth) { Q_D(EventView); d->startDateTime = start; d->endDateTime = end; showDates(start.date(), end.date(), preferredMonth); const QPair adjusted = actualDateRange(start, end, preferredMonth); d->actualStartDateTime = adjusted.first; d->actualEndDateTime = adjusted.second; } QDateTime EventView::startDateTime() const { Q_D(const EventView); return d->startDateTime; } QDateTime EventView::endDateTime() const { Q_D(const EventView); return d->endDateTime; } QDateTime EventView::actualStartDateTime() const { Q_D(const EventView); return d->actualStartDateTime; } QDateTime EventView::actualEndDateTime() const { Q_D(const EventView); return d->actualEndDateTime; } void EventView::showConfigurationDialog(QWidget *) { } bool EventView::processKeyEvent(QKeyEvent *ke) { Q_D(EventView); // If Return is pressed bring up an editor for the current selected time span. if (ke->key() == Qt::Key_Return) { if (ke->type() == QEvent::KeyPress) { d->mReturnPressed = true; } else if (ke->type() == QEvent::KeyRelease) { if (d->mReturnPressed) { Q_EMIT newEventSignal(); d->mReturnPressed = false; return true; } else { d->mReturnPressed = false; } } } // Ignore all input that does not produce any output if (ke->text().isEmpty() || (ke->modifiers() & Qt::ControlModifier)) { return false; } if (ke->type() == QEvent::KeyPress) { switch (ke->key()) { case Qt::Key_Escape: case Qt::Key_Return: case Qt::Key_Enter: case Qt::Key_Tab: case Qt::Key_Backtab: case Qt::Key_Left: case Qt::Key_Right: case Qt::Key_Up: case Qt::Key_Down: case Qt::Key_Backspace: case Qt::Key_Delete: case Qt::Key_PageUp: case Qt::Key_PageDown: case Qt::Key_Home: case Qt::Key_End: case Qt::Key_Control: case Qt::Key_Meta: case Qt::Key_Alt: break; default: d->mTypeAheadEvents.append( new QKeyEvent(ke->type(), ke->key(), ke->modifiers(), ke->text(), ke->isAutoRepeat(), static_cast(ke->count()))); if (!d->mTypeAhead) { d->mTypeAhead = true; Q_EMIT newEventSignal(); } return true; } } return false; } void EventView::setTypeAheadReceiver(QObject *o) { Q_D(EventView); d->mTypeAheadReceiver = o; } void EventView::focusChanged(QWidget *, QWidget *now) { Q_D(EventView); if (d->mTypeAhead && now && now == d->mTypeAheadReceiver) { d->finishTypeAhead(); } } CalendarSupport::CollectionSelection *EventView::collectionSelection() const { Q_D(const EventView); return d->customCollectionSelection ? d->customCollectionSelection : globalCollectionSelection(); } void EventView::setCustomCollectionSelectionProxyModel(KCheckableProxyModel *model) { Q_D(EventView); if (d->collectionSelectionModel == model) { return; } delete d->collectionSelectionModel; d->collectionSelectionModel = model; d->setUpModels(); } KCheckableProxyModel *EventView::customCollectionSelectionProxyModel() const { Q_D(const EventView); return d->collectionSelectionModel; } KCheckableProxyModel *EventView::takeCustomCollectionSelectionProxyModel() { Q_D(EventView); KCheckableProxyModel *m = d->collectionSelectionModel; d->collectionSelectionModel = nullptr; d->setUpModels(); return m; } CalendarSupport::CollectionSelection *EventView::customCollectionSelection() const { Q_D(const EventView); return d->customCollectionSelection; } void EventView::clearSelection() { } bool EventView::eventDurationHint(QDateTime &startDt, QDateTime &endDt, bool &allDay) const { Q_UNUSED(startDt); Q_UNUSED(endDt); Q_UNUSED(allDay); return false; } Akonadi::IncidenceChanger *EventView::changer() const { Q_D(const EventView); return d->mChanger; } void EventView::doRestoreConfig(const KConfigGroup &) { } void EventView::doSaveConfig(KConfigGroup &) { } QPair EventView::actualDateRange(const QDateTime &start, const QDateTime &end, const QDate &preferredMonth) const { Q_UNUSED(preferredMonth); return qMakePair(start, end); } /* void EventView::incidencesAdded( const Akonadi::Item::List & ) { } void EventView::incidencesAboutToBeRemoved( const Akonadi::Item::List & ) { } void EventView::incidencesChanged( const Akonadi::Item::List & ) { } */ void EventView::handleBackendError(const QString &errorString) { qCCritical(CALENDARVIEW_LOG) << errorString; } void EventView::calendarReset() { } CalendarSupport::CollectionSelection *EventView::globalCollectionSelection() { return EventViewPrivate::sGlobalCollectionSelection; } QByteArray EventView::identifier() const { Q_D(const EventView); return d->identifier; } void EventView::setIdentifier(const QByteArray &identifier) { Q_D(EventView); d->identifier = identifier; } void EventView::setChanges(Changes changes) { Q_D(EventView); if (d->mChanges == NothingChanged) { QMetaObject::invokeMethod(this, &EventView::updateView, Qt::QueuedConnection); } d->mChanges = changes; } EventView::Changes EventView::changes() const { Q_D(const EventView); return d->mChanges; } void EventView::restoreConfig(const KConfigGroup &configGroup) { Q_D(EventView); const bool useCustom = configGroup.readEntry("UseCustomCollectionSelection", false); if (!d->collectionSelectionModel && !useCustom) { delete d->collectionSelectionModel; d->collectionSelectionModel = nullptr; d->setUpModels(); } else if (useCustom) { if (!d->collectionSelectionModel) { // Sort the calendar model on calendar name QSortFilterProxyModel *sortProxy = new QSortFilterProxyModel(this); sortProxy->setDynamicSortFilter(true); sortProxy->setSortCaseSensitivity(Qt::CaseInsensitive); if (d->calendar) { sortProxy->setSourceModel(d->calendar->entityTreeModel()); } // Only show the first column. KRearrangeColumnsProxyModel *columnFilterProxy = new KRearrangeColumnsProxyModel(this); columnFilterProxy->setSourceColumns( QVector() << Akonadi::ETMCalendar::CollectionTitle); columnFilterProxy->setSourceModel(sortProxy); // Make the calendar model checkable. d->collectionSelectionModel = new KCheckableProxyModel(this); d->collectionSelectionModel->setSourceModel(columnFilterProxy); d->setUpModels(); } const KConfigGroup selectionGroup = configGroup.config()->group(configGroup.name() + QLatin1String("_selectionSetup")); KViewStateMaintainer maintainer(selectionGroup); maintainer.setSelectionModel(d->collectionSelectionModel->selectionModel()); maintainer.restoreState(); } doRestoreConfig(configGroup); } void EventView::saveConfig(KConfigGroup &configGroup) { Q_D(EventView); configGroup.writeEntry("UseCustomCollectionSelection", d->collectionSelectionModel != nullptr); if (d->collectionSelectionModel) { KConfigGroup selectionGroup = configGroup.config()->group(configGroup.name() + QLatin1String("_selectionSetup")); KViewStateMaintainer maintainer(selectionGroup); maintainer.setSelectionModel(d->collectionSelectionModel->selectionModel()); maintainer.saveState(); } doSaveConfig(configGroup); } void EventView::setCollectionId(Akonadi::Collection::Id id) { Q_D(EventView); if (d->mCollectionId != id) { d->mCollectionId = id; } } Akonadi::Collection::Id EventView::collectionId() const { Q_D(const EventView); return d->mCollectionId; } bool EventView::makesWholeDayBusy(const KCalCore::Incidence::Ptr &incidence) const { // Must be event // Must be all day // Must be marked busy (TRANSP: OPAQUE) // You must be attendee or organizer if (incidence->type() != KCalCore::Incidence::TypeEvent || !incidence->allDay()) { return false; } KCalCore::Event::Ptr ev = incidence.staticCast(); if (ev->transparency() != KCalCore::Event::Opaque) { return false; } // Last check: must be organizer or attendee: if (kcalPreferences()->thatIsMe(ev->organizer().email())) { return true; } KCalCore::Attendee::List attendees = ev->attendees(); KCalCore::Attendee::List::ConstIterator it; for (it = attendees.constBegin(); it != attendees.constEnd(); ++it) { - if (kcalPreferences()->thatIsMe((*it)->email())) { + if (kcalPreferences()->thatIsMe((*it).email())) { return true; } } return false; } /*static*/ QColor EventView::itemFrameColor(const QColor &color, bool selected) { if (color.isValid()) { return selected ? QColor(85 + color.red() * 2.0 / 3, 85 + color.green() * 2.0 / 3, 85 + color.blue() * 2.0 / 3) : color.darker(115); } else { return Qt::black; } } QString EventView::iconForItem(const Akonadi::Item &item) { QString iconName; Akonadi::Collection collection = item.parentCollection(); while (collection.parentCollection().isValid() && collection.parentCollection() != Akonadi::Collection::root()) { collection = calendar()->collection(collection.parentCollection().id()); } if (collection.isValid() && collection.hasAttribute()) { iconName = collection.attribute()->iconName(); } return iconName; } void EventView::onCollectionChanged(const Akonadi::Collection &collection, const QSet &changedAttributes) { Q_UNUSED(collection); if (changedAttributes.contains("AccessRights")) { setChanges(changes() | EventViews::EventView::ResourcesChanged); updateView(); } } diff --git a/src/todo/todomodel.cpp b/src/todo/todomodel.cpp index 821440e..e92c482 100644 --- a/src/todo/todomodel.cpp +++ b/src/todo/todomodel.cpp @@ -1,928 +1,927 @@ /* Copyright (c) 2008 Thomas Thrainer Copyright (c) 2012 SĂ©rgio Martins 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 "todomodel_p.h" #include "incidencetreemodel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "calendarview_debug.h" #include #include struct SourceModelIndex { SourceModelIndex(int _r, int _c, void *_p, QAbstractItemModel *_m) : r(_r) , c(_c) , p(_p) , m(_m) { } operator QModelIndex() { return reinterpret_cast(*this); } int r, c; void *p; const QAbstractItemModel *m = nullptr; }; static bool isDueToday(const KCalCore::Todo::Ptr &todo) { return !todo->isCompleted() && todo->dtDue().date() == QDate::currentDate(); } TodoModel::Private::Private(const EventViews::PrefsPtr &preferences, TodoModel *qq) : QObject() , m_changer(nullptr) , m_preferences(preferences) , q(qq) { } Akonadi::Item TodoModel::Private::findItemByUid(const QString &uid, const QModelIndex &parent) const { Q_ASSERT(!uid.isEmpty()); IncidenceTreeModel *treeModel = qobject_cast(q->sourceModel()); if (treeModel) { // O(1) Shortcut return treeModel->item(uid); } Akonadi::Item item; const int count = q->rowCount(parent); for (int i = 0; i < count; ++i) { const QModelIndex currentIndex = q->index(i, 0, parent); Q_ASSERT(currentIndex.isValid()); item = q->data(currentIndex, Akonadi::EntityTreeModel::ItemRole).value(); if (item.isValid()) { return item; } else { item = findItemByUid(uid, currentIndex); if (item.isValid()) { return item; } } } return item; } void TodoModel::Private::onDataChanged(const QModelIndex &begin, const QModelIndex &end) { Q_ASSERT(begin.isValid()); Q_ASSERT(end.isValid()); const QModelIndex proxyBegin = q->mapFromSource(begin); Q_ASSERT(proxyBegin.column() == 0); const QModelIndex proxyEnd = q->mapFromSource(end); Q_EMIT q->dataChanged(proxyBegin, proxyEnd.sibling(proxyEnd.row(), TodoModel::ColumnCount - 1)); } void TodoModel::Private::onHeaderDataChanged(Qt::Orientation orientation, int first, int last) { Q_EMIT q->headerDataChanged(orientation, first, last); } void TodoModel::Private::onRowsAboutToBeInserted(const QModelIndex &parent, int begin, int end) { const QModelIndex index = q->mapFromSource(parent); Q_ASSERT(!(parent.isValid() ^ index.isValid())); // Both must be valid, or both invalid Q_ASSERT(!(index.isValid() && index.model() != q)); q->beginInsertRows(index, begin, end); } void TodoModel::Private::onRowsInserted(const QModelIndex &, int, int) { q->endInsertRows(); } void TodoModel::Private::onRowsAboutToBeRemoved(const QModelIndex &parent, int begin, int end) { const QModelIndex index = q->mapFromSource(parent); Q_ASSERT(!(parent.isValid() ^ index.isValid())); // Both must be valid, or both invalid Q_ASSERT(!(index.isValid() && index.model() != q)); q->beginRemoveRows(index, begin, end); } void TodoModel::Private::onRowsRemoved(const QModelIndex &, int, int) { q->endRemoveRows(); } void TodoModel::Private::onRowsAboutToBeMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationRow) { Q_UNUSED(sourceParent); Q_UNUSED(sourceStart); Q_UNUSED(sourceEnd); Q_UNUSED(destinationParent); Q_UNUSED(destinationRow); /* Disabled for now, layoutAboutToBeChanged() is emitted q->beginMoveRows( q->mapFromSource( sourceParent ), sourceStart, sourceEnd, q->mapFromSource( destinationParent ), destinationRow ); */ } void TodoModel::Private::onRowsMoved(const QModelIndex &, int, int, const QModelIndex &, int) { /*q->endMoveRows();*/ } void TodoModel::Private::onModelAboutToBeReset() { q->beginResetModel(); } void TodoModel::Private::onModelReset() { q->endResetModel(); } void TodoModel::Private::onLayoutAboutToBeChanged() { Q_ASSERT(m_persistentIndexes.isEmpty()); Q_ASSERT(m_layoutChangePersistentIndexes.isEmpty()); Q_ASSERT(m_columns.isEmpty()); const QModelIndexList persistentIndexes = q->persistentIndexList(); for (const QPersistentModelIndex &persistentIndex : persistentIndexes) { m_persistentIndexes << persistentIndex; // Stuff we have to update onLayoutChanged Q_ASSERT(persistentIndex.isValid()); QModelIndex index_col0 = q->createIndex(persistentIndex.row(), 0, persistentIndex.internalPointer()); const QPersistentModelIndex srcPersistentIndex = q->mapToSource(index_col0); Q_ASSERT(srcPersistentIndex.isValid()); m_layoutChangePersistentIndexes << srcPersistentIndex; m_columns << persistentIndex.column(); } Q_EMIT q->layoutAboutToBeChanged(); } void TodoModel::Private::onLayoutChanged() { for (int i = 0; i < m_persistentIndexes.size(); ++i) { QModelIndex newIndex_col0 = q->mapFromSource(m_layoutChangePersistentIndexes.at(i)); Q_ASSERT(newIndex_col0.isValid()); const int column = m_columns.at(i); QModelIndex newIndex = column == 0 ? newIndex_col0 : q->createIndex(newIndex_col0.row(), column, newIndex_col0.internalPointer()); q->changePersistentIndex(m_persistentIndexes.at(i), newIndex); } m_layoutChangePersistentIndexes.clear(); m_persistentIndexes.clear(); m_columns.clear(); Q_EMIT q->layoutChanged(); } TodoModel::TodoModel(const EventViews::PrefsPtr &preferences, QObject *parent) : QAbstractProxyModel(parent) , d(new Private(preferences, this)) { setObjectName(QStringLiteral("TodoModel")); } TodoModel::~TodoModel() { delete d; } QVariant TodoModel::data(const QModelIndex &index, int role) const { Q_ASSERT(index.isValid()); if (!index.isValid() || !d->m_calendar) { return QVariant(); } const QModelIndex sourceIndex = mapToSource(index.sibling(index.row(), 0)); if (!sourceIndex.isValid()) { return QVariant(); } Q_ASSERT(sourceIndex.isValid()); const Akonadi::Item item = sourceIndex.data(Akonadi::EntityTreeModel::ItemRole).value(); if (!item.isValid()) { qCWarning(CALENDARVIEW_LOG) << "Invalid index: " << sourceIndex; //Q_ASSERT( false ); return QVariant(); } const KCalCore::Todo::Ptr todo = CalendarSupport::todo(item); if (!todo) { qCCritical(CALENDARVIEW_LOG) << "item.hasPayload()" << item.hasPayload(); if (item.hasPayload()) { KCalCore::Incidence::Ptr incidence = item.payload(); if (incidence) { qCCritical(CALENDARVIEW_LOG) << "It's actually " << incidence->type(); } } Q_ASSERT(!"There's no to-do."); return QVariant(); } if (role == Qt::DisplayRole) { switch (index.column()) { case SummaryColumn: return QVariant(todo->summary()); case RecurColumn: if (todo->recurs()) { if (todo->hasRecurrenceId()) { return i18nc("yes, an exception to a recurring to-do", "Exception"); } else { return i18nc("yes, recurring to-do", "Yes"); } } else { return i18nc("no, not a recurring to-do", "No"); } case PriorityColumn: if (todo->priority() == 0) { return QVariant(QStringLiteral("--")); } return QVariant(todo->priority()); case PercentColumn: return QVariant(todo->percentComplete()); case StartDateColumn: return todo->hasStartDate() ? QLocale().toString( todo->dtStart().toLocalTime().date(), QLocale::ShortFormat) : QVariant(QString()); case DueDateColumn: return todo->hasDueDate() ? QLocale().toString( todo->dtDue().toLocalTime().date(), QLocale::ShortFormat) : QVariant(QString()); case CategoriesColumn: { QString categories = todo->categories().join( i18nc("delimiter for joining category names", ",")); return QVariant(categories); } case DescriptionColumn: return QVariant(todo->description()); case CalendarColumn: return QVariant(CalendarSupport::displayName(d->m_calendar.data(), item.parentCollection())); } return QVariant(); } if (role == Qt::EditRole) { switch (index.column()) { case SummaryColumn: return QVariant(todo->summary()); case RecurColumn: return QVariant(todo->recurs()); case PriorityColumn: return QVariant(todo->priority()); case PercentColumn: return QVariant(todo->percentComplete()); case StartDateColumn: return QVariant(todo->dtStart().date()); case DueDateColumn: return QVariant(todo->dtDue().date()); case CategoriesColumn: return QVariant(todo->categories()); case DescriptionColumn: return QVariant(todo->description()); case CalendarColumn: return QVariant(CalendarSupport::displayName(d->m_calendar.data(), item.parentCollection())); } return QVariant(); } // set the tooltip for every item if (role == Qt::ToolTipRole) { if (d->m_preferences->enableToolTips()) { return QVariant(KCalUtils::IncidenceFormatter::toolTipStr( CalendarSupport::displayName(d->m_calendar.data(), item.parentCollection()), todo, QDate(), true)); } else { return QVariant(); } } // background colour for todos due today or overdue todos if (role == Qt::BackgroundRole) { if (todo->isOverdue()) { return QVariant( QBrush(d->m_preferences->todoOverdueColor())); } else if (isDueToday(todo)) { return QVariant( QBrush(d->m_preferences->todoDueTodayColor())); } } // indicate if a row is checked (=completed) only in the first column if (role == Qt::CheckStateRole && index.column() == 0) { if (hasChildren(index) && !index.parent().isValid()) { return QVariant(); } if (todo->isCompleted()) { return QVariant(Qt::Checked); } else { return QVariant(Qt::Unchecked); } } // icon for recurring todos // It's in the summary column so you don't accidentally click // the checkbox ( which increments the next occurrence date ). if (role == Qt::DecorationRole && index.column() == SummaryColumn) { if (todo->recurs()) { return QVariant(QIcon::fromTheme(QStringLiteral("task-recurring"))); } } // category colour if (role == Qt::DecorationRole && index.column() == SummaryColumn) { QStringList categories = todo->categories(); return categories.isEmpty() ? QVariant() : QVariant(CalendarSupport::KCalPrefs::instance()->categoryColor(categories.first())); } else if (role == Qt::DecorationRole) { return QVariant(); } if (role == TodoRole) { return QVariant::fromValue(item); } if (role == IsRichTextRole) { if (index.column() == SummaryColumn) { return QVariant(todo->summaryIsRich()); } else if (index.column() == DescriptionColumn) { return QVariant(todo->descriptionIsRich()); } else { return QVariant(); } } if (role == Qt::TextAlignmentRole) { switch (index.column()) { // If you change this, change headerData() too. case RecurColumn: case PriorityColumn: case PercentColumn: case StartDateColumn: case DueDateColumn: case CategoriesColumn: case CalendarColumn: return QVariant(Qt::AlignHCenter | Qt::AlignVCenter); } return QVariant(Qt::AlignLeft | Qt::AlignVCenter); } if (sourceModel()) { return sourceModel()->data(mapToSource(index.sibling(index.row(), 0)), role); } return QVariant(); } bool TodoModel::setData(const QModelIndex &index, const QVariant &value, int role) { Q_ASSERT(index.isValid()); if (!d->m_changer) { return false; } const QVariant oldValue = data(index, role); if (oldValue == value) { // Nothing changed, the user used one of the QStyledDelegate's editors but seted the old value // Lets just skip this then and avoid a roundtrip to akonadi, and avoid sending invitations return true; } const Akonadi::Item item = data(index, Akonadi::EntityTreeModel::ItemRole).value(); const KCalCore::Todo::Ptr todo = CalendarSupport::todo(item); if (!item.isValid() || !todo) { qCWarning(CALENDARVIEW_LOG) << "TodoModel::setData() called, bug item is invalid or doesn't have payload"; Q_ASSERT(false); return false; } if (d->m_calendar->hasRight(item, Akonadi::Collection::CanChangeItem)) { KCalCore::Todo::Ptr oldTodo(todo->clone()); if (role == Qt::CheckStateRole && index.column() == 0) { const bool checked = static_cast(value.toInt()) == Qt::Checked; if (checked) { todo->setCompleted(QDateTime::currentDateTimeUtc()); // Because it calls Todo::recurTodo() } else { todo->setCompleted(false); } } if (role == Qt::EditRole) { switch (index.column()) { case SummaryColumn: if (!value.toString().isEmpty()) { todo->setSummary(value.toString()); } break; case PriorityColumn: todo->setPriority(value.toInt()); break; case PercentColumn: todo->setPercentComplete(value.toInt()); break; case StartDateColumn: { QDateTime tmp = todo->dtStart(); tmp.setDate(value.toDate()); todo->setDtStart(tmp); break; } case DueDateColumn: { QDateTime tmp = todo->dtDue(); tmp.setDate(value.toDate()); todo->setDtDue(tmp); break; } case CategoriesColumn: todo->setCategories(value.toStringList()); break; case DescriptionColumn: todo->setDescription(value.toString()); break; } } if (!todo->dirtyFields().isEmpty()) { d->m_changer->modifyIncidence(item, oldTodo); // modifyIncidence will eventually call the view's // changeIncidenceDisplay method, which in turn // will call processChange. processChange will then emit // dataChanged to the view, so we don't have to // do it here } return true; } else { if (!(role == Qt::CheckStateRole && index.column() == 0)) { //KOHelper::showSaveIncidenceErrorMsg( 0, todo ); //TODO pass parent qCCritical(CALENDARVIEW_LOG) << "Unable to modify incidence"; } return false; } } int TodoModel::rowCount(const QModelIndex &parent) const { if (sourceModel()) { if (parent.isValid()) { QModelIndex parent_col0 = createIndex(parent.row(), 0, parent.internalPointer()); return sourceModel()->rowCount(mapToSource(parent_col0)); } else { return sourceModel()->rowCount(); } } return 0; } int TodoModel::columnCount(const QModelIndex &) const { return ColumnCount; } void TodoModel::setSourceModel(QAbstractItemModel *model) { if (model == sourceModel()) { return; } beginResetModel(); if (sourceModel()) { disconnect(sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), d, SLOT(onDataChanged(QModelIndex,QModelIndex))); disconnect(sourceModel(), SIGNAL(headerDataChanged(Qt::Orientation,int,int)), d, SLOT(onHeaderDataChanged(Qt::Orientation,int,int))); disconnect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), d, SLOT(onRowsInserted(QModelIndex,int,int))); disconnect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), d, SLOT(onRowsRemoved(QModelIndex,int,int))); disconnect(sourceModel(), SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), d, SLOT(onRowsMoved(QModelIndex,int,int,QModelIndex,int))); disconnect(sourceModel(), SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), d, SLOT(onRowsAboutToBeInserted(QModelIndex,int,int))); disconnect(sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), d, SLOT(onRowsAboutToBeRemoved(QModelIndex,int,int))); disconnect(sourceModel(), SIGNAL(modelAboutToBeReset()), d, SLOT(onModelAboutToBeReset())); disconnect(sourceModel(), SIGNAL(modelReset()), d, SLOT(onModelReset())); disconnect(sourceModel(), SIGNAL(layoutAboutToBeChanged()), d, SLOT(onLayoutAboutToBeChanged())); disconnect(sourceModel(), SIGNAL(layoutChanged()), d, SLOT(onLayoutChanged())); } QAbstractProxyModel::setSourceModel(model); if (sourceModel()) { connect(sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), d, SLOT(onDataChanged(QModelIndex,QModelIndex))); connect(sourceModel(), SIGNAL(headerDataChanged(Qt::Orientation,int,int)), d, SLOT(onHeaderDataChanged(Qt::Orientation,int,int))); connect(sourceModel(), SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), d, SLOT(onRowsAboutToBeInserted(QModelIndex,int,int))); connect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), d, SLOT(onRowsInserted(QModelIndex,int,int))); connect(sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), d, SLOT(onRowsAboutToBeRemoved(QModelIndex,int,int))); connect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), d, SLOT(onRowsRemoved(QModelIndex,int,int))); connect(sourceModel(), SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), d, SLOT(onRowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int))); connect(sourceModel(), SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), d, SLOT(onRowsMoved(QModelIndex,int,int,QModelIndex,int))); connect(sourceModel(), SIGNAL(modelAboutToBeReset()), d, SLOT(onModelAboutToBeReset())); connect(sourceModel(), SIGNAL(modelReset()), d, SLOT(onModelReset())); connect(sourceModel(), SIGNAL(layoutAboutToBeChanged()), d, SLOT(onLayoutAboutToBeChanged())); connect(sourceModel(), SIGNAL(layoutChanged()), d, SLOT(onLayoutChanged())); } endResetModel(); } void TodoModel::setIncidenceChanger(Akonadi::IncidenceChanger *changer) { d->m_changer = changer; } QVariant TodoModel::headerData(int column, Qt::Orientation orientation, int role) const { if (orientation != Qt::Horizontal) { return QVariant(); } if (role == Qt::DisplayRole) { switch (column) { case SummaryColumn: return QVariant(i18n("Summary")); case RecurColumn: return QVariant(i18n("Recurs")); case PriorityColumn: return QVariant(i18n("Priority")); case PercentColumn: return QVariant(i18nc("@title:column percent complete", "Complete")); case StartDateColumn: return QVariant(i18n("Start Date")); case DueDateColumn: return QVariant(i18n("Due Date")); case CategoriesColumn: return QVariant(i18n("Categories")); case DescriptionColumn: return QVariant(i18n("Description")); case CalendarColumn: return QVariant(i18n("Calendar")); } } if (role == Qt::TextAlignmentRole) { switch (column) { // If you change this, change data() too. case RecurColumn: case PriorityColumn: case PercentColumn: case StartDateColumn: case DueDateColumn: case CategoriesColumn: case CalendarColumn: return QVariant(Qt::AlignHCenter); } return QVariant(); } return QVariant(); } void TodoModel::setCalendar(const Akonadi::ETMCalendar::Ptr &calendar) { d->m_calendar = calendar; } Qt::DropActions TodoModel::supportedDropActions() const { // Qt::CopyAction not supported yet return Qt::MoveAction; } QStringList TodoModel::mimeTypes() const { static QStringList list; if (list.isEmpty()) { list << KCalUtils::ICalDrag::mimeType() << KCalUtils::VCalDrag::mimeType(); } return list; } QMimeData *TodoModel::mimeData(const QModelIndexList &indexes) const { Akonadi::Item::List items; for (const QModelIndex &index : indexes) { const Akonadi::Item item = this->data(index, Akonadi::EntityTreeModel::ItemRole).value(); if (item.isValid() && !items.contains(item)) { items.push_back(item); } } return CalendarSupport::createMimeData(items); } bool TodoModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { Q_UNUSED(row); Q_UNUSED(column); if (action != Qt::MoveAction) { qCWarning(CALENDARVIEW_LOG) << "No action other than MoveAction currently supported!"; //TODO return false; } if (d->m_calendar && d->m_changer && (KCalUtils::ICalDrag::canDecode(data) || KCalUtils::VCalDrag::canDecode(data))) { KCalUtils::DndFactory dndFactory(d->m_calendar); KCalCore::Todo::Ptr t = dndFactory.createDropTodo(data); KCalCore::Event::Ptr e = dndFactory.createDropEvent(data); if (t) { // we don't want to change the created todo, but the one which is already // stored in our calendar / tree const Akonadi::Item item = d->findItemByUid(t->uid(), QModelIndex()); KCalCore::Todo::Ptr todo = CalendarSupport::todo(item); KCalCore::Todo::Ptr destTodo; if (parent.isValid()) { const Akonadi::Item parentItem = this->data(parent, Akonadi::EntityTreeModel::ItemRole).value(); if (parentItem.isValid()) { destTodo = CalendarSupport::todo(parentItem); } } KCalCore::Incidence::Ptr tmp = destTodo; while (tmp) { if (tmp->uid() == todo->uid()) { //correct, don't use instanceIdentifier() here KMessageBox::information( nullptr, i18n("Cannot move to-do to itself or a child of itself."), i18n("Drop To-do"), QStringLiteral("NoDropTodoOntoItself")); return false; } const QString parentUid = tmp->relatedTo(); tmp = CalendarSupport::incidence(d->m_calendar->item(parentUid)); } if (!destTodo || !destTodo->hasRecurrenceId()) { KCalCore::Todo::Ptr oldTodo = KCalCore::Todo::Ptr(todo->clone()); // destTodo is empty when we drag a to-do out of a relationship todo->setRelatedTo(destTodo ? destTodo->uid() : QString()); d->m_changer->modifyIncidence(item, oldTodo); // again, no need to Q_EMIT dataChanged, that's done by processChange return true; } else { qCDebug(CALENDARVIEW_LOG) << "Todo's with recurring id can't have child todos yet."; return false; } } else if (e) { // TODO: Implement dropping an event onto a to-do: Generate a relationship to the event! } else { if (!parent.isValid()) { // TODO we should create a new todo with the data in the drop object qCDebug(CALENDARVIEW_LOG) << "TODO: Create a new todo with the given data"; return false; } const Akonadi::Item parentItem = this->data(parent, Akonadi::EntityTreeModel::ItemRole).value(); KCalCore::Todo::Ptr destTodo = CalendarSupport::todo(parentItem); if (data->hasText()) { QString text = data->text(); KCalCore::Todo::Ptr oldTodo = KCalCore::Todo::Ptr(destTodo->clone()); if (text.startsWith(QLatin1String("file:"))) { destTodo->addAttachment(KCalCore::Attachment::Ptr(new KCalCore::Attachment( text))); } else { QStringList emails = KEmailAddress::splitAddressList(text); for (QStringList::ConstIterator it = emails.constBegin(); it != emails.constEnd(); ++it) { QString name, email, comment; if (KEmailAddress::splitAddress(*it, name, email, comment) == KEmailAddress::AddressOk) { - destTodo->addAttendee( - KCalCore::Attendee::Ptr(new KCalCore::Attendee(name, email))); + destTodo->addAttendee(KCalCore::Attendee(name, email)); } } } d->m_changer->modifyIncidence(parentItem, oldTodo); return true; } } } return false; } Qt::ItemFlags TodoModel::flags(const QModelIndex &index) const { if (!index.isValid()) { return nullptr; } Qt::ItemFlags ret = QAbstractItemModel::flags(index); const Akonadi::Item item = data(index, Akonadi::EntityTreeModel::ItemRole).value(); if (!item.isValid()) { Q_ASSERT(mapToSource(index).isValid()); qCWarning(CALENDARVIEW_LOG) << "Item is invalid " << index; Q_ASSERT(false); return nullptr; } ret |= Qt::ItemIsDragEnabled; const KCalCore::Todo::Ptr todo = CalendarSupport::todo(item); if (d->m_calendar->hasRight(item, Akonadi::Collection::CanChangeItem)) { // the following columns are editable: switch (index.column()) { case SummaryColumn: case PriorityColumn: case PercentColumn: case StartDateColumn: case DueDateColumn: case CategoriesColumn: ret |= Qt::ItemIsEditable; break; case DescriptionColumn: if (!todo->descriptionIsRich()) { ret |= Qt::ItemIsEditable; } break; } } if (index.column() == 0) { // whole rows should have checkboxes, so append the flag for the // first item of every row only. Also, only the first item of every // row should be used as a target for a drag and drop operation. ret |= Qt::ItemIsUserCheckable |Qt::ItemIsDropEnabled; } return ret; } QModelIndex TodoModel::mapFromSource(const QModelIndex &sourceIndex) const { if (!sourceModel() || !sourceIndex.isValid()) { return QModelIndex(); } Q_ASSERT(sourceIndex.internalPointer()); return createIndex(sourceIndex.row(), 0, sourceIndex.internalPointer()); } QModelIndex TodoModel::mapToSource(const QModelIndex &proxyIndex) const { if (!sourceModel() || !proxyIndex.isValid()) { return QModelIndex(); } if (proxyIndex.column() != 0) { qCCritical(CALENDARVIEW_LOG) << "Map to source called with column>0, but source model only has 1 column"; Q_ASSERT(false); } Q_ASSERT(proxyIndex.internalPointer()); // we convert to column 0 const QModelIndex sourceIndex = SourceModelIndex(proxyIndex.row(), 0, proxyIndex.internalPointer(), sourceModel()); return sourceIndex; } QModelIndex TodoModel::index(int row, int column, const QModelIndex &parent) const { if (!sourceModel()) { return QModelIndex(); } Q_ASSERT(!parent.isValid() || parent.internalPointer()); QModelIndex parent_col0 = parent.isValid() ? createIndex(parent.row(), 0, parent.internalPointer()) : QModelIndex(); // Lets preserve the original internalPointer const QModelIndex index = mapFromSource(sourceModel()->index(row, 0, mapToSource(parent_col0))); Q_ASSERT(!index.isValid() || index.internalPointer()); if (index.isValid()) { return createIndex(row, column, index.internalPointer()); } return QModelIndex(); } QModelIndex TodoModel::parent(const QModelIndex &child) const { if (!sourceModel() || !child.isValid()) { return QModelIndex(); } Q_ASSERT(child.internalPointer()); const QModelIndex child_col0 = createIndex(child.row(), 0, child.internalPointer()); QModelIndex parentIndex = mapFromSource(sourceModel()->parent(mapToSource(child_col0))); Q_ASSERT(!parentIndex.isValid() || parentIndex.internalPointer()); if (parentIndex.isValid()) { // preserve original column return createIndex(parentIndex.row(), child.column(), parentIndex.internalPointer()); } return QModelIndex(); } QModelIndex TodoModel::buddy(const QModelIndex &index) const { // We reimplement because the default implementation calls mapToSource() and // source model doesn't have the same number of columns. return index; } diff --git a/src/whatsnext/whatsnextview.cpp b/src/whatsnext/whatsnextview.cpp index f9ea531..12a57c9 100644 --- a/src/whatsnext/whatsnextview.cpp +++ b/src/whatsnext/whatsnextview.cpp @@ -1,349 +1,349 @@ /* This file is part of KOrganizer. Copyright (c) 2001 Cornelius Schumacher 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 "whatsnextview.h" #include #include #include #include #include #include using namespace EventViews; void WhatsNextTextBrowser::setSource(const QUrl &name) { QString uri = name.toString(); if (uri.startsWith(QLatin1String("event:"))) { Q_EMIT showIncidence(uri); } else if (uri.startsWith(QLatin1String("todo:"))) { Q_EMIT showIncidence(uri); } else { QTextBrowser::setSource(QUrl(uri)); } } WhatsNextView::WhatsNextView(QWidget *parent) : EventView(parent) { mView = new WhatsNextTextBrowser(this); connect(mView, &WhatsNextTextBrowser::showIncidence, this, &WhatsNextView::showIncidence); QBoxLayout *topLayout = new QVBoxLayout(this); topLayout->setContentsMargins(0, 0, 0, 0); topLayout->addWidget(mView); } WhatsNextView::~WhatsNextView() { } int WhatsNextView::currentDateCount() const { return mStartDate.daysTo(mEndDate); } void WhatsNextView::createTaskRow(KIconLoader *kil) { QString ipath; kil->loadIcon(QStringLiteral("view-calendar-tasks"), KIconLoader::NoGroup, 22, KIconLoader::DefaultState, QStringList(), &ipath); mText += QLatin1String("

"); mText += i18n("To-dos:") + QLatin1String("

\n"); mText += QLatin1String("
    \n"); } void WhatsNextView::updateView() { KIconLoader *kil = KIconLoader::global(); QString ipath; kil->loadIcon(QStringLiteral("office-calendar"), KIconLoader::NoGroup, 32, KIconLoader::DefaultState, QStringList(), &ipath); mText = QStringLiteral("\n"); mText += QLatin1String("\n\n

    "); mText += QLatin1String(""); mText += QLatin1String(" "); mText += i18n("What's Next?") + QLatin1String("

    "); mText += QLatin1String("
    "); mText += QLatin1String("

    "); if (mStartDate.daysTo(mEndDate) < 1) { mText += QLocale::system().toString(mStartDate); } else { mText += i18nc( "date from - to", "%1 - %2", QLocale::system().toString(mStartDate), QLocale::system().toString(mEndDate)); } mText += QLatin1String("

    \n"); KCalCore::Event::List events; events = calendar()->events(mStartDate, mEndDate, QTimeZone::systemTimeZone(), false); events = calendar()->sortEvents(events, KCalCore::EventSortStartDate, KCalCore::SortDirectionAscending); if (!events.isEmpty()) { mText += QLatin1String("

    "); kil->loadIcon(QStringLiteral("view-calendar-day"), KIconLoader::NoGroup, 22, KIconLoader::DefaultState, QStringList(), &ipath); mText += QLatin1String("

    "); mText += i18n("Events:") + QLatin1String("

    \n"); mText += QLatin1String("\n"); for (const KCalCore::Event::Ptr &ev : qAsConst(events)) { if (!ev->recurs()) { appendEvent(ev); } else { KCalCore::Recurrence *recur = ev->recurrence(); int duration = ev->dtStart().secsTo(ev->dtEnd()); QDateTime start = recur->getPreviousDateTime(QDateTime(mStartDate, QTime(), Qt::LocalTime)); QDateTime end = start.addSecs(duration); QDateTime endDate(mEndDate, QTime(23, 59, 59), Qt::LocalTime); if (end.date() >= mStartDate) { appendEvent(ev, start.toLocalTime(), end.toLocalTime()); } const auto times = recur->timesInInterval(start, endDate); int count = times.count(); if (count > 0) { int i = 0; if (times[0] == start) { ++i; // start has already been appended } if (!times[count - 1].isValid()) { --count; // list overflow } for (; i < count && times[i].date() <= mEndDate; ++i) { appendEvent(ev, times[i].toLocalTime()); } } } } mText += QLatin1String("
    \n"); } mTodos.clear(); KCalCore::Todo::List todos = calendar()->todos(KCalCore::TodoSortDueDate, KCalCore::SortDirectionAscending); if (!todos.isEmpty()) { bool taskHeaderWasCreated = false; for (const KCalCore::Todo::Ptr &todo : qAsConst(todos)) { if (!todo->isCompleted() && todo->hasDueDate() && todo->dtDue().date() <= mEndDate) { if (!taskHeaderWasCreated) { createTaskRow(kil); taskHeaderWasCreated = true; } appendTodo(todo); } } bool gotone = false; int priority = 1; while (!gotone && priority <= 9) { for (const KCalCore::Todo::Ptr &todo : qAsConst(todos)) { if (!todo->isCompleted() && (todo->priority() == priority)) { if (!taskHeaderWasCreated) { createTaskRow(kil); taskHeaderWasCreated = true; } appendTodo(todo); gotone = true; } } priority++; } if (taskHeaderWasCreated) { mText += QLatin1String("\n"); } } QStringList myEmails(CalendarSupport::KCalPrefs::instance()->allEmails()); int replies = 0; events = calendar()->events(QDate::currentDate(), QDate(2975, 12, 6), QTimeZone::systemTimeZone()); for (const KCalCore::Event::Ptr &ev : qAsConst(events)) { - KCalCore::Attendee::Ptr me = ev->attendeeByMails(myEmails); - if (me != nullptr) { - if (me->status() == KCalCore::Attendee::NeedsAction && me->RSVP()) { + KCalCore::Attendee me = ev->attendeeByMails(myEmails); + if (!me.isNull()) { + if (me.status() == KCalCore::Attendee::NeedsAction && me.RSVP()) { if (replies == 0) { mText += QLatin1String("

    "); kil->loadIcon(QStringLiteral("mail-reply-sender"), KIconLoader::NoGroup, 22, KIconLoader::DefaultState, QStringList(), &ipath); mText += QLatin1String("

    "); mText += i18n("Events and to-dos that need a reply:") + QLatin1String("

    \n"); mText += QLatin1String("\n"); } replies++; appendEvent(ev); } } } todos = calendar()->todos(); for (const KCalCore::Todo::Ptr &to : qAsConst(todos)) { - KCalCore::Attendee::Ptr me = to->attendeeByMails(myEmails); - if (me != nullptr) { - if (me->status() == KCalCore::Attendee::NeedsAction && me->RSVP()) { + KCalCore::Attendee me = to->attendeeByMails(myEmails); + if (!me.isNull()) { + if (me.status() == KCalCore::Attendee::NeedsAction && me.RSVP()) { if (replies == 0) { mText += QLatin1String("

    "); kil->loadIcon(QStringLiteral("mail-reply-sender"), KIconLoader::NoGroup, 22, KIconLoader::DefaultState, QStringList(), &ipath); mText += QLatin1String("

    "); mText += i18n("Events and to-dos that need a reply:") + QLatin1String("

    \n"); mText += QLatin1String("
    \n"); } replies++; appendEvent(to); } } } if (replies > 0) { mText += QLatin1String("
    \n"); } mText += QLatin1String("
    \n"); mView->setText(mText); } void WhatsNextView::showDates(const QDate &start, const QDate &end, const QDate &) { mStartDate = start; mEndDate = end; updateView(); } void WhatsNextView::showIncidences(const Akonadi::Item::List &incidenceList, const QDate &date) { Q_UNUSED(incidenceList); Q_UNUSED(date); } void WhatsNextView::changeIncidenceDisplay(const Akonadi::Item &, Akonadi::IncidenceChanger::ChangeType) { updateView(); } void WhatsNextView::appendEvent(const KCalCore::Incidence::Ptr &incidence, const QDateTime &start, const QDateTime &end) { mText += QLatin1String(""); if (const KCalCore::Event::Ptr event = incidence.dynamicCast()) { auto starttime = start.toLocalTime(); if (!starttime.isValid()) { starttime = event->dtStart().toLocalTime(); } auto endtime = end.toLocalTime(); if (!endtime.isValid()) { endtime = starttime.addSecs(event->dtStart().secsTo(event->dtEnd())); } if (starttime.date().daysTo(endtime.date()) >= 1) { if (event->allDay()) { mText += i18nc("date from - to", "%1 - %2", QLocale().toString(starttime.date(), QLocale::ShortFormat), QLocale().toString(endtime.date(), QLocale::ShortFormat)); } else { mText += i18nc("date from - to", "%1 - %2", QLocale().toString(starttime, QLocale::ShortFormat), QLocale().toString(endtime, QLocale::ShortFormat)); } } else { if (event->allDay()) { mText += QLocale().toString(starttime.date(), QLocale::ShortFormat); } else { mText += i18nc("date, from - to", "%1, %2 - %3", QLocale().toString(starttime.date(), QLocale::ShortFormat), QLocale().toString(starttime.time(), QLocale::ShortFormat), QLocale().toString(endtime.time(), QLocale::ShortFormat)); } } } mText += QLatin1String("type() == KCalCore::Incidence::TypeEvent) { mText += QLatin1String("href=\"event:"); } if (incidence->type() == KCalCore::Incidence::TypeTodo) { mText += QLatin1String("href=\"todo:"); } mText += incidence->uid() + QLatin1String("\">"); mText += incidence->summary(); mText += QLatin1String("\n"); } void WhatsNextView::appendTodo(const KCalCore::Incidence::Ptr &incidence) { Akonadi::Item aitem = calendar()->item(incidence); if (mTodos.contains(aitem)) { return; } mTodos.append(aitem); mText += QLatin1String("
  • uid() + QLatin1String("\">"); mText += incidence->summary(); mText += QLatin1String(""); if (const KCalCore::Todo::Ptr todo = CalendarSupport::todo(aitem)) { if (todo->hasDueDate()) { mText += i18nc("to-do due date", " (Due: %1)", KCalUtils::IncidenceFormatter::dateTimeToString( todo->dtDue(), todo->allDay())); } mText += QLatin1String("
  • \n"); } } void WhatsNextView::showIncidence(const QString &uid) { Akonadi::Item item; Akonadi::ETMCalendar::Ptr cal = calendar(); if (!cal) { return; } if (uid.startsWith(QLatin1String("event:"))) { item = cal->item(uid.mid(6)); } else if (uid.startsWith(QLatin1String("todo:"))) { item = cal->item(uid.mid(5)); } if (item.isValid()) { Q_EMIT showIncidenceSignal(item); } }