diff --git a/src/contents/ui/GlobalMenu.qml b/src/contents/ui/GlobalMenu.qml index 081dde4734d0aa93a5beb5342e0244c38c00a124..b6fa083c41d26e0317756a4034205d4d1b0934ef 100644 --- a/src/contents/ui/GlobalMenu.qml +++ b/src/contents/ui/GlobalMenu.qml @@ -116,6 +116,11 @@ Labs.MenuBar { } Labs.Menu { title: i18nc("@action:menu", "Settings") + NativeMenuItemFromAction { + kalendarAction: 'open_tag_manager' + } + Labs.MenuSeparator { + } NativeMenuItemFromAction { kalendarAction: 'options_configure_keybinding' } diff --git a/src/contents/ui/IncidenceEditor.qml b/src/contents/ui/IncidenceEditor.qml index d8738b082037d16cdd6ba7e56487082b1f73f736..1350874293a95ff0c2748042819488f198a596f7 100644 --- a/src/contents/ui/IncidenceEditor.qml +++ b/src/contents/ui/IncidenceEditor.qml @@ -741,28 +741,36 @@ Kirigami.ScrollablePage { } } - QQC2.ComboBox { + RowLayout { Kirigami.FormData.label: i18n("Tags:") Layout.fillWidth: true - model: TagManager.tagModel - displayText: root.incidenceWrapper.categories.length > 0 ? - root.incidenceWrapper.categories.join(i18nc("List separator", ", ")) : - Kirigami.Settings.tabletMode ? i18n("Tap to set tags...") : i18n("Click to set tags...") - - delegate: Kirigami.CheckableListItem { - label: model.display - reserveSpaceForIcon: false - checked: root.incidenceWrapper.categories.includes(model.display) - action: QQC2.Action { - onTriggered: { - checked = !checked; - root.incidenceWrapper.categories.includes(model.display) ? - root.incidenceWrapper.categories = root.incidenceWrapper.categories.filter(tag => tag !== model.display) : - root.incidenceWrapper.categories = [...root.incidenceWrapper.categories, model.display] + QQC2.ComboBox { + Layout.fillWidth: true + + model: TagManager.tagModel + displayText: root.incidenceWrapper.categories.length > 0 ? + root.incidenceWrapper.categories.join(i18nc("List separator", ", ")) : + Kirigami.Settings.tabletMode ? i18n("Tap to set tags...") : i18n("Click to set tags...") + + delegate: Kirigami.CheckableListItem { + label: model.display + reserveSpaceForIcon: false + checked: root.incidenceWrapper.categories.includes(model.display) + action: QQC2.Action { + onTriggered: { + checked = !checked; + root.incidenceWrapper.categories.includes(model.display) ? + root.incidenceWrapper.categories = root.incidenceWrapper.categories.filter(tag => tag !== model.display) : + root.incidenceWrapper.categories = [...root.incidenceWrapper.categories, model.display] + } } } } + QQC2.Button { + text: i18n("Manage tags...") + onClicked: KalendarApplication.action("open_tag_manager").trigger() + } } Kirigami.Separator { diff --git a/src/contents/ui/Sidebar.qml b/src/contents/ui/Sidebar.qml index 87b8733422a3ba433fd121a21a8be9f4e6ad551e..e9309c658f6243cd83772d4a07cb4791dd14408a 100644 --- a/src/contents/ui/Sidebar.qml +++ b/src/contents/ui/Sidebar.qml @@ -188,6 +188,9 @@ Kirigami.OverlayDrawer { enabled: CalendarManager.undoRedoData.redoAvailable onTriggered: CalendarManager.redoAction(); }, + KActionFromAction { + kalendarAction: "open_tag_manager" + }, Kirigami.Action { text: i18n("Settings") icon.name: KalendarApplication.iconName(configureAction.icon) @@ -374,7 +377,7 @@ Kirigami.OverlayDrawer { Layout.fillWidth: true } Kirigami.BasicListItem { - Layout.topMargin: -Kirigami.Units.smallSpacing + Layout.topMargin: -Kirigami.Units.smallSpacing - 1 icon: "show-all-effects" label: i18n("View all todos") labelItem.color: Kirigami.Theme.textColor diff --git a/src/contents/ui/TagManagerPage.qml b/src/contents/ui/TagManagerPage.qml new file mode 100644 index 0000000000000000000000000000000000000000..1c2c707a31e3a9c9199bebe29218b1f22618b7a9 --- /dev/null +++ b/src/contents/ui/TagManagerPage.qml @@ -0,0 +1,138 @@ +// SPDX-FileCopyrightText: 2021 Claudio Cambra +// SPDX-License-Identifier: LGPL-2.1-or-later + +import QtQuick 2.15 +import QtQuick.Controls 2.15 as QQC2 +import QtQuick.Layouts 1.15 +import org.kde.kirigami 2.15 as Kirigami +import org.kde.kalendar 1.0 + +Kirigami.ScrollablePage { + id: root + + title: i18n("Manage Tags") + + Kirigami.OverlaySheet { + id: deleteConfirmSheet + title: i18n("Delete Tag") + + property string tagName + property var tag + + ColumnLayout { + QQC2.Label { + Layout.fillWidth: true + text: i18n("Are you sure you want to delete tag \"%1\"?", deleteConfirmSheet.tagName) + wrapMode: Text.Wrap + } + } + + footer: QQC2.DialogButtonBox { + standardButtons: QQC2.DialogButtonBox.Ok | QQC2.DialogButtonBox.Cancel + + onAccepted: { + TagManager.deleteTag(deleteConfirmSheet.tag); + deleteConfirmSheet.close(); + } + onRejected: deleteConfirmSheet.close() + } + } + + ListView { + currentIndex: -1 + model: TagManager.tagModel + delegate: Kirigami.BasicListItem { + contentItem: Item { + implicitWidth: delegateLayout.implicitWidth + implicitHeight: delegateLayout.implicitHeight + RowLayout { + id: delegateLayout + + property bool editMode: false + + anchors { + left: parent.left + top: parent.top + right: parent.right + } + + QQC2.Label { + Layout.fillWidth: true + text: model.display + visible: !delegateLayout.editMode + } + QQC2.ToolButton { + icon.name: "edit-rename" + onClicked: delegateLayout.editMode = true + visible: !delegateLayout.editMode + } + QQC2.ToolButton { + icon.name: "delete" + onClicked: { + deleteConfirmSheet.tag = model.tag; + deleteConfirmSheet.tagName = model.name; + deleteConfirmSheet.open(); + } + visible: !delegateLayout.editMode + } + + QQC2.TextField { + id: tagNameField + Layout.fillWidth: true + text: model.display + visible: delegateLayout.editMode + } + QQC2.ToolButton { + icon.name: "gtk-apply" + visible: delegateLayout.editMode + onClicked: { + TagManager.renameTag(model.tag, tagNameField.text) + delegateLayout.editMode = false; + } + } + QQC2.ToolButton { + icon.name: "gtk-cancel" + onClicked: { + delegateLayout.editMode = false; + tagNameField.text = model.display; + } + visible: delegateLayout.editMode + } + } + } + } + } + + + footer: Kirigami.ActionTextField { + id: newTagField + Layout.fillWidth: true + placeholderText: i18n("Create a New Tag...") + background: Rectangle { + Kirigami.Theme.inherit: false + Kirigami.Theme.colorSet: Kirigami.Theme.Window + color: Kirigami.Theme.backgroundColor + Kirigami.Separator { + anchors { + top: parent.top + left: parent.left + right: parent.right + } + } + } + + function addTag() { + if(newTagField.text.length > 0) { + TagManager.createTag(newTagField.text); + newTagField.text = ""; + } + } + + rightActions: Kirigami.Action { + icon.name: "tag-new" + tooltip: i18n("Quickly Add a New Tag.") + onTriggered: newTagField.addTag() + } + onAccepted: newTagField.addTag() + } +} diff --git a/src/contents/ui/TodoPage.qml b/src/contents/ui/TodoPage.qml index 4daa60868aa448a0a3ae918f10a0da668a1a6a3e..09ae63cdd6d0c65250bdea4a609125f2668c768f 100644 --- a/src/contents/ui/TodoPage.qml +++ b/src/contents/ui/TodoPage.qml @@ -275,6 +275,19 @@ Kirigami.Page { Layout.fillWidth: true placeholderText: i18n("Create a New Todo…") + background: Rectangle { + Kirigami.Theme.inherit: false + Kirigami.Theme.colorSet: Kirigami.Theme.Window + color: Kirigami.Theme.backgroundColor + Kirigami.Separator { + anchors { + top: parent.top + left: parent.left + right: parent.right + } + } + } + function addTodo() { if(addField.text) { let incidenceWrapper = Qt.createQmlObject('import org.kde.kalendar 1.0; IncidenceWrapper {id: incidence}', this, "incidence"); diff --git a/src/contents/ui/WindowMenu.qml b/src/contents/ui/WindowMenu.qml index 68a00804e0aacc0f18e6fa7ddd8f35328fd534b6..aba47e9af3172aeaaa37315356d09acb683cacc1 100644 --- a/src/contents/ui/WindowMenu.qml +++ b/src/contents/ui/WindowMenu.qml @@ -192,6 +192,13 @@ QQC2.MenuBar { QQC2.Menu { title: i18nc("@action:menu", "Settings") + KActionFromAction { + kalendarAction: "open_tag_manager" + } + + QQC2.MenuSeparator { + } + KActionFromAction { kalendarAction: 'options_configure_keybinding' } diff --git a/src/contents/ui/main.qml b/src/contents/ui/main.qml index 5ea616c6b8a78bcc21c8c78a76eaecd0ba9dd790..c021010d2a72632ce412223ff15bec2dbc2d6b8a 100644 --- a/src/contents/ui/main.qml +++ b/src/contents/ui/main.qml @@ -147,6 +147,15 @@ Kirigami.ApplicationWindow { height: root.height - (Kirigami.Units.gridUnit * 3) }) } + + function onOpenTagManager() { + pageStack.pushDialogLayer("qrc:/TagManagerPage.qml", { + width: root.width + }, { + width: Kirigami.Units.gridUnit * 30, + height: Kirigami.Units.gridUnit * 30 + }) + } } Connections { diff --git a/src/kalendarapplication.cpp b/src/kalendarapplication.cpp index 249acbd35c2251464eda6aca2f5d273f926af4cd..9e0d81eda0d32d2c1d2b7131e732ac34c475f027 100644 --- a/src/kalendarapplication.cpp +++ b/src/kalendarapplication.cpp @@ -81,6 +81,12 @@ void KalendarApplication::setupActions(const QString &actionName) mCollection.addAction(action->objectName(), action); } + if (actionName == QLatin1String("open_tag_manager") && KAuthorized::authorizeAction(actionName)) { + auto openTagManagerAction = mCollection.addAction(actionName, this, &KalendarApplication::openTagManager); + openTagManagerAction->setText(i18n("Manage Tags…")); + openTagManagerAction->setIcon(QIcon::fromTheme(QStringLiteral("action-rss_tag"))); + } + if (actionName == QLatin1String("switch_application_language") && KAuthorized::authorizeAction(actionName)) { auto action = KStandardAction::switchApplicationLanguage(this, &KalendarApplication::openLanguageSwitcher, this); mCollection.addAction(action->objectName(), action); diff --git a/src/kalendarapplication.h b/src/kalendarapplication.h index 7ef0bbc35bd18a15db335da6e55871e6cac481ae..d11f3046aeb4e49bea32e126593be478aa0972bc 100644 --- a/src/kalendarapplication.h +++ b/src/kalendarapplication.h @@ -33,6 +33,7 @@ Q_SIGNALS: void windowChanged(); void openSettings(); void openLanguageSwitcher(); + void openTagManager(); void quit(); void undo(); void redo(); diff --git a/src/resources.qrc b/src/resources.qrc index ef36c877de5e993789678cd74c727325594f54f7..d385f6c2e4e3e421c339308a824c78897943e800 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -37,5 +37,6 @@ contents/ui/KActionFromAction.qml contents/ui/ColoredCheckbox.qml contents/ui/Tag.qml + contents/ui/TagManagerPage.qml diff --git a/src/tagmanager.cpp b/src/tagmanager.cpp index 5049e6b6c4d8ca3e65daa7c6320e0cd14b4f5245..130230fdc507b5db17413cc63607fb15ac4ec99f 100644 --- a/src/tagmanager.cpp +++ b/src/tagmanager.cpp @@ -1,6 +1,9 @@ // SPDX-FileCopyrightText: 2021 Claudio Cambra // SPDX-License-Identifier: LGPL-2.1-or-later +#include +#include +#include #include class FlatTagModel : public QSortFilterProxyModel @@ -21,6 +24,18 @@ public: sort(0); }; + QHash roleNames() const override { + auto rolenames = QSortFilterProxyModel::roleNames(); + rolenames[Akonadi::TagModel::Roles::NameRole] = QByteArrayLiteral("name"); + rolenames[Akonadi::TagModel::Roles::IdRole] = QByteArrayLiteral("id"); + rolenames[Akonadi::TagModel::Roles::GIDRole] = QByteArrayLiteral("gid"); + rolenames[Akonadi::TagModel::Roles::TypeRole] = QByteArrayLiteral("type"); + rolenames[Akonadi::TagModel::Roles::ParentRole] = QByteArrayLiteral("parent"); + rolenames[Akonadi::TagModel::Roles::TagRole] = QByteArrayLiteral("tag"); + + return rolenames; + } + protected: bool filterAcceptsRow(int row, const QModelIndex &sourceParent) const override { @@ -45,4 +60,31 @@ QSortFilterProxyModel * TagManager::tagModel() return m_tagModel; } +void TagManager::createTag(const QString &name) +{ + Akonadi::Tag tag(name); + Akonadi::TagCreateJob *job = new Akonadi::TagCreateJob(tag, this); + connect(job, &Akonadi::TagCreateJob::finished, this, [=](KJob *job) { + if (job->error()) + qDebug() << "Error occurred creating tag"; + }); +} + +void TagManager::deleteTag(Akonadi::Tag tag) +{ + Akonadi::TagDeleteJob *job = new Akonadi::TagDeleteJob(tag); + connect(job, &Akonadi::TagDeleteJob::result, this, [=](KJob *job) { + if (job->error()) + qDebug() << "Error occurred renaming tag"; + }); +} +void TagManager::renameTag(Akonadi::Tag tag, const QString &newName) +{ + tag.setName(newName); + Akonadi::TagModifyJob *job = new Akonadi::TagModifyJob(tag); + connect(job, &Akonadi::TagModifyJob::result, this, [=](KJob *job) { + if (job->error()) + qDebug() << "Error occurred renaming tag"; + }); +} diff --git a/src/tagmanager.h b/src/tagmanager.h index 351f17871c5c26e70150df4dfacdcfaae5a92bb6..54ae5736c53c9a1322efb33c3a8c816c951d66cf 100644 --- a/src/tagmanager.h +++ b/src/tagmanager.h @@ -18,6 +18,9 @@ public: ~TagManager() = default; QSortFilterProxyModel *tagModel(); + Q_INVOKABLE void createTag(const QString &name); + Q_INVOKABLE void renameTag(Akonadi::Tag tag, const QString &newName); + Q_INVOKABLE void deleteTag(Akonadi::Tag tag); private: QSortFilterProxyModel *m_tagModel = nullptr;