Commit 94bfb4ab authored by Daniel Vrátil's avatar Daniel Vrátil 🤖

Implement configuration UI for the plugin

User can choose in the UI events from which calendar should be
shown in the applet, the backing ETM/Calendar only load the collections
that are actually enabled.
parent b41a5ff3
set(pimeventsplugin_SRCS
pimeventsplugin.cpp
eventdatavisitor.cpp
settingschangenotifier.cpp
)
ecm_qt_declare_logging_category(pimeventsplugin_SRCS
ecm_qt_declare_logging_category(loggingcategory_SRCS
HEADER pimeventsplugin_debug.h
IDENTIFIER PIMEVENTSPLUGIN_LOG
CATEGORY_NAME log_pimeventsplugin
)
add_library(pimevents MODULE ${pimeventsplugin_SRCS})
add_library(pimevents MODULE ${pimeventsplugin_SRCS} ${loggingcategory_SRCS})
target_link_libraries(pimevents
Qt5::Core
KF5::AkonadiCore
......@@ -23,6 +22,30 @@ target_link_libraries(pimevents
install(TARGETS pimevents
DESTINATION ${PLUGIN_INSTALL_DIR}/plasmacalendarplugins
)
######################### NEXT TARGET #######################33
set(plasmapimcalendarsplugin_SRCS
pimcalendarsplugin.cpp
pimcalendarsmodel.cpp
settingschangenotifier.cpp
)
add_library(pimcalendarsplugin SHARED ${plasmapimcalendarsplugin_SRCS} ${loggingcategory_SRCS})
target_link_libraries(pimcalendarsplugin
Qt5::Core
Qt5::Qml
KF5::AkonadiCore
KF5::CalendarCore
)
install(TARGETS pimcalendarsplugin
DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/plasma/PimCalendars
)
install(FILES qmldir
DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/plasma/PimCalendars
)
install(FILES PimEventsConfig.qml
DESTINATION ${PLUGIN_INSTALL_DIR}/plasmacalendarplugins/pimevents
)
......@@ -19,18 +19,94 @@
*/
import QtQuick 2.0
import QtQuick.Controls 1.2 as QtControls
import QtQuick.Controls 1.4
// gives us TextSingleton
import QtQuick.Controls.Private 1.0
import QtQuick.Layouts 1.0
import QtQuick.Dialogs 1.1
import org.kde.plasma.core 2.0
import org.kde.plasma.PimCalendars 1.0
Item {
id: pimEventsConfig
width: parent.width
height: parent.height
// This is just for getting the column width
QtControls.CheckBox {
id: checkbox
signal configurationChanged
function saveConfig()
{
calendarModel.saveConfig();
}
PimCalendarsModel {
id: calendarModel
}
// Invisible, used to measure implicitHeight of checkboxes so we can
// adjust row height in rowDelegate
CheckBox {
id: checkboxSize
visible: false
}
TreeView {
id: calendarTreeView;
anchors.fill: parent
model: calendarModel
TableViewColumn {
role: "data"
title: "Select Calendars";
delegate: Item {
CheckBox {
id: checkbox
visible: styleData.value["enabled"]
checked: styleData.value["checked"]
onCheckedChanged: {
if (checked == styleData.value["checked"]) {
return;
}
calendarModel.setChecked(styleData.value["id"], checked);
pimEventsConfig.configurationChanged();
}
width: 24
height: 24
}
IconItem {
id: icon
anchors.left: checkbox.visible ? checkbox.right : parent.left
visible: valid
source: styleData.value["iconName"]
height: 20
width: 20
}
Text {
anchors.left: icon.visible ? icon.right : checkbox.visible ? checkbox.right : parent.left
text: styleData.value["name"]
horizontalAlignment: Qt.AlignLeft
verticalAlignment: Qt.AlignVCenter
color: styleData.textColor
height: 24
}
}
}
// Based on Desktop.TableViewStyle
rowDelegate: BorderImage {
visible: styleData.selected || styleData.alternate
source: "image://__tablerow/" + (styleData.alternate ? "alternate_" : "")
+ (styleData.selected ? "selected_" : "")
+ (calendarTreeView.activeFocus ? "active" : "")
// Make sure the checkbox always fits, add 4 for some small margin
height: Math.min(checkboxSize.implicitHeight, Math.max(16, TextSingleton.implicitHeight * 1.2)) + 4
border {
left: 4
right: 4
}
}
}
}
/*
* Copyright (C) 2016 Daniel Vrátil <dvratil@kde.org>
*
* 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 "pimcalendarsmodel.h"
#include "settingschangenotifier.h"
#include <AkonadiCore/ChangeRecorder>
#include <AkonadiCore/CollectionFetchScope>
#include <AkonadiCore/EntityTreeModel>
#include <AkonadiCore/EntityDisplayAttribute>
#include <KCalCore/Event>
#include <KCalCore/Todo>
#include <KSharedConfig>
#include <KConfigGroup>
PimCalendarsModel::PimCalendarsModel(QObject *parent)
: QSortFilterProxyModel(parent)
{
setSortRole(Qt::DisplayRole);
setSortCaseSensitivity(Qt::CaseInsensitive);
setSortLocaleAware(true);
setDynamicSortFilter(true);
auto cr = new Akonadi::ChangeRecorder(this);
cr->setMimeTypeMonitored(KCalCore::Event::eventMimeType());
cr->setMimeTypeMonitored(KCalCore::Todo::todoMimeType());
cr->setTypeMonitored(Akonadi::Monitor::Collections);
cr->collectionFetchScope().setListFilter(Akonadi::CollectionFetchScope::Enabled);
mEtm = new Akonadi::EntityTreeModel(cr);
mEtm->setItemPopulationStrategy(Akonadi::EntityTreeModel::NoItemPopulation);
mEtm->setListFilter(Akonadi::CollectionFetchScope::Enabled);
connect(mEtm, &Akonadi::EntityTreeModel::collectionTreeFetched,
this, [this]() { sort(0, Qt::AscendingOrder); });
setSourceModel(mEtm);
auto config = KSharedConfig::openConfig();
auto group = config->group("PIMEventsPlugin");
mEnabledCalendars = QSet<qint64>::fromList(group.readEntry(QStringLiteral("calendars"), QList<qint64>()));
}
PimCalendarsModel::~PimCalendarsModel()
{
}
QHash<int, QByteArray> PimCalendarsModel::roleNames() const
{
return { { DataRole, "data" } };
}
QVariant PimCalendarsModel::data(const QModelIndex &index, int role) const
{
if (role == Qt::DisplayRole) {
return QSortFilterProxyModel::data(index, role);
}
if (role != DataRole) {
return QVariant();
}
const auto &col = QSortFilterProxyModel::data(index, Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>();
if (!col.isValid()) {
return QVariant();
}
const auto mts = col.contentMimeTypes();
const bool enabled = mts.contains(KCalCore::Event::eventMimeType()) || mts.contains(KCalCore::Todo::todoMimeType());
auto attr = col.attribute<Akonadi::EntityDisplayAttribute>();
const QString icon = attr ? attr->iconName() : QString::null;
return QVariantMap{ { QStringLiteral("id"), col.id() },
{ QStringLiteral("name"), col.displayName() },
{ QStringLiteral("enabled"), enabled },
{ QStringLiteral("checked"), mEnabledCalendars.contains(col.id()) },
{ QStringLiteral("iconName"), icon } };
}
void PimCalendarsModel::setChecked(qint64 collectionId, bool checked)
{
bool done = false;
if (checked) {
done = !mEnabledCalendars.contains(collectionId);
mEnabledCalendars.insert(collectionId);
} else {
done = mEnabledCalendars.remove(collectionId);
}
if (done) {
const QModelIndex idx = Akonadi::EntityTreeModel::modelIndexForCollection(this, Akonadi::Collection(collectionId));
Q_EMIT dataChanged(idx, idx);
}
}
void PimCalendarsModel::saveConfig()
{
auto config = KSharedConfig::openConfig();
auto group = config->group("PIMEventsPlugin");
auto savedList = group.readEntry("calendars", QList<qint64>());
auto currentList = mEnabledCalendars.toList();
qSort(savedList);
qSort(currentList);
if (currentList != savedList) {
group.writeEntry("calendars", currentList);
SettingsChangeNotifier::self()->notifySettingsChanged();
}
}
/*
* Copyright (C) 2016 Daniel Vrátil <dvratil@kde.org>
*
* 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.
*
*/
#ifndef PIMCALENDARSMODEL_H
#define PIMCALENDARSMODEL_H
#include <QSortFilterProxyModel>
#include <QSet>
namespace Akonadi {
class EntityTreeModel;
}
class PimCalendarsModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
enum Roles {
DataRole = Qt::UserRole + 1
};
explicit PimCalendarsModel(QObject *parent = Q_NULLPTR);
~PimCalendarsModel();
QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE;
QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE;
public Q_SLOTS:
void setChecked(qint64 collectionId, bool checked);
void saveConfig();
private:
Akonadi::EntityTreeModel *mEtm;
QSet<qint64> mEnabledCalendars;
};
#endif
/*
* Copyright (C) 2016 Daniel Vrátil <dvratil@kde.org>
*
* 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 <QQmlExtensionPlugin>
#include <QtQml>
#include "pimcalendarsmodel.h"
class PimCalendarsPlugin : public QQmlExtensionPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
public:
void registerTypes(const char *uri) Q_DECL_OVERRIDE
{
qmlRegisterType<PimCalendarsModel>(uri, 1, 0, "PimCalendarsModel");
}
};
#include "pimcalendarsplugin.moc"
......@@ -20,12 +20,22 @@
#include "pimeventsplugin.h"
#include "eventdatavisitor.h"
#include "pimeventsplugin_debug.h"
#include "settingschangenotifier.h"
#include <AkonadiCore/ChangeRecorder>
#include <AkonadiCore/ItemFetchScope>
#include <AkonadiCore/EntityDisplayAttribute>
#include <AkonadiCore/EntityTreeModel>
#include <Akonadi/Calendar/ETMCalendar>
#include <KCalCore/Visitor>
#define TRACE_RESULTS
#include <KSharedConfig>
#include <KConfigGroup>
#include <QSet>
#define TRACE_RESULTS
QDebug operator<<(QDebug dbg, const CalendarEvents::EventData &data)
{
......@@ -42,8 +52,16 @@ PimEventsPlugin::PimEventsPlugin(QObject* parent)
{
qCDebug(PIMEVENTSPLUGIN_LOG) << "PIM Events Plugin activated";
mCalendar = new Akonadi::ETMCalendar(this);
// TODO: Filter out disabled calendars
connect(SettingsChangeNotifier::self(), &SettingsChangeNotifier::settingsChanged,
this, &PimEventsPlugin::onSettingsChanged);
mMonitor = new Akonadi::ChangeRecorder(this);
mMonitor->setChangeRecordingEnabled(false);
mMonitor->itemFetchScope().fetchFullPayload(true);
mMonitor->itemFetchScope().fetchAttribute<Akonadi::EntityDisplayAttribute>();
onSettingsChanged();
mCalendar = new Akonadi::ETMCalendar(mMonitor, this);
// TOOD: Only retrieve PLD:HEAD once it's supported
mCalendar->setCollectionFilteringEnabled(false);
mCalendar->registerObserver(this);
......@@ -135,3 +153,34 @@ void PimEventsPlugin::calendarIncidenceAboutToBeDeleted(const KCalCore::Incidenc
}
}
}
void PimEventsPlugin::onSettingsChanged()
{
QSet<Akonadi::Collection> currentCols;
Q_FOREACH (const Akonadi::Collection &col, mMonitor->collectionsMonitored()) {
currentCols.insert(col);
}
auto config = KSharedConfig::openConfig();
auto group = config->group("PIMEventsPlugin");
const QList<qint64> calendars = group.readEntry(QStringLiteral("calendars"), QList<qint64>());
QSet<Akonadi::Collection> configuredCols;
Q_FOREACH (qint64 colId, calendars) {
configuredCols.insert(Akonadi::Collection(colId));
}
qCDebug(PIMEVENTSPLUGIN_LOG) << configuredCols << currentCols;
Q_FOREACH (const Akonadi::Collection &col, (currentCols - configuredCols)) {
mMonitor->setCollectionMonitored(col, false);
qCDebug(PIMEVENTSPLUGIN_LOG) << "Disabled calendar" << col.id();
}
Q_FOREACH (const Akonadi::Collection &col, (configuredCols - currentCols)) {
mMonitor->setCollectionMonitored(col, true);
qCDebug(PIMEVENTSPLUGIN_LOG) << "Enabled calendar" << col.id();
}
const bool hasSelectedCols = mMonitor->collectionsMonitored().isEmpty();
mMonitor->setMimeTypeMonitored(KCalCore::Event::eventMimeType(), hasSelectedCols);
mMonitor->setMimeTypeMonitored(KCalCore::Todo::todoMimeType(), hasSelectedCols);
mMonitor->setMimeTypeMonitored(KCalCore::Journal::journalMimeType(), hasSelectedCols);
}
......@@ -25,6 +25,7 @@
namespace Akonadi
{
class ChangeRecorder;
class ETMCalendar;
}
......@@ -49,7 +50,11 @@ public:
// to lookup corresponding Akonadi ID in ETMCalendar
void calendarIncidenceAboutToBeDeleted(const KCalCore::Incidence::Ptr &incidence) Q_DECL_OVERRIDE;
private Q_SLOTS:
void onSettingsChanged();
private:
Akonadi::ChangeRecorder *mMonitor;
Akonadi::ETMCalendar *mCalendar;
QDate mStart;
QDate mEnd;
......
module org.kde.plasma.PimCalendars
plugin pimcalendarsplugin
#include "settingschangenotifier.h"
#include "pimeventsplugin_debug.h"
#include <QCoreApplication>
#define APP_PROPERTY_NAME "PIMEventsPluginSettingsChangeNotifier"
SettingsChangeNotifier *SettingsChangeNotifier::self()
{
// We can't easilly use a global static (or a static member) to store the
// global instance of SettingsChangeNotifier. We need the same instance to
// be accessible by both PimEventsPlugin and PimCalendarsPlugin so I would
// have to put this class to a .so and link it from both to get a
// singleton that actually works across the plugins. But being the lazy
// bastard that I am I decided to just abuse QObject::property() and the qApp
// singleton which already comes from an .so linked by both plugins.
//
// Also note the cast to quintptr: we have the same problem as above with
// SettingsChangeNotifier::staticMetaObject as each "copy" of the class
// has its own instance of it, which causes pointer comparision in
// QMetaObject::inherits() to fail. This leads to v.isValid() being true but
// v.value<SettingsChangeNotifier*>() returning a null pointer, because
// the internal qobject_cast fails.
//
// Yeah, I could have totally spent 30 seconds of my time and write the 6
// lines of CMake code to get my own .so and have it linked from both plugins,
// but instead I decided to explain myself in this comment, probably because
// short code with long comments makes it look like I know what I'm doing.
const QVariant v = qApp->property(APP_PROPERTY_NAME);
if (v.isValid()) {
return reinterpret_cast<SettingsChangeNotifier*>(v.value<quintptr>());
}
SettingsChangeNotifier *notifier = new SettingsChangeNotifier();
qApp->setProperty(APP_PROPERTY_NAME, reinterpret_cast<quintptr>(notifier));
return notifier;
}
SettingsChangeNotifier::SettingsChangeNotifier(QObject *parent)
: QObject(parent)
{
qCDebug(PIMEVENTSPLUGIN_LOG) << this << "created";
}
SettingsChangeNotifier::~SettingsChangeNotifier()
{
}
void SettingsChangeNotifier::notifySettingsChanged()
{
qCDebug(PIMEVENTSPLUGIN_LOG) << "====== NOTIFY SETTINGS CHANGED";
Q_EMIT settingsChanged();
}
/*
* Copyright (C) 2016 Daniel Vrátil <dvratil@kde.org>
*
* 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.
*
*/
#ifndef SETTINGSCHANGENOTIFIER_H
#define SETTINGSCHANGENOTIFIER_H
#include <QObject>
class SettingsChangeNotifier : public QObject
{
Q_OBJECT
public:
static SettingsChangeNotifier *self();
~SettingsChangeNotifier();
void notifySettingsChanged();
Q_SIGNALS:
void settingsChanged();
private:
explicit SettingsChangeNotifier(QObject *parent = Q_NULLPTR);
};
#endif
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment