diff --git a/Dockerfile b/Dockerfile index dca5f0650..ddb859321 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,6 +17,7 @@ RUN apt-get update && \ libglib2.0-0 \ libxkbcommon-dev \ ninja-build \ + pkg-config \ python3 \ python3-pip \ unzip \ @@ -42,7 +43,7 @@ RUN ./emsdk activate 4.0.7 && \ qt-cmake -S /app -B /build \ -DQT_HOST_PATH=$QTSDK_DIR/6.10.3/gcc_64 \ -DCMAKE_BUILD_TYPE=Release \ - -DWITH_OPENSSL=OFF -DWITH_TESTS=OFF -DWITH_WEBSOCKET=ON -DWITH_UPDATER=OFF -DPACKAGE_TYPE=Wasm && \ + -DWITH_OPENSSL=OFF -DWITH_TESTS=OFF -DWITH_WEBSOCKET=ON -DWITH_UPDATER=OFF -DWITH_QTKEYCHAIN=ON -DPACKAGE_TYPE=Wasm && \ ACTUAL_JOBS=${JOBS:-$(nproc)} && \ cmake --build /build --parallel ${ACTUAL_JOBS} && \ cmake --install /build --prefix /dist && \ diff --git a/external/qtkeychain/CMakeLists.txt b/external/qtkeychain/CMakeLists.txt index 07f2e088e..5b0430034 100644 --- a/external/qtkeychain/CMakeLists.txt +++ b/external/qtkeychain/CMakeLists.txt @@ -1,11 +1,12 @@ if(QTKEYCHAIN_FOUND) message(STATUS "QtKeychain found: ${QTKEYCHAIN_LIBRARIES} ${QTKEYCHAIN_INCLUDE_DIRS}") else() - if(WIN32 OR APPLE) + if(WIN32 OR APPLE OR EMSCRIPTEN) message(STATUS "Could not find system QtKeychain; building from local source tree") else() message(FATAL_ERROR "QtKeychain NOT found: use `-DWITH_QTKEYCHAIN=OFF` to build without account autologin") endif() + set(QTKEYCHAIN_LIBRARY_NAME qt6keychain) if(WIN32 AND CMAKE_BUILD_TYPE STREQUAL "Debug") set(QTKEYCHAIN_LIBRARY_NAME "${QTKEYCHAIN_LIBRARY_NAME}d") @@ -13,12 +14,14 @@ else() set(QTKEYCHAIN_SHARED_LIBRARY "${CMAKE_CURRENT_BINARY_DIR}/qtkeychain-build/bin/${CMAKE_SHARED_LIBRARY_PREFIX}${QTKEYCHAIN_LIBRARY_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}") - if (MSVC) + + if (MSVC OR EMSCRIPTEN) set(QTKEYCHAIN_LIBRARY - "${CMAKE_CURRENT_BINARY_DIR}/qtkeychain-build/lib/${QTKEYCHAIN_LIBRARY_NAME}.lib") + "${CMAKE_CURRENT_BINARY_DIR}/qtkeychain-build/lib/${CMAKE_STATIC_LIBRARY_PREFIX}${QTKEYCHAIN_LIBRARY_NAME}${CMAKE_STATIC_LIBRARY_SUFFIX}") else() set(QTKEYCHAIN_LIBRARY "${QTKEYCHAIN_SHARED_LIBRARY}") endif() + set(QTKEYCHAIN_LIBRARIES ${QTKEYCHAIN_LIBRARY}) set(QTKEYCHAIN_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/qtkeychain-build/qtkeychain/" PARENT_SCOPE) @@ -32,9 +35,19 @@ else() add_dependencies(qtkeychain qtkeychain_repo) else() include(ExternalProject) + + set(QTKEYCHAIN_EXTRA_ARGS -DBUILD_SHARED_LIBS:BOOL=ON) + if(EMSCRIPTEN) + set(QTKEYCHAIN_EXTRA_ARGS + -DBUILD_SHARED_LIBS:BOOL=OFF + -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} + -DQT_HOST_PATH=${QT_HOST_PATH} + ) + endif() + ExternalProject_Add(qtkeychain_repo - GIT_REPOSITORY "https://github.com/frankosterfeld/qtkeychain.git" - GIT_TAG "7668a63a3669400223c5a563928ae042f499dbf4" + GIT_REPOSITORY "https://github.com/nschimme/qtkeychain.git" + GIT_TAG "wasm" SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/qtkeychain-src" BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/qtkeychain-build" @@ -43,17 +56,18 @@ else() CMAKE_ARGS -DBUILD_WITH_QT6:BOOL=ON -DBUILD_TRANSLATIONS:BOOL=OFF -DBUILD_TEST_APPLICATION:BOOL=OFF - -DBUILD_SHARED_LIBS:BOOL=ON -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} -DCMAKE_OSX_DEPLOYMENT_TARGET:STRING=${CMAKE_OSX_DEPLOYMENT_TARGET} + ${QTKEYCHAIN_EXTRA_ARGS} CMAKE_CACHE_ARGS -DCMAKE_PREFIX_PATH:PATH=${Qt6_DIR} UPDATE_COMMAND "" BUILD_ALWAYS 0 BUILD_BYPRODUCTS "${QTKEYCHAIN_SHARED_LIBRARY}" - "${CMAKE_CURRENT_BINARY_DIR}/qtkeychain-build/lib/${QTKEYCHAIN_LIBRARY_NAME}.lib" + "${CMAKE_CURRENT_BINARY_DIR}/qtkeychain-build/lib/${CMAKE_STATIC_LIBRARY_PREFIX}${QTKEYCHAIN_LIBRARY_NAME}${CMAKE_STATIC_LIBRARY_SUFFIX}" ) + add_custom_target(qtkeychain) if(WIN32) if(MSVC) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 91168fc0c..6226c6907 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -525,6 +525,8 @@ set(mmapper_SRCS preferences/grouppage.h preferences/mumeprotocolpage.cpp preferences/mumeprotocolpage.h + preferences/ManagePasswordDialog.cpp + preferences/ManagePasswordDialog.h preferences/parserpage.cpp preferences/parserpage.h preferences/pathmachinepage.cpp @@ -665,6 +667,7 @@ set(mmapper_UIS preferences/graphicspage.ui preferences/grouppage.ui preferences/mumeprotocolpage.ui + preferences/ManagePasswordDialog.ui preferences/parserpage.ui preferences/pathmachinepage.ui ) diff --git a/src/configuration/PasswordConfig.cpp b/src/configuration/PasswordConfig.cpp index 383eaf8e4..ae2ae093a 100644 --- a/src/configuration/PasswordConfig.cpp +++ b/src/configuration/PasswordConfig.cpp @@ -3,7 +3,9 @@ #include "PasswordConfig.h" -#include "../global/macros.h" +#include "../global/ConfigConsts-Computed.h" +#include "../global/ConfigEnums.h" +#include "configuration.h" #ifndef MMAPPER_NO_QTKEYCHAIN static const QLatin1String PASSWORD_KEY("password"); @@ -15,21 +17,43 @@ PasswordConfig::PasswordConfig(QObject *const parent) #ifndef MMAPPER_NO_QTKEYCHAIN , m_readJob(APP_NAME) , m_writeJob(APP_NAME) + , m_deleteJob(APP_NAME) { m_readJob.setAutoDelete(false); m_writeJob.setAutoDelete(false); + m_deleteJob.setAutoDelete(false); - connect(&m_readJob, &QKeychain::ReadPasswordJob::finished, [this]() { + auto handleError = [this](const QKeychain::Job &job) { + if constexpr (CURRENT_PLATFORM == PlatformEnum::Wasm) { + if (job.error() == QKeychain::AccessDeniedByUser) { + return true; + } + } + emit sig_error(job.errorString()); + return false; + }; + + connect(&m_readJob, &QKeychain::ReadPasswordJob::finished, [this, handleError]() { if (m_readJob.error()) { - emit sig_error(m_readJob.errorString()); + handleError(m_readJob); } else { emit sig_incomingPassword(m_readJob.textData()); } }); - connect(&m_writeJob, &QKeychain::WritePasswordJob::finished, [this]() { + connect(&m_writeJob, &QKeychain::WritePasswordJob::finished, [this, handleError]() { if (m_writeJob.error()) { - emit sig_error(m_writeJob.errorString()); + handleError(m_writeJob); + } else { + emit sig_passwordSaved(); + } + }); + + connect(&m_deleteJob, &QKeychain::DeletePasswordJob::finished, [this, handleError]() { + if (m_deleteJob.error()) { + handleError(m_deleteJob); + } else { + emit sig_passwordDeleted(); } }); } @@ -41,7 +65,11 @@ PasswordConfig::PasswordConfig(QObject *const parent) void PasswordConfig::setPassword(const QString &password) { #ifndef MMAPPER_NO_QTKEYCHAIN - m_writeJob.setKey(PASSWORD_KEY); + if constexpr (CURRENT_PLATFORM == PlatformEnum::Wasm) { + m_writeJob.setKey(getConfig().account.accountName); + } else { + m_writeJob.setKey(PASSWORD_KEY); + } m_writeJob.setTextData(password); m_writeJob.start(); #else @@ -53,9 +81,27 @@ void PasswordConfig::setPassword(const QString &password) void PasswordConfig::getPassword() { #ifndef MMAPPER_NO_QTKEYCHAIN - m_readJob.setKey(PASSWORD_KEY); + if constexpr (CURRENT_PLATFORM == PlatformEnum::Wasm) { + m_readJob.setKey(getConfig().account.accountName); + } else { + m_readJob.setKey(PASSWORD_KEY); + } m_readJob.start(); #else emit sig_error("Password retrieval is not available."); #endif } + +void PasswordConfig::deletePassword() +{ +#ifndef MMAPPER_NO_QTKEYCHAIN + if constexpr (CURRENT_PLATFORM == PlatformEnum::Wasm) { + m_deleteJob.setKey(getConfig().account.accountName); + } else { + m_deleteJob.setKey(PASSWORD_KEY); + } + m_deleteJob.start(); +#else + emit sig_error("Password deletion is not available."); +#endif +} diff --git a/src/configuration/PasswordConfig.h b/src/configuration/PasswordConfig.h index fc8186a5a..495d73c37 100644 --- a/src/configuration/PasswordConfig.h +++ b/src/configuration/PasswordConfig.h @@ -3,6 +3,7 @@ // Copyright (C) 2019 The MMapper Authors #include "../global/RuleOf5.h" +#include "../global/macros.h" #include #include @@ -11,7 +12,7 @@ #include #endif -class PasswordConfig final : public QObject +class NODISCARD_QOBJECT PasswordConfig final : public QObject { Q_OBJECT @@ -19,6 +20,7 @@ class PasswordConfig final : public QObject #ifndef MMAPPER_NO_QTKEYCHAIN QKeychain::ReadPasswordJob m_readJob; QKeychain::WritePasswordJob m_writeJob; + QKeychain::DeletePasswordJob m_deleteJob; #endif public: @@ -28,8 +30,11 @@ class PasswordConfig final : public QObject void setPassword(const QString &password); void getPassword(); + void deletePassword(); signals: void sig_error(const QString &msg); void sig_incomingPassword(const QString &password); + void sig_passwordSaved(); + void sig_passwordDeleted(); }; diff --git a/src/preferences/ManagePasswordDialog.cpp b/src/preferences/ManagePasswordDialog.cpp new file mode 100644 index 000000000..c0b90515e --- /dev/null +++ b/src/preferences/ManagePasswordDialog.cpp @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright (C) 2026 The MMapper Authors + +#include "ManagePasswordDialog.h" + +#include "../global/ConfigConsts-Computed.h" +#include "../global/ConfigEnums.h" +#include "ui_ManagePasswordDialog.h" + +#include +#include + +ManagePasswordDialog::ManagePasswordDialog(QWidget *parent) + : QDialog(parent) + , ui(new Ui::ManagePasswordDialog) +{ + ui->setupUi(this); + + if constexpr (CURRENT_PLATFORM == PlatformEnum::Wasm) { + setWindowTitle("Manage Account"); + ui->label_2->hide(); + ui->accountPassword->hide(); + ui->showPassword->hide(); + ui->deleteButton->hide(); + } else { + connect(ui->showPassword, &QToolButton::toggled, this, [this](bool checked) { + ui->accountPassword->setEchoMode(checked ? QLineEdit::Normal : QLineEdit::Password); + }); + + connect(ui->deleteButton, &QPushButton::clicked, this, [this]() { + emit sig_deleteRequested(); + ui->accountPassword->clear(); + ui->deleteButton->setEnabled(false); + }); + } +} + +ManagePasswordDialog::~ManagePasswordDialog() +{ + delete ui; +} + +void ManagePasswordDialog::setAccountName(const QString &name) +{ + ui->accountName->setText(name); +} + +QString ManagePasswordDialog::accountName() const +{ + return ui->accountName->text(); +} + +void ManagePasswordDialog::setPassword(const QString &password) +{ + ui->accountPassword->setText(password); + if constexpr (CURRENT_PLATFORM != PlatformEnum::Wasm) { + ui->deleteButton->setEnabled(!password.isEmpty()); + } +} + +QString ManagePasswordDialog::password() const +{ + return ui->accountPassword->text(); +} diff --git a/src/preferences/ManagePasswordDialog.h b/src/preferences/ManagePasswordDialog.h new file mode 100644 index 000000000..711a9a3cb --- /dev/null +++ b/src/preferences/ManagePasswordDialog.h @@ -0,0 +1,32 @@ +#pragma once +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright (C) 2026 The MMapper Authors + +#include "../global/macros.h" + +#include + +namespace Ui { +class ManagePasswordDialog; +} + +class NODISCARD_QOBJECT ManagePasswordDialog final : public QDialog +{ + Q_OBJECT + +private: + Ui::ManagePasswordDialog *const ui; + +public: + explicit ManagePasswordDialog(QWidget *parent = nullptr); + ~ManagePasswordDialog() final; + + void setAccountName(const QString &name); + NODISCARD QString accountName() const; + + void setPassword(const QString &password); + NODISCARD QString password() const; + +signals: + void sig_deleteRequested(); +}; diff --git a/src/preferences/ManagePasswordDialog.ui b/src/preferences/ManagePasswordDialog.ui new file mode 100644 index 000000000..438399eab --- /dev/null +++ b/src/preferences/ManagePasswordDialog.ui @@ -0,0 +1,93 @@ + + + ManagePasswordDialog + + + + 0 + 0 + 350 + 180 + + + + Manage Password + + + + + + + + Account: + + + accountName + + + + + + + Password: + + + accountPassword + + + + + + + + + + QLineEdit::Password + + + + + + + View + + + true + + + + + + + + + + + Delete + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Ok + + + + + + + + + + + buttonBox + accepted() + ManagePasswordDialog + accept() + + + diff --git a/src/preferences/generalpage.cpp b/src/preferences/generalpage.cpp index ed75b49f3..4db2c9a23 100644 --- a/src/preferences/generalpage.cpp +++ b/src/preferences/generalpage.cpp @@ -101,15 +101,19 @@ GeneralPage::GeneralPage(QWidget *parent) }); connect(ui->configurationResetButton, &QAbstractButton::clicked, this, [this]() { - QMessageBox::StandardButton reply - = QMessageBox::question(this, + auto *box = new QMessageBox(QMessageBox::Question, "MMapper Factory Reset", "Are you sure you want to perform a factory reset?", - QMessageBox::Yes | QMessageBox::No); - if (reply == QMessageBox::Yes) { - setConfig().reset(); - emit sig_reloadConfig(); - } + QMessageBox::Yes | QMessageBox::No, + this); + box->setAttribute(Qt::WA_DeleteOnClose); + connect(box, &QMessageBox::finished, this, [this](int result) { + if (result == QMessageBox::Yes) { + setConfig().reset(); + emit sig_reloadConfig(); + } + }); + box->open(); }); connect(ui->configurationExportButton, &QAbstractButton::clicked, this, []() { @@ -170,41 +174,79 @@ GeneralPage::GeneralPage(QWidget *parent) QFileDialog::getOpenFileContent(nameFilter, importFile); }); - connect(ui->autoLogin, &QCheckBox::stateChanged, this, [this]() { + connect(ui->autoLogin, &QCheckBox::clicked, this, [this](bool /*checked*/) { setConfig().account.rememberLogin = ui->autoLogin->isChecked(); }); - connect(ui->accountName, &QLineEdit::textChanged, this, [](const QString &account) { - setConfig().account.accountName = account; - }); - connect(&passCfg, &PasswordConfig::sig_error, this, [this](const QString &msg) { qWarning() << msg; - QMessageBox::warning(this, "Password Error", msg); + auto *box = new QMessageBox(QMessageBox::Warning, + "Password Error", + msg, + QMessageBox::Ok, + this); + box->setAttribute(Qt::WA_DeleteOnClose); + box->open(); }); connect(&passCfg, &PasswordConfig::sig_incomingPassword, this, [this](const QString &password) { - ui->showPassword->setText("Hide Password"); - ui->accountPassword->setText(password); - ui->accountPassword->setEchoMode(QLineEdit::Normal); - }); + if (ui->setPassword->property("requesting").toBool()) { + ui->setPassword->setProperty("requesting", false); + const QString accountName = getConfig().account.accountName; + auto *dlg = new ManagePasswordDialog(this); + dlg->setAttribute(Qt::WA_DeleteOnClose); + dlg->setAccountName(accountName); + dlg->setPassword(password); + connect(dlg, &ManagePasswordDialog::sig_deleteRequested, this, [this]() { + passCfg.deletePassword(); + }); - connect(ui->accountPassword, &QLineEdit::textEdited, this, [this](const QString &password) { - setConfig().account.accountPassword = !password.isEmpty(); - passCfg.setPassword(password); + connect(dlg, &QDialog::accepted, this, [this, dlg]() { + const QString newAccountName = dlg->accountName(); + const QString newPassword = dlg->password(); + if (!newPassword.isEmpty()) { + setConfig().account.accountName = newAccountName; + passCfg.setPassword(newPassword); + } + }); + dlg->open(); + } }); - connect(ui->showPassword, &QAbstractButton::clicked, this, [this]() { - if (ui->showPassword->text() == "Hide Password") { - ui->showPassword->setText("Show Password"); - ui->accountPassword->clear(); - ui->accountPassword->setEchoMode(QLineEdit::Password); - } else if (getConfig().account.accountPassword && ui->accountPassword->text().isEmpty()) { - ui->showPassword->setText("Request Password"); - passCfg.getPassword(); + connect(&passCfg, &PasswordConfig::sig_passwordSaved, this, [this]() { + setConfig().account.accountPassword = true; + updateAutoLoginEnabled(); + + if (!ui->autoLogin->isChecked()) { + auto *box = new QMessageBox(QMessageBox::Question, + "Login", + "A password was saved. Would you like to also enable " + "automatic login for this account?", + QMessageBox::Yes | QMessageBox::No, + this); + box->setAttribute(Qt::WA_DeleteOnClose); + connect(box, &QMessageBox::finished, this, [this](int result) { + if (result == QMessageBox::Yes) { + ui->autoLogin->setChecked(true); + setConfig().account.rememberLogin = true; + } + }); + box->open(); } }); + connect(&passCfg, &PasswordConfig::sig_passwordDeleted, this, [this]() { + auto &account = setConfig().account; + account.accountPassword = false; + account.rememberLogin = false; + ui->autoLogin->setChecked(false); + updateAutoLoginEnabled(); + }); + + connect(ui->setPassword, &QPushButton::clicked, this, &GeneralPage::slot_setPasswordClicked); + + connect(ui->remoteName, &QLineEdit::textChanged, this, [this]() { updateAutoLoginEnabled(); }); + connect(ui->resourceLineEdit, &QLineEdit::textChanged, this, [](const QString &text) { setConfig().canvas.resourcesDirectory = text; }); @@ -288,15 +330,10 @@ void GeneralPage::slot_loadConfig() if constexpr (NO_QTKEYCHAIN) { ui->autoLogin->setEnabled(false); - ui->accountName->setEnabled(false); - ui->accountPassword->setEnabled(false); - ui->showPassword->setEnabled(false); + ui->setPassword->setEnabled(false); } else { ui->autoLogin->setChecked(account.rememberLogin); - ui->accountName->setText(account.accountName); - if (!account.accountPassword) { - ui->accountPassword->setPlaceholderText(""); - } + updateAutoLoginEnabled(); } } @@ -375,3 +412,58 @@ void GeneralPage::slot_themeComboBoxChanged(int index) { setConfig().general.setTheme(static_cast(index)); } + +void GeneralPage::updateAutoLoginEnabled() +{ + if constexpr (NO_QTKEYCHAIN) { + ui->autoLogin->setEnabled(false); + return; + } + + const auto &account = getConfig().account; + const bool hasAccountName = !account.accountName.isEmpty(); + const bool hasPassword = account.accountPassword; + + ui->autoLogin->setEnabled(hasAccountName && hasPassword); +} + +void GeneralPage::slot_setPasswordClicked() +{ + const QString accountName = getConfig().account.accountName; + + if constexpr (CURRENT_PLATFORM == PlatformEnum::Wasm) { + auto *dlg = new ManagePasswordDialog(this); + dlg->setAttribute(Qt::WA_DeleteOnClose); + dlg->setAccountName(accountName); + connect(dlg, &QDialog::accepted, this, [this, dlg]() { + const QString newAccountName = dlg->accountName(); + if (!newAccountName.isEmpty()) { + setConfig().account.accountName = newAccountName; + passCfg.setPassword(""); + } + }); + dlg->open(); + } else { + if (getConfig().account.accountPassword) { + ui->setPassword->setProperty("requesting", true); + passCfg.getPassword(); + } else { + auto *dlg = new ManagePasswordDialog(this); + dlg->setAttribute(Qt::WA_DeleteOnClose); + dlg->setAccountName(accountName); + connect(dlg, &ManagePasswordDialog::sig_deleteRequested, this, [this]() { + passCfg.deletePassword(); + }); + + connect(dlg, &QDialog::accepted, this, [this, dlg]() { + const QString newAccountName = dlg->accountName(); + const QString password = dlg->password(); + if (!password.isEmpty()) { + setConfig().account.accountName = newAccountName; + passCfg.setPassword(password); + } + }); + dlg->open(); + } + } +} diff --git a/src/preferences/generalpage.h b/src/preferences/generalpage.h index 2a1e9fe50..7e0014b0d 100644 --- a/src/preferences/generalpage.h +++ b/src/preferences/generalpage.h @@ -7,6 +7,7 @@ #include "../configuration/PasswordConfig.h" #include "../global/macros.h" +#include "ManagePasswordDialog.h" #include #include @@ -48,4 +49,8 @@ public slots: void slot_displayMumeClockStateChanged(int); void slot_displayXPStatusStateChanged(int); void slot_themeComboBoxChanged(int); + void slot_setPasswordClicked(); + +private: + void updateAutoLoginEnabled(); }; diff --git a/src/preferences/generalpage.ui b/src/preferences/generalpage.ui index 532cc0e98..8f6dc4b4b 100644 --- a/src/preferences/generalpage.ui +++ b/src/preferences/generalpage.ui @@ -6,8 +6,8 @@ 0 0 - 459 - 792 + 508 + 944 @@ -16,12 +16,6 @@ - - - 435 - 179 - - Connection @@ -111,7 +105,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -158,53 +152,39 @@ Account - - - - - Account: - - - accountName - - - - - + + + - Password: - - - accountPassword + Automatic login - - - - - - - QLineEdit::Password - - - *********** + + + + + 0 + 0 + - - - - - Show Password + Manage Account - - - - Remember my login + + + + Qt::Orientation::Horizontal - + + + 40 + 20 + + + @@ -215,13 +195,13 @@ Startup - + - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Plain + QFrame::Shadow::Plain 0 @@ -278,7 +258,7 @@ - + Check for update @@ -290,12 +270,6 @@ - - - 435 - 73 - - Apperance @@ -303,22 +277,14 @@ 9 - - - - Show Mume clock - - - - - - - Show Session XP - - - + + + 0 + 0 + + System @@ -338,6 +304,12 @@ + + + 0 + 0 + + Theme: @@ -346,6 +318,20 @@ + + + + Show Mume clock + + + + + + + Show Session XP + + + @@ -374,34 +360,28 @@ - - - 435 - 105 - - Emulated Exits - + Show emulated exits - - + + - Show notes + Show hidden exit flags - - + + - Show hidden exit flags + Show notes @@ -410,20 +390,14 @@ - - - 435 - 107 - - Advanced - - + + - Import Settings... + Reset to Default Settings @@ -437,10 +411,10 @@ - - + + - Export Settings... + Import Settings... @@ -454,17 +428,30 @@ - - + + - Reset to Default Settings + Export Settings... - + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + - Qt::Horizontal + Qt::Orientation::Horizontal @@ -480,7 +467,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -499,9 +486,7 @@ localPort charsetComboBox autoLogin - accountName - accountPassword - showPassword + setPassword checkForUpdateCheckBox autoLoadCheck autoLoadFileName @@ -516,6 +501,9 @@ showNotesCheckBox proxyListensOnAnyInterfaceCheckBox proxyConnectionStatusCheckBox + configurationExportButton + configurationImportButton + configurationResetButton