diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 854c533021d69b2b9b5b3347e7984e93ed322d7b..9b74332697f7792625b86b064b09dfe382cbbdec 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: BSD-2-Clause -add_executable(kalendar about.cpp main.cpp agentconfiguration.cpp incidenceoccurrencemodel.cpp monthviewmodel.cpp calendarmanager.cpp multidayincidencemodel.cpp incidencewrapper.cpp remindersmodel.cpp attendeesmodel.cpp recurrenceexceptionsmodel.cpp attachmentsmodel.cpp contactsmanager.cpp todomodel.cpp incidencetreemodel.cpp todosortfilterproxymodel.cpp kalendarapplication.cpp itemtagsmodel.cpp tagmanager.cpp resources.qrc) +add_executable(kalendar about.cpp main.cpp agentconfiguration.cpp incidenceoccurrencemodel.cpp monthviewmodel.cpp calendarmanager.cpp multidayincidencemodel.cpp incidencewrapper.cpp remindersmodel.cpp attendeesmodel.cpp recurrenceexceptionsmodel.cpp attachmentsmodel.cpp contactsmanager.cpp todomodel.cpp incidencetreemodel.cpp todosortfilterproxymodel.cpp kalendarapplication.cpp itemtagsmodel.cpp tagmanager.cpp extratodomodel.cpp resources.qrc) target_link_libraries(kalendar Qt5::Core diff --git a/src/contents/ui/IncidenceInfo.qml b/src/contents/ui/IncidenceInfo.qml index 482146bbddf61523f48afbd2831b06f33792d177..fdc09164dd1379cd76d7da159d1b7857ff8850aa 100644 --- a/src/contents/ui/IncidenceInfo.qml +++ b/src/contents/ui/IncidenceInfo.qml @@ -215,13 +215,19 @@ Kirigami.OverlayDrawer { text: i18n("Tags:") visible: incidenceInfo.incidenceWrapper.categories.length > 0 } - QQC2.Label { - Layout.alignment: Qt.AlignTop + Flow { Layout.fillWidth: true - visible: incidenceInfo.incidenceWrapper.categories.length > 0 - text: incidenceInfo.incidenceWrapper.categories.join(i18nc("List separator", ", ")) - wrapMode: Text.Wrap + spacing: Kirigami.Units.largeSpacing + Repeater { + model: incidenceInfo.incidenceWrapper.categories + Tag { + text: modelData + icon.name: "edit-delete-remove" + actionText: i18n("Remove %1 tag", modelData) + showAction: false + } + } } QQC2.Label { diff --git a/src/contents/ui/ScheduleView.qml b/src/contents/ui/ScheduleView.qml index b25e41c9334990594e56b2e859c9c75dc993ac99..184fde52807ae557f8810696a12554de015d659f 100644 --- a/src/contents/ui/ScheduleView.qml +++ b/src/contents/ui/ScheduleView.qml @@ -365,7 +365,7 @@ Kirigami.Page { property int dayOfMultidayIncidence: DateUtils.fullDaysBetweenDates(modelData.startTime, periodStartDate) Component.onCompleted: { - incidenceWrapper = Qt.createQmlObject('import org.kde.kalendar 1.0; IncidenceWrapper {id: incidence}', incidenceInfo, "incidence"); + incidenceWrapper = Qt.createQmlObject('import org.kde.kalendar 1.0; IncidenceWrapper {id: incidence}', incidenceCard, "incidence"); incidenceWrapper.incidencePtr = modelData.incidencePtr } diff --git a/src/contents/ui/Tag.qml b/src/contents/ui/Tag.qml new file mode 100644 index 0000000000000000000000000000000000000000..24db54231ef1ba5d328776313a0ebf72105c5aec --- /dev/null +++ b/src/contents/ui/Tag.qml @@ -0,0 +1,114 @@ +/* + * SPDX-FileCopyrightText: (C) 2021 Mikel Johnson + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Controls 2.15 as QQC2 +import org.kde.kirigami 2.15 as Kirigami + +Item { + id: tagRoot + property string text + property alias icon: toolButton.icon + property alias actionText: toolButton.text + property bool showAction: true + property bool isHeading: false + property alias itemLayout: layout + property alias labelItem: label + property alias headingItem: heading + signal clicked() + + implicitWidth: layout.implicitWidth + implicitHeight: layout.implicitHeight + + Kirigami.Theme.colorSet: Kirigami.Theme.View + Kirigami.Theme.inherit: false + + Rectangle { + id: mainBg + anchors.fill: parent + anchors.leftMargin: pointyBit.anchors.leftMargin + pointyBit.width / 2 - radius / 2 + radius: 3 + color: Kirigami.Theme.backgroundColor + border.color: Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, + Kirigami.Theme.textColor, + 0.3) + border.width: 1 + } + Rectangle { + id: pointyBit + antialiasing: true + rotation: 45 + y: (parent.height - height) / 2 + anchors.left: parent.left + anchors.leftMargin: y + radius / 2 + // `parent.height * Math.cos(radians)` fits a rotated square inside the parent. + // `rotation * (Math.PI / 180)` is rotation in radians instead of degrees. + // `Math.PI / 4` is 45 degrees. 180 / 4 is 45. + // `height + radius / 2` accounts for the rounded corners reducing visual size. + height: parent.height * Math.cos(Math.PI / 4) + radius / 2 + width: height + color: mainBg.color + border.width: 1 + border.color: mainBg.border.color + radius: 3 + } + Rectangle { + id: borderCover + antialiasing: true + anchors { + left: parent.left + top: parent.top + bottom: parent.bottom + leftMargin: pointyBit.anchors.leftMargin + pointyBit.width / 2 + margins: mainBg.border.width + } + width: height + color: mainBg.color + radius: mainBg.radius - mainBg.border.width + } + + RowLayout { + id: layout + spacing: Math.round(label.Layout.leftMargin - (toolButton.implicitWidth - toolButton.icon.width)) + anchors.fill: parent + QQC2.Label { + id: label + horizontalAlignment: Qt.AlignLeft + verticalAlignment: Qt.AlignVCenter + Layout.fillWidth: true + Layout.leftMargin: borderCover.anchors.leftMargin + Layout.rightMargin: tagRoot.showAction ? 0 : Kirigami.Units.smallSpacing + Layout.topMargin: tagRoot.showAction ? 0 : Kirigami.Units.smallSpacing + Layout.bottomMargin: tagRoot.showAction ? 0 : Kirigami.Units.smallSpacing + text: tagRoot.text + elide: Text.ElideRight + visible: !tagRoot.isHeading + } + Kirigami.Heading { + id: heading + horizontalAlignment: Qt.AlignLeft + verticalAlignment: Qt.AlignVCenter + Layout.fillWidth: true + Layout.leftMargin: borderCover.anchors.leftMargin + Layout.topMargin: Kirigami.Units.smallSpacing + Layout.bottomMargin: Kirigami.Units.smallSpacing + text: tagRoot.text + elide: Text.ElideRight + visible: tagRoot.isHeading + } + QQC2.ToolButton { + id: toolButton + icon.width: Kirigami.Units.iconSizes.sizeForLabels + icon.height: Kirigami.Units.iconSizes.sizeForLabels + text: i18n("Remove Tag") + display: QQC2.AbstractButton.IconOnly + onClicked: tagRoot.clicked() + visible: tagRoot.showAction + } + } +} + diff --git a/src/contents/ui/TodoPage.qml b/src/contents/ui/TodoPage.qml index a10da2216d9b704f8952b6970e79e1f530f0ea6e..934720af1e94c3f718e3e5235b0336e8ce2be226 100644 --- a/src/contents/ui/TodoPage.qml +++ b/src/contents/ui/TodoPage.qml @@ -163,7 +163,7 @@ Kirigami.Page { Kirigami.Heading { Layout.row: 0 Layout.column: 0 - Layout.columnSpan: root.width < Kirigami.Units.gridUnit * 30 || tagLayout.visible ? 1 : 2 + Layout.columnSpan: root.width < Kirigami.Units.gridUnit * 30 || filterTag.visible ? 1 : 2 Layout.fillWidth: true text: root.filterCollectionDetails && root.filterCollectionId > -1 ? root.filterCollectionDetails.displayName : i18n("All Todos") @@ -173,31 +173,31 @@ Kirigami.Page { elide: Text.ElideRight } - RowLayout { - id: tagLayout + Tag { + id: filterTag Layout.row: 0 Layout.column: 1 Layout.alignment: Qt.AlignRight visible: root.filterCategoryString + text: root.filterCategoryString - Kirigami.Heading { - Layout.alignment: Qt.AlignRight | Qt.AlignVCenter - text: root.filterCategoryString - color: root.filterCollectionDetails ? - LabelUtils.getIncidenceLabelColor(root.filterCollectionDetails.color, root.isDark) : Kirigami.Theme.textColor - } + implicitWidth: itemLayout.implicitWidth > headerLayout.width - Kirigami.Units.gridUnit * 6 ? + headerLayout.width - Kirigami.Units.gridUnit * 6 : itemLayout.implicitWidth + isHeading: true + headingItem.color: root.filterCollectionDetails ? + LabelUtils.getIncidenceLabelColor(root.filterCollectionDetails.color, root.isDark) : Kirigami.Theme.textColor + headingItem.font.weight: Font.Bold - QQC2.ToolButton { - icon.name: "edit-clear" - onClicked: root.filterCategoryString = "" - } + icon.name: "edit-delete-remove" + onClicked: root.filterCategoryString = "" + actionText: i18n("Remove filtering tag") } Kirigami.SearchField { id: searchField - Layout.column: root.width < Kirigami.Units.gridUnit * 30 || tagLayout.visible ? 0 : 1 - Layout.row: root.width < Kirigami.Units.gridUnit * 30 || tagLayout.visible ? 1 : 0 - Layout.columnSpan: root.width < Kirigami.Units.gridUnit * 30 || tagLayout.visible ? 2 : 1 + Layout.column: root.width < Kirigami.Units.gridUnit * 30 || filterTag.visible ? 0 : 1 + Layout.row: root.width < Kirigami.Units.gridUnit * 30 || filterTag.visible ? 1 : 0 + Layout.columnSpan: root.width < Kirigami.Units.gridUnit * 30 || filterTag.visible ? 2 : 1 Layout.fillWidth: Layout.row === 1 onTextChanged: incompleteView.model.filterTodoName(text); } diff --git a/src/contents/ui/TodoTreeView.qml b/src/contents/ui/TodoTreeView.qml index 3194e8daaf3bd69250ea7ba3072c91661bfffaf9..2e875fee4bac6f242eb559382a7c8390b83c2a69 100644 --- a/src/contents/ui/TodoTreeView.qml +++ b/src/contents/ui/TodoTreeView.qml @@ -52,7 +52,7 @@ KirigamiAddonsTreeView.TreeListView { Kirigami.PlaceholderMessage { anchors.centerIn: parent - visible: parent.count === 0 && !root.filterCollectionDetails.isFiltered + visible: parent.count === 0 && root.filterCollectionDetails && !root.filterCollectionDetails.isFiltered text: root.showCompleted === Kalendar.TodoSortFilterProxyModel.ShowCompleteOnly ? i18n("No todos completed") : i18n("No todos left to complete") helpfulAction: Kirigami.Action { @@ -110,6 +110,7 @@ KirigamiAddonsTreeView.TreeListView { Layout.row: 0 Layout.column: 0 + Layout.rowSpan: root.width < Kirigami.Units.gridUnit * 28 || recurIcon.visible || dateLabel.visible ? 1 : 2 color: model.color radius: 100 @@ -122,6 +123,7 @@ KirigamiAddonsTreeView.TreeListView { Layout.row: 0 Layout.column: 1 Layout.columnSpan: root.width < Kirigami.Units.gridUnit * 28 && (recurIcon.visible || dateLabel.visible) ? 2 : 1 + Layout.rowSpan: root.width < Kirigami.Units.gridUnit * 28 || recurIcon.visible || dateLabel.visible ? 1 : 2 Layout.fillWidth: true text: model.text font.strikeout: model.todoCompleted @@ -129,21 +131,32 @@ KirigamiAddonsTreeView.TreeListView { wrapMode: Text.Wrap } - QQC2.Label { - id: tagLabel + Flow { + id: tagFlow Layout.fillWidth: true - Layout.row: root.width < Kirigami.Units.gridUnit * 28 && (recurIcon.visible || dateLabel.visible) ? 1 : 0 + Layout.row: root.width < Kirigami.Units.gridUnit * 28 && (recurIcon.visible || dateLabel.visible || priorityLayout.visible) ? 1 : 0 Layout.column: 2 Layout.rowSpan: root.width < Kirigami.Units.gridUnit * 28 ? 1 : 2 Layout.columnSpan: root.width < Kirigami.Units.gridUnit * 28 ? 2 : 1 - Layout.alignment: Qt.AlignVCenter | Qt.AlignRight Layout.rightMargin: Kirigami.Units.largeSpacing - horizontalAlignment: Text.AlignRight - text: model.categoriesDisplay - elide: Text.ElideRight + + layoutDirection: Qt.RightToLeft + spacing: Kirigami.Units.largeSpacing + + Repeater { + id: tagsRepeater + model: todoModel.data(todoModel.index(index, 0), Kalendar.ExtraTodoModel.CategoriesRole) // Getting categories from the model is *very* faulty + + Tag { + implicitWidth: itemLayout.implicitWidth > tagFlow.width ? tagFlow.width : itemLayout.implicitWidth + text: modelData + showAction: false + } + } } RowLayout { + id: priorityLayout Layout.row: 0 Layout.column: 3 Layout.rowSpan: root.width < Kirigami.Units.gridUnit * 28 ? 1 : 2 diff --git a/src/extratodomodel.cpp b/src/extratodomodel.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8b0666afcacba251e2597478fcde16fbe8ba3fbd --- /dev/null +++ b/src/extratodomodel.cpp @@ -0,0 +1,178 @@ +// SPDX-FileCopyrightText: 2021 Claudio Cambra +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include + +ExtraTodoModel::ExtraTodoModel(QObject *parent) +: KExtraColumnsProxyModel(parent) { + const QString todoMimeType = QStringLiteral("application/x-vnd.akonadi.calendar.todo"); + m_todoTreeModel = new IncidenceTreeModel(QStringList() << todoMimeType, this); + const auto pref = EventViews::PrefsPtr(); + m_baseTodoModel = new TodoModel(pref, this); + m_baseTodoModel->setSourceModel(m_todoTreeModel); + setSourceModel(m_baseTodoModel); + + appendColumn(QLatin1String("StartDateTime")); + appendColumn(QLatin1String("EndDateTime")); + appendColumn(QLatin1String("PriorityInt")); + + KSharedConfig::Ptr config = KSharedConfig::openConfig(); + KConfigGroup rColorsConfig(config, "Resources Colors"); + m_colorWatcher = KConfigWatcher::create(config); + + QObject::connect(m_colorWatcher.data(), &KConfigWatcher::configChanged, this, &ExtraTodoModel::loadColors); + + loadColors(); +} + +QVariant ExtraTodoModel::extraColumnData(const QModelIndex &parent, int row, int extraColumn, int role) const { + if (role != Qt::DisplayRole && role != Qt::EditRole) { + return QVariant(); + } + + const auto todoItem = index(row, extraColumn, parent).data(TodoModel::TodoRole).value(); + const auto todoPtr = CalendarSupport::todo(todoItem); + + switch (extraColumn) { + case StartTimeColumn: + return todoPtr->dtStart(); + case EndTimeColumn: + return todoPtr->dtDue(); + case PriorityIntColumn: + return todoPtr->priority(); + default: + return QVariant(); + } +} + +QVariant ExtraTodoModel::data (const QModelIndex &index, int role) const { + if (!index.isValid()) { + return QVariant(); + } + + auto idx = mapToSource(index); + auto todoItem = idx.data(TodoModel::TodoRole).value(); + auto collectionId = todoItem.parentCollection().id(); + auto todoPtr = CalendarSupport::todo(todoItem); + + if(role == Roles::StartTimeRole) { + return todoPtr->dtStart(); + } else if (role == Roles::EndTimeRole) { + return todoPtr->dtDue(); + } else if(role == Roles::LocationRole) { + return todoPtr->location(); + } else if(role == Roles::AllDayRole) { + return todoPtr->allDay(); + } else if(role == Roles::ColorRole) { + QColor nullcolor; + return m_colors.contains(QString::number(collectionId)) ? m_colors[QString::number(collectionId)] : nullcolor; + } else if(role == Roles::CompletedRole) { + return todoPtr->isCompleted(); + } else if(role == Roles::PriorityRole) { + return todoPtr->priority(); + } else if(role == Roles::CollectionIdRole) { + return collectionId; + } else if (role == DurationStringRole) { + KFormat format; + if (todoPtr->allDay()) { + return format.formatSpelloutDuration(24*60*60*1000); // format milliseconds in 1 day + } + return format.formatSpelloutDuration(todoPtr->duration().asSeconds() * 1000); + } else if (role == Roles::RecursRole) { + return todoPtr->recurs(); + } else if (role == Roles::IsOverdueRole) { + return todoPtr->isOverdue(); + } else if(role == Roles::IncidenceIdRole) { + return todoPtr->uid(); + } else if(role == Roles::IncidenceTypeRole) { + return todoPtr->type(); + } else if(role == Roles::IncidenceTypeStrRole) { + return todoPtr->typeStr(); + } else if(role == Roles::IncidenceTypeIconRole) { + return todoPtr->iconName(); + } else if(role == Roles::IncidencePtrRole) { + return QVariant::fromValue(CalendarSupport::incidence(todoItem)); + } else if(role == Roles::TagsRole) { + return QVariant::fromValue(todoItem.tags()); + } else if(role == Roles::ItemRole) { + return QVariant::fromValue(todoItem); + } else if(role == Roles::CategoriesRole) { + return todoPtr->categories(); + } else if(role == Roles::CategoriesDisplayRole) { + return todoPtr->categories().join(i18nc("List separator", ", ")); + } else if(role == Roles::TreeDepthRole) { + int depth = 0; + auto idx = index; + while(idx.parent().isValid()) { + idx = idx.parent(); + depth++; + } + return depth; + } + + return KExtraColumnsProxyModel::data(index, role); +} + +QHash ExtraTodoModel::roleNames() const { + QHash roleNames = KExtraColumnsProxyModel::roleNames(); + roleNames[TodoModel::SummaryRole] = "text"; + roleNames[Roles::StartTimeRole] = "startTime"; + roleNames[Roles::EndTimeRole] = "endTime"; + roleNames[Roles::LocationRole] = "location"; + roleNames[Roles::AllDayRole] = "allDay"; + roleNames[Roles::ColorRole] = "color"; + roleNames[Roles::CompletedRole] = "todoCompleted"; + roleNames[Roles::PriorityRole] = "priority"; + roleNames[Roles::CollectionIdRole] = "collectionId"; + roleNames[Roles::DurationStringRole] = "durationString"; + roleNames[Roles::RecursRole] = "recurs"; + roleNames[Roles::IsOverdueRole] = "isOverdue"; + roleNames[Roles::IncidenceIdRole] = "incidenceId"; + roleNames[Roles::IncidenceTypeRole] = "incidenceType"; + roleNames[Roles::IncidenceTypeStrRole] = "incidenceTypeStr"; + roleNames[Roles::IncidenceTypeIconRole] = "incidenceTypeIcon"; + roleNames[Roles::IncidencePtrRole] = "incidencePtr"; + roleNames[Roles::TagsRole] = "tags"; + roleNames[Roles::ItemRole] = "item"; + roleNames[Roles::CategoriesRole] = "categories"; + roleNames[Roles::CategoriesDisplayRole] = "categoriesDisplay"; + roleNames[Roles::TreeDepthRole] = "treeDepth"; + + return roleNames; +} + +Akonadi::ETMCalendar::Ptr ExtraTodoModel::calendar() { + return m_calendar; +} + +void ExtraTodoModel::setCalendar(Akonadi::ETMCalendar::Ptr calendar) { + m_calendar = calendar; + m_todoTreeModel->setSourceModel(calendar->model()); + m_baseTodoModel->setCalendar(calendar); +} + +void ExtraTodoModel::setIncidenceChanger(Akonadi::IncidenceChanger* changer) { + m_baseTodoModel->setIncidenceChanger(changer); +} + +QHash ExtraTodoModel::colorCache() { + return m_colors; +} + +void ExtraTodoModel::setColorCache(QHash colorCache) { + m_colors = colorCache; +} + +void ExtraTodoModel::loadColors() { + KSharedConfig::Ptr config = KSharedConfig::openConfig(); + KConfigGroup rColorsConfig(config, "Resources Colors"); + const QStringList colorKeyList = rColorsConfig.keyList(); + + for (const QString &key : colorKeyList) { + QColor color = rColorsConfig.readEntry(key, QColor("blue")); + m_colors[key] = color; + } + Q_EMIT layoutChanged(); +} + +Q_DECLARE_METATYPE(KCalendarCore::Incidence::Ptr) diff --git a/src/extratodomodel.h b/src/extratodomodel.h new file mode 100644 index 0000000000000000000000000000000000000000..bda3c8c5636a7ed711a2c55c5180949214272ed3 --- /dev/null +++ b/src/extratodomodel.h @@ -0,0 +1,72 @@ +// SPDX-FileCopyrightText: 2021 Claudio Cambra +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once +#include +#include +#include +#include +#include +#include +#include + +class ExtraTodoModel : public KExtraColumnsProxyModel +{ + Q_OBJECT + +public: + enum Columns { + StartTimeColumn = 0, + EndTimeColumn, + PriorityIntColumn + }; + Q_ENUM(Columns); + + enum Roles { // Remember to update roles in todosortfilterproxymodel + StartTimeRole = TodoModel::CalendarRole + 1, + EndTimeRole, + LocationRole, + AllDayRole, + CompletedRole, + PriorityRole, + ColorRole, + CollectionIdRole, + DurationStringRole, + RecursRole, + IsOverdueRole, + IncidenceIdRole, + IncidenceTypeRole, + IncidenceTypeStrRole, + IncidenceTypeIconRole, + IncidencePtrRole, + TagsRole, + ItemRole, + CategoriesRole, + CategoriesDisplayRole, + TreeDepthRole + }; + Q_ENUM(Roles); + + ExtraTodoModel(QObject *parent = nullptr); + ~ExtraTodoModel() = default; + + QVariant extraColumnData(const QModelIndex &parent, int row, int extraColumn, int role = Qt::DisplayRole) const override; + QVariant data (const QModelIndex &index, int role = Qt::DisplayRole) const override; + QHash roleNames() const override; + + Akonadi::ETMCalendar::Ptr calendar(); + void setCalendar(Akonadi::ETMCalendar::Ptr calendar); + void setIncidenceChanger(Akonadi::IncidenceChanger* changer); + + QHash colorCache(); + void setColorCache(QHash colorCache); + void loadColors(); + +private: + Akonadi::ETMCalendar::Ptr m_calendar; + IncidenceTreeModel *m_todoTreeModel = nullptr; + TodoModel *m_baseTodoModel = nullptr; + QHash m_colors; + KConfigWatcher::Ptr m_colorWatcher; +}; + diff --git a/src/main.cpp b/src/main.cpp index 20dab3a0c96711926fae5b1545d0af2316413ae1..77873a598a8f06ba3cdd0c44bcfbc026c836d760 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -69,6 +69,7 @@ int main(int argc, char *argv[]) qmlRegisterType("org.kde.kalendar", 1, 0, "AttendeesModel"); qmlRegisterType("org.kde.kalendar", 1, 0, "MultiDayIncidenceModel"); qmlRegisterType("org.kde.kalendar", 1, 0, "IncidenceOccurrenceModel"); + qmlRegisterType("org.kde.kalendar", 1, 0, "ExtraTodoModel"); qmlRegisterType("org.kde.kalendar", 1, 0, "TodoSortFilterProxyModel"); qmlRegisterType("org.kde.kalendar", 1, 0, "MonthViewModel"); qmlRegisterType("org.kde.kalendar", 1, 0, "ItemTagsModel"); diff --git a/src/resources.qrc b/src/resources.qrc index 4f7eafb3eb6a756eb65fc47f99362d077df0a1c8..ef36c877de5e993789678cd74c727325594f54f7 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -36,5 +36,6 @@ contents/ui/NativeMenuItemFromAction.qml contents/ui/KActionFromAction.qml contents/ui/ColoredCheckbox.qml + contents/ui/Tag.qml diff --git a/src/todosortfilterproxymodel.cpp b/src/todosortfilterproxymodel.cpp index c39a4b824adbf5f6bd3365251196e4f950cbe6ab..2d84a1dfc64153f00ace285f6a9fc5ced19482e5 100644 --- a/src/todosortfilterproxymodel.cpp +++ b/src/todosortfilterproxymodel.cpp @@ -1,229 +1,8 @@ // SPDX-FileCopyrightText: 2021 Claudio Cambra // SPDX-License-Identifier: LGPL-2.1-or-later -#include #include -class ExtraTodoModel : public KExtraColumnsProxyModel -{ - Q_OBJECT - -public: - enum Columns { - StartTimeColumn = 0, - EndTimeColumn, - PriorityIntColumn - }; - Q_ENUM(Columns); - enum Roles { // Remember to update roles in todosortfilterproxymodel - StartTimeRole = TodoModel::CalendarRole + 1, - EndTimeRole, - LocationRole, - AllDayRole, - CompletedRole, - PriorityRole, - ColorRole, - CollectionIdRole, - DurationStringRole, - RecursRole, - IsOverdueRole, - IncidenceIdRole, - IncidenceTypeRole, - IncidenceTypeStrRole, - IncidenceTypeIconRole, - IncidencePtrRole, - TagsRole, - ItemRole, - CategoriesRole, - CategoriesDisplayRole, - TreeDepthRole - }; - Q_ENUM(Roles); - - ExtraTodoModel(QObject *parent = nullptr) : KExtraColumnsProxyModel(parent) { - const QString todoMimeType = QStringLiteral("application/x-vnd.akonadi.calendar.todo"); - m_todoTreeModel = new IncidenceTreeModel(QStringList() << todoMimeType, this); - const auto pref = EventViews::PrefsPtr(); - m_baseTodoModel = new TodoModel(pref, this); - m_baseTodoModel->setSourceModel(m_todoTreeModel); - setSourceModel(m_baseTodoModel); - - appendColumn(QLatin1String("StartDateTime")); - appendColumn(QLatin1String("EndDateTime")); - appendColumn(QLatin1String("PriorityInt")); - - KSharedConfig::Ptr config = KSharedConfig::openConfig(); - KConfigGroup rColorsConfig(config, "Resources Colors"); - m_colorWatcher = KConfigWatcher::create(config); - - QObject::connect(m_colorWatcher.data(), &KConfigWatcher::configChanged, this, &ExtraTodoModel::loadColors); - - loadColors(); - }; - ~ExtraTodoModel() = default; - - QVariant extraColumnData(const QModelIndex &parent, int row, int extraColumn, int role = Qt::DisplayRole) const override { - if (role != Qt::DisplayRole && role != Qt::EditRole) { - return QVariant(); - } - - const auto todoItem = index(row, extraColumn, parent).data(TodoModel::TodoRole).value(); - const auto todoPtr = CalendarSupport::todo(todoItem); - - switch (extraColumn) { - case StartTimeColumn: - return todoPtr->dtStart(); - case EndTimeColumn: - return todoPtr->dtDue(); - case PriorityIntColumn: - return todoPtr->priority(); - default: - return QVariant(); - } - }; - - QVariant data (const QModelIndex &index, int role = Qt::DisplayRole) const override { - if (!index.isValid()) { - return QVariant(); - } - - auto idx = mapToSource(index); - auto todoItem = idx.data(TodoModel::TodoRole).value(); - auto collectionId = todoItem.parentCollection().id(); - auto todoPtr = CalendarSupport::todo(todoItem); - - if(role == Roles::StartTimeRole) { - return todoPtr->dtStart(); - } else if (role == Roles::EndTimeRole) { - return todoPtr->dtDue(); - } else if(role == Roles::LocationRole) { - return todoPtr->location(); - } else if(role == Roles::AllDayRole) { - return todoPtr->allDay(); - } else if(role == Roles::ColorRole) { - QColor nullcolor; - return m_colors.contains(QString::number(collectionId)) ? m_colors[QString::number(collectionId)] : nullcolor; - } else if(role == Roles::CompletedRole) { - return todoPtr->isCompleted(); - } else if(role == Roles::PriorityRole) { - return todoPtr->priority(); - } else if(role == Roles::CollectionIdRole) { - return collectionId; - } else if (role == DurationStringRole) { - KFormat format; - if (todoPtr->allDay()) { - return format.formatSpelloutDuration(24*60*60*1000); // format milliseconds in 1 day - } - return format.formatSpelloutDuration(todoPtr->duration().asSeconds() * 1000); - } else if (role == Roles::RecursRole) { - return todoPtr->recurs(); - } else if (role == Roles::IsOverdueRole) { - return todoPtr->isOverdue(); - } else if(role == Roles::IncidenceIdRole) { - return todoPtr->uid(); - } else if(role == Roles::IncidenceTypeRole) { - return todoPtr->type(); - } else if(role == Roles::IncidenceTypeStrRole) { - return todoPtr->typeStr(); - } else if(role == Roles::IncidenceTypeIconRole) { - return todoPtr->iconName(); - } else if(role == Roles::IncidencePtrRole) { - return QVariant::fromValue(CalendarSupport::incidence(todoItem)); - } else if(role == Roles::TagsRole) { - return QVariant::fromValue(todoItem.tags()); - } else if(role == Roles::ItemRole) { - return QVariant::fromValue(todoItem); - } else if(role == Roles::CategoriesRole) { - return todoPtr->categories(); - } else if(role == Roles::CategoriesDisplayRole) { - return todoPtr->categories().join(i18nc("List separator", ", ")); - } else if(role == Roles::TreeDepthRole) { - int depth = 0; - auto idx = index; - while(idx.parent().isValid()) { - idx = idx.parent(); - depth++; - } - return depth; - } - - return KExtraColumnsProxyModel::data(index, role); - }; - - QHash roleNames() const override { - QHash roleNames = KExtraColumnsProxyModel::roleNames(); - roleNames[TodoModel::SummaryRole] = "text"; - roleNames[Roles::StartTimeRole] = "startTime"; - roleNames[Roles::EndTimeRole] = "endTime"; - roleNames[Roles::LocationRole] = "location"; - roleNames[Roles::AllDayRole] = "allDay"; - roleNames[Roles::ColorRole] = "color"; - roleNames[Roles::CompletedRole] = "todoCompleted"; - roleNames[Roles::PriorityRole] = "priority"; - roleNames[Roles::CollectionIdRole] = "collectionId"; - roleNames[Roles::DurationStringRole] = "durationString"; - roleNames[Roles::RecursRole] = "recurs"; - roleNames[Roles::IsOverdueRole] = "isOverdue"; - roleNames[Roles::IncidenceIdRole] = "incidenceId"; - roleNames[Roles::IncidenceTypeRole] = "incidenceType"; - roleNames[Roles::IncidenceTypeStrRole] = "incidenceTypeStr"; - roleNames[Roles::IncidenceTypeIconRole] = "incidenceTypeIcon"; - roleNames[Roles::IncidencePtrRole] = "incidencePtr"; - roleNames[Roles::TagsRole] = "tags"; - roleNames[Roles::ItemRole] = "item"; - roleNames[Roles::CategoriesRole] = "categories"; - roleNames[Roles::CategoriesDisplayRole] = "categoriesDisplay"; - roleNames[Roles::TreeDepthRole] = "treeDepth"; - - return roleNames; - } - - Akonadi::ETMCalendar::Ptr calendar() { - return m_calendar; - } - - void setCalendar(Akonadi::ETMCalendar::Ptr calendar) { - m_calendar = calendar; - m_todoTreeModel->setSourceModel(calendar->model()); - m_baseTodoModel->setCalendar(calendar); - }; - - void setIncidenceChanger(Akonadi::IncidenceChanger* changer) { - m_baseTodoModel->setIncidenceChanger(changer); - }; - - QHash colorCache() { - return m_colors; - }; - - void setColorCache(QHash colorCache) { - m_colors = colorCache; - }; - - void loadColors() { - KSharedConfig::Ptr config = KSharedConfig::openConfig(); - KConfigGroup rColorsConfig(config, "Resources Colors"); - const QStringList colorKeyList = rColorsConfig.keyList(); - - for (const QString &key : colorKeyList) { - QColor color = rColorsConfig.readEntry(key, QColor("blue")); - m_colors[key] = color; - } - Q_EMIT layoutChanged(); - } - -private: - Akonadi::ETMCalendar::Ptr m_calendar; - IncidenceTreeModel *m_todoTreeModel = nullptr; - TodoModel *m_baseTodoModel = nullptr; - QHash m_colors; - KConfigWatcher::Ptr m_colorWatcher; -}; - - - - - TodoSortFilterProxyModel::TodoSortFilterProxyModel(QObject* parent) : QSortFilterProxyModel(parent) { @@ -384,8 +163,3 @@ void TodoSortFilterProxyModel::filterTodoName(QString name, int showCompleted) invalidateFilter(); Q_EMIT layoutChanged(); } - - -Q_DECLARE_METATYPE(KCalendarCore::Incidence::Ptr) -#include "todosortfilterproxymodel.moc" - diff --git a/src/todosortfilterproxymodel.h b/src/todosortfilterproxymodel.h index c84530c8761236fb605991c5f0dff8d2c90a7db5..f346047df28f1f31de8a7620ebc7ae1aca14b078 100644 --- a/src/todosortfilterproxymodel.h +++ b/src/todosortfilterproxymodel.h @@ -3,19 +3,14 @@ #pragma once #include -#include #include #include -#include -#include -#include -#include #include #include #include +#include #include -class ExtraTodoModel; class TodoSortFilterProxyModel : public QSortFilterProxyModel {