mirror of
https://github.com/zealdocs/zeal.git
synced 2026-08-29 08:34:50 +08:00
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,7 @@ add_library(Core STATIC
|
||||
httpserver.cpp
|
||||
networkaccessmanager.cpp
|
||||
settings.cpp
|
||||
session.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(Core PRIVATE Registry)
|
||||
@@ -38,6 +39,15 @@ else()
|
||||
target_link_libraries(Core PRIVATE ${LibArchive_LIBRARIES})
|
||||
endif()
|
||||
|
||||
# Configure toml++ (header-only).
|
||||
find_package(tomlplusplus CONFIG QUIET)
|
||||
if(tomlplusplus_FOUND)
|
||||
target_link_libraries(Core PRIVATE tomlplusplus::tomlplusplus)
|
||||
else()
|
||||
# Use bundled version of toml++ if not found.
|
||||
target_include_directories(Core PRIVATE "${CMAKE_SOURCE_DIR}/src/contrib/tomlplusplus")
|
||||
endif()
|
||||
|
||||
# Configure cpp-httplib.
|
||||
add_definitions(-DCPPHTTPLIB_USE_POLL)
|
||||
|
||||
@@ -55,3 +65,8 @@ if(NOT WIN32)
|
||||
find_package(Threads REQUIRED)
|
||||
target_link_libraries(Core PRIVATE Threads::Threads)
|
||||
endif()
|
||||
|
||||
# Tests
|
||||
if(BUILD_TESTING)
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "filemanager.h"
|
||||
#include "httpserver.h"
|
||||
#include "networkaccessmanager.h"
|
||||
#include "session.h"
|
||||
#include "settings.h"
|
||||
|
||||
#include <registry/docsetregistry.h>
|
||||
@@ -39,6 +40,9 @@ Application::Application(QObject *parent)
|
||||
m_instance = this;
|
||||
|
||||
m_settings = new Settings(this);
|
||||
m_session = new Session();
|
||||
m_session->load();
|
||||
|
||||
m_networkManager = new NetworkAccessManager(this);
|
||||
|
||||
m_fileManager = new FileManager(this);
|
||||
@@ -75,6 +79,9 @@ Application::~Application()
|
||||
m_extractorThread->wait();
|
||||
delete m_extractor;
|
||||
delete m_docsetRegistry;
|
||||
|
||||
m_session->save();
|
||||
delete m_session;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -92,6 +99,11 @@ QNetworkAccessManager *Application::networkManager() const
|
||||
return m_networkManager;
|
||||
}
|
||||
|
||||
Session *Application::session() const
|
||||
{
|
||||
return m_session;
|
||||
}
|
||||
|
||||
Settings *Application::settings() const
|
||||
{
|
||||
return m_settings;
|
||||
|
||||
@@ -23,6 +23,7 @@ class Extractor;
|
||||
class FileManager;
|
||||
class HttpServer;
|
||||
class Settings;
|
||||
struct Session;
|
||||
|
||||
class Application final : public QObject
|
||||
{
|
||||
@@ -35,6 +36,7 @@ public:
|
||||
static Application *instance();
|
||||
|
||||
QNetworkAccessManager *networkManager() const;
|
||||
Session *session() const;
|
||||
Settings *settings() const;
|
||||
|
||||
Registry::DocsetRegistry *docsetRegistry() const;
|
||||
@@ -65,6 +67,7 @@ private:
|
||||
|
||||
static Application *m_instance;
|
||||
|
||||
Session *m_session = nullptr;
|
||||
Settings *m_settings = nullptr;
|
||||
|
||||
QNetworkAccessManager *m_networkManager = nullptr;
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
// Copyright (C) Oleg Shparber, et al. <https://zealdocs.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "session.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QLoggingCategory>
|
||||
#include <QSettings>
|
||||
#include <QStandardPaths>
|
||||
|
||||
#include <sstream>
|
||||
#include <string_view>
|
||||
#include <toml++/toml.hpp>
|
||||
|
||||
namespace Zeal::Core {
|
||||
|
||||
namespace {
|
||||
Q_LOGGING_CATEGORY(log, "zeal.core.session")
|
||||
|
||||
constexpr std::string_view ArrayWindows = "windows";
|
||||
constexpr std::string_view KeyGeometry = "geometry";
|
||||
constexpr std::string_view KeySplitter = "splitter";
|
||||
constexpr std::string_view KeyTocSplitter = "toc_splitter";
|
||||
|
||||
QByteArray readBlob(const toml::table &tbl, std::string_view key)
|
||||
{
|
||||
const auto *node = tbl.get(key);
|
||||
if (node == nullptr || !node->is_string()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const std::string &encoded = node->as_string()->get();
|
||||
return QByteArray::fromBase64(QByteArray::fromStdString(encoded));
|
||||
}
|
||||
|
||||
void writeBlob(toml::table &tbl, std::string_view key, const QByteArray &blob)
|
||||
{
|
||||
tbl.insert_or_assign(key, blob.toBase64().toStdString());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
WindowState &Session::primaryWindow()
|
||||
{
|
||||
if (windows.isEmpty()) {
|
||||
windows.append(WindowState());
|
||||
}
|
||||
|
||||
return windows.first();
|
||||
}
|
||||
|
||||
QString Session::defaultFilePath()
|
||||
{
|
||||
#ifdef PORTABLE_BUILD
|
||||
return QCoreApplication::applicationDirPath() + QLatin1String("/session.toml");
|
||||
#else
|
||||
// Test mode: isolate test runs under ~/.qttest/zeal/, mirroring how
|
||||
// QStandardPaths handles its own path categories in test mode.
|
||||
if (QStandardPaths::isTestModeEnabled()) {
|
||||
return QDir::homePath() + QLatin1String("/.qttest/zeal/session.toml");
|
||||
}
|
||||
|
||||
// Compute the canonical platform path directly rather than via
|
||||
// QStandardPaths::StateLocation, which (a) is Qt 6.7+ only and (b) appends
|
||||
// "<organizationName>/<applicationName>" yielding the doubled "Zeal/Zeal"
|
||||
// path tracked in #1104. The path must not depend on Qt version — distro
|
||||
// Qt upgrades shouldn't relocate the state file.
|
||||
#if defined(Q_OS_WIN)
|
||||
QString base = QString::fromLocal8Bit(qgetenv("LOCALAPPDATA"));
|
||||
if (base.isEmpty()) {
|
||||
base = QDir::homePath() + QLatin1String("/AppData/Local");
|
||||
}
|
||||
return base + QLatin1String("/Zeal/session.toml");
|
||||
#elif defined(Q_OS_MACOS)
|
||||
return QDir::homePath() + QLatin1String("/Library/Application Support/Zeal/session.toml");
|
||||
#else
|
||||
const QByteArray xdgState = qgetenv("XDG_STATE_HOME");
|
||||
const QString base = xdgState.isEmpty() ? QDir::homePath() + QLatin1String("/.local/state")
|
||||
: QString::fromLocal8Bit(xdgState);
|
||||
return base + QLatin1String("/zeal/session.toml");
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
void Session::load()
|
||||
{
|
||||
const QString path = defaultFilePath();
|
||||
if (QFile::exists(path)) {
|
||||
loadFromFile(path);
|
||||
return;
|
||||
}
|
||||
|
||||
// First launch with the new state system: migrate the three blobs from the
|
||||
// legacy QSettings 'state' group. Persist session.toml before removing the
|
||||
// legacy keys so a crash between the two steps doesn't lose user data —
|
||||
// migration retries on the next launch.
|
||||
#ifndef PORTABLE_BUILD
|
||||
QSettings legacy;
|
||||
#else
|
||||
QSettings legacy(QCoreApplication::applicationDirPath() + QLatin1String("/zeal.ini"), QSettings::IniFormat);
|
||||
#endif
|
||||
legacy.beginGroup(QStringLiteral("state"));
|
||||
const QByteArray geometry = legacy.value(QStringLiteral("window_geometry")).toByteArray();
|
||||
const QByteArray splitter = legacy.value(QStringLiteral("splitter_geometry")).toByteArray();
|
||||
const QByteArray tocSplitter = legacy.value(QStringLiteral("toc_splitter_state")).toByteArray();
|
||||
legacy.endGroup();
|
||||
|
||||
if (geometry.isEmpty() && splitter.isEmpty() && tocSplitter.isEmpty()) {
|
||||
return; // Nothing to migrate.
|
||||
}
|
||||
|
||||
WindowState ws;
|
||||
ws.geometry = geometry;
|
||||
ws.splitterState = splitter;
|
||||
ws.tocSplitterState = tocSplitter;
|
||||
windows.append(ws);
|
||||
|
||||
if (!saveToFile(path)) {
|
||||
return; // Leave legacy keys intact so migration retries next launch.
|
||||
}
|
||||
|
||||
legacy.beginGroup(QStringLiteral("state"));
|
||||
legacy.remove(QStringLiteral("window_geometry"));
|
||||
legacy.remove(QStringLiteral("splitter_geometry"));
|
||||
legacy.remove(QStringLiteral("toc_splitter_state"));
|
||||
legacy.endGroup();
|
||||
legacy.sync();
|
||||
}
|
||||
|
||||
bool Session::save() const
|
||||
{
|
||||
return saveToFile(defaultFilePath());
|
||||
}
|
||||
|
||||
void Session::loadFromFile(const QString &path)
|
||||
{
|
||||
windows.clear();
|
||||
|
||||
QFile file(path);
|
||||
if (!file.exists()) {
|
||||
return; // Defaults.
|
||||
}
|
||||
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
qCWarning(log, "Failed to open state file '%s': %s", qPrintable(path), qPrintable(file.errorString()));
|
||||
return;
|
||||
}
|
||||
|
||||
const QByteArray contents = file.readAll();
|
||||
file.close();
|
||||
|
||||
toml::table root;
|
||||
try {
|
||||
root = toml::parse(std::string_view(contents.constData(), static_cast<size_t>(contents.size())));
|
||||
} catch (const toml::parse_error &err) {
|
||||
qCWarning(log,
|
||||
"Failed to parse state file '%s': %s",
|
||||
qPrintable(path),
|
||||
qPrintable(QString::fromUtf8(err.description())));
|
||||
return;
|
||||
}
|
||||
|
||||
const auto *windowsNode = root.get(ArrayWindows);
|
||||
if (windowsNode == nullptr || !windowsNode->is_array()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto &node : *windowsNode->as_array()) {
|
||||
const auto *tbl = node.as_table();
|
||||
if (tbl == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
WindowState ws;
|
||||
ws.geometry = readBlob(*tbl, KeyGeometry);
|
||||
ws.splitterState = readBlob(*tbl, KeySplitter);
|
||||
ws.tocSplitterState = readBlob(*tbl, KeyTocSplitter);
|
||||
windows.append(ws);
|
||||
}
|
||||
}
|
||||
|
||||
bool Session::saveToFile(const QString &path) const
|
||||
{
|
||||
QDir().mkpath(QFileInfo(path).absolutePath());
|
||||
|
||||
toml::array windowsArray;
|
||||
for (const WindowState &ws : windows) {
|
||||
toml::table tbl;
|
||||
writeBlob(tbl, KeyGeometry, ws.geometry);
|
||||
writeBlob(tbl, KeySplitter, ws.splitterState);
|
||||
writeBlob(tbl, KeyTocSplitter, ws.tocSplitterState);
|
||||
windowsArray.push_back(std::move(tbl));
|
||||
}
|
||||
|
||||
toml::table root;
|
||||
root.insert_or_assign(ArrayWindows, std::move(windowsArray));
|
||||
|
||||
std::ostringstream oss;
|
||||
oss << "# Managed by Zeal. Do not edit manually.\n\n";
|
||||
oss << root;
|
||||
const std::string text = oss.str();
|
||||
|
||||
QFile file(path);
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
|
||||
qCWarning(log, "Failed to write state file '%s': %s", qPrintable(path), qPrintable(file.errorString()));
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto expected = static_cast<qint64>(text.size());
|
||||
const qint64 written = file.write(text.c_str(), expected);
|
||||
file.close();
|
||||
|
||||
if (written != expected) {
|
||||
qCWarning(log, "Partial write to state file '%s': %s", qPrintable(path), qPrintable(file.errorString()));
|
||||
// Don't leave a truncated/corrupt file behind — next launch should retry from scratch.
|
||||
QFile::remove(path);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Zeal::Core
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright (C) Oleg Shparber, et al. <https://zealdocs.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef ZEAL_CORE_SESSION_H
|
||||
#define ZEAL_CORE_SESSION_H
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
|
||||
namespace Zeal::Core {
|
||||
|
||||
struct WindowState final
|
||||
{
|
||||
QByteArray geometry;
|
||||
QByteArray splitterState;
|
||||
QByteArray tocSplitterState;
|
||||
};
|
||||
|
||||
struct Session final
|
||||
{
|
||||
QList<WindowState> windows;
|
||||
|
||||
// Returns a reference to the first window's state, creating an empty entry
|
||||
// if the list is empty. Until multi-window support lands, all callers go
|
||||
// through this accessor.
|
||||
WindowState &primaryWindow();
|
||||
|
||||
void load();
|
||||
bool save() const;
|
||||
|
||||
void loadFromFile(const QString &path);
|
||||
bool saveToFile(const QString &path) const;
|
||||
|
||||
static QString defaultFilePath();
|
||||
};
|
||||
|
||||
} // namespace Zeal::Core
|
||||
|
||||
#endif // ZEAL_CORE_SESSION_H
|
||||
@@ -38,7 +38,6 @@ constexpr char GroupGlobalShortcuts[] = "global_shortcuts";
|
||||
constexpr char GroupSearch[] = "search";
|
||||
constexpr char GroupTabs[] = "tabs";
|
||||
constexpr char GroupInternal[] = "internal";
|
||||
constexpr char GroupState[] = "state";
|
||||
constexpr char GroupProxy[] = "proxy";
|
||||
} // namespace
|
||||
|
||||
@@ -277,12 +276,6 @@ void Settings::load()
|
||||
}
|
||||
}
|
||||
|
||||
settings->beginGroup(GroupState);
|
||||
windowGeometry = settings->value(QStringLiteral("window_geometry")).toByteArray();
|
||||
verticalSplitterGeometry = settings->value(QStringLiteral("splitter_geometry")).toByteArray();
|
||||
tocSplitterState = settings->value(QStringLiteral("toc_splitter_state")).toByteArray();
|
||||
settings->endGroup();
|
||||
|
||||
settings->beginGroup(GroupInternal);
|
||||
installId = settings
|
||||
->value(QStringLiteral("install_id"),
|
||||
@@ -352,12 +345,6 @@ void Settings::save()
|
||||
settings->setValue(QStringLiteral("path"), docsetPath);
|
||||
settings->endGroup();
|
||||
|
||||
settings->beginGroup(GroupState);
|
||||
settings->setValue(QStringLiteral("window_geometry"), windowGeometry);
|
||||
settings->setValue(QStringLiteral("splitter_geometry"), verticalSplitterGeometry);
|
||||
settings->setValue(QStringLiteral("toc_splitter_state"), tocSplitterState);
|
||||
settings->endGroup();
|
||||
|
||||
settings->beginGroup(GroupInternal);
|
||||
settings->setValue(QStringLiteral("install_id"), installId);
|
||||
// Version of configuration file format, should match Zeal version. Used for migration rules.
|
||||
@@ -416,17 +403,6 @@ void Settings::migrate(QSettings *settings) const
|
||||
settings->endGroup();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Pre 0.3
|
||||
//
|
||||
|
||||
// Unset 'state/splitter_geometry', because custom styles were removed.
|
||||
if (version < QVersionNumber(0, 3, 0)) {
|
||||
settings->beginGroup(GroupState);
|
||||
settings->remove(QStringLiteral("splitter_geometry"));
|
||||
settings->endGroup();
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
|
||||
@@ -100,11 +100,6 @@ public:
|
||||
// Other
|
||||
QString docsetPath;
|
||||
|
||||
// State
|
||||
QByteArray windowGeometry;
|
||||
QByteArray verticalSplitterGeometry;
|
||||
QByteArray tocSplitterState;
|
||||
|
||||
explicit Settings(QObject *parent = nullptr);
|
||||
~Settings() override;
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
find_package(Qt6 REQUIRED COMPONENTS Test)
|
||||
|
||||
add_executable(session_test session_test.cpp)
|
||||
target_link_libraries(session_test PRIVATE Core Qt6::Test)
|
||||
|
||||
zeal_add_test(session_test)
|
||||
@@ -0,0 +1,212 @@
|
||||
// Copyright (C) Oleg Shparber, et al. <https://zealdocs.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "../session.h"
|
||||
|
||||
#include <QSettings>
|
||||
#include <QStandardPaths>
|
||||
#include <QTemporaryDir>
|
||||
#include <QtTest>
|
||||
|
||||
using Zeal::Core::Session;
|
||||
using Zeal::Core::WindowState;
|
||||
|
||||
class SessionTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void loadMissingFile_usesDefaults();
|
||||
void saveThenLoad_roundTrip();
|
||||
void loadCorruptFile_usesDefaults();
|
||||
void loadEmptyFile_usesDefaults();
|
||||
void loadPartialFile_missingKeysUseDefaults();
|
||||
void load_migratesFromLegacyQSettings();
|
||||
void load_skipsMigrationWhenStateFileExists();
|
||||
};
|
||||
|
||||
void SessionTest::initTestCase()
|
||||
{
|
||||
QStandardPaths::setTestModeEnabled(true);
|
||||
QCoreApplication::setOrganizationName(QStringLiteral("ZealTest"));
|
||||
QCoreApplication::setApplicationName(QStringLiteral("ZealTest"));
|
||||
}
|
||||
|
||||
void SessionTest::loadMissingFile_usesDefaults()
|
||||
{
|
||||
QTemporaryDir dir;
|
||||
QVERIFY(dir.isValid());
|
||||
|
||||
Session session;
|
||||
session.loadFromFile(dir.filePath("missing.toml"));
|
||||
|
||||
QVERIFY(session.windows.isEmpty());
|
||||
}
|
||||
|
||||
void SessionTest::saveThenLoad_roundTrip()
|
||||
{
|
||||
QTemporaryDir dir;
|
||||
QVERIFY(dir.isValid());
|
||||
const QString path = dir.filePath("session.toml");
|
||||
|
||||
const QByteArray geom = QByteArray::fromHex("0102030405deadbeef");
|
||||
const QByteArray splitter = QByteArray::fromHex("aabbccdd");
|
||||
const QByteArray tocSplitter = QByteArray::fromHex("cafebabe");
|
||||
|
||||
{
|
||||
Session out;
|
||||
WindowState ws;
|
||||
ws.geometry = geom;
|
||||
ws.splitterState = splitter;
|
||||
ws.tocSplitterState = tocSplitter;
|
||||
out.windows.append(ws);
|
||||
out.saveToFile(path);
|
||||
}
|
||||
|
||||
Session in;
|
||||
in.loadFromFile(path);
|
||||
QCOMPARE(in.windows.size(), 1);
|
||||
QCOMPARE(in.windows.first().geometry, geom);
|
||||
QCOMPARE(in.windows.first().splitterState, splitter);
|
||||
QCOMPARE(in.windows.first().tocSplitterState, tocSplitter);
|
||||
}
|
||||
|
||||
void SessionTest::loadCorruptFile_usesDefaults()
|
||||
{
|
||||
QTemporaryDir dir;
|
||||
QVERIFY(dir.isValid());
|
||||
const QString path = dir.filePath("session.toml");
|
||||
|
||||
QFile file(path);
|
||||
QVERIFY(file.open(QIODevice::WriteOnly));
|
||||
file.write("this is not [valid toml");
|
||||
file.close();
|
||||
|
||||
Session session;
|
||||
session.loadFromFile(path);
|
||||
QVERIFY(session.windows.isEmpty());
|
||||
}
|
||||
|
||||
void SessionTest::loadEmptyFile_usesDefaults()
|
||||
{
|
||||
QTemporaryDir dir;
|
||||
QVERIFY(dir.isValid());
|
||||
const QString path = dir.filePath("session.toml");
|
||||
|
||||
QFile file(path);
|
||||
QVERIFY(file.open(QIODevice::WriteOnly));
|
||||
file.close();
|
||||
|
||||
Session session;
|
||||
session.loadFromFile(path);
|
||||
QVERIFY(session.windows.isEmpty());
|
||||
}
|
||||
|
||||
void SessionTest::loadPartialFile_missingKeysUseDefaults()
|
||||
{
|
||||
QTemporaryDir dir;
|
||||
QVERIFY(dir.isValid());
|
||||
const QString path = dir.filePath("session.toml");
|
||||
|
||||
QFile file(path);
|
||||
QVERIFY(file.open(QIODevice::WriteOnly));
|
||||
file.write("[[windows]]\ngeometry = \"AQIDBA==\"\n");
|
||||
file.close();
|
||||
|
||||
Session session;
|
||||
session.loadFromFile(path);
|
||||
QCOMPARE(session.windows.size(), 1);
|
||||
QCOMPARE(session.windows.first().geometry, QByteArray::fromHex("01020304"));
|
||||
QCOMPARE(session.windows.first().splitterState, QByteArray());
|
||||
QCOMPARE(session.windows.first().tocSplitterState, QByteArray());
|
||||
}
|
||||
|
||||
void SessionTest::load_migratesFromLegacyQSettings()
|
||||
{
|
||||
const QByteArray geom = QByteArray::fromHex("deadbeef0102");
|
||||
const QByteArray splitter = QByteArray::fromHex("aabbccdd");
|
||||
const QByteArray tocSplitter = QByteArray::fromHex("11223344");
|
||||
|
||||
// Populate legacy QSettings and make sure session.toml does not exist yet.
|
||||
{
|
||||
QSettings legacy;
|
||||
legacy.beginGroup(QStringLiteral("state"));
|
||||
legacy.setValue(QStringLiteral("window_geometry"), geom);
|
||||
legacy.setValue(QStringLiteral("splitter_geometry"), splitter);
|
||||
legacy.setValue(QStringLiteral("toc_splitter_state"), tocSplitter);
|
||||
legacy.endGroup();
|
||||
legacy.sync();
|
||||
}
|
||||
QFile::remove(Session::defaultFilePath());
|
||||
|
||||
Session session;
|
||||
session.load();
|
||||
|
||||
QCOMPARE(session.windows.size(), 1);
|
||||
QCOMPARE(session.windows.first().geometry, geom);
|
||||
QCOMPARE(session.windows.first().splitterState, splitter);
|
||||
QCOMPARE(session.windows.first().tocSplitterState, tocSplitter);
|
||||
|
||||
// Legacy keys should be gone after migration.
|
||||
QSettings legacy;
|
||||
legacy.beginGroup(QStringLiteral("state"));
|
||||
QVERIFY(!legacy.contains(QStringLiteral("window_geometry")));
|
||||
QVERIFY(!legacy.contains(QStringLiteral("splitter_geometry")));
|
||||
QVERIFY(!legacy.contains(QStringLiteral("toc_splitter_state")));
|
||||
|
||||
// Migration must persist the state so future runs load from TOML, not QSettings.
|
||||
QVERIFY(QFile::exists(Session::defaultFilePath()));
|
||||
|
||||
// Cleanup so other tests start from a known state.
|
||||
QFile::remove(Session::defaultFilePath());
|
||||
}
|
||||
|
||||
void SessionTest::load_skipsMigrationWhenStateFileExists()
|
||||
{
|
||||
const QByteArray legacyGeom = QByteArray::fromHex("deadbeef");
|
||||
const QByteArray tomlGeom = QByteArray::fromHex("aabbccdd");
|
||||
|
||||
// Populate legacy QSettings with values that should NOT be loaded.
|
||||
{
|
||||
QSettings legacy;
|
||||
legacy.beginGroup(QStringLiteral("state"));
|
||||
legacy.setValue(QStringLiteral("window_geometry"), legacyGeom);
|
||||
legacy.endGroup();
|
||||
legacy.sync();
|
||||
}
|
||||
|
||||
// Create session.toml with different values that SHOULD be loaded.
|
||||
{
|
||||
Session out;
|
||||
WindowState ws;
|
||||
ws.geometry = tomlGeom;
|
||||
out.windows.append(ws);
|
||||
QVERIFY(out.saveToFile(Session::defaultFilePath()));
|
||||
}
|
||||
|
||||
Session session;
|
||||
session.load();
|
||||
|
||||
// Loaded from TOML, not legacy.
|
||||
QCOMPARE(session.windows.size(), 1);
|
||||
QCOMPARE(session.windows.first().geometry, tomlGeom);
|
||||
|
||||
// Legacy keys must be untouched (migration was skipped).
|
||||
QSettings legacy;
|
||||
legacy.beginGroup(QStringLiteral("state"));
|
||||
QCOMPARE(legacy.value(QStringLiteral("window_geometry")).toByteArray(), legacyGeom);
|
||||
legacy.endGroup();
|
||||
|
||||
// Cleanup so other tests start from a known state.
|
||||
{
|
||||
QSettings cleanup;
|
||||
cleanup.beginGroup(QStringLiteral("state"));
|
||||
cleanup.remove(QStringLiteral("window_geometry"));
|
||||
cleanup.endGroup();
|
||||
}
|
||||
QFile::remove(Session::defaultFilePath());
|
||||
}
|
||||
|
||||
QTEST_GUILESS_MAIN(SessionTest)
|
||||
#include "session_test.moc"
|
||||
@@ -6,7 +6,7 @@
|
||||
#include "view.h"
|
||||
|
||||
#include <core/application.h>
|
||||
#include <core/settings.h>
|
||||
#include <core/session.h>
|
||||
#include <ui/widgets/layouthelper.h>
|
||||
|
||||
#include <QSplitter>
|
||||
@@ -23,7 +23,7 @@ Container::Container(QWidget *parent)
|
||||
m_splitter = new QSplitter();
|
||||
m_splitter->setOrientation(Qt::Vertical);
|
||||
connect(m_splitter, &QSplitter::splitterMoved, this, [this]() {
|
||||
Core::Application::instance()->settings()->tocSplitterState = m_splitter->saveState();
|
||||
Core::Application::instance()->session()->primaryWindow().tocSplitterState = m_splitter->saveState();
|
||||
});
|
||||
|
||||
// Setup main layout.
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <browser/webbridge.h>
|
||||
#include <browser/webcontrol.h>
|
||||
#include <core/application.h>
|
||||
#include <core/session.h>
|
||||
#include <core/settings.h>
|
||||
#include <qxtglobalshortcut/qxtglobalshortcut.h>
|
||||
#include <sidebar/container.h>
|
||||
@@ -73,7 +74,8 @@ MainWindow::MainWindow(Core::Application *app, QWidget *parent)
|
||||
|
||||
setCentralWidget(centralWidget);
|
||||
|
||||
restoreGeometry(m_settings->windowGeometry);
|
||||
Core::WindowState &windowState = m_application->session()->primaryWindow();
|
||||
restoreGeometry(windowState.geometry);
|
||||
|
||||
// Setup sidebar.
|
||||
auto *sbViewProvider = new SidebarViewProvider(this);
|
||||
@@ -84,7 +86,7 @@ MainWindow::MainWindow(Core::Application *app, QWidget *parent)
|
||||
|
||||
// Setup splitter.
|
||||
m_splitter->insertWidget(0, sb);
|
||||
m_splitter->restoreState(m_settings->verticalSplitterGeometry);
|
||||
m_splitter->restoreState(windowState.splitterState);
|
||||
|
||||
// Setup web settings.
|
||||
new Browser::Settings(m_settings, this);
|
||||
@@ -108,8 +110,9 @@ MainWindow::MainWindow(Core::Application *app, QWidget *parent)
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
{
|
||||
m_settings->verticalSplitterGeometry = m_splitter->saveState();
|
||||
m_settings->windowGeometry = saveGeometry();
|
||||
Core::WindowState &windowState = m_application->session()->primaryWindow();
|
||||
windowState.splitterState = m_splitter->saveState();
|
||||
windowState.geometry = saveGeometry();
|
||||
}
|
||||
|
||||
void MainWindow::search(const Registry::SearchQuery &query)
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "widgets/toolbarframe.h"
|
||||
|
||||
#include <core/application.h>
|
||||
#include <core/session.h>
|
||||
#include <core/settings.h>
|
||||
#include <registry/docset.h>
|
||||
#include <registry/docsetregistry.h>
|
||||
@@ -104,7 +105,7 @@ SearchSidebar::SearchSidebar(const SearchSidebar *other, QWidget *parent)
|
||||
m_pageTocView->hide();
|
||||
} else {
|
||||
m_pageTocView->show();
|
||||
m_splitter->restoreState(Core::Application::instance()->settings()->tocSplitterState);
|
||||
m_splitter->restoreState(Core::Application::instance()->session()->primaryWindow().tocSplitterState);
|
||||
}
|
||||
});
|
||||
m_pageTocView->setModel(m_pageTocModel);
|
||||
@@ -192,7 +193,7 @@ SearchSidebar::SearchSidebar(const SearchSidebar *other, QWidget *parent)
|
||||
m_splitter->addWidget(m_treeView);
|
||||
m_splitter->addWidget(m_pageTocView);
|
||||
connect(m_splitter, &QSplitter::splitterMoved, this, [this]() {
|
||||
Core::Application::instance()->settings()->tocSplitterState = m_splitter->saveState();
|
||||
Core::Application::instance()->session()->primaryWindow().tocSplitterState = m_splitter->saveState();
|
||||
});
|
||||
|
||||
// Setup main layout.
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"default-features": false
|
||||
},
|
||||
"sqlite3",
|
||||
"tomlplusplus",
|
||||
"vulkan-headers"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user