Skip to content

Commit

Permalink
App: save/restore application UI state
Browse files Browse the repository at this point in the history
Relates to GitHub #277
  • Loading branch information
HuguesDelorme committed Aug 9, 2024
1 parent 3b4d7e6 commit 0ce65c3
Show file tree
Hide file tree
Showing 18 changed files with 228 additions and 16 deletions.
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,7 @@ list(
APPEND MayoConv_HeaderFiles
${PROJECT_SOURCE_DIR}/src/app/app_module_properties.h
${PROJECT_SOURCE_DIR}/src/app/app_module.h
${PROJECT_SOURCE_DIR}/src/app/app_ui_state.h
${PROJECT_SOURCE_DIR}/src/app/library_info.h
${PROJECT_SOURCE_DIR}/src/app/recent_files.h
)
Expand All @@ -646,6 +647,7 @@ list(
APPEND MayoConv_SourceFiles
${PROJECT_SOURCE_DIR}/src/app/app_module_properties.cpp
${PROJECT_SOURCE_DIR}/src/app/app_module.cpp
${PROJECT_SOURCE_DIR}/src/app/app_ui_state.cpp
${PROJECT_SOURCE_DIR}/src/app/library_info.cpp
${PROJECT_SOURCE_DIR}/src/app/recent_files.cpp
)
Expand Down
6 changes: 3 additions & 3 deletions src/app/app_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ AppContext::AppContext(MainWindow* wnd)
assert(m_wnd->widgetPageDocuments() != nullptr);

QObject::connect(
m_wnd->widgetPageDocuments(), &WidgetMainControl::currentDocumentIndexChanged,
this, &AppContext::onCurrentDocumentIndexChanged
);
m_wnd->widgetPageDocuments(), &WidgetMainControl::currentDocumentIndexChanged,
this, &AppContext::onCurrentDocumentIndexChanged
);
}

GuiApplication* AppContext::guiApp() const
Expand Down
13 changes: 13 additions & 0 deletions src/app/app_module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,12 @@ Settings::Variant AppModule::toVariant(const Property& prop) const
varBlob.setByteArray(true);
return varBlob;
}
else if (isType<PropertyAppUiState>(prop)) {
const QByteArray blob = AppUiState::toBlob(constRef<PropertyAppUiState>(prop));
Variant varBlob(blob.toStdString());
varBlob.setByteArray(true);
return varBlob;
}
else {
return PropertyValueConversion::toVariant(prop);
}
Expand All @@ -184,6 +190,13 @@ bool AppModule::fromVariant(Property* prop, const Settings::Variant& variant) co
ptr<PropertyRecentFiles>(prop)->setValue(recentFiles);
return stream.status() == QDataStream::Ok;
}
else if (isType<PropertyAppUiState>(prop)) {
bool ok = false;
auto blob = QtCoreUtils::QByteArray_frowRawData(variant.toConstRefString());
auto uiState = AppUiState::fromBlob(blob, &ok);
ptr<PropertyAppUiState>(prop)->setValue(uiState);
return ok;
}
else {
return PropertyValueConversion::fromVariant(prop, variant);
}
Expand Down
3 changes: 3 additions & 0 deletions src/app/app_module_properties.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,11 @@ AppModuleProperties::AppModuleProperties(Settings* settings)
settings->addSetting(&this->actionOnDocumentFileChange, groupId_application);
settings->addSetting(&this->linkWithDocumentSelector, groupId_application);
settings->addSetting(&this->forceOpenGlFallbackWidget, groupId_application);
settings->addSetting(&this->appUiState, groupId_application);
this->recentFiles.setUserVisible(false);
this->lastOpenDir.setUserVisible(false);
this->lastSelectedFormatFilter.setUserVisible(false);
this->appUiState.setUserVisible(false);

// Meshing
this->meshingQuality.mutableEnumeration().changeTrContext(AppModuleProperties::textIdContext());
Expand Down Expand Up @@ -88,6 +90,7 @@ AppModuleProperties::AppModuleProperties(Settings* settings)
this->lastSelectedFormatFilter.setValue({});
this->actionOnDocumentFileChange.setValue(ActionOnDocumentFileChange::None);
this->linkWithDocumentSelector.setValue(true);
this->appUiState.setValue({});
#ifndef MAYO_OS_MAC
this->forceOpenGlFallbackWidget.setValue(false);
#else
Expand Down
2 changes: 2 additions & 0 deletions src/app/app_module_properties.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#pragma once

#include "app_ui_state.h"
#include "recent_files.h"

#include "../base/io_format.h"
Expand Down Expand Up @@ -60,6 +61,7 @@ class AppModuleProperties : public PropertyGroup {
PropertyEnum<ActionOnDocumentFileChange> actionOnDocumentFileChange{ this, textId("actionOnDocumentFileChange") };
PropertyBool linkWithDocumentSelector{ this, textId("linkWithDocumentSelector") };
PropertyBool forceOpenGlFallbackWidget{ this, textId("forceOpenGlFallbackWidget") };
PropertyAppUiState appUiState{ this, textId("appUiState") };
// Meshing
const Settings::GroupIndex groupId_meshing;
enum class BRepMeshQuality { VeryCoarse, Coarse, Normal, Precise, VeryPrecise, UserDefined };
Expand Down
49 changes: 49 additions & 0 deletions src/app/app_ui_state.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#include "app_ui_state.h"

#include "../qtcommon/qtcore_utils.h"
#include <QtCore/QDataStream>
#include <QtCore/QIODevice>

namespace Mayo {

template<> const char PropertyAppUiState::TypeName[] = "Mayo::PropertyAppUiState";

QByteArray AppUiState::toBlob(const AppUiState& state)
{
QByteArray blob;
QDataStream stream(&blob, QIODevice::WriteOnly);
stream << QtCoreUtils::QByteArray_frowRawData(std::string_view{PropertyAppUiState::TypeName});
constexpr uint32_t version = 1;
stream << version;
stream << state.mainWindowGeometry;
stream << state.pageDocuments_isLeftSideBarVisible;
return blob;

}

AppUiState AppUiState::fromBlob(const QByteArray& blob, bool* ok)
{
auto fnSetOk = [=](bool v) {
if (ok)
*ok = v;
};

fnSetOk(false);
AppUiState state;
QDataStream stream(blob);
QByteArray identifier;
stream >> identifier;
if (identifier == PropertyAppUiState::TypeName) {
uint32_t version = 0;
stream >> version;
if (version == 1) {
stream >> state.mainWindowGeometry;
stream >> state.pageDocuments_isLeftSideBarVisible;
fnSetOk(true);
}
}

return state;
}

} // namespace Mayo
25 changes: 25 additions & 0 deletions src/app/app_ui_state.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/****************************************************************************
** Copyright (c) 2024, Fougue Ltd. <https://www.fougue.pro>
** All rights reserved.
** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt
****************************************************************************/

#pragma once

#include "../base/property_builtins.h"
#include <QtCore/QByteArray>

namespace Mayo {

// Stores the UI state of the main application widgets
struct AppUiState {
QByteArray mainWindowGeometry; // Provided by QWidget::saveGeometry()
bool pageDocuments_isLeftSideBarVisible = true;

// Serialization functions
static QByteArray toBlob(const AppUiState& state);
static AppUiState fromBlob(const QByteArray& blob, bool* ok = nullptr);
};
using PropertyAppUiState = GenericProperty<AppUiState>;

} // namespace Mayo
1 change: 1 addition & 0 deletions src/app/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,7 @@ static int runApp(QCoreApplication* qtApp)
// Create MainWindow
MainWindow mainWindow(guiApp);
mainWindow.setWindowTitle(QCoreApplication::applicationName());
appModule->settings()->loadProperty(&appModule->properties()->appUiState);
mainWindow.show();
if (!args.listFilepathToOpen.empty()) {
QTimer::singleShot(0, qtApp, [&]{ mainWindow.openDocumentsFromList(args.listFilepathToOpen); });
Expand Down
22 changes: 20 additions & 2 deletions src/app/mainwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ MainWindow::~MainWindow()

void MainWindow::showEvent(QShowEvent* event)
{
const auto& uiState = AppModule::get()->properties()->appUiState.value();
if (!uiState.mainWindowGeometry.isEmpty())
this->restoreGeometry(uiState.mainWindowGeometry);

if (this->widgetPageDocuments())
this->widgetPageDocuments()->widgetLeftSideBar()->setVisible(uiState.pageDocuments_isLeftSideBarVisible);

QMainWindow::showEvent(event);
#if defined(Q_OS_WIN) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
constexpr Qt::FindChildOption findMode = Qt::FindDirectChildrenOnly;
Expand All @@ -84,15 +91,26 @@ void MainWindow::showEvent(QShowEvent* event)
#endif
}

void MainWindow::closeEvent(QCloseEvent* event)
{
AppUiState uiState = AppModule::get()->properties()->appUiState;
uiState.mainWindowGeometry = this->saveGeometry();
if (this->widgetPageDocuments())
uiState.pageDocuments_isLeftSideBarVisible = this->widgetPageDocuments()->widgetLeftSideBar()->isVisible();

AppModule::get()->properties()->appUiState.setValue(uiState);
QMainWindow::closeEvent(event);
}

void MainWindow::addPage(IAppContext::Page page, IWidgetMainPage* pageWidget)
{
assert(m_mapWidgetPage.find(page) == m_mapWidgetPage.cend());
assert(m_ui->stack_Main->indexOf(pageWidget) == -1);
m_mapWidgetPage.insert({ page, pageWidget });
m_ui->stack_Main->addWidget(pageWidget);
QObject::connect(
pageWidget, &IWidgetMainPage::updateGlobalControlsActivationRequired,
this, &MainWindow::updateControlsActivation
pageWidget, &IWidgetMainPage::updateGlobalControlsActivationRequired,
this, &MainWindow::updateControlsActivation
);
}

Expand Down
1 change: 1 addition & 0 deletions src/app/mainwindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class MainWindow : public QMainWindow {

protected:
void showEvent(QShowEvent* event) override;
void closeEvent(QCloseEvent* event) override;

private:
void addPage(IAppContext::Page page, IWidgetMainPage* pageWidget);
Expand Down
16 changes: 8 additions & 8 deletions src/app/widget_main_control.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,21 +66,21 @@ WidgetMainControl::WidgetMainControl(GuiApplication* guiApp, QWidget* parent)

// "Window" actions and navigation in documents
QObject::connect(
m_ui->combo_GuiDocuments, qOverload<int>(&QComboBox::currentIndexChanged),
this, &WidgetMainControl::onCurrentDocumentIndexChanged
m_ui->combo_GuiDocuments, qOverload<int>(&QComboBox::currentIndexChanged),
this, &WidgetMainControl::onCurrentDocumentIndexChanged
);
QObject::connect(
m_ui->widget_FileSystem, &WidgetFileSystem::locationActivated,
this, &WidgetMainControl::onWidgetFileSystemLocationActivated
m_ui->widget_FileSystem, &WidgetFileSystem::locationActivated,
this, &WidgetMainControl::onWidgetFileSystemLocationActivated
);
// ...
QObject::connect(
m_ui->combo_LeftContents, qOverload<int>(&QComboBox::currentIndexChanged),
this, &WidgetMainControl::onLeftContentsPageChanged
m_ui->combo_LeftContents, qOverload<int>(&QComboBox::currentIndexChanged),
this, &WidgetMainControl::onLeftContentsPageChanged
);
QObject::connect(
m_ui->listView_OpenedDocuments, &QListView::clicked,
this, [=](const QModelIndex& index) { this->setCurrentDocumentIndex(index.row()); }
m_ui->listView_OpenedDocuments, &QListView::clicked,
this, [=](const QModelIndex& index) { this->setCurrentDocumentIndex(index.row()); }
);

guiApp->application()->signalDocumentFilePathChanged.connectSlot([=](const DocumentPtr& doc, const FilePath& fp) {
Expand Down
5 changes: 5 additions & 0 deletions src/base/property_value_conversion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,11 @@ static void assignBoolPtr(bool* value, bool on)
*value = on;
}

bool PropertyValueConversion::Variant::isValid() const
{
return this->index() != 0; // not std::monostate
}

bool PropertyValueConversion::Variant::toBool(bool* ok) const
{
assignBoolPtr(ok, true);
Expand Down
5 changes: 3 additions & 2 deletions src/base/property_value_conversion.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ namespace Mayo {
class PropertyValueConversion {
public:
// Variant type to be used when (de)serializing values
class Variant : public std::variant<bool, int, double, std::string> {
class Variant : public std::variant<std::monostate, bool, int, double, std::string> {
public:
using BaseType = std::variant<bool, int, double, std::string>;
using BaseType = std::variant<std::monostate, bool, int, double, std::string>;
Variant() = default;
Variant(bool v) : BaseType(v) {}
Variant(int v) : BaseType(v) {}
Expand All @@ -28,6 +28,7 @@ class PropertyValueConversion {
Variant(const char* str) : BaseType(std::string(str)) {}
Variant(const std::string& str) : BaseType(str) {}

bool isValid() const;
bool toBool(bool* ok = nullptr) const;
int toInt(bool* ok = nullptr) const;
double toDouble(bool* ok = nullptr) const;
Expand Down
2 changes: 1 addition & 1 deletion src/base/settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class Settings::Private {
const std::string settingPath = std::string(sectionPath).append("/").append(propertyKey);
if (source.contains(settingPath)) {
const Settings::Variant value = source.value(settingPath);
const bool ok = m_propValueConverter->fromVariant(property, value);
const bool ok = m_propValueConverter->fromVariant(property, value);
if (!ok) {
// TODO Use other output stream(dedicated Messenger object?)
std::cerr << fmt::format("Failed to load setting [path={}]", settingPath) << std::endl;
Expand Down
16 changes: 16 additions & 0 deletions tests/test_app.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include <QtCore/QVariant>
#include <QtGui/QPainter>
#include <QtGui/QPixmap>
#include <QtWidgets/QWidget>
#include <QtTest/QSignalSpy>

namespace Mayo {
Expand Down Expand Up @@ -262,6 +263,21 @@ void TestApp::RecentFiles_QPixmap_test()
}
}

void TestApp::AppUiState_test()
{
QWidget widget;
AppUiState uiState;
uiState.mainWindowGeometry = widget.saveGeometry();
uiState.pageDocuments_isLeftSideBarVisible = true;
QByteArray blobSave = AppUiState::toBlob(uiState);

bool ok = false;
const AppUiState uiState_read = AppUiState::fromBlob(blobSave, &ok);
QVERIFY(ok);
QCOMPARE(uiState.mainWindowGeometry, uiState_read.mainWindowGeometry);
QCOMPARE(uiState.pageDocuments_isLeftSideBarVisible, uiState_read.pageDocuments_isLeftSideBarVisible);
}

void TestApp::StringConv_test()
{
const QString text = "test_éç²µ§_测试_Тест";
Expand Down
2 changes: 2 additions & 0 deletions tests/test_app.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ private slots:
void RecentFiles_test();
void RecentFiles_QPixmap_test();

void AppUiState_test();

void StringConv_test();

void QtGuiUtils_test();
Expand Down
Loading

0 comments on commit 0ce65c3

Please sign in to comment.