diff --git a/framework/qml/Colors.qml b/framework/qml/Colors.qml index f85436bc..271d3564 100644 --- a/framework/qml/Colors.qml +++ b/framework/qml/Colors.qml @@ -1,55 +1,56 @@ /* Copyright (C) 2017 Michael Bohlender, 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. */ pragma Singleton import QtQuick 2.7 Item { //Colorscheme: //See https://community.kde.org/KDE_Visual_Design_Group/HIG/Color property string paperWhite: "#fcfcfc" property string abyssBlue: "#2980b9" property string charcoalGrey: "#31363b" property string coastalFog: "#7f8c8d" property string cardboardGrey: "#eff0f1" property string plasmaBlue: "#3daee9" property string alternateGrey: "#bdc3c7" property string nobleFir: "#27ae60" //Green property string bewareOrange: "#f67400" property string dangerRed: "#ed1515" property string darkCharcoalGrey: "#232629" + property string jazzberryJam: "#af1a6a" property string lightgrey: "lightgrey" //Colorusage: property string textColor: charcoalGrey property string disabledTextColor: coastalFog property string backgroundColor: cardboardGrey property string viewBackgroundColor: paperWhite property string highlightColor: plasmaBlue property string highlightedTextColor: paperWhite property string buttonColor: alternateGrey property string buttonBorderColor: "#7f8c8d" property string positiveColor: nobleFir property string warningColor: bewareOrange property string negativeColor: dangerRed property string statusbarColor: darkCharcoalGrey property string focusedButtonColor: abyssBlue } diff --git a/framework/src/CMakeLists.txt b/framework/src/CMakeLists.txt index d80fe284..fab7bf20 100644 --- a/framework/src/CMakeLists.txt +++ b/framework/src/CMakeLists.txt @@ -1,92 +1,93 @@ find_package(Qt5 COMPONENTS REQUIRED Core Concurrent Quick Qml WebEngineWidgets Test WebEngine Gui) find_package(KF5Mime 4.87.0 CONFIG REQUIRED) find_package(KF5CalendarCore CONFIG REQUIRED) find_package(Sink 0.6.0 CONFIG REQUIRED) find_package(KAsync CONFIG REQUIRED) find_package(Gpgme REQUIRED) find_package(KF5Codecs CONFIG REQUIRED) find_package(KF5Contacts CONFIG REQUIRED) include(GenerateExportHeader) set(CMAKE_CXX_VISIBILITY_PRESET default) set(CMAKE_CXX_STANDARD_REQUIRED 14) include_directories(. domain/mime/mimetreeparser domain/ domain/mime) add_library(kubeframework SHARED settings/settings.cpp domain/maillistmodel.cpp domain/folderlistmodel.cpp domain/perioddayeventmodel.cpp + domain/daylongeventmodel.cpp domain/composercontroller.cpp domain/modeltest.cpp domain/retriever.cpp domain/outboxmodel.cpp domain/identitiesmodel.cpp domain/recepientautocompletionmodel.cpp domain/settings/accountsettings.cpp domain/selector.cpp domain/completer.cpp domain/mouseproxy.cpp domain/contactcontroller.cpp domain/controller.cpp domain/peoplemodel.cpp domain/textdocumenthandler.cpp domain/mime/htmlutils.cpp domain/mime/messageparser.cpp domain/mime/attachmentmodel.cpp domain/mime/partmodel.cpp domain/mime/mailtemplates.cpp accounts/accountfactory.cpp accounts/accountsmodel.cpp fabric.cpp sinkfabric.cpp kubeimage.cpp clipboardproxy.cpp krecursivefilterproxymodel.cpp webengineprofile.cpp startupcheck.cpp keyring.cpp domainobjectcontroller.cpp extensionmodel.cpp viewhighlighter.cpp ) generate_export_header(kubeframework BASE_NAME Kube EXPORT_FILE_NAME kube_export.h) set_target_properties(kubeframework PROPERTIES ENABLE_EXPORTS 1 WINDOWS_EXPORT_ALL_SYMBOLS 1 ) target_link_libraries(kubeframework sink kube_otp mailcrypto Qt5::Core Qt5::Quick Qt5::Qml Qt5::WebEngineWidgets Qt5::Test Qt5::WebEngine Qt5::Gui KF5::Codecs KF5::Contacts KAsync ) install(TARGETS kubeframework DESTINATION ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) add_library(frameworkplugin SHARED frameworkplugin.cpp) target_link_libraries(frameworkplugin kubeframework ) install(TARGETS frameworkplugin DESTINATION ${FRAMEWORK_INSTALL_DIR}) set(BUILD_TESTING ON) add_subdirectory(tests) add_subdirectory(domain/mime) add_subdirectory(domain/mime/tests) add_subdirectory(domain/mime/mimetreeparser) add_subdirectory(domain/settings/tests) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/framework/src/domain/daylongeventmodel.cpp b/framework/src/domain/daylongeventmodel.cpp new file mode 100644 index 00000000..0ea73709 --- /dev/null +++ b/framework/src/domain/daylongeventmodel.cpp @@ -0,0 +1,123 @@ +/* + Copyright (c) 2018 Michael Bohlender + Copyright (c) 2018 Christian Mollekopf + Copyright (c) 2018 Rémi Nicole + + 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 "daylongeventmodel.h" + +#include +#include +#include + +DayLongEventModel::DayLongEventModel(QObject *parent) : QSortFilterProxyModel(parent) +{ + Sink::Query query; + query.setFlags(Sink::Query::LiveQuery); + query.request(); + query.request(); + query.request(); + query.request(); + + query.filter(true); + + mModel = Sink::Store::loadModel(query); + + setSourceModel(mModel.data()); +} + +QHash DayLongEventModel::roleNames() const +{ + return { + {Summary, "summary"}, + {Description, "description"}, + {StartDate, "starts"}, + {Duration, "duration"}, + }; +} + +QVariant DayLongEventModel::data(const QModelIndex &idx, int role) const +{ + auto srcIdx = mapToSource(idx); + auto event = srcIdx.data(Sink::Store::DomainObjectRole).value(); + + switch (role) { + case Summary: + return event->getSummary(); + case Description: + return event->getDescription(); + case StartDate: { + auto dayIndex = mPeriodStart.daysTo(event->getStartTime().date()); + if (dayIndex < 0) { + return 0; + } + + return dayIndex; + } + case Duration: + return event->getStartTime().date().daysTo(event->getEndTime().date()); + } + + return QSortFilterProxyModel::data(idx, role); +} + +bool DayLongEventModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + auto idx = sourceModel()->index(sourceRow, 0, sourceParent); + auto event = idx.data(Sink::Store::DomainObjectRole).value(); + + auto eventStart = event->getStartTime().date(); + auto eventEnd = event->getEndTime().date(); + + if (!eventStart.isValid() || !eventEnd.isValid()) { + SinkWarning() << "Invalid date in the event model, ignoring..."; + return false; + } + + return eventStart < mPeriodStart.addDays(mPeriodLength) && eventEnd >= mPeriodStart; +} + +QDate DayLongEventModel::periodStart() const +{ + return mPeriodStart; +} + +void DayLongEventModel::setPeriodStart(const QDate &start) +{ + if (!start.isValid()) { + SinkWarning() << "Passed an invalid starting date in setPeriodStart, ignoring..."; + return; + } + + mPeriodStart = start; +} + +void DayLongEventModel::setPeriodStart(const QVariant &start) +{ + setPeriodStart(start.toDate()); +} + +int DayLongEventModel::periodLength() const +{ + return mPeriodLength; +} + +void DayLongEventModel::setPeriodLength(int length) +{ + mPeriodLength = length; +} diff --git a/framework/src/domain/daylongeventmodel.h b/framework/src/domain/daylongeventmodel.h new file mode 100644 index 00000000..21bcbfba --- /dev/null +++ b/framework/src/domain/daylongeventmodel.h @@ -0,0 +1,69 @@ +/* + Copyright (c) 2018 Michael Bohlender + Copyright (c) 2018 Christian Mollekopf + Copyright (c) 2018 Rémi Nicole + + 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. +*/ + +#pragma once +#include "kube_export.h" +#include + +#include +#include +#include +#include + +class KUBE_EXPORT DayLongEventModel : public QSortFilterProxyModel +{ + Q_OBJECT + + Q_PROPERTY(QVariant start READ periodStart WRITE setPeriodStart) + Q_PROPERTY(int length READ periodLength WRITE setPeriodLength) + +public: + using Event = Sink::ApplicationDomain::Event; + + enum Roles + { + Summary = Qt::UserRole + 1, + Description, + StartDate, + Duration, + }; + Q_ENUM(Roles); + + DayLongEventModel(QObject *parent = nullptr); + ~DayLongEventModel() = default; + + QHash roleNames() const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + + QDate periodStart() const; + void setPeriodStart(const QDate &); + void setPeriodStart(const QVariant &); + int periodLength() const; + void setPeriodLength(int); + +private: + QSharedPointer mModel; + + QDate mPeriodStart; + int mPeriodLength = 7; +}; diff --git a/framework/src/domain/perioddayeventmodel.cpp b/framework/src/domain/perioddayeventmodel.cpp index 637e5584..827b4d4f 100644 --- a/framework/src/domain/perioddayeventmodel.cpp +++ b/framework/src/domain/perioddayeventmodel.cpp @@ -1,256 +1,258 @@ /* Copyright (c) 2018 Michael Bohlender Copyright (c) 2018 Christian Mollekopf Copyright (c) 2018 Rémi Nicole 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 "perioddayeventmodel.h" #include #include #include #include #include #include PeriodDayEventModel::PeriodDayEventModel(QObject *parent) : QAbstractItemModel(parent), partitionedEvents(7) { Sink::Query query; query.setFlags(Sink::Query::LiveQuery); query.request(); query.request(); query.request(); query.request(); + query.filter(false); + eventModel = Sink::Store::loadModel(query); QObject::connect(eventModel.data(), &QAbstractItemModel::dataChanged, this, &PeriodDayEventModel::partitionData); QObject::connect(eventModel.data(), &QAbstractItemModel::layoutChanged, this, &PeriodDayEventModel::partitionData); QObject::connect(eventModel.data(), &QAbstractItemModel::modelReset, this, &PeriodDayEventModel::partitionData); QObject::connect(eventModel.data(), &QAbstractItemModel::rowsInserted, this, &PeriodDayEventModel::partitionData); QObject::connect(eventModel.data(), &QAbstractItemModel::rowsMoved, this, &PeriodDayEventModel::partitionData); QObject::connect(eventModel.data(), &QAbstractItemModel::rowsRemoved, this, &PeriodDayEventModel::partitionData); partitionData(); } void PeriodDayEventModel::partitionData() { SinkLog() << "Partitioning event data"; beginResetModel(); partitionedEvents = QVector>>(mPeriodLength); for (int i = 0; i < eventModel->rowCount(); ++i) { auto event = eventModel->index(i, 0).data(Sink::Store::DomainObjectRole).value(); QDate eventDate = event->getStartTime().date(); if (!eventDate.isValid()) { SinkWarning() << "Invalid date in the eventModel, ignoring..."; continue; } int bucket = bucketOf(eventDate); if (bucket >= 0) { SinkTrace() << "Adding event:" << event->getSummary() << "in bucket #" << bucket; partitionedEvents[bucket].append(event); } } endResetModel(); } int PeriodDayEventModel::bucketOf(const QDate &candidate) const { int bucket = mPeriodStart.daysTo(candidate); if (bucket >= mPeriodLength || bucket < 0) { return -1; } return bucket; } QModelIndex PeriodDayEventModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) { return {}; } if (!parent.isValid()) { // Asking for a day if (!(0 <= row && row < mPeriodLength)) { return {}; } return createIndex(row, column, DAY_ID); } // Asking for an Event auto day = static_cast(parent.row()); Q_ASSERT(0 <= day && day <= mPeriodLength); if (row >= partitionedEvents[day].size()) { return {}; } return createIndex(row, column, day); } QModelIndex PeriodDayEventModel::parent(const QModelIndex &index) const { if (!index.isValid()) { return {}; } if (index.internalId() == DAY_ID) { return {}; } auto day = index.internalId(); return this->index(day, 0); } int PeriodDayEventModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) { return mPeriodLength; } auto day = parent.row(); return partitionedEvents[day].size(); } int PeriodDayEventModel::columnCount(const QModelIndex &parent) const { if (!parent.isValid()) { return 1; } return eventModel->columnCount(); } QVariant PeriodDayEventModel::data(const QModelIndex &id, int role) const { if (id.internalId() == DAY_ID) { auto day = id.row(); SinkTrace() << "Fetching data for day" << day << "with role" << QMetaEnum::fromType().valueToKey(role); switch (role) { case Qt::DisplayRole: return mPeriodStart.addDays(day).toString(); case Events: { auto result = QVariantList{}; for (int i = 0; i < partitionedEvents[day].size(); ++i) { auto eventId = index(i, 0, id); SinkTrace() << "Appending event:" << data(eventId, Summary); auto startTime = data(eventId, StartTime).toDateTime().time(); result.append(QVariantMap{ {"text", data(eventId, Summary)}, {"description", data(eventId, Description)}, {"starts", startTime.hour() + startTime.minute() / 60.}, {"duration", data(eventId, Duration)}, {"color", "#134bab"}, {"indention", 0}, }); } return result; } default: SinkWarning() << "Unknown role for day:" << QMetaEnum::fromType().valueToKey(role); return {}; } } else { auto day = id.internalId(); SinkTrace() << "Fetching data for event on day" << day << "with role" << QMetaEnum::fromType().valueToKey(role); auto event = partitionedEvents[day].at(id.row()); switch (role) { case Summary: return event->getSummary(); case Description: return event->getDescription(); case StartTime: return event->getStartTime(); case Duration: { auto start = event->getStartTime(); auto end = event->getEndTime(); return start.secsTo(end) / 3600; } default: SinkWarning() << "Unknown role for event:" << QMetaEnum::fromType().valueToKey(role); return {}; } } } QHash PeriodDayEventModel::roleNames() const { return { {Events, "events"}, {Summary, "summary"}, {Description, "description"}, {StartTime, "starts"}, {Duration, "duration"}, }; } QDate PeriodDayEventModel::periodStart() const { return mPeriodStart; } void PeriodDayEventModel::setPeriodStart(const QDate &start) { if (!start.isValid()) { SinkWarning() << "Passed an invalid starting date in setPeriodStart, ignoring..."; return; } mPeriodStart = start; partitionData(); } void PeriodDayEventModel::setPeriodStart(const QVariant &start) { setPeriodStart(start.toDate()); } int PeriodDayEventModel::periodLength() const { return mPeriodLength; } void PeriodDayEventModel::setPeriodLength(int length) { mPeriodLength = length; partitionData(); } diff --git a/framework/src/domain/perioddayeventmodel.h b/framework/src/domain/perioddayeventmodel.h index ab04df50..9458fc03 100644 --- a/framework/src/domain/perioddayeventmodel.h +++ b/framework/src/domain/perioddayeventmodel.h @@ -1,129 +1,131 @@ /* Copyright (c) 2018 Michael Bohlender Copyright (c) 2018 Christian Mollekopf Copyright (c) 2018 Rémi Nicole 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. */ #pragma once #include "kube_export.h" #include #include #include #include #include #include // Facility used to get a restricted period into a Sink model comprised of // events, partitioned according to the day the events take place. // +// Day-long events are filtered out. +// // Model Format // ============ // // Day 0 // |--- Event 0 starting at `periodStart + 0d` // |--- Event 1 starting at `periodStart + 0d` // '--- Event 2 starting at `periodStart + 0d` // Day 1 // '--- Event 0 starting at `periodStart + 1d` // Day 2 // Day 3 // |--- Event 0 starting at `periodStart + 3d` // '--- Event 1 starting at `periodStart + 3d` // Day 4 // ⋮ // // Implementation notes // ==================== // // On the model side // ----------------- // // Columns are never used. // // Top-level items just contains the ".events" attribute, and their rows // correspond to their offset compared to the start of the period (in number of // days). In that case the internalId contains DAY_ID. // // Direct children are events, and their rows corresponds to their index in // their partition. In that case no internalId / internalPointer is used. // // Internally: // ----------- // // On construction and on dataChanged, all events are processed and partitioned // in partitionedEvents: // // QVector< QList > // ~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~ // | | // | '--- List of event pointers for that day // '--- Partition / day // class KUBE_EXPORT PeriodDayEventModel : public QAbstractItemModel { Q_OBJECT Q_PROPERTY(QVariant start READ periodStart WRITE setPeriodStart) Q_PROPERTY(int length READ periodLength WRITE setPeriodLength) public: using Event = Sink::ApplicationDomain::Event; enum Roles { Events = Qt::UserRole + 1, Summary, Description, StartTime, Duration, }; Q_ENUM(Roles); PeriodDayEventModel(QObject *parent = nullptr); ~PeriodDayEventModel() = default; QModelIndex index(int row, int column, const QModelIndex &parent = {}) const override; QModelIndex parent(const QModelIndex &index) const override; int rowCount(const QModelIndex &parent) const override; int columnCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; QHash roleNames() const override; QDate periodStart() const; void setPeriodStart(const QDate &); void setPeriodStart(const QVariant &); int periodLength() const; void setPeriodLength(int); private: void partitionData(); int bucketOf(const QDate &candidate) const; QDate mPeriodStart; int mPeriodLength = 7; QSharedPointer eventModel; QVector>> partitionedEvents; static const constexpr quintptr DAY_ID = std::numeric_limits::max(); }; diff --git a/framework/src/frameworkplugin.cpp b/framework/src/frameworkplugin.cpp index 2eb53237..91d7a5dd 100644 --- a/framework/src/frameworkplugin.cpp +++ b/framework/src/frameworkplugin.cpp @@ -1,151 +1,153 @@ /* Copyright (c) 2016 Michael Bohlender Copyright (c) 2016 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 "frameworkplugin.h" #include "domain/maillistmodel.h" #include "domain/folderlistmodel.h" #include "domain/perioddayeventmodel.h" +#include "domain/daylongeventmodel.h" #include "domain/composercontroller.h" #include "domain/mime/messageparser.h" #include "domain/retriever.h" #include "domain/outboxmodel.h" #include "domain/mouseproxy.h" #include "domain/contactcontroller.h" #include "domain/peoplemodel.h" #include "domain/textdocumenthandler.h" #include "accounts/accountsmodel.h" #include "accounts/accountfactory.h" #include "settings/settings.h" #include "fabric.h" #include "kubeimage.h" #include "clipboardproxy.h" #include "webengineprofile.h" #include "startupcheck.h" #include "keyring.h" #include "controller.h" #include "domainobjectcontroller.h" #include "extensionmodel.h" #include "viewhighlighter.h" #include #include #include class KubeImageProvider : public QQuickImageProvider { public: KubeImageProvider() : QQuickImageProvider(QQuickImageProvider::Pixmap) { } QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) Q_DECL_OVERRIDE { //The platform theme plugin can overwrite our setting again once it gets loaded, //so we check on every icon load request... if (QIcon::themeName() != "kube") { QIcon::setThemeName("kube"); } const auto icon = QIcon::fromTheme(id); auto expectedSize = requestedSize; //Get the largest size that is still smaller or equal than requested //Except if we only have larger sizes, then just pick the closest one bool first = true; for (const auto s : icon.availableSizes()) { if (first && s.width() > requestedSize.width()) { expectedSize = s; break; } first = false; if (s.width() <= requestedSize.width()) { expectedSize = s; } } const auto pixmap = icon.pixmap(expectedSize); if (size) { *size = pixmap.size(); } return pixmap; } }; static QObject *fabric_singletontype_provider(QQmlEngine *engine, QJSEngine *scriptEngine) { Q_UNUSED(engine) Q_UNUSED(scriptEngine) return new Kube::Fabric::Fabric; } static QObject *webengineprofile_singletontype_provider(QQmlEngine *engine, QJSEngine *scriptEngine) { Q_UNUSED(engine) Q_UNUSED(scriptEngine) return new WebEngineProfile; } static QObject *keyring_singletontype_provider(QQmlEngine *engine, QJSEngine *scriptEngine) { Q_UNUSED(engine) Q_UNUSED(scriptEngine) auto instance = Kube::Keyring::instance(); QQmlEngine::setObjectOwnership(instance, QQmlEngine::CppOwnership); return instance; } void FrameworkPlugin::initializeEngine(QQmlEngine *engine, const char *uri) { Q_UNUSED(uri); engine->addImageProvider(QLatin1String("kube"), new KubeImageProvider); } void FrameworkPlugin::registerTypes (const char *uri) { qmlRegisterType(uri, 1, 0, "FolderListModel"); qmlRegisterType(uri, 1, 0, "MailListModel"); qmlRegisterType(uri, 1, 0, "PeriodDayEventModel"); + qmlRegisterType(uri, 1, 0, "DayLongEventModel"); qmlRegisterType(uri, 1, 0, "ComposerController"); qmlRegisterType(uri, 1, 0, "ControllerAction"); qmlRegisterType(uri, 1, 0, "MessageParser"); qmlRegisterType(uri, 1, 0, "Retriever"); qmlRegisterType(uri, 1, 0, "OutboxModel"); qmlRegisterType(uri, 1, 0, "MouseProxy"); qmlRegisterType(uri, 1, 0,"ContactController"); qmlRegisterType(uri, 1, 0,"PeopleModel"); qmlRegisterType(uri, 1, 0, "TextDocumentHandler"); qmlRegisterType(uri, 1, 0, "AccountFactory"); qmlRegisterType(uri, 1, 0, "AccountsModel"); qmlRegisterType(uri, 1, 0, "ExtensionModel"); qmlRegisterType(uri, 1, 0, "Settings"); qmlRegisterType(uri, 1, 0, "Listener"); qmlRegisterType(uri, 1, 0, "DomainObjectController"); qmlRegisterSingletonType(uri, 1, 0, "Fabric", fabric_singletontype_provider); qmlRegisterType(uri, 1, 0, "KubeImage"); qmlRegisterType(uri, 1, 0, "Clipboard"); qmlRegisterType(uri, 1, 0, "StartupCheck"); qmlRegisterType(uri, 1, 0, "ViewHighlighter"); qmlRegisterSingletonType(uri, 1, 0, "WebEngineProfile", webengineprofile_singletontype_provider); qmlRegisterSingletonType(uri, 1, 0, "Keyring", keyring_singletontype_provider); } diff --git a/tests/teststore.cpp b/tests/teststore.cpp index 3a60676d..73e34101 100644 --- a/tests/teststore.cpp +++ b/tests/teststore.cpp @@ -1,306 +1,310 @@ /* 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 "teststore.h" #include #include #include #include #include #include #include #include #include #include "framework/src/domain/mime/mailtemplates.h" using namespace Kube; static void iterateOverObjects(const QVariantList &list, std::function callback) { for (const auto &entry : list) { auto object = entry.toMap(); callback(object); } } static QStringList toStringList(const QVariantList &list) { QStringList s; for (const auto &e : list) { s << e.toString(); } return s; } static QByteArrayList toByteArrayList(const QVariantList &list) { QByteArrayList s; for (const auto &e : list) { s << e.toByteArray(); } return s; } static void createMail(const QVariantMap &object, const QByteArray &folder = {}) { using namespace Sink::ApplicationDomain; auto toAddresses = toStringList(object["to"].toList()); auto ccAddresses = toStringList(object["cc"].toList()); auto bccAddresses = toStringList(object["bcc"].toList()); QList attachments = {}; if (object.contains("attachments")) { auto attachmentSpecs = object["attachments"].toList(); for (int i = 0; i < attachmentSpecs.size(); ++i) { auto const &spec = attachmentSpecs.at(i).toMap(); attachments << Attachment{spec["name"].toString(), spec["name"].toString(), spec["mimeType"].toByteArray(), false, spec["data"].toByteArray()}; } } KMime::Types::Mailbox mb; mb.fromUnicodeString("identity@example.org"); auto msg = MailTemplates::createMessage({}, toAddresses, ccAddresses, bccAddresses, mb, object["subject"].toString(), object["body"].toString(), object["bodyIsHtml"].toBool(), attachments, {}, {}); if (object.contains("messageId")) { msg->messageID(true)->from7BitString(object["messageId"].toByteArray()); } if (object.contains("inReplyTo")) { msg->inReplyTo(true)->from7BitString(object["inReplyTo"].toByteArray()); } if (object.contains("date")) { msg->date(true)->setDateTime(QDateTime::fromString(object["date"].toString(), Qt::ISODate)); } msg->assemble(); auto mail = ApplicationDomainType::createEntity(object["resource"].toByteArray()); mail.setMimeMessage(msg->encodedContent(true)); mail.setUnread(object["unread"].toBool()); if (!folder.isEmpty()) { mail.setFolder(folder); } Sink::Store::create(mail).exec().waitForFinished(); } static void createFolder(const QVariantMap &object) { using namespace Sink::ApplicationDomain; auto folder = ApplicationDomainType::createEntity(object["resource"].toByteArray()); folder.setName(object["name"].toString()); folder.setSpecialPurpose(toByteArrayList(object["specialpurpose"].toList())); Sink::Store::create(folder).exec().waitForFinished(); iterateOverObjects(object.value("mails").toList(), [=](const QVariantMap &object) { createMail(object, folder.identifier()); }); } static void createEvent(const QVariantMap &object, const QByteArray &calendarId = {}) { using Sink::ApplicationDomain::ApplicationDomainType; using Sink::ApplicationDomain::Event; auto sinkEvent = ApplicationDomainType::createEntity(object["resource"].toByteArray()); auto calcoreEvent = QSharedPointer::create(); QString uid; if (object.contains("uid")) { uid = object["uid"].toString(); } else { uid = QUuid::createUuid().toString(); } calcoreEvent->setUid(uid); auto summary = object["summary"].toString(); calcoreEvent->setSummary(summary); if (object.contains("description")) { auto description = object["description"].toString(); calcoreEvent->setDescription(description); } auto startTime = object["starts"].toDateTime(); auto endTime = object["ends"].toDateTime(); calcoreEvent->setDtStart(startTime); calcoreEvent->setDtEnd(endTime); + if (object.contains("allDay")) { + calcoreEvent->setAllDay(object["allDay"].toBool()); + } + auto ical = KCalCore::ICalFormat().toICalString(calcoreEvent); sinkEvent.setIcal(ical.toUtf8()); sinkEvent.setCalendar(calendarId); Sink::Store::create(sinkEvent).exec().waitForFinished(); } static void createCalendar(const QVariantMap &object) { using Sink::ApplicationDomain::Calendar; using Sink::ApplicationDomain::ApplicationDomainType; auto calendar = ApplicationDomainType::createEntity(object["resource"].toByteArray()); calendar.setName(object["name"].toString()); Sink::Store::create(calendar).exec().waitForFinished(); auto calendarId = calendar.identifier(); iterateOverObjects(object.value("events").toList(), [calendarId](const QVariantMap &object) { createEvent(object, calendarId); }); } void TestStore::setup(const QVariantMap &map) { using namespace Sink::ApplicationDomain; iterateOverObjects(map.value("accounts").toList(), [&] (const QVariantMap &object) { auto account = ApplicationDomainType::createEntity("", object["id"].toByteArray()); account.setName(object["name"].toString()); Sink::Store::create(account).exec().waitForFinished(); }); QByteArrayList resources; iterateOverObjects(map.value("resources").toList(), [&] (const QVariantMap &object) { resources << object["id"].toByteArray(); auto resource = [&] { using namespace Sink::ApplicationDomain; auto resource = ApplicationDomainType::createEntity("", object["id"].toByteArray()); if (object["type"] == "dummy") { resource.setResourceType("sink.dummy"); } else if (object["type"] == "mailtransport") { resource.setResourceType("sink.mailtransport"); resource.setProperty("testmode", true); } else if (object["type"] == "caldav") { resource.setResourceType("sink.caldav"); resource.setProperty("testmode", true); } else { Q_ASSERT(false); } return resource; }(); resource.setAccount(object["account"].toByteArray()); Sink::Store::create(resource).exec().waitForFinished(); Sink::SecretStore::instance().insert(resource.identifier(), "secret"); }); iterateOverObjects(map.value("identities").toList(), [] (const QVariantMap &object) { auto identity = Sink::ApplicationDomain::Identity{}; identity.setAccount(object["account"].toByteArray()); identity.setAddress(object["address"].toString()); identity.setName(object["name"].toString()); Sink::Store::create(identity).exec().waitForFinished(); }); iterateOverObjects(map.value("folders").toList(), createFolder); iterateOverObjects(map.value("mails").toList(), [] (const QVariantMap &map) { createMail(map); }); iterateOverObjects(map.value("calendars").toList(), createCalendar); Sink::ResourceControl::flushMessageQueue(resources).exec().waitForFinished(); } QVariant TestStore::load(const QByteArray &type, const QVariantMap &filter) { using namespace Sink::ApplicationDomain; const auto list = loadList(type, filter); if (!list.isEmpty()) { if (list.size() > 1) { qWarning() << "While loading" << type << "with filter" << filter << "; got multiple elements, but returning the first one."; } return list.first(); } return {}; } template QVariantList toVariantList(const QList &list) { QVariantList result; std::transform(list.constBegin(), list.constEnd(), std::back_inserter(result), [] (const T &m) { return QVariant::fromValue(T::Ptr::create(m)); }); Q_ASSERT(list.size() == result.size()); return result; } QVariantList TestStore::loadList(const QByteArray &type, const QVariantMap &filter) { using namespace Sink::ApplicationDomain; Sink::Query query; if (filter.contains("resource")) { query.resourceFilter(filter.value("resource").toByteArray()); } for (QVariantMap::const_iterator it = filter.begin(); it != filter.end(); ++it) { if (it.key() == "messageId") { query.filter(it.value()); } else if (it.key() == "draft") { query.filter(it.value()); } else if (it.key() == "subject") { query.filter(it.value()); } } if (type == "mail") { return toVariantList(Sink::Store::read(query)); } if (type == "folder") { return toVariantList(Sink::Store::read(query)); } if (type == "resource") { return toVariantList(Sink::Store::read(query)); } if (type == "account") { return toVariantList(Sink::Store::read(query)); } Q_ASSERT(false); return {}; } QVariantMap TestStore::read(const QVariant &object) { using namespace Sink::ApplicationDomain; QVariantMap map; if (auto mail = object.value()) { map.insert("uid", mail->identifier()); map.insert("subject", mail->getSubject()); map.insert("draft", mail->getDraft()); return map; } Q_ASSERT(false); return {}; } diff --git a/views/calendar/main.qml b/views/calendar/main.qml index 26898685..e3915e74 100644 --- a/views/calendar/main.qml +++ b/views/calendar/main.qml @@ -1,90 +1,147 @@ /* * Copyright (C) 2018 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.7 import QtQuick.Controls 2.0 import QtQuick.Window 2.0 import org.kube.framework 1.0 as Kube import org.kube.test 1.0 import "qml" ApplicationWindow { id: app height: Screen.desktopAvailableHeight * 0.8 width: Screen.desktopAvailableWidth * 0.8 Component.onCompleted: { var initialState = { accounts: [{ id: "account1", name: "Test Account" }], identities: [{ account: "account1", name: "Test Identity", address: "identity@example.org" }], resources: [{ id: "caldavresource", account: "account1", type: "caldav", }], calendars: [{ id: "calendar1", resource: "caldavresource", name: "Test Calendar", events: [ { resource: "caldavresource", summary: "Test Event1", description: "This is test event #1", starts: "2018-04-10T14:03:00", ends: "2018-04-10T17:03:00", }, { resource: "caldavresource", summary: "Test Event2", description: "This is test event #2", starts: "2018-04-11T09:03:00", ends: "2018-04-11T14:03:00", }, { resource: "caldavresource", summary: "Test Event3", description: "This is test event #3", starts: "2018-04-11T10:00:00", ends: "2018-04-11T15:00:00", }, { resource: "caldavresource", summary: "Test Event4", - description: "This is test event #3", + description: "This is test event #4", starts: "2018-04-12T22:00:00", ends: "2018-04-15T03:00:00", }, + { + resource: "caldavresource", + summary: "!!! Test Event5", + description: "!!! This is test event #5", + starts: "2018-04-22T22:00:00", + ends: "2018-04-25T03:00:00", + }, + + // Day-long events + { + resource: "caldavresource", + summary: "Test day-long event1", + description: "This is test day-long event #1", + starts: "2018-04-10T00:00:00", + ends: "2018-04-14T00:00:00", + allDay: true, + }, + { + resource: "caldavresource", + summary: "Test day-long event2", + description: "This is test day-long event #2", + starts: "2018-04-11T00:00:00", + ends: "2018-04-23T00:00:00", + allDay: true, + }, + { + resource: "caldavresource", + summary: "Test day-long event3", + description: "This is test day-long event #3", + starts: "2018-04-01T00:00:00", + ends: "2018-04-13T00:00:00", + allDay: true, + }, + { + resource: "caldavresource", + summary: "Test day-long event4", + description: "This is test day-long event #4", + starts: "2018-04-01T00:00:00", + ends: "2018-04-25T00:00:00", + allDay: true, + }, + { + resource: "caldavresource", + summary: "!!! Test day-long event5", + description: "!!! This is test day-long event #5", + starts: "2018-04-01T00:00:00", + ends: "2018-04-05T00:00:00", + allDay: true, + }, + { + resource: "caldavresource", + summary: "!!! Test day-long event6", + description: "!!! This is test day-long event #6", + starts: "2018-04-23T00:00:00", + ends: "2018-04-25T00:00:00", + allDay: true, + }, ], }], } TestStore.setup(initialState) } View { anchors.fill: parent } } diff --git a/views/calendar/qml/DaylongEvents.qml b/views/calendar/qml/DaylongEvents.qml index 340c9d79..a07b66dd 100644 --- a/views/calendar/qml/DaylongEvents.qml +++ b/views/calendar/qml/DaylongEvents.qml @@ -1,16 +1,8 @@ import QtQuick 2.7 -ListModel { - ListElement { - color: "#af1a6a" - starts: 1 - duration: 4 - text: "Baustelle Adalbertstr." - } - ListElement { - color: "#134bab" - starts: 0 - duration: 6 - text: "Urlaub" - } +import org.kube.framework 1.0 as Kube + +Kube.DayLongEventModel { + start: "2018-04-09" + length: 7 } diff --git a/views/calendar/qml/WeekView.qml b/views/calendar/qml/WeekView.qml index f798e489..bcf037fa 100644 --- a/views/calendar/qml/WeekView.qml +++ b/views/calendar/qml/WeekView.qml @@ -1,275 +1,276 @@ /* * Copyright (C) 2018 Michael Bohlender, * * 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.4 import QtQuick.Layouts 1.1 import QtQuick.Controls 2.3 import Qt.labs.calendar 1.0 import org.kube.framework 1.0 as Kube FocusScope { id: root property var dayWidth: (root.width - Kube.Units.gridUnit - Kube.Units.largeSpacing * 2) / 7 property var hourHeight: Kube.Units.gridUnit * 2 Item { anchors { top: parent.top right: parent.right } width: root.dayWidth * 7 + Kube.Units.gridUnit * 2 height: root.height //BEGIN day labels DayOfWeekRow { id: dayLabels anchors.right: parent.right spacing: 0 locale: Qt.locale("en_GB") delegate: Rectangle { width: root.dayWidth height: Kube.Units.gridUnit + Kube.Units.smallSpacing * 3 border.width: 1 border.color: "lightgrey" color: Kube.Colors.viewBackgroundColor Kube.Label { anchors { top: parent.top left: parent.left margins: Kube.Units.smallSpacing } text: model.shortName } } } //END day labels //BEGIN daylong events Rectangle { id: daylong anchors { top: dayLabels.bottom right: parent.right } height: Kube.Units.gridUnit * 3 width: root.dayWidth * 7 color: Kube.Colors.viewBackgroundColor border.width: 1 border.color: Kube.Colors.buttonColor ListView { anchors { fill: parent margins: 1 } model: DaylongEvents {} delegate: Item { height: Kube.Units.gridUnit + 2 // +2 to make good for the white border width: daylong.width Rectangle { width: root.dayWidth * model.duration height: parent.height x: root.dayWidth * model.starts - color: model.color + //color: model.color + color: Kube.Colors.jazzberryJam border.width: 1 border.color: Kube.Colors.viewBackgroundColor Kube.Label { anchors { left: parent.left leftMargin: Kube.Units.smallSpacing } color: Kube.Colors.highlightedTextColor - text: model.text + text: model.summary } } } } } //END daylong events Flickable { id: mainWeekViewer anchors { top: daylong.bottom } Layout.fillWidth: true height: root.height - daylong.height - dayLabels.height - Kube.Units.largeSpacing width: root.dayWidth * 7 + Kube.Units.gridUnit * 2 contentHeight: root.hourHeight * 24 contentWidth: width clip: true boundsBehavior: Flickable.StopAtBounds ScrollBar.vertical: Kube.ScrollBar {} Row { height: root.hourHeight * 24 width: root.dayWidth * 7 + Kube.Units.gridUnit * 2 spacing: 0 //BEGIN time labels Column { anchors.bottom: parent.bottom Repeater { model: ["1:00","2:00","3:00","4:00","5:00","6:00","7:00","8:00","9:00","10:00","11:00","12:00", "13:00","14:00","15:00","16:00","17:00","18:00","19:00","20:00","21:00","22:00","23:00","0:00"] delegate: Item { height: root.hourHeight width: Kube.Units.gridUnit * 2 Kube.Label { anchors { right: parent.right rightMargin: Kube.Units.smallSpacing bottom: parent.bottom } text: model.modelData } } } } //END time labels Repeater { model: WeekEvents{} delegate: Rectangle { id: day property var events: model.events width: root.dayWidth height: root.hourHeight * 24 border.width: 1 border.color: "lightgrey" color: Kube.Colors.viewBackgroundColor Column { anchors.fill: parent Repeater { model: 24 delegate: Rectangle { height: root.hourHeight * 2 width: parent.width color: "transparent" border.width:1 border.color: "lightgrey" } } } Repeater { model: parent.events delegate: Rectangle { id: eventDelegate states: [ State { name: "dnd" when: mouseArea.drag.active PropertyChanges {target: mouseArea; cursorShape: Qt.ClosedHandCursor} PropertyChanges {target: eventDelegate; x: x; y: y} PropertyChanges {target: eventDelegate; parent: root} PropertyChanges {target: eventDelegate; opacity: 0.7} PropertyChanges {target: eventDelegate; anchors.right: ""} PropertyChanges {target: eventDelegate; width: root.dayWidth - Kube.Units.smallSpacing * 2} } ] anchors { right: parent.right rightMargin: Kube.Units.smallSpacing } width: root.dayWidth - Kube.Units.smallSpacing * 2 - Kube.Units.gridUnit * model.modelData.indention height: root.hourHeight * model.modelData.duration y: root.hourHeight * model.modelData.starts x: Kube.Units.gridUnit * model.modelData.indention color: model.modelData.color border.width: 1 border.color: Kube.Colors.viewBackgroundColor Kube.Label { anchors { left: parent.left leftMargin: Kube.Units.smallSpacing } text: model.modelData.text color: Kube.Colors.highlightedTextColor } Drag.active: mouseArea.drag.active Drag.hotSpot.x: mouseArea.mouseX Drag.hotSpot.y: mouseArea.mouseY Drag.source: eventDelegate MouseArea { id: mouseArea anchors.fill: parent hoverEnabled: true drag.target: parent onEntered: { eventDelegate.z = eventDelegate.z + 100 } onExited: { eventDelegate.z = eventDelegate.z - 100 } onReleased: eventDelegate.Drag.drop() } } } DropArea { anchors.fill: parent onDropped: { console.log("DROP") drop.accept(Qt.MoveAction) //drop.source.visible = false console.log((drop.source.y - mainWeekViewer.y + mainWeekViewer.contentY) / hourHeight) } } } } } } } }