diff --git a/framework/src/CMakeLists.txt b/framework/src/CMakeLists.txt index 15e990ad..7f880d3c 100644 --- a/framework/src/CMakeLists.txt +++ b/framework/src/CMakeLists.txt @@ -1,97 +1,98 @@ 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.7.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/eventmodel.cpp + domain/eventcontroller.cpp domain/multidayeventmodel.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 startupcheck.cpp keyring.cpp domainobjectcontroller.cpp extensionmodel.cpp viewhighlighter.cpp file.cpp logmodel.cpp entitymodel.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 KF5::CalendarCore 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/eventcontroller.cpp b/framework/src/domain/eventcontroller.cpp new file mode 100644 index 00000000..47247f09 --- /dev/null +++ b/framework/src/domain/eventcontroller.cpp @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2017 Michael Bohlender, + * 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. + */ + +#include "eventcontroller.h" + +#include +#include +#include + +#include +#include +#include + +using namespace Sink::ApplicationDomain; + +EventController::EventController() + : Kube::Controller(), + action_save{new Kube::ControllerAction{this, &EventController::save}} +{ + updateSaveAction(); +} + +void EventController::save() +{ + using namespace Sink; + using namespace Sink::ApplicationDomain; + + const auto calendar = getCalendar(); + if (!calendar) { + qWarning() << "No calendar selected"; + } + + auto calcoreEvent = QSharedPointer::create(); + calcoreEvent->setUid(QUuid::createUuid().toString()); + calcoreEvent->setSummary(getSummary()); + calcoreEvent->setDescription(getDescription()); + calcoreEvent->setDtStart(getStart()); + calcoreEvent->setDtEnd(getEnd()); + calcoreEvent->setAllDay(getAllDay()); + + Event event(calendar->resourceInstanceIdentifier()); + event.setIcal(KCalCore::ICalFormat().toICalString(calcoreEvent).toUtf8()); + event.setCalendar(*calendar); + + auto job = Store::create(event) + .then([&] (const KAsync::Error &error) { + if (error) { + SinkWarning() << "Failed to save the event: " << error; + } + emit done(); + }); + + run(job); +} + +void EventController::updateSaveAction() +{ + saveAction()->setEnabled(!getSummary().isEmpty()); +} + +void EventController::loadEvent(const QVariant &variant) +{ + using namespace Sink; + + mEvent = variant; + if (auto event = variant.value()) { + setCalendar(ApplicationDomainType::Ptr::create(ApplicationDomainType::createEntity(event->resourceInstanceIdentifier(), event->getCalendar()))); + + auto icalEvent = KCalCore::ICalFormat().readIncidence(event->getIcal()).dynamicCast(); + if(!icalEvent) { + SinkWarning() << "Invalid ICal to process, ignoring..."; + return; + } + setSummary(icalEvent->summary()); + setDescription(icalEvent->description()); + setStart(icalEvent->dtStart()); + setEnd(icalEvent->dtEnd()); + } +} + +void EventController::remove() +{ + if (auto c = mEvent.value()) { + run(Sink::Store::remove(*c)); + } +} + +QVariant EventController::event() const +{ + return mEvent; +} diff --git a/framework/src/domain/eventcontroller.h b/framework/src/domain/eventcontroller.h new file mode 100644 index 00000000..d84cc6bb --- /dev/null +++ b/framework/src/domain/eventcontroller.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2017 Michael Bohlender, + * 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. + */ + + +#pragma once +#include "kube_export.h" +#include +#include +#include +#include +#include + +#include "controller.h" + +class KUBE_EXPORT EventController : public Kube::Controller +{ + Q_OBJECT + + // Input properties + Q_PROPERTY(QVariant event READ event WRITE loadEvent) + + //Interface properties + KUBE_CONTROLLER_PROPERTY(QByteArray, AccountId, accountId) + KUBE_CONTROLLER_PROPERTY(QString, Summary, summary) + KUBE_CONTROLLER_PROPERTY(QString, Description, description) + KUBE_CONTROLLER_PROPERTY(QDateTime, Start, start) + KUBE_CONTROLLER_PROPERTY(QDateTime, End, end) + KUBE_CONTROLLER_PROPERTY(bool, AllDay, allDay) + KUBE_CONTROLLER_PROPERTY(Sink::ApplicationDomain::ApplicationDomainType::Ptr, Calendar, calendar) + + KUBE_CONTROLLER_ACTION(save) + +public: + explicit EventController(); + + Q_INVOKABLE void loadEvent(const QVariant &event); + Q_INVOKABLE void remove(); + + QVariant event() const; + +private slots: + void updateSaveAction(); + +private: + QVariant mEvent; +}; diff --git a/framework/src/domain/eventmodel.cpp b/framework/src/domain/eventmodel.cpp index ade12b0e..2ddfb04e 100644 --- a/framework/src/domain/eventmodel.cpp +++ b/framework/src/domain/eventmodel.cpp @@ -1,258 +1,259 @@ /* 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 "eventmodel.h" #include #include #include +#include -#include -#include #include #include #include #include #include -using Event = Sink::ApplicationDomain::Event; -using Calendar = Sink::ApplicationDomain::Calendar; - +using namespace Sink; EventModel::EventModel(QObject *parent) : QAbstractItemModel(parent), - mCalendarCache{EntityCache::Ptr::create()}, + mCalendarCache{EntityCache::Ptr::create()}, mCalendar{new KCalCore::MemoryCalendar{QTimeZone::systemTimeZone()}} { mRefreshTimer.setSingleShot(true); QObject::connect(&mRefreshTimer, &QTimer::timeout, this, &EventModel::updateFromSource); } void EventModel::setStart(const QDate &start) { mStart = start; updateQuery(); } QDate EventModel::start() const { return mStart; } void EventModel::setLength(int length) { mLength = length; updateQuery(); } int EventModel::length() const { return mLength; } void EventModel::setCalendarFilter(const QSet &calendarFilter) { mCalendarFilter = calendarFilter; updateQuery(); } void EventModel::setFilter(const QVariantMap &filter) { mFilter = filter; updateQuery(); } void EventModel::updateQuery() { + using namespace Sink::ApplicationDomain; if (mCalendarFilter.isEmpty() || !mLength || !mStart.isValid()) { if (rowCount()) { refreshView(); } return; } mEnd = mStart.addDays(mLength); + Sink::Query query; query.setFlags(Sink::Query::LiveQuery); query.request(); query.request(); query.request(); query.request(); query.request(); query.request(); query.request(); query.filter(Sink::Query::Comparator(QVariantList{mStart, mEnd}, Sink::Query::Comparator::Overlap)); - mSourceModel = Sink::Store::loadModel(query); + mSourceModel = Store::loadModel(query); QObject::connect(mSourceModel.data(), &QAbstractItemModel::dataChanged, this, &EventModel::refreshView); QObject::connect(mSourceModel.data(), &QAbstractItemModel::layoutChanged, this, &EventModel::refreshView); QObject::connect(mSourceModel.data(), &QAbstractItemModel::modelReset, this, &EventModel::refreshView); QObject::connect(mSourceModel.data(), &QAbstractItemModel::rowsInserted, this, &EventModel::refreshView); QObject::connect(mSourceModel.data(), &QAbstractItemModel::rowsMoved, this, &EventModel::refreshView); QObject::connect(mSourceModel.data(), &QAbstractItemModel::rowsRemoved, this, &EventModel::refreshView); refreshView(); } void EventModel::refreshView() { if (!mRefreshTimer.isActive()) { //Instant update, but then only refresh every 50ms max. updateFromSource(); mRefreshTimer.start(50); } } void EventModel::updateFromSource() { beginResetModel(); mEvents.clear(); for (int i = 0; i < mSourceModel->rowCount(); ++i) { - auto event = mSourceModel->index(i, 0).data(Sink::Store::DomainObjectRole).value(); + auto event = mSourceModel->index(i, 0).data(Sink::Store::DomainObjectRole).value(); const bool skip = [&] { if (!mCalendarFilter.contains(event->getCalendar())) { return true; } for (auto it = mFilter.constBegin(); it!= mFilter.constEnd(); it++) { if (event->getProperty(it.key().toLatin1()) != it.value()) { return true; } } return false; }(); if (skip) { continue; } //Parse the event auto icalEvent = KCalCore::ICalFormat().readIncidence(event->getIcal()).dynamicCast(); if(!icalEvent) { SinkWarning() << "Invalid ICal to process, ignoring..."; continue; } if (icalEvent->recurs()) { const auto duration = icalEvent->hasDuration() ? icalEvent->duration().asSeconds() : 0; KCalCore::OccurrenceIterator occurrenceIterator{*mCalendar, icalEvent, QDateTime{mStart, {0, 0, 0}}, QDateTime{mEnd, {12, 59, 59}}}; while (occurrenceIterator.hasNext()) { occurrenceIterator.next(); const auto start = occurrenceIterator.occurrenceStartDate(); const auto end = start.addSecs(duration); if (start.date() < mEnd && end.date() >= mStart) { - mEvents.append({start, end, occurrenceIterator.incidence(), getColor(event->getCalendar()), event->getAllDay()}); + mEvents.append({start, end, occurrenceIterator.incidence(), getColor(event->getCalendar()), event->getAllDay(), event}); } } } else { if (icalEvent->dtStart().date() < mEnd && icalEvent->dtEnd().date() >= mStart) { - mEvents.append({icalEvent->dtStart(), icalEvent->dtEnd(), icalEvent, getColor(event->getCalendar()), event->getAllDay()}); + mEvents.append({icalEvent->dtStart(), icalEvent->dtEnd(), icalEvent, getColor(event->getCalendar()), event->getAllDay(), event}); } } } endResetModel(); } QModelIndex EventModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) { return {}; } if (!parent.isValid()) { return createIndex(row, column); } return {}; } QModelIndex EventModel::parent(const QModelIndex &) const { return {}; } int EventModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) { return mEvents.size(); } return 0; } int EventModel::columnCount(const QModelIndex &) const { return 1; } QByteArray EventModel::getColor(const QByteArray &calendar) const { const auto color = mCalendarCache->getProperty(calendar, "color").toByteArray(); if (color.isEmpty()) { qWarning() << "Failed to get color for calendar " << calendar; } return color; } // QDateTime EventModel::getStartTimeOfDay(const QDateTime &dateTime, int day) const // { // if (bucketOf(dateTime.date()) < day) { // return QDateTime{mPeriodStart.addDays(day), QTime{0,0}}; // } // return dateTime; // } // QDateTime EventModel::getEndTimeOfDay(const QDateTime &dateTime, int day) const // { // if (bucketOf(dateTime.date()) > day) { // return QDateTime{mPeriodStart.addDays(day), QTime{23, 59, 59}}; // } // return dateTime; // } QVariant EventModel::data(const QModelIndex &idx, int role) const { if (!hasIndex(idx.row(), idx.column())) { return {}; } auto event = mEvents.at(idx.row()); auto icalEvent = event.incidence; switch (role) { case Summary: return icalEvent->summary(); case Description: return icalEvent->description(); case StartTime: return event.start; case EndTime: return event.end; case Color: return event.color; case AllDay: return event.allDay; + case Event: + return QVariant::fromValue(event.domainObject); default: SinkWarning() << "Unknown role for event:" << QMetaEnum::fromType().valueToKey(role); return {}; } } diff --git a/framework/src/domain/eventmodel.h b/framework/src/domain/eventmodel.h index 2031cec3..a836c894 100644 --- a/framework/src/domain/eventmodel.h +++ b/framework/src/domain/eventmodel.h @@ -1,104 +1,111 @@ /* 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 namespace KCalCore { class MemoryCalendar; class Incidence; } +namespace Sink { + namespace ApplicationDomain { + struct Event; + } +} class EntityCacheInterface; class KUBE_EXPORT EventModel : public QAbstractItemModel { Q_OBJECT Q_PROPERTY(QDate start READ start WRITE setStart) Q_PROPERTY(int length READ length WRITE setLength) Q_PROPERTY(QSet calendarFilter WRITE setCalendarFilter) Q_PROPERTY(QVariantMap filter WRITE setFilter) public: enum Roles { Summary = Qt::UserRole + 1, Description, StartTime, EndTime, Color, AllDay, + Event, LastRole }; Q_ENUM(Roles); EventModel(QObject *parent = nullptr); ~EventModel() = 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; void updateQuery(const QDate &start, const QDate &end, const QSet &calendarFilter); void setStart(const QDate &); QDate start() const; void setLength(int); int length() const; void setCalendarFilter(const QSet &); void setFilter(const QVariantMap &); private: void updateQuery(); void refreshView(); void updateFromSource(); QByteArray getColor(const QByteArray &calendar) const; QSharedPointer mSourceModel; QSet mCalendarFilter; QDate mStart; QDate mEnd; int mLength{0}; QSharedPointer mCalendarCache; QSharedPointer mCalendar; QTimer mRefreshTimer; struct Occurrence { QDateTime start; QDateTime end; QSharedPointer incidence; QByteArray color; bool allDay; + QSharedPointer domainObject; }; QList mEvents; QVariantMap mFilter; }; diff --git a/framework/src/domain/perioddayeventmodel.cpp b/framework/src/domain/perioddayeventmodel.cpp index b37e680c..088f5770 100644 --- a/framework/src/domain/perioddayeventmodel.cpp +++ b/framework/src/domain/perioddayeventmodel.cpp @@ -1,195 +1,196 @@ /* 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 enum Roles { Events = EventModel::LastRole, Date }; PeriodDayEventModel::PeriodDayEventModel(QObject *parent) : QAbstractItemModel(parent) { } void PeriodDayEventModel::setModel(EventModel *model) { beginResetModel(); mSourceModel = model; auto resetModel = [this] { beginResetModel(); endResetModel(); }; QObject::connect(model, &QAbstractItemModel::dataChanged, this, resetModel); QObject::connect(model, &QAbstractItemModel::layoutChanged, this, resetModel); QObject::connect(model, &QAbstractItemModel::modelReset, this, resetModel); QObject::connect(model, &QAbstractItemModel::rowsInserted, this, resetModel); QObject::connect(model, &QAbstractItemModel::rowsMoved, this, resetModel); QObject::connect(model, &QAbstractItemModel::rowsRemoved, this, resetModel); endResetModel(); } QModelIndex PeriodDayEventModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) { return {}; } if (!parent.isValid()) { // Asking for a day return createIndex(row, column, DAY_ID); } return {}; } 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() && mSourceModel) { return mSourceModel->length(); } return 0; } int PeriodDayEventModel::columnCount(const QModelIndex &) const { return 1; } QDateTime PeriodDayEventModel::getStartTimeOfDay(const QDateTime &dateTime, const QDate &today) const { if (dateTime.date() < today) { return QDateTime{today, QTime{0,0}}; } return dateTime; } QDateTime PeriodDayEventModel::getEndTimeOfDay(const QDateTime &dateTime, const QDate &today) const { if (dateTime.date() > today) { return QDateTime{today, QTime{23, 59, 59}}; } return dateTime; } QVariant PeriodDayEventModel::data(const QModelIndex &idx, int role) const { if (!mSourceModel) { return {}; } const auto dayOffset = idx.row(); const QDate startDate = mSourceModel->start(); const auto today = startDate.addDays(dayOffset); switch (role) { case Date: return today; case Events: { auto result = QVariantList{}; QMultiMap sorted; for (int row = 0; row < mSourceModel->rowCount(); row++) { const auto srcIdx = mSourceModel->index(row, 0, {}); if (srcIdx.data(EventModel::AllDay).toBool()) { continue; } const auto start = srcIdx.data(EventModel::StartTime).toDateTime(); const auto end = srcIdx.data(EventModel::EndTime).toDateTime(); if (end.date() < today || start.date() > today) { continue; } sorted.insert(srcIdx.data(EventModel::StartTime).toDateTime().time(), srcIdx); } QMap indentationStack; for (auto it = sorted.begin(); it != sorted.end(); it++) { // auto eventid = index(it.value(), 0, idx); const auto srcIdx = it.value(); SinkTrace() << "Appending event:" << srcIdx.data(EventModel::Summary); const auto startTime = srcIdx.data(EventModel::StartTime).toDateTime().time(); auto endTime = srcIdx.data(EventModel::EndTime).toDateTime().time(); if (!endTime.isValid()) { //Even without duration we still take some space visually endTime = startTime.addSecs(60 * 20); } //Remove all dates before startTime for (auto it = indentationStack.begin(); it != indentationStack.end();) { if (it.key() < startTime) { it = indentationStack.erase(it); } else { ++it; } } const int indentation = indentationStack.size(); indentationStack.insert(endTime, 0); const auto duration = [&] { const auto start = getStartTimeOfDay(srcIdx.data(EventModel::StartTime).toDateTime(), today); const auto end = getEndTimeOfDay(srcIdx.data(EventModel::EndTime).toDateTime(), today); return qRound(start.secsTo(end) / 3600.0); }(); result.append(QVariantMap{ {"text", srcIdx.data(EventModel::Summary)}, {"description", srcIdx.data(EventModel::Description)}, {"starts", startTime.hour() + startTime.minute() / 60.}, {"duration", duration}, {"color", srcIdx.data(EventModel::Color)}, {"indentation", indentation}, + {"event", srcIdx.data(EventModel::Event)} }); } return result; } default: Q_ASSERT(false); return {}; } } QHash PeriodDayEventModel::roleNames() const { return { {Events, "events"}, {Date, "date"} }; } diff --git a/framework/src/frameworkplugin.cpp b/framework/src/frameworkplugin.cpp index 63e17b91..bd723be9 100644 --- a/framework/src/frameworkplugin.cpp +++ b/framework/src/frameworkplugin.cpp @@ -1,197 +1,199 @@ /* 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/multidayeventmodel.h" #include "domain/eventmodel.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/eventcontroller.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 "startupcheck.h" #include "keyring.h" #include "controller.h" #include "domainobjectcontroller.h" #include "extensionmodel.h" #include "viewhighlighter.h" #include "file.h" #include "logmodel.h" #include "entitymodel.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 *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; } static QString findFile(const QString file, const QStringList importPathList) { for (const auto &path : importPathList) { const QString f = path + file; if (QFileInfo::exists(f)) { return f; } } return {}; } void FrameworkPlugin::initializeEngine(QQmlEngine *engine, const char *uri) { Q_UNUSED(uri); engine->addImageProvider(QLatin1String("kube"), new KubeImageProvider); QString kubeIcons = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("kube-icons.rcc")); //For windows if (kubeIcons.isEmpty()) { const auto locations = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation) + QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); kubeIcons = findFile(QStringLiteral("/kube/kube-icons.rcc"), locations); } //For osx if (kubeIcons.isEmpty()) { //On Mac OS we want to include Contents/Resources/ in the bundle, and that path is in AppDataLocations. QStringList iconSearchPaths; for (const auto &p : QStandardPaths::standardLocations(QStandardPaths::AppDataLocation)) { auto iconPath = p; //I'm getting broken paths reported from standardLocations if (iconPath.contains("kube.appContents")) { iconPath.replace("kube.appContents", "kube.app/Contents"); } if (iconPath.contains("kube-kolabnow.appContents")) { iconPath.replace("kube-kolabnow.appContents", "kube-kolabnow.app/Contents"); } iconSearchPaths << iconPath; } kubeIcons = findFile(QStringLiteral("/kube/kube-icons.rcc"), iconSearchPaths); } if (!QResource::registerResource(kubeIcons, "/icons/kube")) { qWarning() << "Failed to register icon resource!" << kubeIcons; qWarning() << "Searched paths: " << QStandardPaths::standardLocations(QStandardPaths::AppDataLocation) + QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); Q_ASSERT(false); } else { QIcon::setThemeSearchPaths(QStringList() << QStringLiteral(":/icons")); QIcon::setThemeName(QStringLiteral("kube")); } } 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, "MultiDayEventModel"); qmlRegisterType(uri, 1, 0, "EventModel"); + qmlRegisterType(uri, 1, 0, "EventController"); 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, "LogModel"); qmlRegisterType(uri, 1, 0, "EntityModel"); qmlRegisterType(uri, 1, 0, "CheckableEntityModel"); qmlRegisterType(uri, 1, 0, "AccountFactory"); qmlRegisterType(uri, 1, 0, "AccountsModel"); qmlRegisterType(uri, 1, 0, "ExtensionModel"); qmlRegisterType(uri, 1, 0, "File"); 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, "Keyring", keyring_singletontype_provider); } diff --git a/views/calendar/qml/EventView.qml b/views/calendar/qml/EventView.qml index ee0006e3..64281160 100644 --- a/views/calendar/qml/EventView.qml +++ b/views/calendar/qml/EventView.qml @@ -1,105 +1,106 @@ /* * 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 controller - property bool daylong width: Kube.Units.gridUnit * 7 * 7 + Kube.Units.gridUnit * 2 height: Kube.Units.gridUnit * 27 Rectangle { anchors { fill: parent } - color: Kube.Colors.viewBackgroundColor Column { anchors { fill: parent margins: Kube.Units.smallSpacing } spacing: Kube.Units.largeSpacing Kube.TextField { width: parent.width placeholderText: "Title" + text: controller.summary } RowLayout { spacing: Kube.Units.smallSpacing DayChooser { } TimeChooser { visible: !root.daylong } Kube.Label { - text: " " + qsTr("till") + " " + text: " " + qsTr("until") + " " } DayChooser { } TimeChooser { visible: !root.daylong } } RowLayout { spacing: Kube.Units.largeSpacing RowLayout { Layout.fillHeight: true Kube.CheckBox { - checked: root.daylong + checked: controller.allDay onClicked: { - root.daylong = !root.daylong + controller.allDay = !controller.allDay } } Kube.Label { - text: "daylong" + text: "All day" } } Kube.ComboBox { model: ["once", "dayly", "weekly"] } } Kube.TextEditor { width: parent.width height: 200 + text: controller.description } } } } diff --git a/views/calendar/qml/WeekView.qml b/views/calendar/qml/WeekView.qml index d46ed5e0..55d5e6fa 100644 --- a/views/calendar/qml/WeekView.qml +++ b/views/calendar/qml/WeekView.qml @@ -1,301 +1,304 @@ /* * Copyright (C) 2018 Michael Bohlender, * 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.4 import QtQuick.Layouts 1.1 import QtQuick.Controls 2.2 import org.kube.framework 1.0 as Kube import "dateutils.js" as DateUtils FocusScope { id: root property int daysToShow: 7 property var dayWidth: (root.width - Kube.Units.gridUnit - Kube.Units.largeSpacing * 2) / root.daysToShow property var hourHeight: Kube.Units.gridUnit * 2 property date currentDate property date startDate: currentDate property var calendarFilter Item { anchors { top: parent.top right: parent.right rightMargin: Kube.Units.largeSpacing } width: root.dayWidth * root.daysToShow + Kube.Units.gridUnit * 2 height: root.height Item { id: weekNumber anchors { top: parent.top left: parent.left } width: Kube.Units.gridUnit * 2 height: Kube.Units.gridUnit * 2 Label { anchors.centerIn: parent text: DateUtils.getWeek(startDate, Qt.locale().firstDayOfWeek) font.bold: true } } MultiDayView { id: daylong anchors { top: parent.top right: parent.right left: parent.left leftMargin: Kube.Units.gridUnit * 2 } dayWidth: root.dayWidth daysToShow: root.daysToShow currentDate: root.currentDate startDate: root.startDate calendarFilter: root.calendarFilter filter: {"allDay": true} paintGrid: true showDayIndicator: false dayHeaderDelegate: Item { height: Kube.Units.gridUnit + Kube.Units.smallSpacing * 3 Column { anchors.centerIn: parent Kube.Label { anchors.horizontalCenter: parent.horizontalCenter font.bold: true text: day.toLocaleString(Qt.locale(), "dddd") } Kube.Label { anchors.horizontalCenter: parent.horizontalCenter text: day.toLocaleString(Qt.locale(), "d") color: Kube.Colors.disabledTextColor font.pointSize: Kube.Units.tinyFontSize } } } } Flickable { id: mainWeekViewer anchors { top: daylong.bottom } Layout.fillWidth: true height: root.height - daylong.height - Kube.Units.largeSpacing width: root.dayWidth * root.daysToShow + Kube.Units.gridUnit * 2 contentHeight: root.hourHeight * 24 contentWidth: width clip: true boundsBehavior: Flickable.StopAtBounds ScrollBar.vertical: Kube.ScrollBar {} Kube.ScrollHelper { id: scrollHelper flickable: mainWeekViewer anchors.fill: parent } Row { height: root.hourHeight * 24 width: root.dayWidth * root.daysToShow + 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: Kube.PeriodDayEventModel { model: Kube.EventModel { start: root.startDate length: root.daysToShow calendarFilter: root.calendarFilter } } delegate: Rectangle { id: dayDelegate width: root.dayWidth height: root.hourHeight * 24 clip: true color: Kube.Colors.viewBackgroundColor property bool isInPast: DateUtils.roundToDay(root.currentDate) > DateUtils.roundToDay(date) property bool isToday: DateUtils.sameDay(root.currentDate, date) //Dimm days in the past Rectangle { anchors.fill: parent color: Kube.Colors.buttonColor opacity: 0.2 visible: isInPast } //Grid Column { anchors.fill: parent Repeater { model: 12 delegate: Rectangle { height: root.hourHeight * 2 width: parent.width color: "transparent" border.width: 1 border.color: Kube.Colors.lightgrey } } } Repeater { model: 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 } radius: 2 width: root.dayWidth - Kube.Units.smallSpacing * 2 - Kube.Units.gridUnit * model.modelData.indentation height: Math.max(root.hourHeight * 0.5, root.hourHeight * model.modelData.duration) y: root.hourHeight * model.modelData.starts x: Kube.Units.gridUnit * model.modelData.indentation color: model.modelData.color border.width: 1 border.color: Kube.Colors.viewBackgroundColor Kube.Label { anchors { fill: parent leftMargin: Kube.Units.smallSpacing rightMargin: Kube.Units.smallSpacing } text: model.modelData.text color: Kube.Colors.highlightedTextColor wrapMode: Text.Wrap elide: Text.ElideRight } 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 onReleased: eventDelegate.Drag.drop() onClicked: { eventDetails.open() } Kube.Popup { id: eventDetails width: Kube.Units.gridUnit * 7 * 7 + Kube.Units.gridUnit * 2 height: Kube.Units.gridUnit * 27 x: 0 y: 0 EventView { anchors.fill: parent + controller: Kube.EventController { + event: model.modelData.event + } } } } } } Rectangle { id: currentTimeLine anchors { right: parent.right left: parent.left } y: root.hourHeight * root.currentDate.getHours() + root.hourHeight / 60 * root.currentDate.getMinutes() height: 2 color: Kube.Colors.plasmaBlue visible: isToday opacity: 0.8 } 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) } } } } } } } }