Commit 36641244 authored by Dimitris Kardarakos's avatar Dimitris Kardarakos

Implement calendar import functionality

parent a42b436f
......@@ -22,26 +22,74 @@ import QtQuick 2.0
import QtQuick.Controls 2.4 as Controls2
import QtQuick.Layouts 1.11
import org.kde.kirigami 2.4 as Kirigami
import org.kde.phone.calindori 0.1 as Calindori
Kirigami.Page {
id: root
property alias calendarName: name.text
property alias calendarName: nameInput.text
property alias activeCalendar: isactive.checked
property string mode: "add"
property var configuration
property alias fileToImport: importFilePath.text
signal calendarAdded;
signal calendarAddCanceled;
title: qsTr("New calendar")
function importCalendar() {
if(root.fileToImport.text == "") {
showPassiveNotification("Please import a calendar file");
return;
}
var canAddResult = configuration.canAddCalendar(root.calendarName);
if(!(canAddResult.success)) {
showPassiveNotification(canAddResult.reason);
return;
}
var importResult = calendarController.importCalendar(root.calendarName, root.fileToImport);
if(!(importResult.success)) {
showPassiveNotification("Calendar not imported. " + importResult.reason);
return;
}
addCalendarToConfig(false);
}
function addCalendarToConfig(validateEntry=true) {
var canAddResult = validateEntry ? configuration.canAddCalendar(root.calendarName) : null;
if(canAddResult && !(canAddResult.success)) {
showPassiveNotification(canAddResult.reason);
return;
}
var insertResult = configuration.addCalendar(root.calendarName);
if(!(insertResult.success)) {
showPassiveNotification(insertResult.reason);
return;
}
if(root.activeCalendar) {
configuration.activeCalendar = root.calendarName;
}
calendarAdded();
}
Kirigami.FormLayout {
id: calendarInputPage
anchors.centerIn: parent
Controls2.TextField {
id: name
id: nameInput
Kirigami.FormData.label: qsTr("Name:")
}
......@@ -51,6 +99,22 @@ Kirigami.Page {
Kirigami.FormData.label: qsTr("Active:")
}
RowLayout {
visible: (mode == "import")
Kirigami.FormData.label: qsTr("File:")
Controls2.TextField {
id: importFilePath
}
Controls2.Button {
text: "Add"
onClicked: fileChooser.open()
}
}
}
actions {
......@@ -66,7 +130,6 @@ Kirigami.Page {
}
}
main: Kirigami.Action {
id: info
......@@ -85,8 +148,29 @@ Kirigami.Page {
icon.name : "dialog-ok"
onTriggered: {
calendarAdded();
if(root.calendarName == "") {
showPassiveNotification("Please enter a calendar name");
return;
}
if(mode == "import") {
importCalendar();
}
else {
addCalendarToConfig();
}
}
}
}
FileChooser {
id: fileChooser
onAccepted: root.fileToImport = fileUrl.toString().replace('qrc:','')
}
Calindori.LocalCalendar {
id: calendarController
}
}
/*
* Kaidan - A user-friendly XMPP client for every device!
*
* Copyright (C) 2016-2019 Kaidan developers and contributors
* (see the LICENSE file for a full list of copyright authors)
*
* Kaidan 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 3 of the License, or
* (at your option) any later version.
*
* In addition, as a special exception, the author of Kaidan gives
* permission to link the code of its release with the OpenSSL
* project's "OpenSSL" library (or with modified versions of it that
* use the same license as the "OpenSSL" library), and distribute the
* linked executables. You must obey the GNU General Public License in
* all respects for all of the code used other than "OpenSSL". If you
* modify this file, you may extend this exception to your version of
* the file, but you are not obligated to do so. If you do not wish to
* do so, delete this exception statement from your version.
*
* Kaidan 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 Kaidan. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.6
import org.kde.kirigami 2.0 as Kirigami
Item {
id: root
property string filter: "*"
property string filterName: "All files"
property string fileUrl
property bool selectFolder: false
property string title: qsTr("Select a file")
signal accepted
Loader {
id: fileChooserLoader
}
function open() {
fileChooserLoader.item.open()
}
Component.onCompleted: {
if (Kirigami.Settings.isMobile) {
fileChooserLoader.setSource("FileChooserMobile.qml",
{
"nameFilter": filter,
"title": title
})
}
else if (!Kirigami.Settings.isMobile) {
var selectedNameFilter = filterName + " (" + filter + ")"
fileChooserLoader.setSource("FileChooserDesktop.qml",
{
"selectedNameFilter": selectedNameFilter,
"selectFolder": selectFolder,
"title": title
})
}
else {
fileChooserLoader.setSource("FileChooserDesktop.qml")
}
}
Connections {
target: fileChooserLoader.item
onAccepted: {
fileUrl = fileChooserLoader.item.fileUrl
root.accepted()
console.log("Child file dialog accepted. URL: " + fileUrl)
}
}
}
/*
* Kaidan - A user-friendly XMPP client for every device!
*
* Copyright (C) 2016-2019 Kaidan developers and contributors
* (see the LICENSE file for a full list of copyright authors)
*
* Kaidan 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 3 of the License, or
* (at your option) any later version.
*
* In addition, as a special exception, the author of Kaidan gives
* permission to link the code of its release with the OpenSSL
* project's "OpenSSL" library (or with modified versions of it that
* use the same license as the "OpenSSL" library), and distribute the
* linked executables. You must obey the GNU General Public License in
* all respects for all of the code used other than "OpenSSL". If you
* modify this file, you may extend this exception to your version of
* the file, but you are not obligated to do so. If you do not wish to
* do so, delete this exception statement from your version.
*
* Kaidan 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 Kaidan. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.6
import QtQuick.Dialogs 1.2
FileDialog {
id: fileDialog
folder: shortcuts.home
nameFilters: [
"Images (*.jpg *.jpeg *.png *.gif)",
"Videos (*.mp4 *.mkv *.avi *.webm)",
"Audio files (*.mp3 *.wav *.flac *.ogg *.m4a *.mka)",
"Documents (*.doc *.docx *.odt)",
"All files (*)",
selectedNameFilter
]
// TODO: support multiple files
// Currently the problem is that the fileUrls list isn't cleared
}
/*
* Kaidan - A user-friendly XMPP client for every device!
*
* Copyright (C) 2016-2019 Kaidan developers and contributors
* (see the LICENSE file for a full list of copyright authors)
*
* Kaidan 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 3 of the License, or
* (at your option) any later version.
*
* In addition, as a special exception, the author of Kaidan gives
* permission to link the code of its release with the OpenSSL
* project's "OpenSSL" library (or with modified versions of it that
* use the same license as the "OpenSSL" library), and distribute the
* linked executables. You must obey the GNU General Public License in
* all respects for all of the code used other than "OpenSSL". If you
* modify this file, you may extend this exception to your version of
* the file, but you are not obligated to do so. If you do not wish to
* do so, delete this exception statement from your version.
*
* Kaidan 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 Kaidan. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.7
import QtQuick.Layouts 1.1
import org.kde.kirigami 2.0 as Kirigami
import Qt.labs.folderlistmodel 2.1
Item {
id: root
property url fileUrl
property string title
signal accepted
property string nameFilter
Component {
id: fileChooserPage
Kirigami.ScrollablePage {
title: root.title
actions {
main: Kirigami.Action {
id: parentFolderButton
tooltip: qsTr("Go to parent folder")
icon.name: "go-parent-folder"
icon.color: "transparent"
onTriggered: fileModel.folder = fileModel.parentFolder
enabled: fileModel.folder != "file:///"
}
right: Kirigami.Action {
tooltip: qsTr("Close")
icon.name: "dialog-close"
icon.color: "transparent"
onTriggered: pageStack.pop()
enabled: true
}
}
FolderListModel {
id: fileModel
nameFilters: root.nameFilter
showDirsFirst: true
showDotAndDotDot: false // replaced by the main action Button
showOnlyReadable: true
}
ListView {
id: view
model: fileModel
anchors.fill: parent
delegate: Kirigami.BasicListItem {
width: parent.width
reserveSpaceForIcon: true
icon: (fileIsDir ? "folder" : "text-x-plain")
iconColor: "transparent"
label: fileName + (fileIsDir ? "/" : "")
onClicked: {
if (fileIsDir) {
if (fileName === "..")
fileModel.folder = fileModel.parentFolder
else if (fileName !== ".")
fileModel.folder = "file://" + filePath
} else {
root.fileUrl = filePath
root.accepted()
pageStack.pop()
}
}
}
}
}
}
function open() {
pageStack.push(fileChooserPage)
}
}
......@@ -44,9 +44,16 @@ Kirigami.ApplicationWindow {
iconName: "view-calendar"
Kirigami.Action {
text: "Add calendar..."
text: "Create"
iconName: "list-add"
onTriggered: root.pageStack.push(calendarInputPage);
onTriggered: root.pageStack.push(calendarEditor, {mode: "add"});
}
Kirigami.Action {
text: "Import"
iconName: "document-import"
onTriggered: root.pageStack.push(calendarEditor, {mode: "import"});
}
Kirigami.Action {
......@@ -106,6 +113,7 @@ Kirigami.ApplicationWindow {
root.refreshNeeded();
if (root.pageStack.depth > 1) {
root.pageStack.pop(null);
root.pageStack.push(calendarDashboardComponent);
}
}
}
......@@ -217,7 +225,6 @@ Kirigami.ApplicationWindow {
}
}
Component {
id: eventsView
......@@ -234,32 +241,20 @@ Kirigami.ApplicationWindow {
}
}
Component {
id: calendarInputPage
id: calendarEditor
CalendarInputPage {
CalendarEditor {
onCalendarAdded: {
var calendarAddResult = "";
calendarAddResult = calindoriConfig.addCalendar(calendarName);
if(calendarAddResult != "")
{
showPassiveNotification(calendarAddResult);
return;
}
configuration: calindoriConfig
if(activeCalendar)
{
calindoriConfig.activeCalendar = calendarName;
}
onCalendarAdded: {
root.refreshNeeded();
root.pageStack.pop(calendarInputPage);
root.pageStack.pop(calendarEditor);
}
onCalendarAddCanceled: {
root.pageStack.pop(calendarInputPage);
root.pageStack.pop(calendarEditor);
}
}
......
/*
* Copyright (C) 2018 Dimitris Kardarakos
* Copyright (C) 2019 Dimitris Kardarakos
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
......@@ -21,6 +21,8 @@
#include <KConfig>
#include <KConfigGroup>
#include <QDebug>
#include <QRegExp>
class CalindoriConfig::Private
{
public:
......@@ -66,35 +68,66 @@ void CalindoriConfig::setActiveCalendar(const QString & calendar)
emit activeCalendarChanged();
}
QString CalindoriConfig::addCalendar(const QString & calendar)
QVariantMap CalindoriConfig::canAddCalendar(const QString& calendar)
{
if(calendar.contains(";"))
QVariantMap result;
result["success"] = QVariant(true);
result["reason"] = QVariant(QString());
QRegExp invalidChars("[\\;\\\\/<>:\\?\\*|\"\']");
if(calendar.contains(invalidChars))
{
return "Calendar name should not contain semicolons";
result["success"] = QVariant(false);
result["reason"] = QVariant("Calendar name contains invalid characters");
return result;
}
if(d->config.group("general").readEntry("calendars", QString()).isEmpty())
{
qDebug() << "Calendar list is empty";
d->config.group("general").writeEntry("calendars", calendar);
return QString();
return result;
}
qDebug() << "Calendar list is not empty, adding calendar " << calendar;
QStringList calendarsList = d->config.group("general").readEntry("calendars", QString()).split(";");
if(calendarsList.contains(calendar))
{
return "Calendar already exists";
result["success"] = QVariant(false);
result["reason"] = QVariant("Calendar already exists");
return result;
}
return result;
}
QVariantMap CalindoriConfig::addCalendar(const QString & calendar)
{
QVariantMap result;
result["success"] = QVariant(true);
result["reason"] = QVariant(QString());
QVariantMap canAddResult = canAddCalendar(calendar);
if(!(canAddResult["success"].toBool()))
{
result["success"] = QVariant(false);
result["reason"] = QVariant(canAddResult["reason"].toString());
return result;
}
if(d->config.group("general").readEntry("calendars", QString()).isEmpty())
{
d->config.group("general").writeEntry("calendars", calendar);
return result;
}
QStringList calendarsList = d->config.group("general").readEntry("calendars", QString()).split(";");
calendarsList.append(calendar);
d->config.group("general").writeEntry("calendars", calendarsList.join(";"));
d->config.sync();
emit calendarsChanged();
return QString();
return result;
}
void CalindoriConfig::removeCalendar(const QString& calendar)
......
/*
* Copyright (C) 2018 Dimitris Kardarakos
* Copyright (C) 2019 Dimitris Kardarakos
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
......@@ -20,6 +20,7 @@
#define MOBILECALENDARCONFIG_H
#include <QObject>
#include <QVariantMap>
class CalindoriConfig : public QObject
{
......@@ -39,7 +40,8 @@ public:
Q_SIGNAL void activeCalendarChanged();
public Q_SLOTS:
QString addCalendar(const QString& calendar);
QVariantMap canAddCalendar(const QString& calendar);
QVariantMap addCalendar(const QString& calendar);
void removeCalendar(const QString& calendar);
private:
......
......@@ -49,19 +49,22 @@ QString LocalCalendar::name() const
void LocalCalendar::setName(QString calendarName)
{
if (m_name != calendarName) {
if (m_name != calendarName)
{
MemoryCalendar::Ptr calendar(new MemoryCalendar(QTimeZone::systemTimeZoneId()));
FileStorage::Ptr storage(new FileStorage(calendar));
m_fullpath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/" + calendarName + "_local.ical" ;
m_fullpath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/calindori_" + calendarName + ".ics" ;
QFile calendarFile(m_fullpath);
storage->setFileName(m_fullpath);
if (!calendarFile.exists()) {
if (!calendarFile.exists())
{
qDebug() << "Creating file" << storage->save();
}
if(storage->load()) {
if(storage->load())
{
m_name = calendarName;
m_calendar = calendar;
m_cal_storage = storage;
......@@ -75,19 +78,19 @@ void LocalCalendar::setName(QString calendarName)
void LocalCalendar::setMemorycalendar(MemoryCalendar::Ptr memoryCalendar)
{
if(m_calendar != memoryCalendar) {
if(m_calendar != memoryCalendar)
{
m_calendar = memoryCalendar;
qDebug() << "Calendar succesfully set";
}
}
void LocalCalendar::setCalendarstorage(FileStorage::Ptr calendarStorage)
{
if(m_cal_storage != calendarStorage) {
if(m_cal_storage != calendarStorage)
{
m_cal_storage = calendarStorage;
qDebug() << "Storage succesfully set";
}
}
......@@ -97,7 +100,7 @@ int LocalCalendar::todosCount(const QDate &date) const {
return 0;
}
Todo::List todoList = m_calendar->rawTodos(date,date);
//DEBUG qDebug() << todoList.size() << " todos found in " << date.toString();
return todoList.size();
}
......@@ -106,7 +109,8 @@ void LocalCalendar::deleteCalendar()
qDebug() << "Deleting calendar at " << m_fullpath;
QFile calendarFile(m_fullpath);
if (calendarFile.exists()) {
if (calendarFile.exists())
{
calendarFile.remove();
}
}
......@@ -123,6 +127,7 @@ int LocalCalendar::eventsCount(const QDate& date) const {
return 0;
}
Event::List eventList = m_calendar->rawEvents(date,date);
return eventList.count();
}
......@@ -130,3 +135,65 @@ bool LocalCalendar::save()
{
return m_cal_storage->save();
}
QVariantMap LocalCalendar::canCreateFile(const QString& calendarName)
{
QVariantMap result;
result["success"] = QVariant(true);
result["reason"] = QVariant(QString());
QString targetPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/calindori_" + calendarName + ".ics" ;
QFile calendarFile(targetPath);
if(calendarFile.exists())
{
result["success"] = QVariant(false);
result["reason"] = QVariant(QString("A calendar with the same name already exists"));
return result;
}
result["targetPath"] = QVariant(QString(targetPath));
return result;
}
QVariantMap LocalCalendar::importCalendar(const QString& calendarName, const QString& sourcePath)
{
QVariantMap result;
result["success"] = QVariant(false);
MemoryCalendar::Ptr calendar(new MemoryCalendar(QTimeZone::systemTimeZoneId()));
FileStorage::Ptr storage(new FileStorage(calendar));
QVariantMap canCreateCheck = canCreateFile(calendarName);
if(!(canCreateCheck["success"].toBool()))
{
result["reason"] = QVariant(canCreateCheck["reason"].toString());
return result;
}
storage->setFileName(sourcePath);
if(!(storage->load()))
{
result["reason"] = QVariant(QString("The calendar file is not valid"));
return result;
}
storage->setFileName(canCreateCheck["targetPath"].toString());
if(!(storage->save()))
{
result["reason"] = QVariant(QString("The calendar file cannot be saved"));
return result;
}
result["success"] = QVariant(true);
result["reason"] = QVariant(QString());
return result;
}
......@@ -23,6 +23,7 @@
#include <KCalCore/MemoryCalendar>
#include <KCalCore/FileStorage>
#include <KCalCore/Event>
#include <QVariantMap>
using namespace KCalCore;
......@@ -52,16 +53,20 @@ public Q_SLOTS:
void deleteCalendar();
int eventsCount(const QDate& date) const;
bool save();
static QVariantMap importCalendar(const QString& calendarName, const QString& sourcePath);
Q_SIGNALS:
void memorycalendarChanged();
void calendarstorageChanged();
void nameChanged();
private:
static QVariantMap canCreateFile(const QString& calendarName);
MemoryCalendar::Ptr m_calendar;
FileStorage::Ptr m_cal_storage;
QString m_name;
QString m_fullpath;
};
#endif // LOCALCALENDAR_H
......
......@@ -10,7 +10,7 @@
<file alias="CalendarHeader.qml">contents/ui/kirigami-playground/CalendarHeader.qml</file>
<file alias="TimePicker.qml">contents/ui/kirigami-playground/TimePicker.qml</file>
<file alias="ClockElement.qml">contents/ui/kirigami-playground/ClockElement.qml</file>