diff --git a/liri-browser.pro b/liri-browser.pro index d49b846..2109d32 100644 --- a/liri-browser.pro +++ b/liri-browser.pro @@ -33,3 +33,4 @@ include(src/main/main.pri) include(src/ui/ui.pri) RESOURCES += res/icons/icons.qrc +QML_IMPORT_PATH = $$PWD diff --git a/src/core/core.pri b/src/core/core.pri index 757d219..1b98dc3 100644 --- a/src/core/core.pri +++ b/src/core/core.pri @@ -9,6 +9,8 @@ HEADERS += \ $$PWD/settings/themeconfig.h \ $$PWD/global/paths.h \ $$PWD/utils/darkthemetimer.h \ + $$PWD/session/session.h \ + $$PWD/session/tabstate.h SOURCES += \ $$PWD/models/tabsmodel.cpp \ @@ -20,3 +22,5 @@ SOURCES += \ $$PWD/settings/searchconfig.cpp \ $$PWD/settings/themeconfig.cpp \ $$PWD/utils/darkthemetimer.cpp \ + $$PWD/session/session.cpp \ + $$PWD/session/tabstate.cpp diff --git a/src/core/global/paths.h b/src/core/global/paths.h index bb737b0..6bb7107 100644 --- a/src/core/global/paths.h +++ b/src/core/global/paths.h @@ -9,6 +9,7 @@ namespace Paths { const QString DataLocation = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/liri-browser/"; const QString SettingsFile = ConfigLocation + "settings.json"; const QString ExtensionsLocation = DataLocation + "extensions/"; + const QString SessionDataFile = DataLocation + "session.json"; } #endif // PATHS_H diff --git a/src/core/session/session.cpp b/src/core/session/session.cpp new file mode 100644 index 0000000..d14b9b8 --- /dev/null +++ b/src/core/session/session.cpp @@ -0,0 +1,93 @@ +#include "Session.h" + +#include +#include +#include +#include +#include + +#include "../global/paths.h" +#include "../models/tabsmodel.h" +#include "tabstate.h" + +Session::Session(QObject *parent) : QObject(parent) +{ + load(); +} + +void Session::save(TabsModel* tabs) +{ + QFile file(Paths::SessionDataFile); + if (!file.open(QIODevice::WriteOnly)) { + qWarning("Couldn't open session file for write!"); + return; + } + QTextStream stream(&file); + stream << json(tabs); + file.close(); + + qDebug() << "Session written to" << Paths::SessionDataFile; +} + +QVariantList Session::getTabsToRestore() +{ + QVariantList tabs; + for (TabState* state : m_tabs) + { + tabs.append(QVariant::fromValue(state)); + } + m_tabs.clear(); + return tabs; +} + +void Session::load() +{ + QFile file(Paths::SessionDataFile); + if (!file.open(QIODevice::ReadOnly)) { + qWarning("Couldn't open session file for read!"); + return; + } + + QByteArray bytes = file.readAll(); + QJsonDocument doc(QJsonDocument::fromJson(bytes)); + + QJsonObject root = doc.object(); + QJsonObject meta = root["meta"].toObject(); + QString metaSchema = meta["schema"].toString(); + if (metaSchema != "0.1") { + qWarning() << "Unknown session schema version " << metaSchema << "!"; + return; + } + QJsonArray tabs = root["tabs"].toArray(); + for (QJsonValue tab : tabs) + { + auto tabObj = tab.toObject(); + auto state = new TabState(this); + state->setUrl(tabObj["url"].toString()); + m_tabs.append(state); + } +} + +QByteArray Session::json(TabsModel* tabs) +{ + QJsonObject meta { + {"schema", "0.1"} + }; + + QJsonArray tabsArray; + + for (int i = 0; i < tabs->count(); ++i) { + QJsonObject tabObject; + tabObject["url"] = tabs->get(i)->url().toString(); + tabObject["readingProgress"] = 0.f; + tabsArray.append(tabObject); + } + + QJsonObject root { + {"meta", meta}, + {"tabs", tabsArray}, + }; + + QJsonDocument doc(root); + return doc.toJson(); +} diff --git a/src/core/session/session.h b/src/core/session/session.h new file mode 100644 index 0000000..4d06497 --- /dev/null +++ b/src/core/session/session.h @@ -0,0 +1,31 @@ +#ifndef SESSION_H +#define SESSION_H + +#include +#include +#include + +class TabsModel; +class TabState; + +class Session : public QObject +{ + Q_OBJECT +public: + explicit Session(QObject *parent = 0); + + Q_INVOKABLE void save(TabsModel* tabs); + Q_INVOKABLE QVariantList getTabsToRestore(); +signals: + +public slots: + +private: + void load(); + QByteArray json(TabsModel *tabs); + +private: + QList m_tabs; +}; + +#endif // SESSION_H diff --git a/src/core/session/tabstate.cpp b/src/core/session/tabstate.cpp new file mode 100644 index 0000000..3f04702 --- /dev/null +++ b/src/core/session/tabstate.cpp @@ -0,0 +1,29 @@ +#include "tabstate.h" + +TabState::TabState(QObject *parent) : QObject(parent) +{ + +} + +void TabState::setUrl(QString url) +{ + if (m_url == url) + return; + + m_url = url; +} + +QString TabState::url() const +{ + return m_url; +} + +float TabState::readingProgress() const +{ + return m_readingProgress; +} + +void TabState::setReadingProgress(float readingProgress) +{ + m_readingProgress = readingProgress; +} diff --git a/src/core/session/tabstate.h b/src/core/session/tabstate.h new file mode 100644 index 0000000..ac6073d --- /dev/null +++ b/src/core/session/tabstate.h @@ -0,0 +1,30 @@ +#ifndef TABSTATE_H +#define TABSTATE_H + +#include + +class TabState : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString url READ url WRITE setUrl) + Q_PROPERTY(float readingProgress READ readingProgress WRITE setReadingProgress) + +public: + explicit TabState(QObject *parent = 0); + + void setUrl(QString url); + QString url() const; + float readingProgress() const; + void setReadingProgress(float readingProgress); + +signals: + +public slots: + +private: + QString m_url; + float m_readingProgress; +}; + +#endif // TABSTATE_H diff --git a/src/core/settings/settings.cpp b/src/core/settings/settings.cpp index 7ec8f3c..1b9154f 100644 --- a/src/core/settings/settings.cpp +++ b/src/core/settings/settings.cpp @@ -82,6 +82,17 @@ void Settings::load() m_startConfig->setPrimaryStartUrl(QUrl(dataStart["primary_url"].toString())); m_startConfig->setDarkStartUrl(QUrl(dataStart["dark_theme_url"].toString())); m_startConfig->setIncognitoStartUrl(QUrl(dataStart["incognito_url"].toString())); + + StartConfig::StartupType startupType; + QString startupTypeString = dataStart["startupType"].toString(); + + if (startupTypeString == "start_from_new_page") + startupType = StartConfig::StartupType::StartFromNewPage; + else if (startupTypeString == "start_from_previously_opened_tabs") + startupType = StartConfig::StartupType::StartFromPreviouslyOpenedTabs; + + m_startConfig->setStartupType(startupType); + QJsonObject dataSearch = data["search"].toObject(); QString searchEngineString = dataSearch["engine"].toString(); SearchConfig::SearchEngine searchEngine; @@ -133,7 +144,8 @@ QByteArray Settings::defaultJSON() QJsonObject dataStart { {"primary_url", m_startConfig->defaultPrimaryStartUrl().toString()}, {"dark_theme_url", m_startConfig->defaultDarkStartUrl().toString()}, - {"incognito_url", m_startConfig->defaultIncognitoStartUrl().toString()} + {"incognito_url", m_startConfig->defaultIncognitoStartUrl().toString()}, + {"startupType", "start_from_new_page"} }; QJsonObject dataSearch { {"engine", "duckduckgo"}, @@ -163,11 +175,25 @@ QByteArray Settings::json() QJsonObject meta { {"schema", "0.1"} }; + + QString startupTypeString; + + switch (m_startConfig->startupType()) { + case StartConfig::StartupType::StartFromNewPage: + startupTypeString = "start_from_new_page"; + break; + case StartConfig::StartupType::StartFromPreviouslyOpenedTabs: + startupTypeString = "start_from_previously_opened_tabs"; + break; + } + QJsonObject dataStart { {"primary_url", m_startConfig->primaryStartUrl().toString()}, {"dark_theme_url", m_startConfig->darkStartUrl().toString()}, {"incognito_url", m_startConfig->incognitoStartUrl().toString()}, + {"startupType", startupTypeString}, }; + QString searchEngineString; switch (m_searchConfig->searchEngine()) { case SearchConfig::SearchEngine::DuckDuckGo: diff --git a/src/core/settings/startconfig.cpp b/src/core/settings/startconfig.cpp index 6ddde2d..5f094eb 100644 --- a/src/core/settings/startconfig.cpp +++ b/src/core/settings/startconfig.cpp @@ -29,4 +29,5 @@ StartConfig::StartConfig(QObject *parent) m_defaultPrimaryStartUrl = QUrl("https://duckduckgo.com"); m_defaultDarkStartUrl = QUrl("https://duckduckgo.com/?kae=#303030"); m_defaultIncognitoStartUrl = QUrl("https://duckduckgo.com/?kae=#37474f"); + m_startupType = StartupType::StartFromNewPage; } diff --git a/src/core/settings/startconfig.h b/src/core/settings/startconfig.h index 1494cc8..836e0d8 100644 --- a/src/core/settings/startconfig.h +++ b/src/core/settings/startconfig.h @@ -36,9 +36,16 @@ class StartConfig : public QObject Q_PROPERTY(QUrl defaultDarkStartUrl MEMBER m_defaultDarkStartUrl NOTIFY defaultDarkStartUrlChanged) Q_PROPERTY(QUrl incognitoStartUrl READ incognitoStartUrl WRITE setIncognitoStartUrl NOTIFY incognitoStartUrlChanged) Q_PROPERTY(QUrl defaultIncognitoStartUrl MEMBER m_defaultIncognitoStartUrl NOTIFY defaultIncognitoStartUrlChanged) + Q_PROPERTY(StartupType startupType MEMBER m_startupType NOTIFY startupTypeChanged) + Q_ENUMS(StartupType) public: explicit StartConfig(QObject *parent = nullptr); + enum StartupType { + StartFromNewPage, + StartFromPreviouslyOpenedTabs, + }; + QUrl primaryStartUrl() const { return m_primaryStartUrl; } void setPrimaryStartUrl(QUrl url) { primaryStartUrlChanged(m_primaryStartUrl = url); } @@ -52,6 +59,9 @@ class StartConfig : public QObject QUrl defaultIncognitoStartUrl() const { return m_defaultIncognitoStartUrl; } QUrl defaultDarkStartUrl() const { return m_defaultDarkStartUrl; } + StartupType startupType() const { return m_startupType; } + void setStartupType(const StartupType &startupType) { startupTypeChanged(m_startupType = startupType); } + signals: void primaryStartUrlChanged(QUrl url); void defaultPrimaryStartUrlChanged(QUrl url); @@ -59,6 +69,7 @@ class StartConfig : public QObject void defaultDarkStartUrlChanged(QUrl url); void incognitoStartUrlChanged(QUrl url); void defaultIncognitoStartUrlChanged(QUrl url); + void startupTypeChanged(StartupType url); private: QUrl m_primaryStartUrl; @@ -67,6 +78,7 @@ class StartConfig : public QObject QUrl m_defaultIncognitoStartUrl; QUrl m_darkStartUrl; QUrl m_defaultDarkStartUrl; + StartupType m_startupType; }; diff --git a/src/main/main.cpp b/src/main/main.cpp index a267604..6bf10e1 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -35,6 +35,7 @@ #include "../core/models/downloadsmodel.h" #include "../core/models/webdownload.h" #include "../core/settings/settings.h" +#include "../core/session/session.h" #include "../core/utils/darkthemetimer.h" #ifdef Q_OS_MACOS @@ -63,6 +64,8 @@ int main(int argc, char *argv[]) Settings settings; settings.load(); + Session session; + // Create and start dark theme time DarkThemeTimer darkThemeTimer; darkThemeTimer.start(); @@ -72,6 +75,7 @@ int main(int argc, char *argv[]) // register core types qmlRegisterUncreatableType("core", 1, 0, "SearchConfig", "SearchConfig (from module core) may not be created directly."); + qmlRegisterUncreatableType("core", 1, 0, "StartConfig", "StartConfig (from module core) may not be created directly."); qmlRegisterUncreatableType("core", 1, 0, "Tab", "Tab (from module core) may not be created directly."); qmlRegisterType("core", 1, 0, "TabsModel"); @@ -81,6 +85,7 @@ int main(int argc, char *argv[]) // Register context properties engine.rootContext()->setContextProperty("Settings", &settings); + engine.rootContext()->setContextProperty("Session", &session); engine.rootContext()->setContextProperty("DarkThemeTimer", &darkThemeTimer); #ifdef Q_OS_MACOS engine.rootContext()->setContextProperty("MacEvents", &evListener); diff --git a/src/ui/tabview/content/SettingsContent.qml b/src/ui/tabview/content/SettingsContent.qml index bb07000..d45ded3 100644 --- a/src/ui/tabview/content/SettingsContent.qml +++ b/src/ui/tabview/content/SettingsContent.qml @@ -61,11 +61,35 @@ TabContent { } TitleLabel { - text: "Start" + text: "Startup" + } + + Column { + Label { text: "Start new window with" } + RadioButton { + text: "New page" + checked: Settings.startConfig.startupType === StartConfig.StartFromNewPage + onClicked: { + if (Settings.startConfig.startupType !== StartConfig.StartFromNewPage) { + Settings.startConfig.startupType = StartConfig.StartFromNewPage; + Settings.dirty = true; + } + } + } + RadioButton { + text: "Continue where you left off" + checked: Settings.startConfig.startupType === StartConfig.StartFromPreviouslyOpenedTabs + onClicked: { + if (Settings.startConfig.startupType !== StartConfig.StartFromPreviouslyOpenedTabs) { + Settings.startConfig.startupType = StartConfig.StartFromPreviouslyOpenedTabs; + Settings.dirty = true; + } + } + } } Label { - text: "Start url" + text: "New page url" font.pixelSize: 16 } diff --git a/src/ui/window/BrowserWindow.qml b/src/ui/window/BrowserWindow.qml index 195c7c4..8d8cde2 100644 --- a/src/ui/window/BrowserWindow.qml +++ b/src/ui/window/BrowserWindow.qml @@ -118,6 +118,10 @@ ApplicationWindow { Material.theme: darkThemeActive || incognito ? Material.Dark : Material.Light + onClosing: { + Session.save(tabsModel); + } + MouseArea { id: topAreaTrigger parent: window.overlay @@ -480,7 +484,14 @@ ApplicationWindow { } Component.onCompleted: { - if (openStartUrl) + var restoreTabs = Session.getTabsToRestore(); + if (Settings.startConfig.startupType === StartConfig.StartFromPreviouslyOpenedTabs && + restoreTabs.length > 0) { + for(var i = 0; i < restoreTabs.length; ++i) { + tabController.openUrl(restoreTabs[i].url, false); + } + } else if (openStartUrl) { tabController.openUrl(startUrl); + } } }