Commit 36641244 authored by Dimitris Kardarakos's avatar Dimitris Kardarakos

Implement calendar import functionality

parent a42b436f
...@@ -22,71 +22,155 @@ import QtQuick 2.0 ...@@ -22,71 +22,155 @@ import QtQuick 2.0
import QtQuick.Controls 2.4 as Controls2 import QtQuick.Controls 2.4 as Controls2
import QtQuick.Layouts 1.11 import QtQuick.Layouts 1.11
import org.kde.kirigami 2.4 as Kirigami import org.kde.kirigami 2.4 as Kirigami
import org.kde.phone.calindori 0.1 as Calindori
Kirigami.Page { Kirigami.Page {
id: root id: root
property alias calendarName: name.text property alias calendarName: nameInput.text
property alias activeCalendar: isactive.checked property alias activeCalendar: isactive.checked
property string mode: "add"
property var configuration
property alias fileToImport: importFilePath.text
signal calendarAdded; signal calendarAdded;
signal calendarAddCanceled; signal calendarAddCanceled;
title: qsTr("New calendar") title: qsTr("New calendar")
Kirigami.FormLayout { 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 id: calendarInputPage
anchors.centerIn: parent anchors.centerIn: parent
Controls2.TextField { Controls2.TextField {
id: name id: nameInput
Kirigami.FormData.label: qsTr("Name:") Kirigami.FormData.label: qsTr("Name:")
} }
Controls2.CheckBox { Controls2.CheckBox {
id: isactive id: isactive
Kirigami.FormData.label: qsTr("Active:") 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 { actions {
left: Kirigami.Action { left: Kirigami.Action {
id: cancelAction id: cancelAction
text: qsTr("Cancel") text: qsTr("Cancel")
icon.name : "dialog-cancel" icon.name : "dialog-cancel"
onTriggered: { onTriggered: {
calendarAddCanceled(); calendarAddCanceled();
} }
} }
main: Kirigami.Action { main: Kirigami.Action {
id: info id: info
text: qsTr("Info") text: qsTr("Info")
icon.name : "documentinfo" icon.name : "documentinfo"
onTriggered: { onTriggered: {
showPassiveNotification("Please save or cancel the creation of the new calendar"); showPassiveNotification("Please save or cancel the creation of the new calendar");
} }
} }
right: Kirigami.Action { right: Kirigami.Action {
id: saveAction id: saveAction
text: qsTr("Save") text: qsTr("Save")
icon.name : "dialog-ok" icon.name : "dialog-ok"
onTriggered: { 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 { ...@@ -44,9 +44,16 @@ Kirigami.ApplicationWindow {
iconName: "view-calendar" iconName: "view-calendar"
Kirigami.Action { Kirigami.Action {
text: "Add calendar..." text: "Create"
iconName: "list-add" 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 { Kirigami.Action {
...@@ -106,6 +113,7 @@ Kirigami.ApplicationWindow { ...@@ -106,6 +113,7 @@ Kirigami.ApplicationWindow {
root.refreshNeeded(); root.refreshNeeded();
if (root.pageStack.depth > 1) { if (root.pageStack.depth > 1) {
root.pageStack.pop(null); root.pageStack.pop(null);
root.pageStack.push(calendarDashboardComponent);
} }
} }
} }
...@@ -217,7 +225,6 @@ Kirigami.ApplicationWindow { ...@@ -217,7 +225,6 @@ Kirigami.ApplicationWindow {
} }
} }
Component { Component {
id: eventsView id: eventsView
...@@ -234,32 +241,20 @@ Kirigami.ApplicationWindow { ...@@ -234,32 +241,20 @@ Kirigami.ApplicationWindow {
} }
} }
Component { Component {
id: calendarInputPage id: calendarEditor
CalendarInputPage { CalendarEditor {
onCalendarAdded: { configuration: calindoriConfig
var calendarAddResult = "";
calendarAddResult = calindoriConfig.addCalendar(calendarName);
if(calendarAddResult != "")
{
showPassiveNotification(calendarAddResult);
return;
}
if(activeCalendar) onCalendarAdded: {
{
calindoriConfig.activeCalendar = calendarName;
}
root.refreshNeeded(); root.refreshNeeded();
root.pageStack.pop(calendarInputPage); root.pageStack.pop(calendarEditor);
} }
onCalendarAddCanceled: { 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 * This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as * modify it under the terms of the GNU General Public License as
...@@ -21,6 +21,8 @@ ...@@ -21,6 +21,8 @@
#include <KConfig> #include <KConfig>
#include <KConfigGroup> #include <KConfigGroup>
#include <QDebug> #include <QDebug>
#include <QRegExp>
class CalindoriConfig::Private class CalindoriConfig::Private
{ {
public: public:
...@@ -66,35 +68,66 @@ void CalindoriConfig::setActiveCalendar(const QString & calendar) ...@@ -66,35 +68,66 @@ void CalindoriConfig::setActiveCalendar(const QString & calendar)
emit activeCalendarChanged(); 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()) if(d->config.group("general").readEntry("calendars", QString()).isEmpty())
{ {
qDebug() << "Calendar list is empty"; return result;
d->config.group("general").writeEntry("calendars", calendar);
return QString();
} }
qDebug() << "Calendar list is not empty, adding calendar " << calendar;
QStringList calendarsList = d->config.group("general").readEntry("calendars", QString()).split(";"); QStringList calendarsList = d->config.group("general").readEntry("calendars", QString()).split(";");
if(calendarsList.contains(calendar)) 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); calendarsList.append(calendar);
d->config.group("general").writeEntry("calendars", calendarsList.join(";")); d->config.group("general").writeEntry("calendars", calendarsList.join(";"));
d->config.sync(); d->config.sync();
emit calendarsChanged(); emit calendarsChanged();
return QString(); return result;
} }
void CalindoriConfig::removeCalendar(const QString& calendar) 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 * This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as * modify it under the terms of the GNU General Public License as
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
#define MOBILECALENDARCONFIG_H #define MOBILECALENDARCONFIG_H
#include <QObject> #include <QObject>
#include <QVariantMap>
class CalindoriConfig : public QObject class CalindoriConfig : public QObject
{ {
...@@ -39,7 +40,8 @@ public: ...@@ -39,7 +40,8 @@ public:
Q_SIGNAL void activeCalendarChanged(); Q_SIGNAL void activeCalendarChanged();
public Q_SLOTS: public Q_SLOTS:
QString addCalendar(const QString& calendar); QVariantMap canAddCalendar(const QString& calendar);
QVariantMap addCalendar(const QString& calendar);
void removeCalendar(const QString& calendar); void removeCalendar(const QString& calendar);
private: private:
......
...@@ -49,19 +49,22 @@ QString LocalCalendar::name() const ...@@ -49,19 +49,22 @@ QString LocalCalendar::name() const
void LocalCalendar::setName(QString calendarName) void LocalCalendar::setName(QString calendarName)
{ {
if (m_name != calendarName) { if (m_name != calendarName)
{
MemoryCalendar::Ptr calendar(new MemoryCalendar(QTimeZone::systemTimeZoneId())); MemoryCalendar::Ptr calendar(new MemoryCalendar(QTimeZone::systemTimeZoneId()));
FileStorage::Ptr storage(new FileStorage(calendar)); 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); QFile calendarFile(m_fullpath);
storage->setFileName(m_fullpath); storage->setFileName(m_fullpath);
if (!calendarFile.exists()) { if (!calendarFile.exists())
{
qDebug() << "Creating file" << storage->save(); qDebug() << "Creating file" << storage->save();
} }
if(storage->load()) { if(storage->load())
{
m_name = calendarName; m_name = calendarName;
m_calendar = calendar; m_calendar = calendar;
m_cal_storage = storage; m_cal_storage = storage;
...@@ -75,19 +78,19 @@ void LocalCalendar::setName(QString calendarName) ...@@ -75,19 +78,19 @@ void LocalCalendar::setName(QString calendarName)