From f1aff0b13e4c31e7ef6001c016fc99ebf6d5c6f3 Mon Sep 17 00:00:00 2001 From: Devin Lin Date: Sun, 22 Oct 2023 10:17:09 -0700 Subject: [PATCH] homescreens/folio: Add import/export homescreen layout setting --- .../homescreens/folio/favouritesmodel.cpp | 22 +++++-- .../homescreens/folio/favouritesmodel.h | 4 +- .../folio/folioapplicationfolder.cpp | 3 + .../homescreens/folio/foliosettings.cpp | 60 ++++++++++++++++++ .../homescreens/folio/foliosettings.h | 3 + .../homescreens/folio/homescreenstate.cpp | 2 +- .../package/contents/ui/AppDrawerGrid.qml | 2 +- .../contents/ui/settings/SettingsWindow.qml | 63 +++++++++++++++++-- .../homescreens/folio/pagelistmodel.cpp | 20 ++++-- .../homescreens/folio/pagelistmodel.h | 2 + 10 files changed, 163 insertions(+), 18 deletions(-) diff --git a/containments/homescreens/folio/favouritesmodel.cpp b/containments/homescreens/folio/favouritesmodel.cpp index 1447995b..259efe33 100644 --- a/containments/homescreens/folio/favouritesmodel.cpp +++ b/containments/homescreens/folio/favouritesmodel.cpp @@ -212,12 +212,8 @@ void FavouritesModel::deleteGhostEntry() } } -void FavouritesModel::save() +QJsonArray FavouritesModel::exportToJson() { - if (!m_applet) { - return; - } - QJsonArray arr; for (int i = 0; i < m_delegates.size(); i++) { FolioDelegate *delegate = m_delegates[i].delegate; @@ -229,6 +225,16 @@ void FavouritesModel::save() arr.append(delegate->toJson()); } + return arr; +} + +void FavouritesModel::save() +{ + if (!m_applet) { + return; + } + + QJsonArray arr = exportToJson(); QByteArray data = QJsonDocument(arr).toJson(QJsonDocument::Compact); m_applet->config().writeEntry("Favourites", QString::fromStdString(data.toStdString())); @@ -242,12 +248,16 @@ void FavouritesModel::load() } QJsonDocument doc = QJsonDocument::fromJson(m_applet->config().readEntry("Favourites", "{}").toUtf8()); + loadFromJson(doc.array()); +} +void FavouritesModel::loadFromJson(QJsonArray arr) +{ beginResetModel(); m_delegates.clear(); - for (QJsonValueRef r : doc.array()) { + for (QJsonValueRef r : arr) { QJsonObject obj = r.toObject(); FolioDelegate *delegate = FolioDelegate::fromJson(obj, this); diff --git a/containments/homescreens/folio/favouritesmodel.h b/containments/homescreens/folio/favouritesmodel.h index 93381759..11837cc1 100644 --- a/containments/homescreens/folio/favouritesmodel.h +++ b/containments/homescreens/folio/favouritesmodel.h @@ -59,12 +59,14 @@ public: QPointF getDelegateScreenPosition(int position) const; + QJsonArray exportToJson(); + void save(); Q_INVOKABLE void load(); + void loadFromJson(QJsonArray arr); void setApplet(Plasma::Applet *applet); private: - void save(); void evaluateDelegatePositions(bool emitSignal = true); // get the x (or y) position where delegates start being placed diff --git a/containments/homescreens/folio/folioapplicationfolder.cpp b/containments/homescreens/folio/folioapplicationfolder.cpp index 13eddba0..f2ecfc2c 100644 --- a/containments/homescreens/folio/folioapplicationfolder.cpp +++ b/containments/homescreens/folio/folioapplicationfolder.cpp @@ -265,6 +265,9 @@ void ApplicationFolderModel::removeDelegate(int index) QPointF ApplicationFolderModel::getDelegatePosition(int index) { + if (index < 0 || index >= m_folder->m_delegates.size()) { + return {0, 0}; + } auto delegate = m_folder->m_delegates[index]; return {delegate.xPosition, delegate.yPosition}; } diff --git a/containments/homescreens/folio/foliosettings.cpp b/containments/homescreens/folio/foliosettings.cpp index d3c2aa5d..7ef571a3 100644 --- a/containments/homescreens/folio/foliosettings.cpp +++ b/containments/homescreens/folio/foliosettings.cpp @@ -2,6 +2,13 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "foliosettings.h" +#include "favouritesmodel.h" +#include "pagelistmodel.h" + +#include +#include +#include +#include FolioSettings::FolioSettings(QObject *parent) : QObject{parent} @@ -139,3 +146,56 @@ void FolioSettings::load() Q_EMIT showFavouritesAppLabelsChanged(); Q_EMIT delegateIconSizeChanged(); } + +bool FolioSettings::saveLayoutToFile(QString path) +{ + if (path.startsWith(QStringLiteral("file://"))) { + path = path.replace(QStringLiteral("file://"), QString()); + } + + QJsonArray favourites = FavouritesModel::self()->exportToJson(); + QJsonArray pages = PageListModel::self()->exportToJson(); + + QJsonObject obj; + obj[QStringLiteral("Favourites")] = favourites; + obj[QStringLiteral("Pages")] = pages; + + QByteArray data = QJsonDocument(obj).toJson(QJsonDocument::Compact); + + QFile file{path}; + if (file.open(QIODevice::WriteOnly | QIODevice::Text)) { + file.write(data); + } else { + qDebug() << "failed to write to file:" << file.errorString(); + return false; + } + file.close(); + + return true; +} + +bool FolioSettings::loadLayoutFromFile(QString path) +{ + if (path.startsWith(QStringLiteral("file://"))) { + path = path.replace(QStringLiteral("file://"), QString()); + } + + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) { + qDebug() << "failed to open file:" << file.errorString(); + return false; + } + + QTextStream in(&file); + QString contents = in.readAll(); + file.close(); + + QJsonDocument doc = QJsonDocument::fromJson(contents.toUtf8()); + QJsonObject obj = doc.object(); + + // TODO error checking + FavouritesModel::self()->loadFromJson(obj[QStringLiteral("Favourites")].toArray()); + PageListModel::self()->loadFromJson(obj[QStringLiteral("Pages")].toArray()); + + return true; +} diff --git a/containments/homescreens/folio/foliosettings.h b/containments/homescreens/folio/foliosettings.h index fb036e91..3cfe3953 100644 --- a/containments/homescreens/folio/foliosettings.h +++ b/containments/homescreens/folio/foliosettings.h @@ -45,6 +45,9 @@ public: Q_INVOKABLE void load(); + Q_INVOKABLE bool saveLayoutToFile(QString path); + Q_INVOKABLE bool loadLayoutFromFile(QString path); + Q_INVOKABLE void setApplet(Plasma::Applet *applet); Q_SIGNALS: diff --git a/containments/homescreens/folio/homescreenstate.cpp b/containments/homescreens/folio/homescreenstate.cpp index dde9802d..67539e3c 100644 --- a/containments/homescreens/folio/homescreenstate.cpp +++ b/containments/homescreens/folio/homescreenstate.cpp @@ -644,7 +644,7 @@ QPointF HomeScreenState::getFavouritesDelegateScreenPosition(int position) QPointF HomeScreenState::getFolderDelegateScreenPosition(int position) { - if (!m_currentFolder) { + if (!m_currentFolder || position < 0 || position >= m_currentFolder->applications()->rowCount()) { return {0, 0}; } auto pos = m_currentFolder->applications()->getDelegatePosition(position); diff --git a/containments/homescreens/folio/package/contents/ui/AppDrawerGrid.qml b/containments/homescreens/folio/package/contents/ui/AppDrawerGrid.qml index 03b972d2..5838e4b2 100644 --- a/containments/homescreens/folio/package/contents/ui/AppDrawerGrid.qml +++ b/containments/homescreens/folio/package/contents/ui/AppDrawerGrid.qml @@ -34,7 +34,7 @@ MobileShell.GridView { cellWidth: effectiveContentWidth / Math.min(Math.floor(effectiveContentWidth / (Folio.FolioSettings.delegateIconSize + Kirigami.Units.largeSpacing * 3)), 8) cellHeight: cellWidth + reservedSpaceForLabel - boundsBehavior: Flickable.OvershootBounds + boundsBehavior: Flickable.StopAtBounds readonly property int columns: Math.floor(effectiveContentWidth / cellWidth) readonly property int rows: Math.ceil(root.count / columns) diff --git a/containments/homescreens/folio/package/contents/ui/settings/SettingsWindow.qml b/containments/homescreens/folio/package/contents/ui/settings/SettingsWindow.qml index 01b37e92..1159705d 100644 --- a/containments/homescreens/folio/package/contents/ui/settings/SettingsWindow.qml +++ b/containments/homescreens/folio/package/contents/ui/settings/SettingsWindow.qml @@ -4,6 +4,7 @@ import QtQuick import QtQuick.Window import QtQuick.Layouts +import QtQuick.Dialogs import QtQuick.Controls as QQC2 import org.kde.kirigami 2.20 as Kirigami @@ -178,6 +179,7 @@ Kirigami.ApplicationWindow { } FormCard.FormCard { + Layout.bottomMargin: Kirigami.Units.gridUnit FormCard.FormButtonDelegate { id: containmentSettings text: i18n('Switch Homescreen') @@ -189,20 +191,73 @@ Kirigami.ApplicationWindow { FormCard.FormButtonDelegate { id: exportSettings - enabled: false - text: 'Export layout (in development)' + text: i18n('Export layout') icon.name: 'document-export' + onClicked: exportFileDialog.open() } FormCard.FormDelegateSeparator { above: exportSettings; below: importSettings } FormCard.FormButtonDelegate { id: importSettings - enabled: false - text: 'Import layout (in development)' + text: i18n('Import layout') icon.name: 'document-import' + onClicked: importFileDialog.open() } } } + + FileDialog { + id: exportFileDialog + title: i18n("Export layout to") + currentFolder: StandardPaths.standardLocations(StandardPaths.DownloadsLocation) + fileMode: FileDialog.SaveFile + defaultSuffix: 'json' + nameFilters: ["JSON files (*.json)"] + onAccepted: { + console.log('saving layout to ' + selectedFile); + if (selectedFile) { + let status = Folio.FolioSettings.saveLayoutToFile(selectedFile); + if (status) { + exportedSuccessfullyPrompt.open(); + } else { + exportFailedPrompt.open(); + } + } + } + } + + FileDialog { + id: importFileDialog + currentFolder: StandardPaths.standardLocations(StandardPaths.DownloadsLocation) + fileMode: FileDialog.OpenFile + nameFilters: ["JSON files (*.json)"] + onAccepted: { + console.log('about to load layout from ' + selectedFile); + confirmImportPrompt.open(); + } + } + + Kirigami.PromptDialog { + id: exportFailedPrompt + title: i18n("Export Status") + subtitle: i18n("Failed to export to %1", String(exportFileDialog.selectedFile).substring('file://'.length)) + standardButtons: Kirigami.Dialog.Close + } + + Kirigami.PromptDialog { + id: exportedSuccessfullyPrompt + title: i18n("Export Status") + subtitle: i18n("Homescreen layout exported successfully to %1", String(exportFileDialog.selectedFile).substring('file://'.length)) + standardButtons: Kirigami.Dialog.Close + } + + Kirigami.PromptDialog { + id: confirmImportPrompt + title: i18n("Confirm Import") + subtitle: i18n("This will overwrite your existing homescreen layout!") + standardButtons: Kirigami.Dialog.Ok | Kirigami.Dialog.Cancel + onAccepted: Folio.FolioSettings.loadLayoutFromFile(importFileDialog.selectedFile); + } } } diff --git a/containments/homescreens/folio/pagelistmodel.cpp b/containments/homescreens/folio/pagelistmodel.cpp index 9b3ae2d5..1dd9575d 100644 --- a/containments/homescreens/folio/pagelistmodel.cpp +++ b/containments/homescreens/folio/pagelistmodel.cpp @@ -94,16 +94,22 @@ bool PageListModel::isLastPageEmpty() return m_pages.size() == 0 ? true : m_pages[m_pages.size() - 1]->isPageEmpty(); } +QJsonArray PageListModel::exportToJson() +{ + QJsonArray arr; + for (auto &page : m_pages) { + arr.push_back(page->toJson()); + } + return arr; +} + void PageListModel::save() { if (!m_applet) { return; } - QJsonArray arr; - for (auto &page : m_pages) { - arr.push_back(page->toJson()); - } + QJsonArray arr = exportToJson(); QByteArray data = QJsonDocument(arr).toJson(QJsonDocument::Compact); m_applet->config().writeEntry("Pages", QString::fromStdString(data.toStdString())); @@ -117,12 +123,16 @@ void PageListModel::load() } QJsonDocument doc = QJsonDocument::fromJson(m_applet->config().readEntry("Pages", "{}").toUtf8()); + loadFromJson(doc.array()); +} +void PageListModel::loadFromJson(QJsonArray arr) +{ beginResetModel(); m_pages.clear(); - for (QJsonValueRef r : doc.array()) { + for (QJsonValueRef r : arr) { QJsonArray obj = r.toArray(); PageModel *page = PageModel::fromJson(obj, this); diff --git a/containments/homescreens/folio/pagelistmodel.h b/containments/homescreens/folio/pagelistmodel.h index 867ed314..bc78ed64 100644 --- a/containments/homescreens/folio/pagelistmodel.h +++ b/containments/homescreens/folio/pagelistmodel.h @@ -33,8 +33,10 @@ public: Q_INVOKABLE void addPageAtEnd(); bool isLastPageEmpty(); + QJsonArray exportToJson(); void save(); Q_INVOKABLE void load(); + void loadFromJson(QJsonArray arr); void setApplet(Plasma::Applet *applet);