diff --git a/CMakeLists.txt b/CMakeLists.txt index b817eda..f0ee2ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,98 +1,98 @@ cmake_minimum_required(VERSION 3.0) -set(PIM_VERSION "5.8.41") +set(PIM_VERSION "5.8.42") project(calendarsupport VERSION ${PIM_VERSION}) set(KF5_VERSION "5.46.0") find_package(ECM ${KF5_VERSION} CONFIG REQUIRED) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) set(LIBRARY_NAMELINK) include(GenerateExportHeader) include(ECMSetupVersion) include(ECMGenerateHeaders) include(ECMGeneratePriFile) include(CMakePackageConfigHelpers) include(ECMSetupVersion) include(FeatureSummary) include(KDEInstallDirs) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(ECMQtDeclareLoggingCategory) include(ECMAddTests) include(ECMCoverageOption) set(CALENDARSUPPORT_LIB_VERSION ${PIM_VERSION}) set(AKONADI_MIMELIB_VERSION "5.8.40") set(KDEPIM_LIB_VERSION "5.8.40") set(QT_REQUIRED_VERSION "5.9.0") set(KMIME_LIB_VERSION "5.8.40") set(CALENDARUTILS_LIB_VERSION "5.8.40") set(KCALENDARCORE_LIB_VERSION "5.8.40") set(IDENTITYMANAGEMENT_LIB_VERSION "5.8.40") set(AKONADICALENDAR_LIB_VERSION "5.8.40") set(PIMCOMMON_LIB_VERSION "5.8.40") set(AKONADI_VERSION "5.8.40") set(KIMAP_LIB_VERSION "5.8.40") find_package(KF5Akonadi ${AKONADI_VERSION} CONFIG REQUIRED) find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED Widgets Test UiTools PrintSupport) find_package(KF5I18n ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5IconThemes ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5GuiAddons ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5KIO ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5Mime ${KMIME_LIB_VERSION} CONFIG REQUIRED) find_package(KF5AkonadiMime ${AKONADI_MIMELIB_VERSION} CONFIG REQUIRED) find_package(KF5Codecs ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5CalendarUtils ${CALENDARUTILS_LIB_VERSION} CONFIG REQUIRED) find_package(KF5CalendarCore ${KCALENDARCORE_LIB_VERSION} CONFIG REQUIRED) find_package(KF5IdentityManagement ${IDENTITYMANAGEMENT_LIB_VERSION} CONFIG REQUIRED) find_package(KF5Holidays ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5AkonadiCalendar ${AKONADICALENDAR_LIB_VERSION} CONFIG REQUIRED) find_package(KF5PimCommon ${PIMCOMMON_LIB_VERSION} CONFIG REQUIRED) find_package(KF5KdepimDBusInterfaces ${KDEPIM_LIB_VERSION} CONFIG REQUIRED) find_package(KF5IMAP ${KIMAP_LIB_VERSION} CONFIG REQUIRED) ecm_setup_version(PROJECT VARIABLE_PREFIX CALENDARSUPPORT VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/calendarsupport_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5CalendarSupportConfigVersion.cmake" SOVERSION 5 ) ########### Targets ########### add_definitions("-DQT_NO_CAST_FROM_ASCII -DQT_NO_CAST_TO_ASCII") add_definitions(-DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT) #add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x060000) add_definitions(-DQT_NO_URL_CAST_FROM_STRING) ########### CMake Config Files ########### set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF5CalendarSupport") configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/KF5CalendarSupportConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KF5CalendarSupportConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KF5CalendarSupportConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KF5CalendarSupportConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) install(EXPORT KF5CalendarSupportTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KF5CalendarSupportTargets.cmake NAMESPACE KF5::) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/calendarsupport_version.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5} COMPONENT Devel ) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) add_subdirectory(src) install( FILES calendarsupport.categories calendarsupport.renamecategories DESTINATION ${KDE_INSTALL_CONFDIR} ) diff --git a/src/kcalprefs.kcfg b/src/kcalprefs.kcfg index e33398b..b7de260 100644 --- a/src/kcalprefs.kcfg +++ b/src/kcalprefs.kcfg @@ -1,227 +1,227 @@ Check this box to use the KDE-wide e-mail settings, which are defined using the System Settings "About Me" Module. Uncheck this box to be able to specify your full name and e-mail. true Enter the default time for events here. The default is used if you do not supply a start time. QDateTime(QDate(1752,1,1), QTime(10,0)) Enter default duration for events here. The default is used if you do not supply an end time. QDateTime(QDate(1752,1,1), QTime(1,0)) Check this box if you want to enable the specified file to be used as the default sound file for new reminders. You can always specify another file in the Reminder configuration accessible from the Event or To-do editors. false Set a file to be used as the default sound file for new reminders. You can always specify another file in the Reminder configuration accessible from the Event or To-do editors. Check this box if you want to enable reminders for all newly created Events. You can always turn-off the reminders in the Event editor dialog. By default, enable reminders for new events false Check this box if you want to enable reminders for all newly created To-dos. You can always turn-off the reminders in the To-do editor dialog. By default, enable reminders for new to-dos false Enter the default reminder time for all newly created items. The time unit is specified in the adjacent combobox. Default time for reminders 15 Enter the default reminder time units for all newly created items. The time is specified in the adjacent spinbox. Default time unit for reminders 0 Enter the start time for events here. This time should be the earliest time that you use for events. QDateTime(QDate(1752,1,1), QTime(7,0)) Check this box to prevent KOrganizer from marking the working hours on holidays. true - - - Select from which region you want to use the holidays here. Defined holidays are shown as non-working days in the date navigator, the agenda view, etc. + + + Select which regions you want to use the holidays here. Defined holidays are shown as non-working days in the date navigator, the agenda view, etc. Check this box to enable automatic generation of mails when creating, updating or deleting events (or to-dos) involving other attendees. You should check this box if you want to use the groupware functionality (e.g. Configuring Kontact as a KDE Kolab client). true InvitationPolicySend true false 1 UnitMonths true true actionArchive 31 Enter your full name here. This name will be displayed as "Organizer" in to-dos and events you create. i18n("Anonymous") Enter here your e-mail address. This e-mail address will be used to identify the owner of the calendar, and displayed in events and to-dos you create. i18n("nobody@nowhere") Select a color to use for the "no category" or "unset category" situation, when an item does not belong to any category. This color is used when drawing items in the agenda or month views using the "Only category" scheme. Use this color when drawing items without a category 151, 235, 121 diff --git a/src/utils.cpp b/src/utils.cpp index 2b4f09c..7e79e07 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -1,858 +1,888 @@ /* Copyright (c) 2009, 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Frank Osterfeld Author: Andras Mantia 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 "utils.h" #include "kcalprefs.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include "calendarsupport_debug.h" using namespace CalendarSupport; using namespace KHolidays; using namespace KCalCore; KCalCore::Incidence::Ptr CalendarSupport::incidence(const Akonadi::Item &item) { //relying on exception for performance reasons try { return item.payload(); } catch (Akonadi::PayloadException) { return KCalCore::Incidence::Ptr(); } } KCalCore::Event::Ptr CalendarSupport::event(const Akonadi::Item &item) { //relying on exception for performance reasons try { KCalCore::Incidence::Ptr incidence = item.payload(); if (hasEvent(incidence)) { return item.payload(); } } catch (Akonadi::PayloadException) { return KCalCore::Event::Ptr(); } return KCalCore::Event::Ptr(); } KCalCore::Event::Ptr CalendarSupport::event(const KCalCore::Incidence::Ptr &incidence) { if (hasEvent(incidence)) { return incidence.staticCast(); } return KCalCore::Event::Ptr(); } KCalCore::Incidence::List CalendarSupport::incidencesFromItems(const Akonadi::Item::List &items) { KCalCore::Incidence::List incidences; for (const Akonadi::Item &item : items) { if (const KCalCore::Incidence::Ptr e = CalendarSupport::incidence(item)) { incidences.push_back(e); } } return incidences; } KCalCore::Todo::Ptr CalendarSupport::todo(const Akonadi::Item &item) { try { KCalCore::Incidence::Ptr incidence = item.payload(); if (hasTodo(incidence)) { return item.payload(); } } catch (Akonadi::PayloadException) { return KCalCore::Todo::Ptr(); } return KCalCore::Todo::Ptr(); } KCalCore::Todo::Ptr CalendarSupport::todo(const KCalCore::Incidence::Ptr &incidence) { if (hasTodo(incidence)) { return incidence.staticCast(); } return KCalCore::Todo::Ptr(); } KCalCore::Journal::Ptr CalendarSupport::journal(const Akonadi::Item &item) { try { KCalCore::Incidence::Ptr incidence = item.payload(); if (hasJournal(incidence)) { return item.payload(); } } catch (Akonadi::PayloadException) { return KCalCore::Journal::Ptr(); } return KCalCore::Journal::Ptr(); } KCalCore::Journal::Ptr CalendarSupport::journal(const KCalCore::Incidence::Ptr &incidence) { if (hasJournal(incidence)) { return incidence.staticCast(); } return KCalCore::Journal::Ptr(); } bool CalendarSupport::hasIncidence(const Akonadi::Item &item) { return item.hasPayload(); } bool CalendarSupport::hasEvent(const Akonadi::Item &item) { return item.hasPayload(); } bool CalendarSupport::hasEvent(const KCalCore::Incidence::Ptr &incidence) { return incidence && incidence->type() == KCalCore::Incidence::TypeEvent; } bool CalendarSupport::hasTodo(const Akonadi::Item &item) { return item.hasPayload(); } bool CalendarSupport::hasTodo(const KCalCore::Incidence::Ptr &incidence) { return incidence && incidence->type() == KCalCore::Incidence::TypeTodo; } bool CalendarSupport::hasJournal(const Akonadi::Item &item) { return item.hasPayload(); } bool CalendarSupport::hasJournal(const KCalCore::Incidence::Ptr &incidence) { return incidence && incidence->type() == KCalCore::Incidence::TypeJournal; } QMimeData *CalendarSupport::createMimeData(const Akonadi::Item::List &items) { if (items.isEmpty()) { return nullptr; } KCalCore::MemoryCalendar::Ptr cal(new KCalCore::MemoryCalendar(QTimeZone::systemTimeZone())); QList urls; int incidencesFound = 0; for (const Akonadi::Item &item : items) { const KCalCore::Incidence::Ptr incidence(CalendarSupport::incidence(item)); if (!incidence) { continue; } ++incidencesFound; urls.push_back(item.url()); KCalCore::Incidence::Ptr i(incidence->clone()); cal->addIncidence(i); } if (incidencesFound == 0) { return nullptr; } std::unique_ptr mimeData(new QMimeData); mimeData->setUrls(urls); KCalUtils::ICalDrag::populateMimeData(mimeData.get(), cal); return mimeData.release(); } QMimeData *CalendarSupport::createMimeData(const Akonadi::Item &item) { return createMimeData(Akonadi::Item::List() << item); } #ifndef QT_NO_DRAGANDDROP QDrag *CalendarSupport::createDrag(const Akonadi::Item &item, QWidget *parent) { return createDrag(Akonadi::Item::List() << item, parent); } #endif static QByteArray findMostCommonType(const Akonadi::Item::List &items) { QByteArray prev; if (items.isEmpty()) { return "Incidence"; } for (const Akonadi::Item &item : items) { if (!CalendarSupport::hasIncidence(item)) { continue; } const QByteArray type = CalendarSupport::incidence(item)->typeStr(); if (!prev.isEmpty() && type != prev) { return "Incidence"; } prev = type; } return prev; } #ifndef QT_NO_DRAGANDDROP QDrag *CalendarSupport::createDrag(const Akonadi::Item::List &items, QWidget *parent) { std::unique_ptr drag(new QDrag(parent)); drag->setMimeData(CalendarSupport::createMimeData(items)); const QByteArray common = findMostCommonType(items); if (common == "Event") { drag->setPixmap(BarIcon(QStringLiteral("view-calendar-day"))); } else if (common == "Todo") { drag->setPixmap(BarIcon(QStringLiteral("view-calendar-tasks"))); } return drag.release(); } #endif static bool itemMatches(const Akonadi::Item &item, const KCalCore::CalFilter *filter) { assert(filter); KCalCore::Incidence::Ptr inc = CalendarSupport::incidence(item); if (!inc) { return false; } return filter->filterIncidence(inc); } Akonadi::Item::List CalendarSupport::applyCalFilter(const Akonadi::Item::List &items_, const KCalCore::CalFilter *filter) { Q_ASSERT(filter); Akonadi::Item::List items(items_); items.erase(std::remove_if(items.begin(), items.end(), [filter](const Akonadi::Item &item) { return !itemMatches(item, filter); }), items.end()); return items; } bool CalendarSupport::isValidIncidenceItemUrl(const QUrl &url, const QStringList &supportedMimeTypes) { if (!url.isValid()) { return false; } if (url.scheme() != QLatin1String("akonadi")) { return false; } return supportedMimeTypes.contains(QUrlQuery(url).queryItemValue(QStringLiteral("type"))); } bool CalendarSupport::isValidIncidenceItemUrl(const QUrl &url) { return isValidIncidenceItemUrl(url, QStringList() << KCalCore::Event::eventMimeType() << KCalCore::Todo::todoMimeType() << KCalCore::Journal::journalMimeType() << KCalCore::FreeBusy::freeBusyMimeType()); } static bool containsValidIncidenceItemUrl(const QList &urls) { return std::find_if(urls.begin(), urls.end(), [](const QUrl &url) { return CalendarSupport::isValidIncidenceItemUrl(url); }) != urls.constEnd(); } bool CalendarSupport::canDecode(const QMimeData *md) { if (md) { return containsValidIncidenceItemUrl(md->urls()) || KCalUtils::ICalDrag::canDecode(md) || KCalUtils::VCalDrag::canDecode(md); } else { return false; } } QList CalendarSupport::incidenceItemUrls(const QMimeData *mimeData) { QList urls; const QList urlsList = mimeData->urls(); for (const QUrl &i : urlsList) { if (isValidIncidenceItemUrl(i)) { urls.push_back(i); } } return urls; } QList CalendarSupport::todoItemUrls(const QMimeData *mimeData) { QList urls; const QList urlList = mimeData->urls(); for (const QUrl &i : urlList) { if (isValidIncidenceItemUrl(i, QStringList() << KCalCore::Todo::todoMimeType())) { urls.push_back(i); } } return urls; } bool CalendarSupport::mimeDataHasIncidence(const QMimeData *mimeData) { return !incidenceItemUrls(mimeData).isEmpty() || !incidences(mimeData).isEmpty(); } KCalCore::Todo::List CalendarSupport::todos(const QMimeData *mimeData) { KCalCore::Todo::List todos; #ifndef QT_NO_DRAGANDDROP KCalCore::Calendar::Ptr cal(KCalUtils::DndFactory::createDropCalendar(mimeData)); if (cal) { const KCalCore::Todo::List calTodos = cal->todos(); todos.reserve(calTodos.count()); for (const KCalCore::Todo::Ptr &i : calTodos) { todos.push_back(KCalCore::Todo::Ptr(i->clone())); } } #endif return todos; } KCalCore::Incidence::List CalendarSupport::incidences(const QMimeData *mimeData) { KCalCore::Incidence::List incidences; #ifndef QT_NO_DRAGANDDROP KCalCore::Calendar::Ptr cal(KCalUtils::DndFactory::createDropCalendar(mimeData)); if (cal) { const KCalCore::Incidence::List calIncidences = cal->incidences(); incidences.reserve(calIncidences.count()); for (const KCalCore::Incidence::Ptr &i : calIncidences) { incidences.push_back(KCalCore::Incidence::Ptr(i->clone())); } } #endif return incidences; } Akonadi::Collection CalendarSupport::selectCollection(QWidget *parent, int &dialogCode, const QStringList &mimeTypes, const Akonadi::Collection &defCollection) { QPointer dlg(new Akonadi::CollectionDialog(parent)); dlg->setWindowTitle(i18n("Select Calendar")); dlg->setDescription(i18n("Select the calendar where this item will be stored.")); dlg->changeCollectionDialogOptions(Akonadi::CollectionDialog::KeepTreeExpanded); qCDebug(CALENDARSUPPORT_LOG) << "selecting collections with mimeType in " << mimeTypes; dlg->setMimeTypeFilter(mimeTypes); dlg->setAccessRightsFilter(Akonadi::Collection::CanCreateItem); if (defCollection.isValid()) { dlg->setDefaultCollection(defCollection); } Akonadi::Collection collection; // FIXME: don't use exec. dialogCode = dlg->exec(); if (dlg && dialogCode == QDialog::Accepted) { collection = dlg->selectedCollection(); if (!collection.isValid()) { qCWarning(CALENDARSUPPORT_LOG) << "An invalid collection was selected!"; } } delete dlg; return collection; } Akonadi::Item CalendarSupport::itemFromIndex(const QModelIndex &idx) { Akonadi::Item item = idx.data(Akonadi::EntityTreeModel::ItemRole).value(); item.setParentCollection( idx.data(Akonadi::EntityTreeModel::ParentCollectionRole).value()); return item; } Akonadi::Collection::List CalendarSupport::collectionsFromModel(const QAbstractItemModel *model, const QModelIndex &parentIndex, int start, int end) { const int endRow = end >= 0 ? end : model->rowCount(parentIndex) - 1; Akonadi::Collection::List collections; int row = start; QModelIndex i = model->index(row, 0, parentIndex); while (row <= endRow) { const Akonadi::Collection collection = collectionFromIndex(i); if (collection.isValid()) { collections << collection; QModelIndex childIndex = i.child(0, 0); if (childIndex.isValid()) { collections << collectionsFromModel(model, i); } } ++row; i = i.sibling(row, 0); } return collections; } Akonadi::Item::List CalendarSupport::itemsFromModel(const QAbstractItemModel *model, const QModelIndex &parentIndex, int start, int end) { const int endRow = end >= 0 ? end : model->rowCount(parentIndex) - 1; Akonadi::Item::List items; int row = start; QModelIndex i = model->index(row, 0, parentIndex); while (row <= endRow) { const Akonadi::Item item = itemFromIndex(i); if (CalendarSupport::hasIncidence(item)) { items << item; } else { QModelIndex childIndex = i.child(0, 0); if (childIndex.isValid()) { items << itemsFromModel(model, i); } } ++row; i = i.sibling(row, 0); } return items; } Akonadi::Collection CalendarSupport::collectionFromIndex(const QModelIndex &index) { return index.data(Akonadi::EntityTreeModel::CollectionRole).value(); } Akonadi::Collection::Id CalendarSupport::collectionIdFromIndex(const QModelIndex &index) { return index.data(Akonadi::EntityTreeModel::CollectionIdRole).value(); } Akonadi::Collection::List CalendarSupport::collectionsFromIndexes(const QModelIndexList &indexes) { Akonadi::Collection::List l; l.reserve(indexes.count()); for (const QModelIndex &idx : indexes) { l.push_back(collectionFromIndex(idx)); } return l; } QString CalendarSupport::displayName(Akonadi::ETMCalendar *calendar, const Akonadi::Collection &c) { Akonadi::Collection fullCollection; if (calendar && calendar->collection(c.id()).isValid()) { fullCollection = calendar->collection(c.id()); } else { fullCollection = c; } QString cName = fullCollection.name(); const QString resourceName = fullCollection.resource(); // Kolab Groupware if (resourceName.contains(QStringLiteral("kolab"))) { QString typeStr = cName; // contents type: "Calendar", "Tasks", etc QString ownerStr; // folder owner: "fred", "ethel", etc QString nameStr; // folder name: "Public", "Test", etc if (calendar) { Akonadi::Collection p = c.parentCollection(); while (p != Akonadi::Collection::root()) { Akonadi::Collection tCol = calendar->collection(p.id()); const QString tName = tCol.name(); if (tName.startsWith(QLatin1String("shared.cal"), Qt::CaseInsensitive)) { ownerStr = QStringLiteral("Shared"); nameStr = cName; typeStr = i18n("Calendar"); break; } else if (tName.startsWith(QLatin1String("shared.tasks"), Qt::CaseInsensitive) || tName.startsWith(QLatin1String("shared.todo"), Qt::CaseInsensitive)) { ownerStr = QStringLiteral("Shared"); nameStr = cName; typeStr = i18n("Tasks"); break; } else if (tName.startsWith(QLatin1String("shared.journal"), Qt::CaseInsensitive)) { ownerStr = QStringLiteral("Shared"); nameStr = cName; typeStr = i18n("Journal"); break; } else if (tName.startsWith(QLatin1String("shared.notes"), Qt::CaseInsensitive)) { ownerStr = QStringLiteral("Shared"); nameStr = cName; typeStr = i18n("Notes"); break; } else if (tName != i18n("Calendar") && tName != i18n("Tasks") && tName != i18n("Journal") && tName != i18n("Notes")) { ownerStr = tName; break; } else { nameStr = typeStr; typeStr = tName; } p = p.parentCollection(); } } if (!ownerStr.isEmpty()) { if (!ownerStr.compare(QLatin1String("INBOX"), Qt::CaseInsensitive)) { return i18nc("%1 is folder contents", "My Kolab %1", typeStr); } else if (!ownerStr.compare(QLatin1String("SHARED"), Qt::CaseInsensitive) || !ownerStr.compare(QLatin1String("CALENDAR"), Qt::CaseInsensitive) || !ownerStr.compare(QLatin1String("RESOURCES"), Qt::CaseInsensitive)) { return i18nc("%1 is folder name, %2 is folder contents", "Shared Kolab %1 %2", nameStr, typeStr); } else { if (nameStr.isEmpty()) { return i18nc("%1 is folder owner name, %2 is folder contents", "%1's Kolab %2", ownerStr, typeStr); } else { return i18nc( "%1 is folder owner name, %2 is folder name, %3 is folder contents", "%1's %2 Kolab %3", ownerStr, nameStr, typeStr); } } } else { return i18nc("%1 is folder contents", "Kolab %1", typeStr); } } //end kolab section // Dav Groupware if (resourceName.contains(QStringLiteral("davgroupware"))) { const QString resourceDisplayName = Akonadi::AgentManager::self()->instance(resourceName).name(); return i18nc("%1 is the folder name", "%1 in %2", fullCollection.displayName(), resourceDisplayName); } //end caldav section // Google if (resourceName.contains(QStringLiteral("google"))) { QString ownerStr; // folder owner: "user@gmail.com" if (calendar) { Akonadi::Collection p = c.parentCollection(); ownerStr = calendar->collection(p.id()).displayName(); } const QString nameStr = c.displayName(); // folder name: can be anything QString typeStr; const QString mimeStr = c.contentMimeTypes().join(QLatin1Char(',')); if (mimeStr.contains(QStringLiteral(".event"))) { typeStr = i18n("Calendar"); } else if (mimeStr.contains(QStringLiteral(".todo"))) { typeStr = i18n("Tasks"); } else if (mimeStr.contains(QStringLiteral(".journal"))) { typeStr = i18n("Journal"); } else if (mimeStr.contains(QStringLiteral(".note"))) { typeStr = i18n("Notes"); } else { typeStr = mimeStr; } if (!ownerStr.isEmpty()) { const int atChar = ownerStr.lastIndexOf(QLatin1Char('@')); if (atChar >= 0) { ownerStr.truncate(atChar); } if (nameStr.isEmpty()) { return i18nc("%1 is folder owner name, %2 is folder contents", "%1's Google %2", ownerStr, typeStr); } else { return i18nc("%1 is folder owner name, %2 is folder name", "%1's %2", ownerStr, nameStr); } } else { return i18nc("%1 is folder contents", "Google %1", typeStr); } } //end google section // Not groupware so the collection is "mine" const QString dName = fullCollection.displayName(); if (!dName.isEmpty()) { return fullCollection.name().startsWith(QLatin1String("akonadi_")) ? i18n("My %1", dName) : dName; } else if (!fullCollection.name().isEmpty()) { return fullCollection.name(); } else { return i18nc("unknown resource", "Unknown"); } } QString CalendarSupport::toolTipString(const Akonadi::Collection &coll, bool richText) { Q_UNUSED(richText); QString str = QLatin1String(""); // Display Name QString displayName; if (coll.hasAttribute()) { displayName = coll.attribute()->displayName(); } if (displayName.isEmpty()) { displayName = coll.name(); } str += QLatin1String("") + displayName + QLatin1String(""); str += QLatin1String("
"); // Calendar Type QString calendarType; if (!coll.isVirtual()) { const Akonadi::AgentInstance instance = Akonadi::AgentManager::self()->instance(coll.resource()); calendarType = instance.type().name(); } else { calendarType = i18nc("unknown calendar type", "unknown"); } str += QLatin1String("") + i18n("Calendar type:") + QLatin1String(""); str += QLatin1String(" ") + calendarType; // Read only? bool isReadOnly = !(coll.rights() & Akonadi::Collection::CanChangeItem); str += QLatin1String("
"); str += QLatin1String("") + i18n("Rights:") + QLatin1String(""); str += QLatin1String(" "); if (isReadOnly) { str += i18nc("the calendar is read-only", "read-only"); } else { str += i18nc("the calendar is read and write", "read+write"); } str += QLatin1String("
"); str += QLatin1String("
"); return str; } QString CalendarSupport::subMimeTypeForIncidence(const KCalCore::Incidence::Ptr &incidence) { return incidence->mimeType(); } QList CalendarSupport::workDays(const QDate &startDate, const QDate &endDate) { QList result; const int mask(~(KCalPrefs::instance()->mWorkWeekMask)); const int numDays = startDate.daysTo(endDate) + 1; for (int i = 0; i < numDays; ++i) { const QDate date = startDate.addDays(i); if (!(mask & (1 << (date.dayOfWeek() - 1)))) { result.append(date); } } if (KCalPrefs::instance()->mExcludeHolidays) { - // NOTE: KOGlobals, where this method comes from, used to hold a pointer to - // a KHolidays object. I'm not sure about how expensive it is, just - // creating one here. - const HolidayRegion holidays(KCalPrefs::instance()->mHolidays); - const Holiday::List list = holidays.holidays(startDate, endDate); - const int listCount(list.count()); - for (int i = 0; i < listCount; ++i) { - const Holiday &h = list.at(i); - if (h.dayType() == Holiday::NonWorkday) { - result.removeAll(h.observedStartDate()); + foreach (const QString ®ionStr, KCalPrefs::instance()->mHolidays) { + KHolidays::HolidayRegion region(regionStr); + if (region.isValid()) { + const KHolidays::Holiday::List list = region.holidays(startDate, endDate); + const int listCount(list.count()); + for (int i = 0; i < listCount; ++i) { + const Holiday &h = list.at(i); + if (h.dayType() == Holiday::NonWorkday) { + result.removeAll(h.observedStartDate()); + } + } } } } return result; } QStringList CalendarSupport::holiday(const QDate &date) { QStringList hdays; - const HolidayRegion holidays(KCalPrefs::instance()->mHolidays); - const Holiday::List list = holidays.holidays(date); - const int listCount = list.count(); - hdays.reserve(listCount); - for (int i = 0; i < listCount; ++i) { - hdays.append(list.at(i).name()); + bool showCountryCode = (KCalPrefs::instance()->mHolidays.count() > 1); + foreach (const QString ®ionStr, KCalPrefs::instance()->mHolidays) { + KHolidays::HolidayRegion region(regionStr); + if (region.isValid()) { + const Holiday::List list = region.holidays(date); + const int listCount = list.count(); + for (int i = 0; i < listCount; ++i) { + // don't add duplicates. + // TODO: won't find duplicates in different languages however. + const QString name = list.at(i).name(); + if (showCountryCode) { + // If more than one holiday region, append the country code to the holiday + // display name to help the user identify which region it belongs to. + const QRegularExpression holidaySE( + i18nc("search pattern for holidayname", "^%1", name)); + if (hdays.filter(holidaySE).isEmpty()) { + const QString pholiday = i18n("%1 (%2)", name, region.countryCode()); + hdays.append(pholiday); + } else { + // More than 1 region has the same holiday => remove the country code + // i.e don't show "Holiday (US)" and "Holiday(FR)"; just show "Holiday". + const QRegularExpression holidayRE( + i18nc("replace pattern for holidayname (countrycode)", "^%1 \\(.*\\)", name)); + hdays.replaceInStrings(holidayRE, name); + hdays.removeDuplicates(); + } + } else { + if (!hdays.contains(name)) { + hdays.append(name); + } + } + } + } } + return hdays; } void CalendarSupport::saveAttachments(const Akonadi::Item &item, QWidget *parentWidget) { Incidence::Ptr incidence = CalendarSupport::incidence(item); if (!incidence) { KMessageBox::sorry( parentWidget, i18n("No item selected.")); return; } Attachment::List attachments = incidence->attachments(); if (attachments.empty()) { return; } QString targetFile, targetDir; if (attachments.count() > 1) { // get the dir targetDir = QFileDialog::getExistingDirectory(parentWidget, i18n("Save Attachments To")); if (targetDir.isEmpty()) { return; } // we may not get a slash-terminated url out of KFileDialog if (!targetDir.endsWith(QLatin1Char('/'))) { targetDir.append(QLatin1Char('/')); } } else { // only one item, get the desired filename QString fileName = attachments.first()->label(); if (fileName.isEmpty()) { fileName = i18nc("filename for an unnamed attachment", "attachment.1"); } targetFile = QFileDialog::getSaveFileName(parentWidget, i18n("Save Attachment"), fileName); if (targetFile.isEmpty()) { return; } targetDir = QFileInfo(targetFile).absolutePath() + QLatin1Char('/'); } for (const Attachment::Ptr &attachment : qAsConst(attachments)) { targetFile = targetDir + attachment->label(); QUrl sourceUrl; if (attachment->isUri()) { sourceUrl = QUrl(attachment->uri()); } else { sourceUrl = QUrl::fromLocalFile(incidence->writeAttachmentToTempFile(attachment)); } // save the attachment url auto job = KIO::file_copy(sourceUrl, QUrl::fromLocalFile(targetFile)); if (!job->exec() && job->error()) { KMessageBox::error(parentWidget, job->errorString()); } } } QStringList CalendarSupport::categories(const KCalCore::Incidence::List &incidences) { QStringList cats, thisCats; // @TODO: For now just iterate over all incidences. In the future, // the list of categories should be built when reading the file. for (const KCalCore::Incidence::Ptr &incidence : incidences) { thisCats = incidence->categories(); const QStringList::ConstIterator send(thisCats.constEnd()); for (QStringList::ConstIterator si = thisCats.constBegin(); si != send; ++si) { if (!cats.contains(*si)) { cats.append(*si); } } } return cats; } bool CalendarSupport::mergeCalendar(const QString &srcFilename, const KCalCore::Calendar::Ptr &destCalendar) { if (srcFilename.isEmpty()) { qCCritical(CALENDARSUPPORT_LOG) << "Empty filename."; return false; } if (!QFile::exists(srcFilename)) { qCCritical(CALENDARSUPPORT_LOG) << "File'" << srcFilename << "' doesn't exist."; } // merge in a file destCalendar->startBatchAdding(); KCalCore::FileStorage storage(destCalendar); storage.setFileName(srcFilename); bool loadedSuccesfully = storage.load(); destCalendar->endBatchAdding(); return loadedSuccesfully; }