diff options
30 files changed, 590 insertions, 460 deletions
diff --git a/.clang-tidy b/.clang-tidy index 5813ee3..4963ca4 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,5 +1,5 @@ --- -Checks: 'clang-diagnostic-*,clang-analyzer-*,bugprone-*,cert-*,cppcoreguidelines-*,hicpp-*,misc-*,modernize-*,performance-*,portability-*,readability-*' +Checks: 'clang-diagnostic-*,clang-analyzer-*,bugprone-*,cert-*,cppcoreguidelines-*,hicpp-*,misc-*,modernize-*,performance-*,portability-*,readability-*, -modernize-use-trailing-return-type' WarningsAsErrors: '' HeaderFilterRegex: '' AnalyzeTemporaryDtors: false diff --git a/include/session.hpp b/include/session.hpp new file mode 100644 index 0000000..0146802 --- /dev/null +++ b/include/session.hpp @@ -0,0 +1,41 @@ +/* + * This file is part of smolbote. It's copyrighted by the contributors recorded + * in the version control history of the file, available from its original + * location: https://library.iserlohn-fortress.net/aqua/smolbote.git + * + * SPDX-License-Identifier: MIT + */ + +#ifndef SMOLBOTE_SESSION_HPP +#define SMOLBOTE_SESSION_HPP + +#include <QByteArray> +#include <QString> +#include <QStringList> +#include <QVector> + +class [[nodiscard]] Session +{ +public: + struct WebView { + QString profile; + QString url; + QByteArray history; + }; + + struct SubWindow { + QString profile; + QVector<WebView> tabs; + }; + + struct MainWindow { + QVector<SubWindow> subwindows; + }; + + virtual ~Session() = default; + + [[nodiscard]] virtual QByteArray serialize() const = 0; + [[nodiscard]] virtual QVector<MainWindow> get() const = 0; +}; + +#endif // SMOLBOTE_SESSION_HPP diff --git a/lib/session_formats/meson.build b/lib/session_formats/meson.build new file mode 100644 index 0000000..9abf5a2 --- /dev/null +++ b/lib/session_formats/meson.build @@ -0,0 +1,13 @@ +lib_session_formats = declare_dependency( + include_directories: [ '.', include ], + link_with: library('sessionformats', + [ 'session_json.cpp' ], + include_directories: include, + dependencies: dep_qt5 + ) +) + +test('session: json format', executable('session_json', + sources: 'test/json.cpp', + dependencies: [ dep_qt5, dep_catch, lib_session_formats ] +)) diff --git a/lib/session_formats/session_json.cpp b/lib/session_formats/session_json.cpp new file mode 100644 index 0000000..eca96f2 --- /dev/null +++ b/lib/session_formats/session_json.cpp @@ -0,0 +1,97 @@ +/* + * This file is part of smolbote. It's copyrighted by the contributors recorded + * in the version control history of the file, available from its original + * location: https://library.iserlohn-fortress.net/aqua/smolbote + * + * SPDX-License-Identifier: GPL-3.0 + */ + +#include "session_json.hpp" +#include <QJsonArray> +#include <QJsonDocument> + +[[nodiscard]] QVector<Session::MainWindow> toWindowList(const QString &profile, const QStringList &urls) +{ + Session::SubWindow main_; + main_.profile = profile; + for(const auto &url : urls) { + main_.tabs.append({ profile, url, QByteArray() }); + } + Session::MainWindow main; + main.subwindows.append(main_); + return { main }; +} + +[[nodiscard]] QJsonObject toJson(const QVector<Session::MainWindow> &windows) +{ + QJsonObject root; + QJsonArray windows_; + { + for(const auto &window : windows) { + QJsonObject window_; + QJsonArray subwindows; + for(const auto &subwindow : window.subwindows) { + QJsonObject subwindow_json; + subwindow_json.insert("profile", subwindow.profile); + QJsonArray tabs; + for(const auto &tab : subwindow.tabs) { + QJsonObject tab_; + tab_.insert("profile", tab.profile); + tab_.insert("url", tab.url); + tab_.insert("history", QString::fromLatin1(tab.history.toBase64())); + tabs.append(tab_); + } + subwindow_json.insert("tabs", tabs); + subwindows.append(subwindow_json); + } + window_.insert("subwindows", subwindows); + windows_.append(window_); + } + } + root.insert("windows", windows_); + return root; +} + +JsonSession::JsonSession(const QByteArray &data) + : root(QJsonDocument::fromJson(data).object()) +{ +} + +JsonSession::JsonSession(const QVector<MainWindow> &windows) + : root(toJson(windows)) +{ +} + +JsonSession::JsonSession(const QString &profile, const QStringList &urls) + : root(toJson(toWindowList(profile, urls))) +{ +} + +QByteArray JsonSession::serialize() const +{ + QJsonDocument doc(root); + return doc.toJson(QJsonDocument::Compact); +} + +QVector<Session::MainWindow> JsonSession::get() const +{ + QVector<Session::MainWindow> windows; + for(const auto &windowData : root["windows"].toArray()) { + Session::MainWindow window; + for(const auto &subwindowData : windowData.toObject()["subwindows"].toArray()) { + Session::SubWindow subwindow; + subwindow.profile = subwindowData.toObject()["profile"].toString(); + for(const auto &tabData : subwindowData.toObject()["tabs"].toArray()) { + Session::WebView tab{ + tabData.toObject()["profile"].toString(), + tabData.toObject()["url"].toString(), + QByteArray::fromBase64(tabData.toObject()["history"].toString().toLatin1()) + }; + subwindow.tabs.append(tab); + } + window.subwindows.append(subwindow); + } + windows.append(window); + } + return windows; +} diff --git a/lib/session_formats/session_json.hpp b/lib/session_formats/session_json.hpp new file mode 100644 index 0000000..3332229 --- /dev/null +++ b/lib/session_formats/session_json.hpp @@ -0,0 +1,36 @@ +/* + * This file is part of smolbote. It's copyrighted by the contributors recorded + * in the version control history of the file, available from its original + * location: https://library.iserlohn-fortress.net/aqua/smolbote + * + * SPDX-License-Identifier: GPL-3.0 + */ + +#ifndef SESSION_JSON_HPP +#define SESSION_JSON_HPP + +#include <QJsonObject> +#include <session.hpp> + +class JsonSession : public Session +{ +public: + explicit JsonSession() = default; + JsonSession(const JsonSession &) = default; + JsonSession(JsonSession &&) = default; + JsonSession &operator=(const JsonSession &) = delete; + JsonSession &operator=(JsonSession &&) = delete; + + explicit JsonSession(const QByteArray &data); + explicit JsonSession(const QVector<MainWindow> &windows); + JsonSession(const QString &profile, const QStringList &urls); + ~JsonSession() override = default; + + [[nodiscard]] QByteArray serialize() const override; + [[nodiscard]] QVector<MainWindow> get() const override; + +private: + const QJsonObject root; +}; + +#endif // SESSION_JSON_HPP diff --git a/lib/session_formats/test/json.cpp b/lib/session_formats/test/json.cpp new file mode 100644 index 0000000..4c6b683 --- /dev/null +++ b/lib/session_formats/test/json.cpp @@ -0,0 +1,103 @@ +#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file +#include <catch2/catch.hpp> +#include <session_json.hpp> + +TEST_CASE("JsonSession default constructor") +{ + JsonSession session; + REQUIRE(session.serialize() == "{}"); + const auto tree = session.get(); + REQUIRE(tree.count() == 0); +} + +TEST_CASE("JsonSession command line constructor, single URL") +{ + const QString profile = ""; + const QString url = "https://some.url"; + JsonSession session(profile, QStringList(url)); + + const auto tree = session.get(); + REQUIRE(tree.count() == 1); + + const auto window = tree.at(0); + REQUIRE(window.subwindows.count() == 1); + + const auto subwindow = window.subwindows.at(0); + REQUIRE(subwindow.profile == profile); + REQUIRE(subwindow.tabs.count() == 1); + + const auto tab = subwindow.tabs.at(0); + REQUIRE(tab.profile == profile); + REQUIRE(tab.url == url); + REQUIRE(tab.history.isEmpty()); +} + +TEST_CASE("JsonSession command line constructor, multiple URLs") +{ + const QString profile = "default"; + const QStringList urls{ "https://some.url", "http://other.url", "a random string" }; + JsonSession session(profile, urls); + + const auto tree = session.get(); + REQUIRE(tree.count() == 1); + + const auto window = tree.at(0); + REQUIRE(window.subwindows.count() == 1); + + const auto subwindow = window.subwindows.at(0); + REQUIRE(subwindow.profile == profile); + REQUIRE(subwindow.tabs.count() == 3); + + for(int i = 0; i < urls.size(); ++i) { + const auto tab = subwindow.tabs.at(i); + REQUIRE(tab.profile == profile); + REQUIRE(tab.url == urls.at(i)); + REQUIRE(tab.history.isEmpty()); + } +} + +TEST_CASE("JsonSession QByteArray constuctor") +{ + const QString profile = ""; + const QString url = "about:blank"; + JsonSession donor(profile, { url }); + JsonSession session(donor.serialize()); + + const auto tree = session.get(); + REQUIRE(tree.count() == 1); + + const auto window = tree.at(0); + REQUIRE(window.subwindows.count() == 1); + + const auto subwindow = window.subwindows.at(0); + REQUIRE(subwindow.profile == profile); + REQUIRE(subwindow.tabs.count() == 1); + + const auto tab = subwindow.tabs.at(0); + REQUIRE(tab.profile == profile); + REQUIRE(tab.url == url); + REQUIRE(tab.history.isEmpty()); +} + +TEST_CASE("JsonSession MainWindowVector constructor") +{ + const Session::WebView cview{ "profile", "url", "history" }; + const Session::SubWindow csubwindow{ "profile", { cview } }; + const Session::MainWindow cwindow{ { csubwindow } }; + + const JsonSession session{ { cwindow } }; + const auto tree = session.get(); + REQUIRE(tree.count() == 1); + + const auto window = tree.at(0); + REQUIRE(window.subwindows.count() == 1); + + const auto subwindow = window.subwindows.at(0); + REQUIRE(subwindow.profile == csubwindow.profile); + REQUIRE(subwindow.tabs.count() == 1); + + const auto tab = subwindow.tabs.at(0); + REQUIRE(tab.profile == cview.profile); + REQUIRE(tab.url == cview.url); + REQUIRE(tab.history == cview.history); +} diff --git a/linux/makepkg/PKGBUILD b/linux/makepkg/PKGBUILD index 78549c3..b7086ad 100644 --- a/linux/makepkg/PKGBUILD +++ b/linux/makepkg/PKGBUILD @@ -20,7 +20,7 @@ arch=('x86_64' 'aarch64') license=('GPL3') depends=('qt5-webengine>=5.11.0' 'spdlog') -makedepends=('git' 'meson' 'python-kconfiglib' 'openssl' 'qt5-tools' 'scdoc') +makedepends=('git' 'meson' 'python-kconfiglib' 'openssl' 'qt5-tools' 'scdoc' 'catch2') if [ $_enableBreakpad == "1" ]; then makedepends+=('breakpad-git') fi @@ -76,7 +76,7 @@ build() { # b_lto: Use link time optimization meson --buildtype=release --wrap-mode=nodownload \ --prefix=$_prefix --auto-features=disabled \ - -Db_pie=true \ + -Db_pie=true -Ddefault_library=static \ -Dmanpage=enabled \ $srcdir/build diff --git a/meson.build b/meson.build index 003d312..62d517d 100644 --- a/meson.build +++ b/meson.build @@ -65,6 +65,7 @@ if dep_breakpad.found() endif dep_gtest = dependency('gtest', required: false, disabler: true) +dep_catch = dependency('catch2', required: true, fallback: ['catch2', 'catch2_dep'] ) dep_SingleApplication = dependency('singleapplication', fallback: [ 'singleapplication', 'SingleApplication_dep' ]) # Generate config header @@ -80,6 +81,7 @@ subdir('lib/configuration') subdir('lib/downloads') subdir('lib/pluginloader') subdir('lib/urlfilter') +subdir('lib/session_formats') subdir('3rd-party/args') @@ -99,7 +101,7 @@ poi_exe = executable(get_option('poi'), cpp_args: ['-DQAPPLICATION_CLASS=QApplication', poi_cpp_args], sources: [ssconfig.sources()], include_directories: [include, include_directories('src')], - dependencies: [ dep_qt5, dep_spdlog, dep_SingleApplication, dep_args, optional_deps, dep_about, dep_bookmarks, dep_configuration, dep_downloads, dep_pluginloader, dep_urlfilter, dep_plugininterface, ssconfig.dependencies() ], + dependencies: [ dep_qt5, dep_spdlog, dep_SingleApplication, dep_args, optional_deps, dep_about, dep_bookmarks, dep_configuration, dep_downloads, dep_pluginloader, dep_urlfilter, dep_plugininterface, ssconfig.dependencies(), lib_session_formats ], install: true, ) diff --git a/src/browser.cpp b/src/browser.cpp index 02d5bc9..3c39ec7 100644 --- a/src/browser.cpp +++ b/src/browser.cpp @@ -218,22 +218,44 @@ void Browser::showWidget(QWidget *widget, MainWindow *where) const where->addDockWidget(Qt::RightDockWidgetArea, widget); } -MainWindow *Browser::createWindow() +void Browser::open(const QVector<Session::MainWindow> &data, bool merge) { - // the window will delete itself when it closes, so we don't need to delete it - auto *window = new MainWindow(); - connect(window->addressBar, &AddressBar::complete, m_bookmarks.get(), &BookmarksWidget::search); + if(data.count() == 0 && merge) { + m_windows.at(0)->createTab(QUrl()); + } + + if(data.count() == 1 && m_windows.count() >= 1 && merge) { + const auto windowData = data.at(0); + auto *window = m_windows.at(0); + + if(windowData.subwindows.count() == 1) { + const auto subwindowData = windowData.subwindows.at(0); + for(const auto &tab : subwindowData.tabs) { + window->createTab(tab); + } + } else { + for(const auto &data : windowData.subwindows) { + window->createSubWindow(data); + } + } - for(auto *info : qAsConst(m_plugins)) { - addPluginTo(info, window); + return; } - m_windows.append(window); - connect(window, &MainWindow::destroyed, this, [this, window]() { - m_windows.removeOne(window); - }); + for(const auto &windowData : data) { + // the window will delete itself when it closes, so we don't need to delete it + auto *window = new MainWindow(windowData); + connect(window->addressBar, &AddressBar::complete, m_bookmarks.get(), &BookmarksWidget::search); - return window; + for(auto *info : qAsConst(m_plugins)) { + addPluginTo(info, window); + } + + m_windows.append(window); + connect(window, &MainWindow::destroyed, this, [this, window]() { + m_windows.removeOne(window); + }); + } } void Browser::addPluginTo(PluginInfo *info, MainWindow *window) diff --git a/src/browser.h b/src/browser.h index 646d57e..95ce936 100644 --- a/src/browser.h +++ b/src/browser.h @@ -9,7 +9,7 @@ #ifndef SMOLBOTE_BROWSER_H #define SMOLBOTE_BROWSER_H -#include "session/session.h" +#include "session.hpp" #include <QJsonObject> #include <QMenu> #include <QPluginLoader> @@ -60,7 +60,7 @@ public: public slots: void showWidget(QWidget *widget, MainWindow *where) const; - MainWindow *createWindow(); + void open(const QVector<Session::MainWindow> &data, bool merge = true); private: struct PluginInfo { diff --git a/src/main.cpp b/src/main.cpp index e04f58a..9ceda77 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,8 +10,8 @@ #include "builtins.h" #include "configuration.h" #include "crashhandler.h" -#include "session/session.h" #include "session/sessiondialog.h" +#include "session_json.hpp" #include "settings.h" #include "util.h" #include "version.h" @@ -145,37 +145,35 @@ int main(int argc, char **argv) // if app is primary, create new sessions from received messages if(app.isPrimary() && !cmd_noRemote) { - QObject::connect(&app, &Browser::receivedMessage, &app, [](quint32 instanceId, QByteArray message) { + QObject::connect(&app, &Browser::receivedMessage, &app, [&app](quint32 instanceId, QByteArray message) { Q_UNUSED(instanceId); - auto doc = QJsonDocument::fromJson(message); - Session::restoreSession(doc.object()); + JsonSession session(message); + app.open(session.get()); }); } { - QJsonObject sessionData; - - if(cmd_pickSession) { - auto *dlg = new SessionDialog(); - if(const auto pick = dlg->pickSession()) - sessionData = pick.value(); - else - sessionData = Session::fromCommandLine(profile, urls); - } else if(cmd_session) { - QFile sessionJson(QString::fromStdString(args::get(cmd_session))); - if(sessionJson.open(QIODevice::ReadOnly | QIODevice::Text)) { - sessionData = QJsonDocument::fromJson(sessionJson.readAll()).object(); - sessionJson.close(); + const auto session = [&]() { + if(cmd_session) { + QFile sessionJson(QString::fromStdString(args::get(cmd_session))); + if(sessionJson.open(QIODevice::ReadOnly | QIODevice::Text)) { + return JsonSession(sessionJson.readAll()); + } } - } else { - sessionData = Session::fromCommandLine(profile, urls); - } + if(cmd_pickSession) { + SessionDialog dlg; + if(const auto pick = dlg.pickSession()) { + return JsonSession(pick.value()); + } + } + return JsonSession(profile, urls); + }(); if(app.isPrimary() || cmd_noRemote) { - Session::restoreSession(sessionData); + app.open(session.get()); } else { // app is secondary and not standalone - return app.sendMessage(QJsonDocument(sessionData).toJson()); + return app.sendMessage(session.serialize()); } } diff --git a/src/mainwindow/mainwindow.cpp b/src/mainwindow/mainwindow.cpp index b607a63..e16f34f 100644 --- a/src/mainwindow/mainwindow.cpp +++ b/src/mainwindow/mainwindow.cpp @@ -14,6 +14,7 @@ #include "configuration.h" #include "menubar.h" #include "webengine/webprofile.h" +#include "webengine/webprofilemanager.h" #include "webengine/webview.h" #include "widgets/dockwidget.h" #include "widgets/navigationbar.h" @@ -23,10 +24,9 @@ #include <QMessageBox> #include <QStatusBar> -MainWindow::MainWindow(QWidget *parent) +MainWindow::MainWindow(const Session::MainWindow &data, QWidget *parent) : QMainWindow(parent) { - Configuration config; // create UI @@ -46,7 +46,7 @@ MainWindow::MainWindow(QWidget *parent) addressBar = new AddressBar(this); navigationToolBar->addWidget(addressBar); - Browser *app = qobject_cast<Browser *>(qApp); + auto *app = qobject_cast<Browser *>(qApp); this->addToolBarBreak(); this->addToolBar(new BookmarksToolbar(app->bookmarks()->model(), this)); @@ -65,6 +65,14 @@ MainWindow::MainWindow(QWidget *parent) searchBox->setVisible(!searchBox->isVisible()); }); QMainWindow::addAction(searchAction); + + for(const auto &s : data.subwindows) { + createSubWindow(s); + } + if(m_subwindows.isEmpty()) { + const Session::SubWindow s; + createSubWindow(s); + } } void MainWindow::addDockWidget(Qt::DockWidgetArea area, QWidget *widget) @@ -72,18 +80,20 @@ void MainWindow::addDockWidget(Qt::DockWidgetArea area, QWidget *widget) QDockWidget *lastDock = nullptr; const auto docks = findChildren<QDockWidget *>(); for(QDockWidget *dock : docks) { - if(dockWidgetArea(dock) == area) + if(dockWidgetArea(dock) == area) { lastDock = dock; + } } - DockWidget *dock = new DockWidget(widget->windowTitle(), this); + auto *dock = new DockWidget(widget->windowTitle(), this); dock->setMinimumWidth(460); dock->setWidget(widget); - if(lastDock == nullptr) + if(lastDock == nullptr) { QMainWindow::addDockWidget(area, dock); - else + } else { tabifyDockWidget(lastDock, dock); + } } void MainWindow::removeDockWidget(QWidget *widget) @@ -105,8 +115,23 @@ void MainWindow::createTab(const QUrl &url) } } -SubWindow *MainWindow::createSubWindow(WebProfile *profile, bool openProfileNewtab) +void MainWindow::createTab(const Session::WebView &data) +{ + auto *w = qobject_cast<SubWindow *>(centralWidget()); + if(w != nullptr) { + w->addTab(data); + } +} + +SubWindow *MainWindow::createSubWindow(const Session::SubWindow &data) { + const auto *profileManager = WebProfileManager::instance(); + Q_CHECK_PTR(profileManager); + + auto *profile = profileManager->profile(data.profile); + if(profile == nullptr) { + profile = WebProfile::defaultProfile(); + } auto *w = new SubWindow(this); w->setProfile(profile); m_subwindows.append(w); @@ -114,8 +139,9 @@ SubWindow *MainWindow::createSubWindow(WebProfile *profile, bool openProfileNewt m_menuBar->insertSubWindow(w); connect(w, &SubWindow::windowTitleChanged, this, [this, w](const QString &title) { - if(w == currentSubWindow()) + if(w == currentSubWindow()) { setWindowTitle(QString("[%1] - %2").arg(title, defaultWindowTitle)); + } }); connect(w, &SubWindow::aboutToClose, this, [this, w]() { @@ -128,9 +154,13 @@ SubWindow *MainWindow::createSubWindow(WebProfile *profile, bool openProfileNewt } }); - if(openProfileNewtab) - w->addTab(w->profile()->newtab()); - + if(data.tabs.count() == 0) { + w->addTab(profile->newtab()); + return w; + } + for(const auto &tab : data.tabs) { + w->addTab(tab); + } return w; } diff --git a/src/mainwindow/mainwindow.h b/src/mainwindow/mainwindow.h index 98bd6ee..2b9f82b 100644 --- a/src/mainwindow/mainwindow.h +++ b/src/mainwindow/mainwindow.h @@ -9,7 +9,10 @@ #ifndef SMOLBOTE_MAINWINDOW_H #define SMOLBOTE_MAINWINDOW_H +#include "session.hpp" #include "subwindow/subwindow.h" +#include <QJsonArray> +#include <QJsonObject> #include <QMainWindow> #include <QUrl> @@ -31,9 +34,17 @@ public: ToolsMenu }; - explicit MainWindow(QWidget *parent = nullptr); + explicit MainWindow(const Session::MainWindow &data, QWidget *parent = nullptr); MainWindow(const MainWindow &) = delete; ~MainWindow() = default; + [[nodiscard]] Session::MainWindow serialize() const + { + QVector<Session::SubWindow> subwindows(m_subwindows.size()); + for(int i = 0; i < m_subwindows.size(); ++i) { + subwindows[i] = m_subwindows.at(i)->serialize(); + } + return { subwindows }; + } void addDockWidget(Qt::DockWidgetArea area, QWidget *widget); void removeDockWidget(QWidget *widget); @@ -53,7 +64,8 @@ public: public slots: void createTab(const QUrl &url); - SubWindow *createSubWindow(WebProfile *profile = nullptr, bool openProfileNewtab = false); + void createTab(const Session::WebView &data); + SubWindow *createSubWindow(const Session::SubWindow &data); void setCurrentSubWindow(SubWindow *subwindow); private slots: diff --git a/src/mainwindow/menubar.cpp b/src/mainwindow/menubar.cpp index c0bda61..83cafee 100644 --- a/src/mainwindow/menubar.cpp +++ b/src/mainwindow/menubar.cpp @@ -33,14 +33,16 @@ inline void run_if(SubWindow *_subwindow, const std::function<void(SubWindow *, int)> &f) { - if(_subwindow != nullptr) + if(_subwindow != nullptr) { f(_subwindow, _subwindow->currentTabIndex()); + } } inline void trigger_if(WebView *_view, QWebEnginePage::WebAction action) { - if(_view != nullptr) + if(_view != nullptr) { _view->triggerPageAction(action); + } } inline QDialog *createDevToolsDialog(QWebEnginePage *page) @@ -88,8 +90,9 @@ MenuBar::MenuBar(MainWindow *parent) connect(findMenu, &QMenu::aboutToShow, [findMenu, find_lineEdit]() { find_lineEdit->clear(); const auto actions = findMenu->actions(); - for(int i = 1; i < actions.length(); i++) + for(int i = 1; i < actions.length(); i++) { findMenu->removeAction(actions.at(i)); + } find_lineEdit->setFocus(); }); @@ -155,11 +158,15 @@ MenuBar::MenuBar(MainWindow *parent) window = this->addMenu(tr("&Window")); { - auto *actionNewWindow = window->addAction(tr("New Window"), browser, &Browser::createWindow); + auto *actionNewWindow = window->addAction(tr("New Window"), browser, [browser]() { + const Session::MainWindow window; + browser->open({ window }, false); + }); conf.shortcut<QAction>(*actionNewWindow, "shortcuts.window.newwindow"); auto *actionNewSubwindow = window->addAction(tr("New Subwindow"), parent, [parent]() { - parent->createSubWindow(nullptr, true); + const Session::SubWindow session; + parent->createSubWindow(session); }); conf.shortcut<QAction>(*actionNewSubwindow, "shortcuts.window.newgroup"); diff --git a/src/meson.build b/src/meson.build index edc9db4..31f4ffc 100644 --- a/src/meson.build +++ b/src/meson.build @@ -11,13 +11,13 @@ poi_sourceset.add(mod_qt5.preprocess( moc_headers: ['browser.h', 'mainwindow/mainwindow.h', 'mainwindow/addressbar.h', 'mainwindow/menubar.h', 'mainwindow/widgets/completer.h', 'mainwindow/widgets/urllineedit.h', 'mainwindow/widgets/dockwidget.h', 'mainwindow/widgets/navigationbar.h', 'mainwindow/widgets/searchform.h', 'bookmarks/bookmarkswidget.h', 'bookmarks/editbookmarkdialog.h', - 'session/savesessiondialog.h', 'session/sessiondialog.h', 'session/sessionform.h', + 'session/savesessiondialog.h', 'session/sessiondialog.h', 'subwindow/subwindow.h', 'subwindow/tabwidget.h', 'webengine/urlinterceptor.h', 'webengine/webpage.h', 'webengine/webview.h', 'webengine/webprofilemanager.h', 'webengine/webprofile.h'], ui_files: [ 'mainwindow/addressbar.ui', 'mainwindow/widgets/searchform.ui', 'bookmarks/bookmarksform.ui', 'bookmarks/editbookmarkdialog.ui', - 'session/savesessiondialog.ui', 'session/sessiondialog.ui', 'session/sessionform.ui'], + 'session/savesessiondialog.ui', 'session/sessiondialog.ui' ], qresources: '../data/resources.qrc', rcc_extra_arguments: ['--format-version=1'], dependencies: dep_qt5 @@ -40,10 +40,8 @@ poi_sourceset.add(files( 'bookmarks/builtins.cpp', 'bookmarks/bookmarkswidget.cpp', 'bookmarks/editbookmarkdialog.cpp', 'bookmarks/bookmarkstoolbar.cpp', - 'session/session.cpp', 'session/savesessiondialog.cpp', 'session/sessiondialog.cpp', - 'session/sessionform.cpp', 'subwindow/subwindow.cpp', 'subwindow/tabwidget.cpp', diff --git a/src/session/savesessiondialog.cpp b/src/session/savesessiondialog.cpp index 507cddb..ff5e696 100644 --- a/src/session/savesessiondialog.cpp +++ b/src/session/savesessiondialog.cpp @@ -1,7 +1,7 @@ /* * This file is part of smolbote. It's copyrighted by the contributors recorded * in the version control history of the file, available from its original - * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote + * location: https://library.iserlohn-fortress.net/aqua/smolbote.git * * SPDX-License-Identifier: GPL-3.0 */ @@ -9,6 +9,7 @@ #include "savesessiondialog.h" #include "browser.h" #include "mainwindow/mainwindow.h" +#include "session_json.hpp" #include "subwindow/subwindow.h" #include "ui_savesessiondialog.h" #include "webengine/webprofilemanager.h" @@ -61,18 +62,17 @@ void SaveSessionDialog::save(const QString &sessionPath) QFile output(filename); if(output.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { - QVector<MainWindow *> windows; + QVector<Session::MainWindow> windows; for(int i = 0; i < ui->treeWidget->topLevelItemCount(); ++i) { QTreeWidgetItem *item = ui->treeWidget->topLevelItem(i); if(item->checkState(0) == Qt::Checked) { auto *window = static_cast<MainWindow *>(item->data(0, Qt::UserRole).value<void *>()); - Q_CHECK_PTR(window); - windows.append(window); + windows.append(window->serialize()); } } - auto data = Session::_session(windows); - output.write(QJsonDocument(data).toJson()); + JsonSession session(windows); + output.write(session.serialize()); output.close(); } } diff --git a/src/session/savesessiondialog.h b/src/session/savesessiondialog.h index ade1d23..8a162d1 100644 --- a/src/session/savesessiondialog.h +++ b/src/session/savesessiondialog.h @@ -1,7 +1,7 @@ /* * This file is part of smolbote. It's copyrighted by the contributors recorded * in the version control history of the file, available from its original - * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote + * location: https://library.iserlohn-fortress.net/aqua/smolbote.git * * SPDX-License-Identifier: GPL-3.0 */ @@ -22,7 +22,11 @@ class SaveSessionDialog : public QDialog public: explicit SaveSessionDialog(QWidget *parent = nullptr); - ~SaveSessionDialog(); + explicit SaveSessionDialog(const SaveSessionDialog &) = delete; + explicit SaveSessionDialog(SaveSessionDialog &&) = delete; + SaveSessionDialog &operator=(const SaveSessionDialog &) = delete; + SaveSessionDialog &operator=(SaveSessionDialog &&) = delete; + ~SaveSessionDialog() override; public slots: void save(const QString &sessionPath); diff --git a/src/session/session.cpp b/src/session/session.cpp deleted file mode 100644 index c97c22b..0000000 --- a/src/session/session.cpp +++ /dev/null @@ -1,177 +0,0 @@ -/* - * This file is part of smolbote. It's copyrighted by the contributors recorded - * in the version control history of the file, available from its original - * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote - * - * SPDX-License-Identifier: GPL-3.0 - */ - -#include "session.h" -#include "../webengine/webview.h" -#include "browser.h" -#include "configuration.h" -#include "mainwindow/mainwindow.h" -#include "subwindow/subwindow.h" -#include "webengine/webprofilemanager.h" -#include "webengine/webview.h" -#include <QJsonArray> -#include <QJsonObject> -#include <QWebEngineHistory> -#include <memory> - -QJsonObject Session::fromCommandLine(const QString &profile, const QStringList &urls) -{ - QJsonObject session; - - QJsonArray subwindows; - { - QJsonObject window; - window.insert("profile", profile); - - QJsonArray tabs; - for(const auto &url : urls) { - QJsonObject tab; - tab.insert("url", url); - tab.insert("profile", profile); - tabs.append(tab); - } - window.insert("tabs", tabs); - - subwindows.append(window); - } - session.insert("subwindows", subwindows); - - return session; -} - -////// - -QJsonObject Session::_session(const QVector<MainWindow *> windows) -{ - QJsonObject sessionData; - - { - QJsonArray windowList; - for(const auto *window : windows) { - windowList.append(Session::_window(window)); - } - sessionData.insert("windows", windowList); - } - - return sessionData; -} - -QJsonObject Session::_window(const MainWindow *window) -{ - QJsonObject windowData; - - { - QJsonArray subwindows; - for(const auto *subwindow : window->subWindows()) { - subwindows.append(Session::_subwindow(subwindow)); - } - windowData.insert("subwindows", subwindows); - } - - return windowData; -} - -QJsonObject Session::_subwindow(const SubWindow *subwindow) -{ - const auto *profileManager = WebProfileManager::instance(); - Q_CHECK_PTR(profileManager); - - QJsonObject subwindowData; - - subwindowData.insert("profile", profileManager->id(subwindow->profile())); - - { - QJsonArray tabs; - for(int i = 0; i < subwindow->tabCount(); ++i) { - tabs.append(Session::view(subwindow->view(i))); - } - subwindowData.insert("tabs", tabs); - } - - return subwindowData; -} - -QJsonObject Session::view(const WebView *view) -{ - const auto *profileManager = WebProfileManager::instance(); - Q_CHECK_PTR(profileManager); - - QByteArray historyData; - QDataStream historyStream(&historyData, QIODevice::WriteOnly); - historyStream << *view->history(); - - QJsonObject viewData; - viewData.insert("profile", profileManager->id(view->profile())); - - // store history: compress, toBase64 (to make printable), toQString (to store in json) - viewData.insert("history", QString(qCompress(historyData).toBase64())); - - return viewData; -} - -void Session::restoreView(WebView *view, const QJsonObject &data) -{ - const auto *profileManager = WebProfileManager::instance(); - Q_CHECK_PTR(profileManager); - - auto *profile = profileManager->profile(data["profile"].toString()); - if(profile != nullptr) - view->setProfile(profile); - - auto url = data.value("url"); - if(url.isString()) - view->load(QUrl::fromUserInput(url.toString())); - else { - // restore history: toLatin1 (turn into bytearray), fromBase64, uncompress - QByteArray historyData = qUncompress(QByteArray::fromBase64(data["history"].toString().toLatin1())); - QDataStream historyStream(&historyData, QIODevice::ReadOnly); - historyStream >> *view->history(); - } -} - -void Session::restoreSession(const QJsonObject &sessionData) -{ - auto *browser = dynamic_cast<Browser *>(qApp); - Q_CHECK_PTR(browser); - const auto *profileManager = WebProfileManager::instance(); - Q_CHECK_PTR(profileManager); - - for(const auto subwindowData : sessionData["subwindows"].toArray()) { - MainWindow *window = nullptr; - SubWindow *subwindow = nullptr; - - if(!browser->windows().isEmpty()) { - window = browser->windows().last(); - subwindow = window->currentSubWindow(); - } else { - window = browser->createWindow(); - subwindow = window->createSubWindow(profileManager->profile(subwindowData.toObject()["profile"].toString())); - } - - Q_CHECK_PTR(window); - Q_CHECK_PTR(subwindow); - - for(const auto tabData : subwindowData.toObject()["tabs"].toArray()) { - auto *view = subwindow->view(subwindow->addTab()); - Session::restoreView(view, tabData.toObject()); - } - } - - for(const auto windowData : sessionData["windows"].toArray()) { - auto *window = browser->createWindow(); - - for(const auto subwindowData : windowData.toObject()["subwindows"].toArray()) { - auto *subwindow = window->createSubWindow(profileManager->profile(subwindowData.toObject()["profile"].toString())); - - for(const auto tabData : subwindowData.toObject()["tabs"].toArray()) { - auto *view = subwindow->view(subwindow->addTab()); - Session::restoreView(view, tabData.toObject()); - } - } - } -} diff --git a/src/session/session.h b/src/session/session.h deleted file mode 100644 index 8a96bb2..0000000 --- a/src/session/session.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * This file is part of smolbote. It's copyrighted by the contributors recorded - * in the version control history of the file, available from its original - * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote - * - * SPDX-License-Identifier: GPL-3.0 - */ - -#ifndef SMOLBOTE_SESSION_H -#define SMOLBOTE_SESSION_H - -#include <QJsonDocument> - -class MainWindow; -class SubWindow; -class WebView; - -namespace Session { -QJsonObject fromCommandLine(const QString &profile, const QStringList &urls); - -// TODO: -QJsonObject _session(const QVector<MainWindow *> windows); -QJsonObject _window(const MainWindow *window); -QJsonObject _subwindow(const SubWindow *subwindow); - - -QJsonObject view(const WebView *view); -void restoreView(WebView *view, const QJsonObject &data); - -void restoreSession(const QJsonObject &sessionData); - -} // namespace Session - -#endif // SMOLBOTE_SESSION_H diff --git a/src/session/sessiondialog.cpp b/src/session/sessiondialog.cpp index 301b4b6..36cf9cb 100644 --- a/src/session/sessiondialog.cpp +++ b/src/session/sessiondialog.cpp @@ -1,7 +1,7 @@ /* * This file is part of smolbote. It's copyrighted by the contributors recorded * in the version control history of the file, available from its original - * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote + * location: https://library.iserlohn-fortress.net/aqua/smolbote.git * * SPDX-License-Identifier: GPL-3.0 */ @@ -9,56 +9,59 @@ #include "sessiondialog.h" #include "../browser.h" #include "../util.h" -#include "sessionform.h" +#include "configuration.h" +#include "session_json.hpp" #include "ui_sessiondialog.h" -#include "ui_sessionform.h" #include <QFile> #include <QFileDialog> -#include <QToolButton> #include <QStyle> -#include "configuration.h" +#include <QToolButton> SessionDialog::SessionDialog(QWidget *parent) : QDialog(parent) + , create_window_str(QObject::tr("Create a new window")) , ui(new Ui::SessionDialog) { ui->setupUi(this); - ui->open->setIcon(style()->standardIcon(QStyle::SP_DirOpenIcon)); auto *browser = qobject_cast<Browser *>(qApp); Q_CHECK_PTR(browser); Configuration conf; for(const QString &path : Util::files(conf.value<QString>("session.path").value(), { "*.json" })) { - addItem(path); + ui->listWidget->addItem(path); } - ui->listWidget->addItem(tr("Create a new window")); + ui->listWidget->addItem(create_window_str); - connect(ui->listWidget, &QListWidget::currentItemChanged, this, [this](QListWidgetItem *currentItem, QListWidgetItem *previousItem) { - auto *currentWidget = qobject_cast<SessionForm *>(ui->listWidget->itemWidget(currentItem)); - if(currentWidget != nullptr) - currentWidget->ui->delete_toolButton->show(); - - auto *previousWidget = qobject_cast<SessionForm *>(ui->listWidget->itemWidget(previousItem)); - if(previousWidget != nullptr) - previousWidget->ui->delete_toolButton->hide(); + connect(ui->listWidget, &QListWidget::currentItemChanged, this, [this](QListWidgetItem *currentItem, QListWidgetItem * /*previousItem*/) { + ui->delete_toolButton->setEnabled(currentItem->text() != create_window_str); }); - connect(ui->open, &QPushButton::clicked, this, [this]() { + connect(ui->open_toolButton, &QToolButton::clicked, this, [this]() { const QString filename = QFileDialog::getOpenFileName(this, tr("Select Session file"), QDir::homePath(), tr("JSON (*.json)")); if(!filename.isEmpty()) { - ui->listWidget->setCurrentItem(addItem(filename)); + ui->listWidget->addItem(filename); + ui->listWidget->setCurrentRow(ui->listWidget->count() - 1); accept(); } }); + connect(ui->delete_toolButton, &QToolButton::clicked, this, [this]() { + const auto path = ui->listWidget->currentItem()->text(); + if(path != create_window_str) { + QFile(path).remove(); + delete ui->listWidget->currentItem(); + } + }); + accepted_connection = connect(this, &SessionDialog::accepted, this, [this, browser]() { - auto *currentWidget = qobject_cast<SessionForm *>(ui->listWidget->itemWidget(ui->listWidget->currentItem())); - if(currentWidget != nullptr) - this->openSession(currentWidget->ui->label->text()); - else - browser->createWindow(); + const auto path = ui->listWidget->currentItem()->text(); + if(path != create_window_str) { + openSession(path); + } else { + browser->open({}); + } }); connect(ui->search, &QLineEdit::textEdited, this, &SessionDialog::search); @@ -69,7 +72,7 @@ SessionDialog::~SessionDialog() delete ui; } -std::optional<QJsonObject> SessionDialog::pickSession() +std::optional<QVector<Session::MainWindow>> SessionDialog::pickSession() { disconnect(accepted_connection); @@ -77,52 +80,32 @@ std::optional<QJsonObject> SessionDialog::pickSession() return std::nullopt; } - auto *currentWidget = qobject_cast<SessionForm *>(ui->listWidget->itemWidget(ui->listWidget->currentItem())); - if(currentWidget != nullptr) { - QFile json(currentWidget->ui->label->text()); + const auto path = ui->listWidget->currentItem()->text(); + if(path != create_window_str) { + QFile json(path); if(json.open(QIODevice::ReadOnly | QIODevice::Text)) { - auto doc = QJsonDocument::fromJson(json.readAll()); - return doc.object(); + JsonSession session(json.readAll()); + return session.get(); } } return std::nullopt; } -QListWidgetItem *SessionDialog::addItem(const QString &path) -{ - auto *item = new QListWidgetItem(ui->listWidget); - auto *widget = new SessionForm(path, this); - connect(widget->ui->delete_toolButton, &QToolButton::clicked, this, [item, widget]() { - delete item; - delete widget; - }); - ui->listWidget->setItemWidget(item, widget); - return item; -} - void SessionDialog::search(const QString &text) { for(int i = 0; i < ui->listWidget->count(); ++i) { auto *item = ui->listWidget->item(i); - auto *widget = qobject_cast<SessionForm *>(ui->listWidget->itemWidget(item)); - - if(widget == nullptr) - item->setHidden(!text.isEmpty()); - else - item->setHidden(!widget->ui->label->text().contains(text)); + item->setHidden(!item->text().contains(text)); } } void SessionDialog::openSession(const QString &filename) { auto *browser = qobject_cast<Browser *>(qApp); - Q_CHECK_PTR(browser); - QFile json(filename); - if(json.open(QIODevice::ReadOnly | QIODevice::Text)) { - auto doc = QJsonDocument::fromJson(json.readAll()); - Session::restoreSession(doc.object()); - json.close(); + if(json.open(QIODevice::ReadOnly | QIODevice::Text) && browser != nullptr) { + JsonSession session(json.readAll()); + browser->open(session.get()); } } diff --git a/src/session/sessiondialog.h b/src/session/sessiondialog.h index 9bf8a68..0a04940 100644 --- a/src/session/sessiondialog.h +++ b/src/session/sessiondialog.h @@ -1,7 +1,7 @@ /* * This file is part of smolbote. It's copyrighted by the contributors recorded * in the version control history of the file, available from its original - * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote + * location: https://library.iserlohn-fortress.net/aqua/smolbote.git * * SPDX-License-Identifier: GPL-3.0 */ @@ -9,6 +9,7 @@ #ifndef SMOLBOTE_SESSIONDIALOG_H #define SMOLBOTE_SESSIONDIALOG_H +#include "session.hpp" #include <QDialog> namespace Ui @@ -22,16 +23,20 @@ class SessionDialog : public QDialog public: explicit SessionDialog(QWidget *parent = nullptr); + explicit SessionDialog(const SessionDialog &) = delete; + explicit SessionDialog(SessionDialog &&) = delete; + SessionDialog &operator=(const SessionDialog &) = delete; + SessionDialog &operator=(SessionDialog &&) = delete; ~SessionDialog() override; - std::optional<QJsonObject> pickSession(); + std::optional<QVector<Session::MainWindow>> pickSession(); private slots: - QListWidgetItem *addItem(const QString &path); void search(const QString &text); - void openSession(const QString &filename); + static void openSession(const QString &filename); private: + const QString create_window_str; Ui::SessionDialog *ui; QMetaObject::Connection accepted_connection; }; diff --git a/src/session/sessiondialog.ui b/src/session/sessiondialog.ui index dab49d9..4049b75 100644 --- a/src/session/sessiondialog.ui +++ b/src/session/sessiondialog.ui @@ -41,13 +41,23 @@ <item> <layout class="QHBoxLayout" name="horizontalLayout_2"> <item> - <widget class="QPushButton" name="open"> + <widget class="QToolButton" name="open_toolButton"> <property name="text"> <string>Open from File</string> </property> </widget> </item> <item> + <widget class="QToolButton" name="delete_toolButton"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Delete</string> + </property> + </widget> + </item> + <item> <widget class="QDialogButtonBox" name="buttonBox"> <property name="orientation"> <enum>Qt::Horizontal</enum> diff --git a/src/session/sessionform.cpp b/src/session/sessionform.cpp deleted file mode 100644 index 761cb42..0000000 --- a/src/session/sessionform.cpp +++ /dev/null @@ -1,36 +0,0 @@ -/* - * This file is part of smolbote. It's copyrighted by the contributors recorded - * in the version control history of the file, available from its original - * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote - * - * SPDX-License-Identifier: GPL-3.0 - */ - -#include "sessionform.h" -#include "ui_sessionform.h" -#include <QStyle> - -SessionForm::SessionForm(const QString &path, QWidget *parent) - : QWidget(parent) - , ui(new Ui::SessionForm) -{ - ui->setupUi(this); - ui->label->setText(path); - ui->delete_toolButton->setIcon(style()->standardIcon(QStyle::SP_TrashIcon)); - ui->delete_toolButton->hide(); -} - -SessionForm::~SessionForm() -{ - delete ui; -} - -void SessionForm::showDetails() -{ - ui->delete_toolButton->show(); -} - -void SessionForm::hideDetails() -{ - ui->delete_toolButton->hide(); -} diff --git a/src/session/sessionform.h b/src/session/sessionform.h deleted file mode 100644 index 5e5f52e..0000000 --- a/src/session/sessionform.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * This file is part of smolbote. It's copyrighted by the contributors recorded - * in the version control history of the file, available from its original - * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote - * - * SPDX-License-Identifier: GPL-3.0 - */ - -#ifndef SMOLBOTE_SESSIONFORM_H -#define SMOLBOTE_SESSIONFORM_H - -#include <QWidget> - -namespace Ui -{ -class SessionForm; -} - -class SessionForm : public QWidget -{ - Q_OBJECT - - friend class SessionDialog; - -public: - explicit SessionForm(const QString &path, QWidget *parent = nullptr); - ~SessionForm() override; - -public slots: - void showDetails(); - void hideDetails(); - -private: - Ui::SessionForm *ui; -}; - -#endif // SMOLBOTE_SESSIONFORM_H diff --git a/src/session/sessionform.ui b/src/session/sessionform.ui deleted file mode 100644 index dbf0237..0000000 --- a/src/session/sessionform.ui +++ /dev/null @@ -1,41 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>SessionForm</class> - <widget class="QWidget" name="SessionForm"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>711</width> - <height>34</height> - </rect> - </property> - <property name="windowTitle"> - <string>Form</string> - </property> - <layout class="QHBoxLayout" name="horizontalLayout"> - <property name="topMargin"> - <number>0</number> - </property> - <property name="bottomMargin"> - <number>0</number> - </property> - <item> - <widget class="QLabel" name="label"> - <property name="text"> - <string>TextLabel</string> - </property> - </widget> - </item> - <item> - <widget class="QToolButton" name="delete_toolButton"> - <property name="text"> - <string>...</string> - </property> - </widget> - </item> - </layout> - </widget> - <resources/> - <connections/> -</ui> diff --git a/src/subwindow/subwindow.cpp b/src/subwindow/subwindow.cpp index 58acee6..8c2e3e7 100644 --- a/src/subwindow/subwindow.cpp +++ b/src/subwindow/subwindow.cpp @@ -10,10 +10,12 @@ #include "browser.h" #include "configuration.h" #include "webengine/webprofile.h" +#include "webengine/webprofilemanager.h" #include "webengine/webview.h" #include <QAction> #include <QCloseEvent> #include <QHideEvent> +#include <QJsonArray> #include <QMenu> #include <QShortcut> #include <QShowEvent> @@ -76,6 +78,35 @@ SubWindow::SubWindow(QWidget *parent, Qt::WindowFlags flags) }); } +SubWindow::SubWindow(const Session::SubWindow &data, QWidget *parent, Qt::WindowFlags flags) + : SubWindow(parent, flags) +{ + const auto *profileManager = WebProfileManager::instance(); + Q_CHECK_PTR(profileManager); + + auto *profile = profileManager->profile(data.profile); + if(profile != nullptr) { + setProfile(profile); + } + + for(const auto &data : data.tabs) { + addTab(data); + } +} + +Session::SubWindow SubWindow::serialize() const +{ + const auto *profileManager = WebProfileManager::instance(); + Q_CHECK_PTR(profileManager); + + QVector<Session::WebView> tabs(tabCount()); + for(int i = 0; i < tabCount(); ++i) { + tabs[i] = view(i)->serialize(); + } + + return { profileManager->id(profile()), tabs }; +} + void SubWindow::setProfile(WebProfile *profile) { if(profile == nullptr) { @@ -115,6 +146,12 @@ int SubWindow::addTab(const QUrl &url, WebProfile *profile) return tabWidget->addTab(view); } +int SubWindow::addTab(const Session::WebView &data) +{ + auto *view = new WebView(data, this); + return tabWidget->addTab(view); +} + void SubWindow::moveTab(int from, int to) { tabWidget->tabBar()->moveTab(from, to); diff --git a/src/subwindow/subwindow.h b/src/subwindow/subwindow.h index 8343a9b..02f50d0 100644 --- a/src/subwindow/subwindow.h +++ b/src/subwindow/subwindow.h @@ -9,6 +9,7 @@ #ifndef SMOLBOTE_SUBWINDOW_H #define SMOLBOTE_SUBWINDOW_H +#include "session.hpp" #include "tabwidget.h" #include "webengine/webview.h" #include <QMenu> @@ -27,7 +28,9 @@ public: }; explicit SubWindow(QWidget *parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags()); + explicit SubWindow(const Session::SubWindow &data, QWidget *parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags()); ~SubWindow() = default; + [[nodiscard]] Session::SubWindow serialize() const; [[nodiscard]] int currentTabIndex() const { @@ -66,6 +69,7 @@ signals: public slots: int addTab(const QUrl &url = QUrl(), WebProfile *profile = nullptr); + int addTab(const Session::WebView &data); void closeTab(int index) { tabWidget->removeTab(index); diff --git a/src/webengine/webview.cpp b/src/webengine/webview.cpp index d42bad5..c64333e 100644 --- a/src/webengine/webview.cpp +++ b/src/webengine/webview.cpp @@ -13,15 +13,12 @@ #include "webprofilemanager.h" #include "webviewcontextmenu.h" #include <QContextMenuEvent> +#include <QJsonObject> #include <QWebEngineHistoryItem> -WebView::WebView(WebProfile *profile, QWidget *parent) +WebView::WebView(QWidget *parent) : QWebEngineView(parent) { - Q_CHECK_PTR(profile); - m_profile = profile; - setPage(new WebPage(profile, this)); - m_parentWindow = qobject_cast<SubWindow *>(parent); // load status and progress @@ -40,6 +37,34 @@ WebView::WebView(WebProfile *profile, QWidget *parent) }); } +WebView::WebView(WebProfile *profile, QWidget *parent) + : WebView(parent) +{ + Q_CHECK_PTR(profile); + m_profile = profile; + setPage(new WebPage(profile, this)); +} + +WebView::WebView(const Session::WebView &data, QWidget *parent) + : WebView(parent) +{ + const auto *profileManager = WebProfileManager::instance(); + Q_CHECK_PTR(profileManager); + + auto *profile = profileManager->profile(data.profile); + if(profile != nullptr) { + setProfile(profile); + } + + if(!data.url.isEmpty()) + load(QUrl::fromUserInput(data.url)); + else { + QByteArray copy(data.history); + QDataStream historyStream(©, QIODevice::ReadOnly); + historyStream >> *history(); + } +} + void WebView::setProfile(WebProfile *profile) { m_profile = profile; @@ -48,6 +73,18 @@ void WebView::setProfile(WebProfile *profile) this->load(url); } +Session::WebView WebView::serialize() const +{ + const auto *profileManager = WebProfileManager::instance(); + Q_CHECK_PTR(profileManager); + + QByteArray historyData; + QDataStream historyStream(&historyData, QIODevice::WriteOnly); + historyStream << *history(); + + return { profileManager->id(profile()), QString(), historyData }; +} + bool WebView::isLoaded() const { return m_loaded; diff --git a/src/webengine/webview.h b/src/webengine/webview.h index 0223b78..5748691 100644 --- a/src/webengine/webview.h +++ b/src/webengine/webview.h @@ -11,26 +11,32 @@ #include "webpage.h" #include <QWebEngineView> +#include <session.hpp> class WebProfile; class SubWindow; class WebViewContextMenu; -class WebView : public QWebEngineView +class WebView final : public QWebEngineView { friend class WebViewContextMenu; Q_OBJECT + explicit WebView(QWidget *parent = nullptr); + public: explicit WebView(WebProfile *profile = nullptr, QWidget *parent = nullptr); + explicit WebView(const Session::WebView &data, QWidget *parent = nullptr); ~WebView() = default; - WebProfile *profile() const + [[nodiscard]] WebProfile *profile() const { return m_profile; } void setProfile(WebProfile *profile); + [[nodiscard]] Session::WebView serialize() const; + bool isLoaded() const; public slots: diff --git a/subprojects/catch2.wrap b/subprojects/catch2.wrap new file mode 100644 index 0000000..2e20085 --- /dev/null +++ b/subprojects/catch2.wrap @@ -0,0 +1,10 @@ +[wrap-file] +directory = Catch2-2.11.3 + +source_url = https://github.com/catchorg/Catch2/archive/v2.11.3.zip +source_filename = Catch2-2.11.3.zip +source_hash = c5a0a7510379c6f37f70b329986a335a7b8489d67ac417ce8f4262d0cae4cc5d + +patch_url = https://wrapdb.mesonbuild.com/v1/projects/catch2/2.11.3/1/get_zip +patch_filename = catch2-2.11.3-1-wrap.zip +patch_hash = 63c09cb68280435040ad304b3dd87ecfe69dbc216608991d0a82569a63119e57 |