diff --git a/framework/qml/CalendarSelector.qml b/framework/qml/CalendarSelector.qml index d0c3740b..e08c2969 100644 --- a/framework/qml/CalendarSelector.qml +++ b/framework/qml/CalendarSelector.qml @@ -1,133 +1,134 @@ /* * Copyright (C) 2018 Michael Bohlender, * Copyright (C) 2019 Christian Mollekopf, * * 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. */ import QtQuick 2.9 import QtQuick.Controls 2 import QtQuick.Layouts 1.2 import org.kube.framework 1.0 as Kube Kube.InlineAccountSwitcher { id: root property alias enabledCalendars: calendarFilterCollector.checkedEntities property bool editMode: false + property string contentType: "event" Kube.CheckedEntities { id: calendarFilterCollector } buttonDelegate: Kube.IconButton { height: Kube.Units.gridUnit padding: 0 iconName: Kube.Icons.overflowMenu_inverted onClicked: root.editMode = !root.editMode; checkable: true checked: root.editMode } delegate: Kube.ListView { id: listView Layout.fillWidth: true Layout.maximumHeight: Math.min(contentHeight, parent.height - Kube.Units.gridUnit) Layout.preferredHeight: contentHeight spacing: Kube.Units.smallSpacing model: Kube.CheckableEntityModel { id: calendarModel type: "calendar" roles: ["name", "color", "enabled"] sortRole: "name" - filter: root.editMode ? {} : {enabled: true} + filter: root.editMode ? {"contentTypes": contentType} : {"contentTypes": contentType, enabled: true} accountId: listView.parent.accountId checkedEntities: calendarFilterCollector onInitialItemsLoaded: { //Automatically enable edit mode if no calendars are enabled if (rowCount() == 0) { root.editMode = true; } } } delegate: Kube.ListDelegate { id: delegate width: listView.availableWidth height: Kube.Units.gridUnit hoverEnabled: true background: Item {} RowLayout { anchors.fill: parent spacing: Kube.Units.smallSpacing Kube.CheckBox { id: checkBox opacity: 0.9 visible: root.editMode checked: model.checked || model.enabled onCheckedChanged: { model.checked = checked model.enabled = checked } indicator: Rectangle { width: Kube.Units.gridUnit * 0.8 height: Kube.Units.gridUnit * 0.8 color: model.color Rectangle { id: highlight anchors.fill: parent visible: checkBox.hovered || checkBox.visualFocus color: Kube.Colors.highlightColor opacity: 0.4 } Kube.Icon { anchors.centerIn: parent height: Kube.Units.gridUnit width: Kube.Units.gridUnit visible: checkBox.checked iconName: Kube.Icons.checkbox_inverted } } } Kube.Label { id: label Layout.fillWidth: true text: model.name color: Kube.Colors.highlightedTextColor elide: Text.ElideLeft clip: true } Rectangle { visible: !root.editMode width: Kube.Units.gridUnit * 0.8 height: Kube.Units.gridUnit * 0.8 radius: width / 2 color: model.color } ToolTip { id: toolTipItem visible: delegate.hovered && label.truncated text: label.text } } } } } diff --git a/framework/src/entitymodel.cpp b/framework/src/entitymodel.cpp index 6d83c74d..23a2d4c4 100644 --- a/framework/src/entitymodel.cpp +++ b/framework/src/entitymodel.cpp @@ -1,352 +1,357 @@ /* Copyright (c) 2018 Christian Mollekopf This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "entitymodel.h" #include #include using namespace Sink; using namespace Sink::ApplicationDomain; enum Roles { IdRole = Qt::UserRole + 1, ObjectRole, LastRole }; EntityModel::EntityModel(QObject *parent) : QSortFilterProxyModel(parent) { setDynamicSortFilter(true); setFilterCaseSensitivity(Qt::CaseInsensitive); setSortCaseSensitivity(Qt::CaseInsensitive); } EntityModel::~EntityModel() { } QHash EntityModel::roleNames() const { return mRoleNames; } QVariant EntityModel::data(const QModelIndex &idx, int role) const { auto srcIdx = mapToSource(idx); if (auto entity = srcIdx.data(Sink::Store::DomainObjectBaseRole).value()) { const auto roleName = mRoleNames.value(role); if (roleName == "identifier") { return entity->identifier(); } else if (roleName == "object") { return QVariant::fromValue(entity); } else { return entity->getProperty(roleName); } } //We can run into this when passing in an invalid index return {}; } bool EntityModel::lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const { auto left = sourceLeft.data(Sink::Store::DomainObjectBaseRole).value(); auto right = sourceRight.data(Sink::Store::DomainObjectBaseRole).value(); const auto leftProperty = left->getProperty(mSortRole.toUtf8()).toString(); const auto rightProperty = right->getProperty(mSortRole.toUtf8()).toString(); if (leftProperty == rightProperty) { return left->identifier() < right->identifier(); } return leftProperty < rightProperty; } void EntityModel::runQuery(const Query &query) { if (mType == "calendar") { mModel = Store::loadModel(query); } else if (mType == "addressbook") { mModel = Store::loadModel(query); } else if (mType == "folder") { mModel = Store::loadModel(query); } else if (mType == "todo") { mModel = Store::loadModel(query); } else { qWarning() << "Type not supported " << mType; Q_ASSERT(false); } QObject::connect(mModel.data(), &QAbstractItemModel::dataChanged, this, [this](const QModelIndex &, const QModelIndex &, const QVector &roles) { if (roles.contains(Sink::Store::ChildrenFetchedRole)) { emit initialItemsLoaded(); } }); setSourceModel(mModel.data()); } void EntityModel::updateQuery() { if (mType.isEmpty() || mRoles.isEmpty()) { return; } Query query; if (!mAccountId.isEmpty()) { query.resourceFilter(mAccountId.toUtf8()); } if (!mResourceId.isEmpty()) { query.resourceFilter(mResourceId.toUtf8()); } if (!mEntityId.isEmpty()) { query.filter(mEntityId.toUtf8()); } query.setFlags(Sink::Query::LiveQuery | Sink::Query::UpdateStatus); for (const auto &property: mRoles.keys()) { query.requestedProperties << property; } for (const auto &property: mFilter.keys()) { - query.filter(property.toUtf8(), {mFilter.value(property)}); + //FIXME we lack a way to select a filter type + if (property == "contentTypes") { + query.filter(property.toUtf8(), Sink::Query::Comparator(mFilter.value(property), Sink::Query::Comparator::Contains)); + } else { + query.filter(property.toUtf8(), {mFilter.value(property)}); + } } runQuery(query); } void EntityModel::setAccountId(const QString &id) { if (mAccountId != id) { mAccountId = id; updateQuery(); } } QString EntityModel::accountId() const { return mAccountId; } void EntityModel::setResourceId(const QString &id) { if (mResourceId != id) { mResourceId = id; updateQuery(); } } QString EntityModel::resourceId() const { return mResourceId; } void EntityModel::setEntityId(const QString &id) { if (mEntityId != id) { mEntityId = id; updateQuery(); } } QString EntityModel::entityId() const { return mEntityId; } void EntityModel::setType(const QString &type) { mType = type; updateQuery(); } QString EntityModel::type() const { return {}; } void EntityModel::setRoles(const QStringList &roles) { mRoleNames.clear(); mRoleNames.insert(IdRole, "identifier"); mRoleNames.insert(ObjectRole, "object"); int role = LastRole; for (int i = 0; i < roles.size(); i++) { mRoleNames.insert(role++, roles.at(i).toLatin1()); } mRoles.clear(); for (const auto &r : mRoleNames.keys()) { mRoles.insert(mRoleNames.value(r), r); } updateQuery(); } QStringList EntityModel::roles() const { // return mRoleNames.values(); return {}; } void EntityModel::setFilter(const QVariantMap &filter) { mFilter = filter; updateQuery(); } QVariantMap EntityModel::filter() const { return {}; } void EntityModel::setSortRole(const QString &sortRole) { mSortRole = sortRole; sort(0, Qt::AscendingOrder); } QString EntityModel::sortRole() const { return mSortRole; } QVariantMap EntityModel::data(int row) const { QVariantMap map; for (const auto &r : mRoleNames.keys()) { map.insert(mRoleNames.value(r), data(index(row, 0), r)); } return map; } bool EntityModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!mRoleNames.contains(role)) { return false; } const auto entity = EntityModel::data(index, ObjectRole).value(); //FIXME hardcoding calendar is not a great idea here. Sink::ApplicationDomain::Calendar modifiedEntity{*entity}; const auto propertyName = mRoleNames.value(role); modifiedEntity.setProperty(propertyName, value.toBool()); //Ignore if we didn't modify anything. if (!modifiedEntity.changedProperties().isEmpty()) { Sink::Store::modify(modifiedEntity).exec(); } return true; } EntityLoader::EntityLoader(QObject *parent) : EntityModel(parent) { QObject::connect(this, &QAbstractItemModel::rowsInserted, this, [this] (const QModelIndex &parent, int first, int last) { for (int row = first; row <= last; row++) { auto idx = index(row, 0, parent); for (const auto &r : mRoleNames.keys()) { setProperty(mRoleNames.value(r), data(idx, r)); } //We only ever use the first index (we're not expecting any more either) break; } }); } EntityLoader::~EntityLoader() { } void EntityLoader::updateQuery() { if (entityId().isEmpty()) { for (const auto &r : mRoleNames.keys()) { setProperty(mRoleNames.value(r), {}); } return; } EntityModel::updateQuery(); } CheckableEntityModel::CheckableEntityModel(QObject *parent) : EntityModel(parent) { } CheckableEntityModel::~CheckableEntityModel() { } QHash CheckableEntityModel::roleNames() const { auto roleNames = EntityModel::roleNames(); roleNames.insert(Qt::CheckStateRole, "checked"); return roleNames; } QVariant CheckableEntityModel::data(const QModelIndex &index, int role) const { if (mCheckedEntities && role == Qt::CheckStateRole) { return mCheckedEntities->contains(EntityModel::data(index, IdRole).toByteArray()); } return EntityModel::data(index, role); } bool CheckableEntityModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (mCheckedEntities && role == Qt::CheckStateRole) { auto identifier = EntityModel::data(index, IdRole).toByteArray(); if (value.toBool()) { mCheckedEntities->insert(identifier); } else { mCheckedEntities->remove(identifier); } return true; } return EntityModel::setData(index, value, role); } CheckedEntities *CheckableEntityModel::checkedEntities() const { return mCheckedEntities; } void CheckableEntityModel::setCheckedEntities(CheckedEntities *checkedEntities) { mCheckedEntities = checkedEntities; } void CheckedEntities::insert(const QByteArray &id) { mCheckedEntities.insert(id); emit checkedEntitiesChanged(); } void CheckedEntities::remove(const QByteArray &id) { mCheckedEntities.remove(id); emit checkedEntitiesChanged(); } bool CheckedEntities::contains(const QByteArray &id) const { return mCheckedEntities.contains(id); } QSet CheckedEntities::checkedEntities() const { return mCheckedEntities; } diff --git a/views/calendar/qml/View.qml b/views/calendar/qml/View.qml index b2df0215..ce6b0b41 100644 --- a/views/calendar/qml/View.qml +++ b/views/calendar/qml/View.qml @@ -1,242 +1,243 @@ /* * Copyright (C) 2018 Michael Bohlender, * Copyright (C) 2019 Christian Mollekopf, * * 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. */ import QtQuick 2.9 import QtQuick.Controls 2 import QtQuick.Layouts 1.2 import org.kube.framework 1.0 as Kube import "dateutils.js" as DateUtils Kube.View { id: root property date currentDate: new Date() property date selectedDate: currentDate property bool autoUpdateDate: true onSelectedDateChanged: { console.log("Selected date changed", selectedDate) } onRefresh: { Kube.Fabric.postMessage(Kube.Messages.synchronize, {"type": "calendar"}) Kube.Fabric.postMessage(Kube.Messages.synchronize, {"type": "event"}) } RowLayout { Timer { running: autoUpdateDate interval: 2000; repeat: true onTriggered: root.currentDate = new Date() } Rectangle { Layout.fillHeight: true width: Kube.Units.gridUnit * 10 color: Kube.Colors.darkBackgroundColor Column { id: topLayout anchors { top: parent.top left: parent.left right: parent.right margins: Kube.Units.largeSpacing } spacing: Kube.Units.largeSpacing Kube.PositiveButton { id: newEventButton objectName: "newEventButton" anchors { left: parent.left right: parent.right } focus: true text: qsTr("New Event") onClicked: eventPopup.createObject(root, {start: DateUtils.sameDay(root.currentDate, root.selectedDate) ? root.currentDate : root.selectedDate}).open() } RowLayout { anchors { left: parent.left right: parent.right } spacing: Kube.Units.smallSpacing ButtonGroup { id: viewButtonGroup } Kube.TextButton { id: weekViewButton Layout.fillWidth: true text: qsTr("Week") textColor: Kube.Colors.highlightedTextColor checkable: true checked: true ButtonGroup.group: viewButtonGroup } Kube.TextButton { id: monthViewButton Layout.fillWidth: true text: qsTr("Month") textColor: Kube.Colors.highlightedTextColor checkable: true ButtonGroup.group: viewButtonGroup } } DateView { anchors { left: parent.left right: parent.right } date: root.currentDate MouseArea { anchors.fill: parent onClicked: { root.selectedDate = root.currentDate } } } DateSelector { id: dateSelector anchors { left: parent.left right: parent.right } selectedDate: root.selectedDate onSelected: { root.selectedDate = date } onNext: { if (weekViewButton.checked) { root.selectedDate = DateUtils.nextWeek(root.selectedDate) } else { root.selectedDate = DateUtils.nextMonth(root.selectedDate) } } onPrevious: { if (weekViewButton.checked) { root.selectedDate = DateUtils.previousWeek(root.selectedDate) } else { root.selectedDate = DateUtils.previousMonth(root.selectedDate) } } } } Kube.CalendarSelector { id: accountSwitcher //Grow from the button but don't go over topLayout anchors { bottom: statusBarContainer.top left: topLayout.left right: parent.right bottomMargin: Kube.Units.largeSpacing rightMargin: Kube.Units.largeSpacing } height: parent.height - (topLayout.y + topLayout.height) - Kube.Units.largeSpacing - anchors.bottomMargin - statusBarContainer.height + contentType: "event" } Item { id: statusBarContainer anchors { topMargin: Kube.Units.smallSpacing bottom: parent.bottom left: parent.left right: parent.right } height: childrenRect.height Rectangle { id: border visible: statusBar.visible anchors { right: parent.right left: parent.left margins: Kube.Units.smallSpacing } height: 1 color: Kube.Colors.viewBackgroundColor opacity: 0.3 } Kube.StatusBar { id: statusBar accountId: accountSwitcher.currentAccount height: Kube.Units.gridUnit * 2 anchors { top: border.bottom left: statusBarContainer.left right: statusBarContainer.right } } } } WeekView { visible: weekViewButton.checked Layout.fillHeight: true Layout.fillWidth: true currentDate: root.currentDate startDate: DateUtils.getFirstDayOfWeek(root.selectedDate) calendarFilter: accountSwitcher.enabledCalendars } MonthView { visible: monthViewButton.checked Layout.fillHeight: true Layout.fillWidth: true currentDate: root.currentDate startDate: DateUtils.getFirstDayOfWeek(DateUtils.getFirstDayOfMonth(root.selectedDate)) month: root.selectedDate.getMonth() calendarFilter: accountSwitcher.enabledCalendars } } Kube.Listener { filter: Kube.Messages.eventEditor onMessageReceived: eventPopup.createObject(root, message).open() } Component { id: eventPopup Kube.Popup { id: popup property alias start: editor.start property alias allDay: editor.allDay x: root.width * 0.15 y: root.height * 0.15 width: root.width * 0.7 height: root.height * 0.7 padding: 0 EventEditor { id: editor anchors.fill: parent onDone: popup.close() accountId: accountSwitcher.currentAccount } } } } diff --git a/views/todo/qml/View.qml b/views/todo/qml/View.qml index f2bb4bb0..570052c7 100644 --- a/views/todo/qml/View.qml +++ b/views/todo/qml/View.qml @@ -1,391 +1,392 @@ /* * Copyright (C) 2017 Michael Bohlender, * Copyright (C) 2017 Christian Mollekopf, * * 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. */ import QtQuick 2.9 import QtQuick.Controls 1.3 as Controls1 import QtQuick.Controls 2 import QtQuick.Layouts 1.1 import org.kube.framework 1.0 as Kube Kube.View { id: root property alias currentAccount: accountSwitcher.currentAccount // property variant currentFolder: null //We have to hardcode because all the mapToItem/mapFromItem functions are garbage // searchArea: Qt.rect(ApplicationWindow.window.sidebarWidth + mailListView.parent.x, 0, (mailView.x + mailView.width) - mailListView.parent.x, (mailView.y + mailView.height) - mailListView.y) onFilterChanged: { Kube.Fabric.postMessage(Kube.Messages.searchString, {"searchString": filter}) } onRefresh: { // if (!!root.currentFolder) { // Kube.Fabric.postMessage(Kube.Messages.synchronize, {"folder": root.currentFolder}); // Kube.Fabric.postMessage(Kube.Messages.synchronize, {"accountId": root.currentAccount, "type": "folder"}) // } else { Kube.Fabric.postMessage(Kube.Messages.synchronize, {"accountId": root.currentAccount}) // } } Kube.Listener { filter: Kube.Messages.search onMessageReceived: root.triggerSearch() } helpViewComponent: Kube.HelpPopup { ListModel { ListElement { description: qsTr("Jump to next thread:"); shortcut: "j" } ListElement { description: qsTr("Jump to previous thread:"); shortcut: "k" } ListElement { description: qsTr("Jump to next message:"); shortcut: "n" } ListElement { description: qsTr("Jump to previous message:"); shortcut: "p" } ListElement { description: qsTr("Jump to next folder:"); shortcut: "f,n" } ListElement { description: qsTr("Jump to previous previous folder:"); shortcut: "f,p" } ListElement { description: qsTr("Compose new message:"); shortcut: "c" } ListElement { description: qsTr("Reply to the currently focused message:"); shortcut: "r" } ListElement { description: qsTr("Delete the currently focused message:"); shortcut: "d" } ListElement { description: qsTr("Mark the currently focused message as important:"); shortcut: "i" } ListElement { description: qsTr("Mark the currently focused message as unread:"); shortcut: "u" } ListElement { description: qsTr("Show this help text:"); shortcut: "?" } } } Shortcut { sequences: ['j'] onActivated: Kube.Fabric.postMessage(Kube.Messages.selectNextConversation, {}) } Shortcut { sequences: ['k'] onActivated: Kube.Fabric.postMessage(Kube.Messages.selectPreviousConversation, {}) } Shortcut { sequences: ['f,n'] onActivated: Kube.Fabric.postMessage(Kube.Messages.selectNextFolder, {}) } Shortcut { sequences: ['f,p'] onActivated: Kube.Fabric.postMessage(Kube.Messages.selectPreviousFolder, {}) } Shortcut { sequences: ['c'] onActivated: Kube.Fabric.postMessage(Kube.Messages.compose, {}) } Shortcut { sequence: "?" onActivated: root.showHelp() } ButtonGroup { id: viewButtonGroup } Controls1.SplitView { Layout.fillWidth: true Layout.fillHeight: true Rectangle { width: Kube.Units.gridUnit * 10 Layout.fillHeight: parent.height color: Kube.Colors.darkBackgroundColor Column { id: topLayout anchors { top: parent.top left: parent.left right: parent.right margins: Kube.Units.largeSpacing } Kube.PositiveButton { id: newMailButton objectName: "newMailButton" anchors { left: parent.left right: parent.right } focus: true text: qsTr("New Todo") onClicked: editorPopup.createObject(root, {}).open() } Item { anchors { left: parent.left right: parent.right } height: Kube.Units.gridUnit } Kube.TextButton { anchors { left: parent.left right: parent.right } text: qsTr("Today") textColor: Kube.Colors.highlightedTextColor checkable: true checked: true horizontalAlignment: Text.AlignHLeft ButtonGroup.group: viewButtonGroup onClicked: todoModel.filter = {"doing": true} } Kube.TextButton { id: allViewButton anchors { left: parent.left right: parent.right } text: qsTr("All") textColor: Kube.Colors.highlightedTextColor checkable: true horizontalAlignment: Text.AlignHLeft ButtonGroup.group: viewButtonGroup onClicked: todoModel.filter = {} } } Kube.CalendarSelector { id: accountSwitcher activeFocusOnTab: true anchors { top: topLayout.bottom topMargin: Kube.Units.largeSpacing bottom: statusBarContainer.top left: topLayout.left right: parent.right } + contentType: "todo" } Item { id: statusBarContainer anchors { topMargin: Kube.Units.smallSpacing bottom: parent.bottom left: parent.left right: parent.right } height: childrenRect.height Rectangle { id: border visible: statusBar.visible anchors { right: parent.right left: parent.left margins: Kube.Units.smallSpacing } height: 1 color: Kube.Colors.viewBackgroundColor opacity: 0.3 } Kube.StatusBar { id: statusBar accountId: root.currentAccount height: Kube.Units.gridUnit * 2 anchors { top: border.bottom left: statusBarContainer.left right: statusBarContainer.right } } } } Rectangle { width: Kube.Units.gridUnit * 18 Layout.fillHeight: parent.height color: "transparent" border.width: 1 border.color: Kube.Colors.buttonColor Kube.ListView { id: todoView anchors.fill: parent Layout.minimumWidth: Kube.Units.gridUnit * 10 onCurrentItemChanged: { if (currentItem) { var currentData = currentItem.currentData; todoDetails.controller = controllerComponent.createObject(parent, {"todo": currentData.domainObject}) } } model: Kube.TodoModel { id: todoModel calendarFilter: accountSwitcher.enabledCalendars filter: {"doing": true} } delegate: Kube.ListDelegate { id: delegateRoot //Required for D&D // property var mail: model.mail property bool buttonsVisible: delegateRoot.hovered width: todoView.availableWidth height: Kube.Units.gridUnit * 3 color: Kube.Colors.viewBackgroundColor border.color: Kube.Colors.backgroundColor border.width: 1 Item { id: content anchors { fill: parent margins: Kube.Units.smallSpacing } Column { anchors { verticalCenter: parent.verticalCenter left: parent.left leftMargin: Kube.Units.largeSpacing } Kube.Label{ id: subject width: content.width - Kube.Units.gridUnit * 3 text: model.summary color: delegateRoot.textColor font.strikeout: model.complete font.bold: model.doing && allViewButton.checked maximumLineCount: 2 wrapMode: Text.WordWrap elide: Text.ElideRight } } Kube.Label { id: date anchors { right: parent.right bottom: parent.bottom } visible: model.date && !delegateRoot.buttonsVisible text: Qt.formatDateTime(model.date, "dd MMM yyyy") font.italic: true color: delegateRoot.disabledTextColor font.pointSize: Kube.Units.tinyFontSize } } Kube.Icon { anchors { right: parent.right verticalCenter: parent.verticalCenter margins: Kube.Units.smallSpacing } visible: model.important && !delegateRoot.buttonsVisible iconName: Kube.Icons.isImportant } Column { id: buttons anchors { right: parent.right margins: Kube.Units.smallSpacing verticalCenter: parent.verticalCenter } visible: delegateRoot.buttonsVisible opacity: 0.7 Kube.IconButton { iconName: model.doing ? Kube.Icons.listRemove : Kube.Icons.addNew activeFocusOnTab: false tooltip: model.doing ? qsTr("Unpick") : qsTr("Pick") onClicked: { var controller = controllerComponent.createObject(parent, {"todo": model.domainObject}); if (controller.complete) { controller.complete = false } controller.doing = !controller.doing; controller.saveAction.execute(); } } Kube.IconButton { iconName: Kube.Icons.checkbox checked: model.complete activeFocusOnTab: false tooltip: qsTr("Done!") onClicked: { var controller = controllerComponent.createObject(parent, {"todo": model.domainObject}); controller.complete = !controller.complete; controller.saveAction.execute(); } } } } } } Rectangle { Layout.fillHeight: parent.height Layout.fillWidth: true color: "transparent" border.width: 1 border.color: Kube.Colors.buttonColor TodoView { id: todoDetails anchors.fill: parent // onDone: popup.close() } } Component { id: controllerComponent Kube.TodoController { } } } Kube.Listener { filter: Kube.Messages.eventEditor onMessageReceived: eventPopup.createObject(root, message).open() } Component { id: editorPopup Kube.Popup { id: popup x: root.width * 0.15 y: root.height * 0.15 width: root.width * 0.7 height: root.height * 0.7 padding: 0 TodoEditor { id: editor anchors.fill: parent accountId: root.currentAccount onDone: popup.close() } } } }