diff --git a/CMakeLists.txt b/CMakeLists.txt index c8918b798..ccb3344de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ project(mmapper CXX) list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake) -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) diff --git a/src/configuration/NamedConfig.h b/src/configuration/NamedConfig.h index 271964ddd..334b6f7b1 100644 --- a/src/configuration/NamedConfig.h +++ b/src/configuration/NamedConfig.h @@ -3,32 +3,55 @@ // Copyright (C) 2019 The MMapper Authors #include "../global/ChangeMonitor.h" +#include "../global/Color.h" +#include "../global/INamedConfig.h" +#include +#include + +#include +#include #include +#include #include #include #include #include template -class NODISCARD NamedConfig final +class NODISCARD NamedConfig final : public INamedConfig { +public: + using OnAfterChange = std::function; + private: std::string m_name; ChangeMonitor m_changeMonitor; - T m_value = 0; + T m_value; + OnAfterChange m_onAfterChange; bool m_notifying = false; public: NamedConfig() = delete; DELETE_CTORS_AND_ASSIGN_OPS(NamedConfig); - explicit NamedConfig(std::string name, T initialValue) + + explicit NamedConfig(std::string name, T initialValue, OnAfterChange onAfterChange = nullptr) : m_name{std::move(name)} , m_value{std::move(initialValue)} + , m_onAfterChange{std::move(onAfterChange)} {} + explicit NamedConfig(std::string name, T initialValue, std::function simpleCallback) + : m_name{std::move(name)} + , m_value{std::move(initialValue)} + { + if (simpleCallback) { + m_onAfterChange = [cb = std::move(simpleCallback)](const T &) { cb(); }; + } + } + public: - NODISCARD const std::string &getName() { return m_name; } + NODISCARD const std::string &getName() const override { return m_name; } NODISCARD inline T get() const { return m_value; } void set(const T newValue) { @@ -62,9 +85,21 @@ class NODISCARD NamedConfig final } notification_guard{*this}; m_value = newValue; + if (m_onAfterChange) { + m_onAfterChange(m_value); + } m_changeMonitor.notifyAll(); } + void setFromNotifier(std::function simpleCallback) + { + if (simpleCallback) { + m_onAfterChange = [cb = std::move(simpleCallback)](const T &) { cb(); }; + } else { + m_onAfterChange = nullptr; + } + } + void clamp(const T lo, const T hi) { // don't try to call this for boolean or string. @@ -78,8 +113,82 @@ class NODISCARD NamedConfig final } public: + std::string toString() const override + { + if constexpr (std::is_same_v) { + return m_value; + } else if constexpr (std::is_same_v) { + return m_value.toStdString(); + } else if constexpr (std::is_same_v) { + return m_value ? "true" : "false"; + } else if constexpr (std::is_integral_v || std::is_floating_point_v) { + return std::to_string(m_value); + } else if constexpr (std::is_same_v) { + return m_value.toHex(); + } else if constexpr (std::is_same_v) { + return m_value.name().toStdString(); + } else if constexpr (std::is_same_v) { + return m_value.toBase64().toStdString(); + } else { + return ""; + } + } + + bool fromString(const std::string &str) override + { + if constexpr (std::is_same_v) { + set(str); + return true; + } else if constexpr (std::is_same_v) { + set(QString::fromStdString(str)); + return true; + } else if constexpr (std::is_same_v) { + if (str == "true" || str == "1" || str == "on") { + set(true); + return true; + } else if (str == "false" || str == "0" || str == "off") { + set(false); + return true; + } + return false; + } else if constexpr (std::is_integral_v) { + try { + set(static_cast(std::stoll(str))); + return true; + } catch (...) { + return false; + } + } else if constexpr (std::is_floating_point_v) { + try { + set(static_cast(std::stold(str))); + return true; + } catch (...) { + return false; + } + } else if constexpr (std::is_same_v) { + try { + set(Color::fromHex(str)); + return true; + } catch (...) { + return false; + } + } else if constexpr (std::is_same_v) { + QColor c(QString::fromStdString(str)); + if (c.isValid()) { + set(c); + return true; + } + return false; + } else if constexpr (std::is_same_v) { + set(QByteArray::fromBase64(QByteArray::fromStdString(str))); + return true; + } else { + return false; + } + } + void registerChangeCallback(const ChangeMonitor::Lifetime &lifetime, - ChangeMonitor::Function callback) + ChangeMonitor::Function callback) override { return m_changeMonitor.registerChangeCallback(lifetime, std::move(callback)); } diff --git a/src/configuration/configuration.cpp b/src/configuration/configuration.cpp index a8d58c8bd..8f3df1184 100644 --- a/src/configuration/configuration.cpp +++ b/src/configuration/configuration.cpp @@ -209,9 +209,150 @@ ConstString GRP_ROOMEDIT_DIALOG = "RoomEdit Dialog"; Configuration::Configuration() : hotkeys(GRP_HOTKEYS) { + registerConfig(&general.firstRun); + registerConfig(&general.windowGeometry); + registerConfig(&general.windowState); + registerConfig(&general.alwaysOnTop); + registerConfig(&general.showStatusBar); + registerConfig(&general.showScrollBars); + registerConfig(&general.showMenuBar); + registerConfig(&general.mapMode); + registerConfig(&general.checkForUpdate); + registerConfig(&general.characterEncoding); + registerConfig(&general.theme); + + registerConfig(&connection.remoteServerName); + registerConfig(&connection.remotePort); + registerConfig(&connection.localPort); + registerConfig(&connection.tlsEncryption); + registerConfig(&connection.proxyConnectionStatus); + registerConfig(&connection.proxyListensOnAnyInterface); + + registerConfig(&parser.roomNameColor); + registerConfig(&parser.roomDescColor); + registerConfig(&parser.prefixChar); + registerConfig(&parser.encodeEmoji); + registerConfig(&parser.decodeEmoji); + + registerConfig(&mumeClientProtocol.internalRemoteEditor); + registerConfig(&mumeClientProtocol.externalRemoteEditorCommand); + + registerConfig(&mumeNative.emulatedExits); + registerConfig(&mumeNative.showHiddenExitFlags); + registerConfig(&mumeNative.showNotes); + + registerConfig(&canvas.backgroundColor); + registerConfig(&canvas.connectionNormalColor); + registerConfig(&canvas.roomDarkColor); + registerConfig(&canvas.roomDarkLitColor); + registerConfig(&canvas.antialiasingSamples); + registerConfig(&canvas.trilinearFiltering); + registerConfig(&canvas.showMissingMapId); + registerConfig(&canvas.showUnsavedChanges); + registerConfig(&canvas.showUnmappedExits); + registerConfig(&canvas.drawUpperLayersTextured); + registerConfig(&canvas.drawDoorNames); + registerConfig(&canvas.softwareOpenGL); + registerConfig(&canvas.resourcesDirectory); + registerConfig(&canvas.drawCharBeacons); + registerConfig(&canvas.charBeaconScaleCutoff); + registerConfig(&canvas.doorNameScaleCutoff); + registerConfig(&canvas.infomarkScaleCutoff); + registerConfig(&canvas.extraDetailScaleCutoff); + registerConfig(&canvas.weatherAtmosphereIntensity); + registerConfig(&canvas.weatherPrecipitationIntensity); + registerConfig(&canvas.weatherTimeOfDayIntensity); + + registerConfig(&canvas.advanced.use3D); + registerConfig(&canvas.advanced.autoTilt); + registerConfig(&canvas.advanced.printPerfStats); + + registerConfig(&account.accountName); + registerConfig(&account.accountPassword); + registerConfig(&account.rememberLogin); + + registerConfig(&autoLoad.autoLoadMap); + registerConfig(&autoLoad.fileName); + registerConfig(&autoLoad.lastMapDirectory); + + registerConfig(&autoLog.autoLogDirectory); + registerConfig(&autoLog.autoLog); + registerConfig(&autoLog.cleanupStrategy); + registerConfig(&autoLog.deleteWhenLogsReachDays); + registerConfig(&autoLog.deleteWhenLogsReachBytes); + registerConfig(&autoLog.askDelete); + registerConfig(&autoLog.rotateWhenLogsReachBytes); + + registerConfig(&pathMachine.acceptBestRelative); + registerConfig(&pathMachine.acceptBestAbsolute); + registerConfig(&pathMachine.newRoomPenalty); + registerConfig(&pathMachine.multipleConnectionsPenalty); + registerConfig(&pathMachine.correctPositionBonus); + registerConfig(&pathMachine.maxPaths); + registerConfig(&pathMachine.matchingTolerance); + + registerConfig(&groupManager.color); + registerConfig(&groupManager.npcColor); + registerConfig(&groupManager.npcColorOverride); + registerConfig(&groupManager.npcSortBottom); + registerConfig(&groupManager.npcHide); + + registerConfig(&mumeClock.startEpoch); + registerConfig(&mumeClock.display); + + registerConfig(&adventurePanel.displayXPStatus); + + registerConfig(&audio.musicVolume); + registerConfig(&audio.soundVolume); + registerConfig(&audio.outputDeviceId); + registerConfig(&audio.unlocked); + + registerConfig(&integratedClient.font); + registerConfig(&integratedClient.foregroundColor); + registerConfig(&integratedClient.backgroundColor); + registerConfig(&integratedClient.commandSeparator); + registerConfig(&integratedClient.columns); + registerConfig(&integratedClient.rows); + registerConfig(&integratedClient.linesOfScrollback); + registerConfig(&integratedClient.linesOfInputHistory); + registerConfig(&integratedClient.tabCompletionDictionarySize); + registerConfig(&integratedClient.clearInputOnEnter); + registerConfig(&integratedClient.autoResizeTerminal); + registerConfig(&integratedClient.linesOfPeekPreview); + registerConfig(&integratedClient.audibleBell); + registerConfig(&integratedClient.visualBell); + registerConfig(&integratedClient.useCommandSeparator); + + registerConfig(&roomPanel.geometry); + registerConfig(&infomarksDialog.geometry); + registerConfig(&roomEditDialog.geometry); + registerConfig(&findRoomsDialog.geometry); + read(); // read the settings or set them to the default values } +void Configuration::registerConfig(INamedConfig *config) +{ + if (config == nullptr) { + return; + } + m_registry[config->getName()] = config; +} + +INamedConfig *Configuration::lookup(const std::string &name) const +{ + auto it = m_registry.find(name); + if (it != m_registry.end()) { + return it->second; + } + return nullptr; +} + +const std::map &Configuration::getRegistry() const +{ + return m_registry; +} + ConstString KEY_ABSOLUTE_PATH_ACCEPTANCE = "absolute path acceptance"; ConstString KEY_ACCOUNT_NAME = "account name"; ConstString KEY_ACCOUNT_PASSWORD = "account password"; @@ -516,23 +657,26 @@ void Configuration::readFrom(QSettings &conf) // reset to defaults before reading colors that might override them colorSettings.resetToDefaults(); + // Ensure m_notifier is initialized before subgroups read + // But subgroups are members, so they are already initialized. + FOREACH_CONFIG_GROUP(read); // This logic only runs once on a MMapper fresh install (or factory reset) // Subsequent MMapper starts will always read "firstRun" as false - if (general.firstRun) { + if (general.firstRun.get()) { // New users get the 3D canvas but old users do not canvas.advanced.use3D.set(true); // New users get autologger turned on by default - autoLog.autoLog = (CURRENT_PLATFORM != PlatformEnum::Wasm); + autoLog.autoLog.set(CURRENT_PLATFORM != PlatformEnum::Wasm); hotkeys.resetToDefault(); } - assert(canvas.backgroundColor == colorSettings.BACKGROUND); - assert(canvas.roomDarkColor == colorSettings.ROOM_DARK); - assert(canvas.roomDarkLitColor == colorSettings.ROOM_NO_SUNDEATH); + assert(canvas.backgroundColor.get() == colorSettings.BACKGROUND.getColor()); + assert(canvas.roomDarkColor.get() == colorSettings.ROOM_DARK.getColor()); + assert(canvas.roomDarkLitColor.get() == colorSettings.ROOM_NO_SUNDEATH.getColor()); assert(colorSettings.TRANSPARENT.isInitialized() && colorSettings.TRANSPARENT.getColor().isTransparent()); @@ -576,7 +720,7 @@ NODISCARD static QString getDefaultDirectory() void Configuration::GeneralSettings::read(const QSettings &conf) { - firstRun = conf.value(KEY_RUN_FIRST_TIME, true).toBool(); + firstRun.set(conf.value(KEY_RUN_FIRST_TIME, true).toBool()); /* * REVISIT: It's basically impossible to verify that this state is valid, * because we have no idea what it contains! @@ -588,40 +732,40 @@ void Configuration::GeneralSettings::read(const QSettings &conf) * (or better yet sign it), and record the OS config, so that we won't * try to apply Windows settings to Mac, or Gnome settings to KDE, etc? */ - windowGeometry = conf.value(KEY_WINDOW_GEOMETRY).toByteArray(); - windowState = conf.value(KEY_WINDOW_STATE).toByteArray(); - alwaysOnTop = conf.value(KEY_ALWAYS_ON_TOP, false).toBool(); - showStatusBar = conf.value(KEY_SHOW_STATUS_BAR, true).toBool(); - showScrollBars = conf.value(KEY_SHOW_SCROLL_BARS, true).toBool(); - showMenuBar = conf.value(KEY_SHOW_MENU_BAR, true).toBool(); - mapMode = sanitizeMapMode( - conf.value(KEY_MAP_MODE, static_cast(MapModeEnum::PLAY)).toUInt()); - checkForUpdate = conf.value(KEY_CHECK_FOR_UPDATE, true).toBool(); - characterEncoding = sanitizeCharacterEncoding( + windowGeometry.set(conf.value(KEY_WINDOW_GEOMETRY).toByteArray()); + windowState.set(conf.value(KEY_WINDOW_STATE).toByteArray()); + alwaysOnTop.set(conf.value(KEY_ALWAYS_ON_TOP, false).toBool()); + showStatusBar.set(conf.value(KEY_SHOW_STATUS_BAR, true).toBool()); + showScrollBars.set(conf.value(KEY_SHOW_SCROLL_BARS, true).toBool()); + showMenuBar.set(conf.value(KEY_SHOW_MENU_BAR, true).toBool()); + mapMode.set(static_cast(sanitizeMapMode( + conf.value(KEY_MAP_MODE, static_cast(MapModeEnum::PLAY)).toUInt()))); + checkForUpdate.set(conf.value(KEY_CHECK_FOR_UPDATE, true).toBool()); + characterEncoding.set(static_cast(sanitizeCharacterEncoding( conf.value(KEY_CHARACTER_ENCODING, static_cast(CharacterEncodingEnum::LATIN1)) - .toUInt()); - m_theme = sanitizeTheme( - conf.value(KEY_THEME, static_cast(ThemeEnum::System)).toUInt()); + .toUInt()))); + theme.set(static_cast(sanitizeTheme( + conf.value(KEY_THEME, static_cast(ThemeEnum::System)).toUInt()))); } void Configuration::ConnectionSettings::read(const QSettings &conf) { static constexpr const int DEFAULT_PORT = 4242; - remoteServerName = conf.value(KEY_SERVER_NAME, "mume.org").toString(); - remotePort = sanitizeUint16(conf.value(KEY_MUME_REMOTE_PORT, DEFAULT_PORT).toInt(), - static_cast(DEFAULT_PORT)); - localPort = sanitizeUint16(conf.value(KEY_PROXY_LOCAL_PORT, DEFAULT_PORT).toInt(), - static_cast(DEFAULT_PORT)); + remoteServerName.set(conf.value(KEY_SERVER_NAME, "mume.org").toString()); + remotePort.set(sanitizeUint16(conf.value(KEY_MUME_REMOTE_PORT, DEFAULT_PORT).toInt(), + static_cast(DEFAULT_PORT))); + localPort.set(sanitizeUint16(conf.value(KEY_PROXY_LOCAL_PORT, DEFAULT_PORT).toInt(), + static_cast(DEFAULT_PORT))); #ifndef Q_OS_WASM // REVISIT: This should be true if WebSocket mode is enabled? - tlsEncryption = QSslSocket::supportsSsl() ? conf.value(KEY_TLS_ENCRYPTION, true).toBool() - : false; + tlsEncryption.set(QSslSocket::supportsSsl() ? conf.value(KEY_TLS_ENCRYPTION, true).toBool() + : false); #else - tlsEncryption = true; + tlsEncryption.set(true); #endif - proxyConnectionStatus = conf.value(KEY_PROXY_CONNECTION_STATUS, false).toBool(); - proxyListensOnAnyInterface = conf.value(KEY_PROXY_LISTENS_ON_ANY_INTERFACE, false).toBool(); + proxyConnectionStatus.set(conf.value(KEY_PROXY_CONNECTION_STATUS, false).toBool()); + proxyListensOnAnyInterface.set(conf.value(KEY_PROXY_LISTENS_ON_ANY_INTERFACE, false).toBool()); } // closest well-known color is "Outer Space" @@ -631,6 +775,30 @@ static constexpr const std::string_view DEFAULT_DARK_COLOR = "#A19494"; // closest well-known color is "Cold Turkey" static constexpr const std::string_view DEFAULT_NO_SUNDEATH_COLOR = "#D4C7C7"; +Configuration::CanvasSettings::CanvasSettings() +{ + auto updateGlobalColors = [this]() { + if (auto opt = XNamedColor::lookup("background")) { + opt->setColor(backgroundColor.get()); + } + if (auto opt = XNamedColor::lookup("connection-normal")) { + opt->setColor(connectionNormalColor.get()); + } + if (auto opt = XNamedColor::lookup("room-dark")) { + opt->setColor(roomDarkColor.get()); + } + if (auto opt = XNamedColor::lookup("room-no-sundeath")) { + opt->setColor(roomDarkLitColor.get()); + } + m_notifier(); + }; + + backgroundColor.setFromNotifier(updateGlobalColors); + connectionNormalColor.setFromNotifier(updateGlobalColors); + roomDarkColor.setFromNotifier(updateGlobalColors); + roomDarkLitColor.setFromNotifier(updateGlobalColors); +} + void Configuration::CanvasSettings::read(const QSettings &conf) { // REVISIT: Consider just using the "current" value of the named color object, @@ -642,20 +810,20 @@ void Configuration::CanvasSettings::read(const QSettings &conf) return Color(QColor(conf.value(key, qdef).toString())); }; - resourcesDirectory = conf.value(KEY_RESOURCES_DIRECTORY, - getDefaultDirectory() - .append(DEFAULT_MMAPPER_SUBDIR) - .append(DEFAULT_RESOURCES_SUBDIR)) - .toString(); + resourcesDirectory.set(conf.value(KEY_RESOURCES_DIRECTORY, + getDefaultDirectory() + .append(DEFAULT_MMAPPER_SUBDIR) + .append(DEFAULT_RESOURCES_SUBDIR)) + .toString()); showMissingMapId.set(conf.value(KEY_SHOW_MISSING_MAP_ID, true).toBool()); showUnsavedChanges.set(conf.value(KEY_SHOW_UNSAVED_CHANGES, true).toBool()); showUnmappedExits.set(conf.value(KEY_DRAW_NOT_MAPPED_EXITS, true).toBool()); - drawUpperLayersTextured = conf.value(KEY_DRAW_UPPER_LAYERS_TEXTURED, false).toBool(); - drawDoorNames = conf.value(KEY_DRAW_DOOR_NAMES, true).toBool(); - backgroundColor = lookupColor(KEY_BACKGROUND_COLOR, DEFAULT_BGCOLOR); - connectionNormalColor = lookupColor(KEY_CONNECTION_NORMAL_COLOR, Colors::white.toHex()); - roomDarkColor = lookupColor(KEY_ROOM_DARK_COLOR, DEFAULT_DARK_COLOR); - roomDarkLitColor = lookupColor(KEY_ROOM_DARK_LIT_COLOR, DEFAULT_NO_SUNDEATH_COLOR); + drawUpperLayersTextured.set(conf.value(KEY_DRAW_UPPER_LAYERS_TEXTURED, false).toBool()); + drawDoorNames.set(conf.value(KEY_DRAW_DOOR_NAMES, true).toBool()); + backgroundColor.set(lookupColor(KEY_BACKGROUND_COLOR, DEFAULT_BGCOLOR)); + connectionNormalColor.set(lookupColor(KEY_CONNECTION_NORMAL_COLOR, Colors::white.toHex())); + roomDarkColor.set(lookupColor(KEY_ROOM_DARK_COLOR, DEFAULT_DARK_COLOR)); + roomDarkLitColor.set(lookupColor(KEY_ROOM_DARK_LIT_COLOR, DEFAULT_NO_SUNDEATH_COLOR)); antialiasingSamples.set(conf.value(KEY_NUMBER_OF_ANTI_ALIASING_SAMPLES, 0).toInt()); trilinearFiltering.set(conf.value(KEY_USE_TRILINEAR_FILTERING, true).toBool()); advanced.use3D.set(conf.value(KEY_3D_CANVAS, false).toBool()); @@ -678,36 +846,36 @@ void Configuration::CanvasSettings::read(const QSettings &conf) void Configuration::AccountSettings::read(const QSettings &conf) { - accountName = conf.value(KEY_ACCOUNT_NAME, "").toString(); - accountPassword = conf.value(KEY_ACCOUNT_PASSWORD, false).toBool(); - rememberLogin = NO_QTKEYCHAIN ? false : conf.value(KEY_REMEMBER_LOGIN, false).toBool(); + accountName.set(conf.value(KEY_ACCOUNT_NAME, "").toString()); + accountPassword.set(conf.value(KEY_ACCOUNT_PASSWORD, false).toBool()); + rememberLogin.set(NO_QTKEYCHAIN ? false : conf.value(KEY_REMEMBER_LOGIN, false).toBool()); } void Configuration::AutoLoadSettings::read(const QSettings &conf) { - autoLoadMap = conf.value(KEY_AUTO_LOAD, true).toBool(); - fileName = conf.value(KEY_FILE_NAME, "").toString(); - lastMapDirectory = conf.value(KEY_LAST_MAP_LOAD_DIRECTORY, - getDefaultDirectory().append(DEFAULT_MMAPPER_SUBDIR)) - .toString(); + autoLoadMap.set(conf.value(KEY_AUTO_LOAD, true).toBool()); + fileName.set(conf.value(KEY_FILE_NAME, "").toString()); + lastMapDirectory.set(conf.value(KEY_LAST_MAP_LOAD_DIRECTORY, + getDefaultDirectory().append(DEFAULT_MMAPPER_SUBDIR)) + .toString()); } void Configuration::AutoLogSettings::read(const QSettings &conf) { - autoLogDirectory = conf.value(KEY_AUTO_LOG_DIRECTORY, - getDefaultDirectory() - .append(DEFAULT_MMAPPER_SUBDIR) - .append(DEFAULT_LOGS_SUBDIR)) - .toString(); - autoLog = conf.value(KEY_AUTO_LOG, false).toBool(); - rotateWhenLogsReachBytes = conf.value(KEY_AUTO_LOG_ROTATE_SIZE_BYTES, 10 * 1000000) - .toInt(); // 10 Megabytes - askDelete = conf.value(KEY_AUTO_LOG_ASK_DELETE, false).toBool(); - cleanupStrategy = sanitizeAutoLoggerState( + autoLogDirectory.set(conf.value(KEY_AUTO_LOG_DIRECTORY, + getDefaultDirectory() + .append(DEFAULT_MMAPPER_SUBDIR) + .append(DEFAULT_LOGS_SUBDIR)) + .toString()); + autoLog.set(conf.value(KEY_AUTO_LOG, false).toBool()); + rotateWhenLogsReachBytes.set(conf.value(KEY_AUTO_LOG_ROTATE_SIZE_BYTES, 10 * 1000000) + .toInt()); // 10 Megabytes + askDelete.set(conf.value(KEY_AUTO_LOG_ASK_DELETE, false).toBool()); + cleanupStrategy.set(static_cast(sanitizeAutoLoggerState( conf.value(KEY_AUTO_LOG_CLEANUP_STRATEGY, static_cast(AutoLoggerEnum::DeleteDays)) - .toInt()); - deleteWhenLogsReachDays = conf.value(KEY_AUTO_LOG_DELETE_AFTER_DAYS, 30).toInt(); - deleteWhenLogsReachBytes = conf.value(KEY_AUTO_LOG_DELETE_AFTER_BYTES, 100 * 1000000).toInt(); + .toInt()))); + deleteWhenLogsReachDays.set(conf.value(KEY_AUTO_LOG_DELETE_AFTER_DAYS, 30).toInt()); + deleteWhenLogsReachBytes.set(conf.value(KEY_AUTO_LOG_DELETE_AFTER_BYTES, 100 * 1000000).toInt()); } void Configuration::ParserSettings::read(const QSettings &conf) @@ -715,135 +883,135 @@ void Configuration::ParserSettings::read(const QSettings &conf) static constexpr const char *const ANSI_GREEN = "[32m"; static constexpr const char *const ANSI_RESET = "[0m"; - roomNameColor = sanitizeAnsi(conf.value(KEY_ROOM_NAME_ANSI_COLOR, ANSI_GREEN).toString(), - QString(ANSI_GREEN)); - roomDescColor = sanitizeAnsi(conf.value(KEY_ROOM_DESC_ANSI_COLOR, ANSI_RESET).toString(), - QString(ANSI_RESET)); - prefixChar = mmqt::toLatin1( - conf.value(KEY_COMMAND_PREFIX_CHAR, QChar::fromLatin1(char_consts::C_UNDERSCORE)).toChar()); - encodeEmoji = conf.value(KEY_EMOJI_ENCODE, true).toBool(); - decodeEmoji = conf.value(KEY_EMOJI_DECODE, true).toBool(); + roomNameColor.set(sanitizeAnsi(conf.value(KEY_ROOM_NAME_ANSI_COLOR, ANSI_GREEN).toString(), + QString(ANSI_GREEN))); + roomDescColor.set(sanitizeAnsi(conf.value(KEY_ROOM_DESC_ANSI_COLOR, ANSI_RESET).toString(), + QString(ANSI_RESET))); + prefixChar.set(static_cast(mmqt::toLatin1( + conf.value(KEY_COMMAND_PREFIX_CHAR, QChar::fromLatin1(char_consts::C_UNDERSCORE)).toChar()))); + encodeEmoji.set(conf.value(KEY_EMOJI_ENCODE, true).toBool()); + decodeEmoji.set(conf.value(KEY_EMOJI_DECODE, true).toBool()); } void Configuration::MumeClientProtocolSettings::read(const QSettings &conf) { - internalRemoteEditor = conf.value(KEY_USE_INTERNAL_EDITOR, true).toBool(); - externalRemoteEditorCommand = conf.value(KEY_EXTERNAL_EDITOR_COMMAND, getPlatformEditor()) - .toString(); + internalRemoteEditor.set(conf.value(KEY_USE_INTERNAL_EDITOR, true).toBool()); + externalRemoteEditorCommand.set(conf.value(KEY_EXTERNAL_EDITOR_COMMAND, getPlatformEditor()) + .toString()); } void Configuration::MumeNativeSettings::read(const QSettings &conf) { - emulatedExits = conf.value(KEY_EMULATED_EXITS, true).toBool(); - showHiddenExitFlags = conf.value(KEY_SHOW_HIDDEN_EXIT_FLAGS, true).toBool(); - showNotes = conf.value(KEY_SHOW_NOTES, true).toBool(); + emulatedExits.set(conf.value(KEY_EMULATED_EXITS, true).toBool()); + showHiddenExitFlags.set(conf.value(KEY_SHOW_HIDDEN_EXIT_FLAGS, true).toBool()); + showNotes.set(conf.value(KEY_SHOW_NOTES, true).toBool()); } void Configuration::PathMachineSettings::read(const QSettings &conf) { - acceptBestRelative = conf.value(KEY_RELATIVE_PATH_ACCEPTANCE, 25).toDouble(); - acceptBestAbsolute = conf.value(KEY_ABSOLUTE_PATH_ACCEPTANCE, 6).toDouble(); - newRoomPenalty = conf.value(KEY_ROOM_CREATION_PENALTY, 5).toDouble(); - correctPositionBonus = conf.value(KEY_CORRECT_POSITION_BONUS, 5).toDouble(); - multipleConnectionsPenalty = conf.value(KEY_MULTIPLE_CONNECTIONS_PENALTY, 2.0).toDouble(); - maxPaths = utils::clampNonNegative(conf.value(KEY_MAXIMUM_NUMBER_OF_PATHS, 1000).toInt()); - matchingTolerance = utils::clampNonNegative(conf.value(KEY_ROOM_MATCHING_TOLERANCE, 8).toInt()); + acceptBestRelative.set(conf.value(KEY_RELATIVE_PATH_ACCEPTANCE, 25).toDouble()); + acceptBestAbsolute.set(conf.value(KEY_ABSOLUTE_PATH_ACCEPTANCE, 6).toDouble()); + newRoomPenalty.set(conf.value(KEY_ROOM_CREATION_PENALTY, 5).toDouble()); + correctPositionBonus.set(conf.value(KEY_CORRECT_POSITION_BONUS, 5).toDouble()); + multipleConnectionsPenalty.set(conf.value(KEY_MULTIPLE_CONNECTIONS_PENALTY, 2.0).toDouble()); + maxPaths.set(utils::clampNonNegative(conf.value(KEY_MAXIMUM_NUMBER_OF_PATHS, 1000).toInt())); + matchingTolerance.set(utils::clampNonNegative(conf.value(KEY_ROOM_MATCHING_TOLERANCE, 8).toInt())); } void Configuration::GroupManagerSettings::read(const QSettings &conf) { - color = QColor(conf.value(KEY_GROUP_YOUR_COLOR, "#FFFF00").toString()); - npcColor = QColor(conf.value(KEY_GROUP_NPC_COLOR, QColor(Qt::lightGray)).toString()); - npcColorOverride = conf.value(KEY_GROUP_NPC_COLOR_OVERRIDE, false).toBool(); - npcHide = conf.value(KEY_GROUP_NPC_HIDE, false).toBool(); - npcSortBottom = conf.value(KEY_GROUP_NPC_SORT_BOTTOM, false).toBool(); + color.set(QColor(conf.value(KEY_GROUP_YOUR_COLOR, "#FFFF00").toString())); + npcColor.set(QColor(conf.value(KEY_GROUP_NPC_COLOR, QColor(Qt::lightGray)).toString())); + npcColorOverride.set(conf.value(KEY_GROUP_NPC_COLOR_OVERRIDE, false).toBool()); + npcHide.set(conf.value(KEY_GROUP_NPC_HIDE, false).toBool()); + npcSortBottom.set(conf.value(KEY_GROUP_NPC_SORT_BOTTOM, false).toBool()); } void Configuration::MumeClockSettings::read(const QSettings &conf) { // NOTE: old values might be stored as int32 - startEpoch = conf.value(KEY_MUME_START_EPOCH, 1517443173).toLongLong(); - display = conf.value(KEY_DISPLAY_CLOCK, true).toBool(); + startEpoch.set(conf.value(KEY_MUME_START_EPOCH, 1517443173).toLongLong()); + display.set(conf.value(KEY_DISPLAY_CLOCK, true).toBool()); } void Configuration::AdventurePanelSettings::read(const QSettings &conf) { - m_displayXPStatus = conf.value(KEY_DISPLAY_XP_STATUS, true).toBool(); + displayXPStatus.set(conf.value(KEY_DISPLAY_XP_STATUS, true).toBool()); } void Configuration::AudioSettings::read(const QSettings &conf) { - m_unlocked = (CURRENT_PLATFORM == PlatformEnum::Wasm) + unlocked.set((CURRENT_PLATFORM == PlatformEnum::Wasm) ? false - : conf.value(KEY_AUDIO_UNLOCKED, false).toBool(); - m_musicVolume = std::clamp(conf.value(KEY_MUSIC_VOLUME, 50).toInt(), 0, 100); - m_soundVolume = std::clamp(conf.value(KEY_SOUND_VOLUME, 50).toInt(), 0, 100); - m_outputDeviceId = conf.value(KEY_AUDIO_OUTPUT_DEVICE).toByteArray(); + : conf.value(KEY_AUDIO_UNLOCKED, false).toBool()); + musicVolume.set(std::clamp(conf.value(KEY_MUSIC_VOLUME, 50).toInt(), 0, 100)); + soundVolume.set(std::clamp(conf.value(KEY_SOUND_VOLUME, 50).toInt(), 0, 100)); + outputDeviceId.set(conf.value(KEY_AUDIO_OUTPUT_DEVICE).toByteArray()); } void Configuration::IntegratedMudClientSettings::read(const QSettings &conf) { - font = conf.value(KEY_FONT, "").toString(); - backgroundColor = conf.value(KEY_BACKGROUND_COLOR, QColor(Qt::black).name()).toString(); - foregroundColor = conf.value(KEY_FOREGROUND_COLOR, QColor(Qt::lightGray).name()).toString(); - columns = conf.value(KEY_COLUMNS, 80).toInt(); - rows = conf.value(KEY_ROWS, 24).toInt(); - linesOfScrollback = conf.value(KEY_LINES_OF_SCROLLBACK, 10000).toInt(); - linesOfInputHistory = conf.value(KEY_LINES_OF_INPUT_HISTORY, 100).toInt(); - tabCompletionDictionarySize = conf.value(KEY_TAB_COMPLETION_DICTIONARY_SIZE, 100).toInt(); - clearInputOnEnter = conf.value(KEY_CLEAR_INPUT_ON_ENTER, false).toBool(); - autoResizeTerminal = conf.value(KEY_AUTO_RESIZE_TERMINAL, true).toBool(); - linesOfPeekPreview = conf.value(KEY_LINES_OF_PEEK_PREVIEW, 7).toInt(); - audibleBell = conf.value(KEY_BELL_AUDIBLE, true).toBool(); - visualBell = conf.value(KEY_BELL_VISUAL, (CURRENT_PLATFORM == PlatformEnum::Wasm)).toBool(); - useCommandSeparator = conf.value(KEY_USE_COMMAND_SEPARATOR, false).toBool(); - commandSeparator = conf.value(KEY_COMMAND_SEPARATOR, QString(char_consts::C_SEMICOLON)) - .toString(); + font.set(conf.value(KEY_FONT, "").toString()); + backgroundColor.set(QColor(conf.value(KEY_BACKGROUND_COLOR, QColor(Qt::black).name()).toString())); + foregroundColor.set(QColor(conf.value(KEY_FOREGROUND_COLOR, QColor(Qt::lightGray).name()).toString())); + columns.set(conf.value(KEY_COLUMNS, 80).toInt()); + rows.set(conf.value(KEY_ROWS, 24).toInt()); + linesOfScrollback.set(conf.value(KEY_LINES_OF_SCROLLBACK, 10000).toInt()); + linesOfInputHistory.set(conf.value(KEY_LINES_OF_INPUT_HISTORY, 100).toInt()); + tabCompletionDictionarySize.set(conf.value(KEY_TAB_COMPLETION_DICTIONARY_SIZE, 100).toInt()); + clearInputOnEnter.set(conf.value(KEY_CLEAR_INPUT_ON_ENTER, false).toBool()); + autoResizeTerminal.set(conf.value(KEY_AUTO_RESIZE_TERMINAL, true).toBool()); + linesOfPeekPreview.set(conf.value(KEY_LINES_OF_PEEK_PREVIEW, 7).toInt()); + audibleBell.set(conf.value(KEY_BELL_AUDIBLE, true).toBool()); + visualBell.set(conf.value(KEY_BELL_VISUAL, (CURRENT_PLATFORM == PlatformEnum::Wasm)).toBool()); + useCommandSeparator.set(conf.value(KEY_USE_COMMAND_SEPARATOR, false).toBool()); + commandSeparator.set(conf.value(KEY_COMMAND_SEPARATOR, QString(char_consts::C_SEMICOLON)) + .toString()); } void Configuration::RoomPanelSettings::read(const QSettings &conf) { - geometry = conf.value(KEY_WINDOW_GEOMETRY).toByteArray(); + geometry.set(conf.value(KEY_WINDOW_GEOMETRY).toByteArray()); } void Configuration::InfomarksDialog::read(const QSettings &conf) { - geometry = conf.value(KEY_WINDOW_GEOMETRY).toByteArray(); + geometry.set(conf.value(KEY_WINDOW_GEOMETRY).toByteArray()); } void Configuration::RoomEditDialog::read(const QSettings &conf) { - geometry = conf.value(KEY_WINDOW_GEOMETRY).toByteArray(); + geometry.set(conf.value(KEY_WINDOW_GEOMETRY).toByteArray()); } void Configuration::FindRoomsDialog::read(const QSettings &conf) { - geometry = conf.value(KEY_WINDOW_GEOMETRY).toByteArray(); + geometry.set(conf.value(KEY_WINDOW_GEOMETRY).toByteArray()); } void Configuration::GeneralSettings::write(QSettings &conf) const { conf.setValue(KEY_RUN_FIRST_TIME, false); - conf.setValue(KEY_WINDOW_GEOMETRY, windowGeometry); - conf.setValue(KEY_WINDOW_STATE, windowState); - conf.setValue(KEY_ALWAYS_ON_TOP, alwaysOnTop); - conf.setValue(KEY_SHOW_STATUS_BAR, showStatusBar); - conf.setValue(KEY_SHOW_SCROLL_BARS, showScrollBars); - conf.setValue(KEY_SHOW_MENU_BAR, showMenuBar); - conf.setValue(KEY_MAP_MODE, static_cast(mapMode)); - conf.setValue(KEY_CHECK_FOR_UPDATE, checkForUpdate); - conf.setValue(KEY_CHARACTER_ENCODING, static_cast(characterEncoding)); - conf.setValue(KEY_THEME, static_cast(m_theme)); + conf.setValue(KEY_WINDOW_GEOMETRY, windowGeometry.get()); + conf.setValue(KEY_WINDOW_STATE, windowState.get()); + conf.setValue(KEY_ALWAYS_ON_TOP, alwaysOnTop.get()); + conf.setValue(KEY_SHOW_STATUS_BAR, showStatusBar.get()); + conf.setValue(KEY_SHOW_SCROLL_BARS, showScrollBars.get()); + conf.setValue(KEY_SHOW_MENU_BAR, showMenuBar.get()); + conf.setValue(KEY_MAP_MODE, static_cast(mapMode.get())); + conf.setValue(KEY_CHECK_FOR_UPDATE, checkForUpdate.get()); + conf.setValue(KEY_CHARACTER_ENCODING, static_cast(characterEncoding.get())); + conf.setValue(KEY_THEME, static_cast(theme.get())); } void Configuration::ConnectionSettings::write(QSettings &conf) const { - conf.setValue(KEY_SERVER_NAME, remoteServerName); - conf.setValue(KEY_MUME_REMOTE_PORT, static_cast(remotePort)); - conf.setValue(KEY_PROXY_LOCAL_PORT, static_cast(localPort)); - conf.setValue(KEY_TLS_ENCRYPTION, tlsEncryption); - conf.setValue(KEY_PROXY_CONNECTION_STATUS, proxyConnectionStatus); - conf.setValue(KEY_PROXY_LISTENS_ON_ANY_INTERFACE, proxyListensOnAnyInterface); + conf.setValue(KEY_SERVER_NAME, remoteServerName.get()); + conf.setValue(KEY_MUME_REMOTE_PORT, static_cast(remotePort.get())); + conf.setValue(KEY_PROXY_LOCAL_PORT, static_cast(localPort.get())); + conf.setValue(KEY_TLS_ENCRYPTION, tlsEncryption.get()); + conf.setValue(KEY_PROXY_CONNECTION_STATUS, proxyConnectionStatus.get()); + conf.setValue(KEY_PROXY_LISTENS_ON_ANY_INTERFACE, proxyListensOnAnyInterface.get()); } NODISCARD static auto getQColorName(const XNamedColor &color) @@ -853,16 +1021,16 @@ NODISCARD static auto getQColorName(const XNamedColor &color) void Configuration::CanvasSettings::write(QSettings &conf) const { - conf.setValue(KEY_RESOURCES_DIRECTORY, resourcesDirectory); + conf.setValue(KEY_RESOURCES_DIRECTORY, resourcesDirectory.get()); conf.setValue(KEY_SHOW_MISSING_MAP_ID, showMissingMapId.get()); conf.setValue(KEY_SHOW_UNSAVED_CHANGES, showUnsavedChanges.get()); conf.setValue(KEY_DRAW_NOT_MAPPED_EXITS, showUnmappedExits.get()); - conf.setValue(KEY_DRAW_UPPER_LAYERS_TEXTURED, drawUpperLayersTextured); - conf.setValue(KEY_DRAW_DOOR_NAMES, drawDoorNames); - conf.setValue(KEY_BACKGROUND_COLOR, getQColorName(backgroundColor)); - conf.setValue(KEY_ROOM_DARK_COLOR, getQColorName(roomDarkColor)); - conf.setValue(KEY_ROOM_DARK_LIT_COLOR, getQColorName(roomDarkLitColor)); - conf.setValue(KEY_CONNECTION_NORMAL_COLOR, getQColorName(connectionNormalColor)); + conf.setValue(KEY_DRAW_UPPER_LAYERS_TEXTURED, drawUpperLayersTextured.get()); + conf.setValue(KEY_DRAW_DOOR_NAMES, drawDoorNames.get()); + conf.setValue(KEY_BACKGROUND_COLOR, backgroundColor.get().getQColor().name()); + conf.setValue(KEY_ROOM_DARK_COLOR, roomDarkColor.get().getQColor().name()); + conf.setValue(KEY_ROOM_DARK_LIT_COLOR, roomDarkLitColor.get().getQColor().name()); + conf.setValue(KEY_CONNECTION_NORMAL_COLOR, connectionNormalColor.get().getQColor().name()); conf.setValue(KEY_NUMBER_OF_ANTI_ALIASING_SAMPLES, antialiasingSamples.get()); conf.setValue(KEY_USE_TRILINEAR_FILTERING, trilinearFiltering.get()); conf.setValue(KEY_3D_CANVAS, advanced.use3D.get()); @@ -881,130 +1049,130 @@ void Configuration::CanvasSettings::write(QSettings &conf) const void Configuration::AccountSettings::write(QSettings &conf) const { - conf.setValue(KEY_ACCOUNT_NAME, accountName); - conf.setValue(KEY_ACCOUNT_PASSWORD, accountPassword); - conf.setValue(KEY_REMEMBER_LOGIN, rememberLogin); + conf.setValue(KEY_ACCOUNT_NAME, accountName.get()); + conf.setValue(KEY_ACCOUNT_PASSWORD, accountPassword.get()); + conf.setValue(KEY_REMEMBER_LOGIN, rememberLogin.get()); } void Configuration::AutoLoadSettings::write(QSettings &conf) const { - conf.setValue(KEY_AUTO_LOAD, autoLoadMap); - conf.setValue(KEY_FILE_NAME, fileName); - conf.setValue(KEY_LAST_MAP_LOAD_DIRECTORY, lastMapDirectory); + conf.setValue(KEY_AUTO_LOAD, autoLoadMap.get()); + conf.setValue(KEY_FILE_NAME, fileName.get()); + conf.setValue(KEY_LAST_MAP_LOAD_DIRECTORY, lastMapDirectory.get()); } void Configuration::AutoLogSettings::write(QSettings &conf) const { - conf.setValue(KEY_AUTO_LOG, autoLog); - conf.setValue(KEY_AUTO_LOG_CLEANUP_STRATEGY, static_cast(cleanupStrategy)); - conf.setValue(KEY_AUTO_LOG_DIRECTORY, autoLogDirectory); - conf.setValue(KEY_AUTO_LOG_ROTATE_SIZE_BYTES, rotateWhenLogsReachBytes); - conf.setValue(KEY_AUTO_LOG_ASK_DELETE, askDelete); - conf.setValue(KEY_AUTO_LOG_DELETE_AFTER_DAYS, deleteWhenLogsReachDays); - conf.setValue(KEY_AUTO_LOG_DELETE_AFTER_BYTES, deleteWhenLogsReachBytes); + conf.setValue(KEY_AUTO_LOG, autoLog.get()); + conf.setValue(KEY_AUTO_LOG_CLEANUP_STRATEGY, static_cast(cleanupStrategy.get())); + conf.setValue(KEY_AUTO_LOG_DIRECTORY, autoLogDirectory.get()); + conf.setValue(KEY_AUTO_LOG_ROTATE_SIZE_BYTES, rotateWhenLogsReachBytes.get()); + conf.setValue(KEY_AUTO_LOG_ASK_DELETE, askDelete.get()); + conf.setValue(KEY_AUTO_LOG_DELETE_AFTER_DAYS, deleteWhenLogsReachDays.get()); + conf.setValue(KEY_AUTO_LOG_DELETE_AFTER_BYTES, deleteWhenLogsReachBytes.get()); } void Configuration::ParserSettings::write(QSettings &conf) const { - conf.setValue(KEY_ROOM_NAME_ANSI_COLOR, roomNameColor); - conf.setValue(KEY_ROOM_DESC_ANSI_COLOR, roomDescColor); - conf.setValue(KEY_COMMAND_PREFIX_CHAR, QChar::fromLatin1(prefixChar)); - conf.setValue(KEY_EMOJI_ENCODE, encodeEmoji); - conf.setValue(KEY_EMOJI_DECODE, decodeEmoji); + conf.setValue(KEY_ROOM_NAME_ANSI_COLOR, roomNameColor.get()); + conf.setValue(KEY_ROOM_DESC_ANSI_COLOR, roomDescColor.get()); + conf.setValue(KEY_COMMAND_PREFIX_CHAR, QChar::fromLatin1(static_cast(prefixChar.get()))); + conf.setValue(KEY_EMOJI_ENCODE, encodeEmoji.get()); + conf.setValue(KEY_EMOJI_DECODE, decodeEmoji.get()); } void Configuration::MumeNativeSettings::write(QSettings &conf) const { - conf.setValue(KEY_EMULATED_EXITS, emulatedExits); - conf.setValue(KEY_SHOW_HIDDEN_EXIT_FLAGS, showHiddenExitFlags); - conf.setValue(KEY_SHOW_NOTES, showNotes); + conf.setValue(KEY_EMULATED_EXITS, emulatedExits.get()); + conf.setValue(KEY_SHOW_HIDDEN_EXIT_FLAGS, showHiddenExitFlags.get()); + conf.setValue(KEY_SHOW_NOTES, showNotes.get()); } void Configuration::MumeClientProtocolSettings::write(QSettings &conf) const { - conf.setValue(KEY_USE_INTERNAL_EDITOR, internalRemoteEditor); - conf.setValue(KEY_EXTERNAL_EDITOR_COMMAND, externalRemoteEditorCommand); + conf.setValue(KEY_USE_INTERNAL_EDITOR, internalRemoteEditor.get()); + conf.setValue(KEY_EXTERNAL_EDITOR_COMMAND, externalRemoteEditorCommand.get()); } void Configuration::PathMachineSettings::write(QSettings &conf) const { - conf.setValue(KEY_RELATIVE_PATH_ACCEPTANCE, acceptBestRelative); - conf.setValue(KEY_ABSOLUTE_PATH_ACCEPTANCE, acceptBestAbsolute); - conf.setValue(KEY_ROOM_CREATION_PENALTY, newRoomPenalty); - conf.setValue(KEY_CORRECT_POSITION_BONUS, correctPositionBonus); - conf.setValue(KEY_MAXIMUM_NUMBER_OF_PATHS, utils::clampNonNegative(maxPaths)); - conf.setValue(KEY_ROOM_MATCHING_TOLERANCE, utils::clampNonNegative(matchingTolerance)); - conf.setValue(KEY_MULTIPLE_CONNECTIONS_PENALTY, multipleConnectionsPenalty); + conf.setValue(KEY_RELATIVE_PATH_ACCEPTANCE, acceptBestRelative.get()); + conf.setValue(KEY_ABSOLUTE_PATH_ACCEPTANCE, acceptBestAbsolute.get()); + conf.setValue(KEY_ROOM_CREATION_PENALTY, newRoomPenalty.get()); + conf.setValue(KEY_CORRECT_POSITION_BONUS, correctPositionBonus.get()); + conf.setValue(KEY_MAXIMUM_NUMBER_OF_PATHS, utils::clampNonNegative(maxPaths.get())); + conf.setValue(KEY_ROOM_MATCHING_TOLERANCE, utils::clampNonNegative(matchingTolerance.get())); + conf.setValue(KEY_MULTIPLE_CONNECTIONS_PENALTY, multipleConnectionsPenalty.get()); } void Configuration::GroupManagerSettings::write(QSettings &conf) const { - conf.setValue(KEY_GROUP_YOUR_COLOR, color.name()); - conf.setValue(KEY_GROUP_NPC_COLOR, npcColor); - conf.setValue(KEY_GROUP_NPC_COLOR_OVERRIDE, npcColorOverride); - conf.setValue(KEY_GROUP_NPC_HIDE, npcHide); - conf.setValue(KEY_GROUP_NPC_SORT_BOTTOM, npcSortBottom); + conf.setValue(KEY_GROUP_YOUR_COLOR, color.get().name()); + conf.setValue(KEY_GROUP_NPC_COLOR, npcColor.get()); + conf.setValue(KEY_GROUP_NPC_COLOR_OVERRIDE, npcColorOverride.get()); + conf.setValue(KEY_GROUP_NPC_HIDE, npcHide.get()); + conf.setValue(KEY_GROUP_NPC_SORT_BOTTOM, npcSortBottom.get()); } void Configuration::MumeClockSettings::write(QSettings &conf) const { // Note: There's no QVariant(int64_t) constructor. - conf.setValue(KEY_MUME_START_EPOCH, static_cast(startEpoch)); - conf.setValue(KEY_DISPLAY_CLOCK, display); + conf.setValue(KEY_MUME_START_EPOCH, static_cast(startEpoch.get())); + conf.setValue(KEY_DISPLAY_CLOCK, display.get()); } void Configuration::AdventurePanelSettings::write(QSettings &conf) const { - conf.setValue(KEY_DISPLAY_XP_STATUS, m_displayXPStatus); + conf.setValue(KEY_DISPLAY_XP_STATUS, displayXPStatus.get()); } void Configuration::AudioSettings::write(QSettings &conf) const { if constexpr (CURRENT_PLATFORM != PlatformEnum::Wasm) { - conf.setValue(KEY_AUDIO_UNLOCKED, m_unlocked); + conf.setValue(KEY_AUDIO_UNLOCKED, unlocked.get()); } - conf.setValue(KEY_MUSIC_VOLUME, m_musicVolume); - conf.setValue(KEY_SOUND_VOLUME, m_soundVolume); - conf.setValue(KEY_AUDIO_OUTPUT_DEVICE, m_outputDeviceId); + conf.setValue(KEY_MUSIC_VOLUME, musicVolume.get()); + conf.setValue(KEY_SOUND_VOLUME, soundVolume.get()); + conf.setValue(KEY_AUDIO_OUTPUT_DEVICE, outputDeviceId.get()); } void Configuration::IntegratedMudClientSettings::write(QSettings &conf) const { - conf.setValue(KEY_FONT, font); - conf.setValue(KEY_BACKGROUND_COLOR, backgroundColor.name()); - conf.setValue(KEY_FOREGROUND_COLOR, foregroundColor.name()); - conf.setValue(KEY_COLUMNS, columns); - conf.setValue(KEY_ROWS, rows); - conf.setValue(KEY_LINES_OF_SCROLLBACK, linesOfScrollback); - conf.setValue(KEY_LINES_OF_INPUT_HISTORY, linesOfInputHistory); - conf.setValue(KEY_TAB_COMPLETION_DICTIONARY_SIZE, tabCompletionDictionarySize); - conf.setValue(KEY_CLEAR_INPUT_ON_ENTER, clearInputOnEnter); - conf.setValue(KEY_AUTO_RESIZE_TERMINAL, autoResizeTerminal); - conf.setValue(KEY_LINES_OF_PEEK_PREVIEW, linesOfPeekPreview); - conf.setValue(KEY_BELL_AUDIBLE, audibleBell); - conf.setValue(KEY_BELL_VISUAL, visualBell); - conf.setValue(KEY_USE_COMMAND_SEPARATOR, useCommandSeparator); - conf.setValue(KEY_COMMAND_SEPARATOR, commandSeparator); + conf.setValue(KEY_FONT, font.get()); + conf.setValue(KEY_BACKGROUND_COLOR, backgroundColor.get().name()); + conf.setValue(KEY_FOREGROUND_COLOR, foregroundColor.get().name()); + conf.setValue(KEY_COLUMNS, columns.get()); + conf.setValue(KEY_ROWS, rows.get()); + conf.setValue(KEY_LINES_OF_SCROLLBACK, linesOfScrollback.get()); + conf.setValue(KEY_LINES_OF_INPUT_HISTORY, linesOfInputHistory.get()); + conf.setValue(KEY_TAB_COMPLETION_DICTIONARY_SIZE, tabCompletionDictionarySize.get()); + conf.setValue(KEY_CLEAR_INPUT_ON_ENTER, clearInputOnEnter.get()); + conf.setValue(KEY_AUTO_RESIZE_TERMINAL, autoResizeTerminal.get()); + conf.setValue(KEY_LINES_OF_PEEK_PREVIEW, linesOfPeekPreview.get()); + conf.setValue(KEY_BELL_AUDIBLE, audibleBell.get()); + conf.setValue(KEY_BELL_VISUAL, visualBell.get()); + conf.setValue(KEY_USE_COMMAND_SEPARATOR, useCommandSeparator.get()); + conf.setValue(KEY_COMMAND_SEPARATOR, commandSeparator.get()); } void Configuration::RoomPanelSettings::write(QSettings &conf) const { - conf.setValue(KEY_WINDOW_GEOMETRY, geometry); + conf.setValue(KEY_WINDOW_GEOMETRY, geometry.get()); } void Configuration::InfomarksDialog::write(QSettings &conf) const { - conf.setValue(KEY_WINDOW_GEOMETRY, geometry); + conf.setValue(KEY_WINDOW_GEOMETRY, geometry.get()); } void Configuration::RoomEditDialog::write(QSettings &conf) const { - conf.setValue(KEY_WINDOW_GEOMETRY, geometry); + conf.setValue(KEY_WINDOW_GEOMETRY, geometry.get()); } void Configuration::FindRoomsDialog::write(QSettings &conf) const { - conf.setValue(KEY_WINDOW_GEOMETRY, geometry); + conf.setValue(KEY_WINDOW_GEOMETRY, geometry.get()); } Configuration &setConfig() @@ -1067,6 +1235,12 @@ void Configuration::NamedColorOptions::resetToDefaults() Configuration::CanvasSettings::Advanced::Advanced() { + maximumFps.setFromNotifier(deref(setConfig().canvas.m_notifier)); + fov.setFromNotifier(deref(setConfig().canvas.m_notifier)); + verticalAngle.setFromNotifier(deref(setConfig().canvas.m_notifier)); + horizontalAngle.setFromNotifier(deref(setConfig().canvas.m_notifier)); + layerHeight.setFromNotifier(deref(setConfig().canvas.m_notifier)); + for (NamedConfig *const it : {&use3D, &autoTilt, &printPerfStats}) { const char *const name = it->getName().c_str(); qInfo() << "Checking environment variable" << name; @@ -1113,7 +1287,7 @@ void Configuration::ResolvedNamedColorOptions::setFrom(const NamedColorOptions & void Configuration::ResolvedCanvasNamedColorOptions::setFrom(const CanvasNamedColorOptions &from) { -#define X_CLONE(_id, _name) (this->_id) = (from._id).getColor(); +#define X_CLONE(_id, _name) (this->_id) = (from._id).get(); XFOREACH_CANVAS_NAMED_COLOR_OPTIONS(X_CLONE) #undef X_CLONE } diff --git a/src/configuration/configuration.h b/src/configuration/configuration.h index 6f174fd0a..5266aa40c 100644 --- a/src/configuration/configuration.h +++ b/src/configuration/configuration.h @@ -17,6 +17,7 @@ #include "GroupConfig.h" #include "NamedConfig.h" +#include #include #include @@ -46,12 +47,19 @@ class NODISCARD Configuration final void readFrom(QSettings &conf); void writeTo(QSettings &conf) const; + NODISCARD INamedConfig *lookup(const std::string &name) const; + NODISCARD const std::map &getRegistry() const; + +private: + void registerConfig(INamedConfig *config); + std::map m_registry; + public: struct NODISCARD GeneralSettings final { private: ChangeMonitor m_changeMonitor; - ThemeEnum m_theme = ThemeEnum::System; + const std::shared_ptr> m_notifier = std::make_shared>([this]() { m_changeMonitor.notifyAll(); }); public: explicit GeneralSettings() = default; @@ -59,30 +67,29 @@ class NODISCARD Configuration final DELETE_CTORS_AND_ASSIGN_OPS(GeneralSettings); public: - NODISCARD ThemeEnum getTheme() const { return m_theme; } - void setTheme(const ThemeEnum theme) - { - m_theme = theme; - m_changeMonitor.notifyAll(); - } - void registerChangeCallback(const ChangeMonitor::Lifetime &lifetime, const ChangeMonitor::Function &callback) { return m_changeMonitor.registerChangeCallback(lifetime, callback); } + void notifyChanged() { m_changeMonitor.notifyAll(); } + + NODISCARD ThemeEnum getTheme() const { return static_cast(theme.get()); } + void setTheme(const ThemeEnum t) { theme.set(static_cast(t)); } + public: - bool firstRun = false; - QByteArray windowGeometry; - QByteArray windowState; - bool alwaysOnTop = false; - bool showStatusBar = true; - bool showScrollBars = true; - bool showMenuBar = true; - MapModeEnum mapMode = MapModeEnum::PLAY; - bool checkForUpdate = true; - CharacterEncodingEnum characterEncoding = CharacterEncodingEnum::LATIN1; + NamedConfig firstRun{"GENERAL_FIRST_RUN", false, m_notifier}; + NamedConfig windowGeometry{"GENERAL_WINDOW_GEOMETRY", QByteArray(), m_notifier}; + NamedConfig windowState{"GENERAL_WINDOW_STATE", QByteArray(), m_notifier}; + NamedConfig alwaysOnTop{"GENERAL_ALWAYS_ON_TOP", false, m_notifier}; + NamedConfig showStatusBar{"GENERAL_SHOW_STATUS_BAR", true, m_notifier}; + NamedConfig showScrollBars{"GENERAL_SHOW_SCROLL_BARS", true, m_notifier}; + NamedConfig showMenuBar{"GENERAL_SHOW_MENU_BAR", true, m_notifier}; + NamedConfig mapMode{"GENERAL_MAP_MODE", static_cast(MapModeEnum::PLAY), m_notifier}; + NamedConfig checkForUpdate{"GENERAL_CHECK_FOR_UPDATE", true, m_notifier}; + NamedConfig characterEncoding{"GENERAL_CHARACTER_ENCODING", static_cast(CharacterEncodingEnum::LATIN1), m_notifier}; + NamedConfig theme{"GENERAL_THEME", static_cast(ThemeEnum::System), m_notifier}; private: SUBGROUP(); @@ -90,12 +97,29 @@ class NODISCARD Configuration final struct NODISCARD ConnectionSettings final { - QString remoteServerName; /// Remote host and port settings - uint16_t remotePort = 0u; - uint16_t localPort = 0u; /// Port to bind to on local machine - bool tlsEncryption = false; - bool proxyConnectionStatus = false; - bool proxyListensOnAnyInterface = false; + private: + ChangeMonitor m_changeMonitor; + const std::shared_ptr> m_notifier = std::make_shared>([this]() { m_changeMonitor.notifyAll(); }); + + public: + explicit ConnectionSettings() = default; + ~ConnectionSettings() = default; + DELETE_CTORS_AND_ASSIGN_OPS(ConnectionSettings); + + public: + void registerChangeCallback(const ChangeMonitor::Lifetime &lifetime, + const ChangeMonitor::Function &callback) + { + return m_changeMonitor.registerChangeCallback(lifetime, callback); + } + + public: + NamedConfig remoteServerName{"CONNECTION_REMOTE_SERVER_NAME", "mume.org", m_notifier}; + NamedConfig remotePort{"CONNECTION_REMOTE_PORT", 4242, m_notifier}; + NamedConfig localPort{"CONNECTION_LOCAL_PORT", 4242, m_notifier}; + NamedConfig tlsEncryption{"CONNECTION_TLS_ENCRYPTION", false, m_notifier}; + NamedConfig proxyConnectionStatus{"CONNECTION_PROXY_CONNECTION_STATUS", false, m_notifier}; + NamedConfig proxyListensOnAnyInterface{"CONNECTION_PROXY_LISTENS_ON_ANY_INTERFACE", false, m_notifier}; private: SUBGROUP(); @@ -103,11 +127,28 @@ class NODISCARD Configuration final struct NODISCARD ParserSettings final { - QString roomNameColor; // ANSI room name color - QString roomDescColor; // ANSI room descriptions color - char prefixChar = char_consts::C_UNDERSCORE; - bool encodeEmoji = true; - bool decodeEmoji = true; + private: + ChangeMonitor m_changeMonitor; + const std::shared_ptr> m_notifier = std::make_shared>([this]() { m_changeMonitor.notifyAll(); }); + + public: + explicit ParserSettings() = default; + ~ParserSettings() = default; + DELETE_CTORS_AND_ASSIGN_OPS(ParserSettings); + + public: + void registerChangeCallback(const ChangeMonitor::Lifetime &lifetime, + const ChangeMonitor::Function &callback) + { + return m_changeMonitor.registerChangeCallback(lifetime, callback); + } + + public: + NamedConfig roomNameColor{"PARSER_ROOM_NAME_COLOR", "[32m", m_notifier}; + NamedConfig roomDescColor{"PARSER_ROOM_DESC_COLOR", "[0m", m_notifier}; + NamedConfig prefixChar{"PARSER_PREFIX_CHAR", static_cast(char_consts::C_UNDERSCORE), m_notifier}; + NamedConfig encodeEmoji{"PARSER_ENCODE_EMOJI", true, m_notifier}; + NamedConfig decodeEmoji{"PARSER_DECODE_EMOJI", true, m_notifier}; private: SUBGROUP(); @@ -115,8 +156,25 @@ class NODISCARD Configuration final struct NODISCARD MumeClientProtocolSettings final { - bool internalRemoteEditor = false; - QString externalRemoteEditorCommand; + private: + ChangeMonitor m_changeMonitor; + const std::shared_ptr> m_notifier = std::make_shared>([this]() { m_changeMonitor.notifyAll(); }); + + public: + explicit MumeClientProtocolSettings() = default; + ~MumeClientProtocolSettings() = default; + DELETE_CTORS_AND_ASSIGN_OPS(MumeClientProtocolSettings); + + public: + void registerChangeCallback(const ChangeMonitor::Lifetime &lifetime, + const ChangeMonitor::Function &callback) + { + return m_changeMonitor.registerChangeCallback(lifetime, callback); + } + + public: + NamedConfig internalRemoteEditor{"MUME_PROTOCOL_INTERNAL_REMOTE_EDITOR", false, m_notifier}; + NamedConfig externalRemoteEditorCommand{"MUME_PROTOCOL_EXTERNAL_REMOTE_EDITOR_COMMAND", "", m_notifier}; private: SUBGROUP(); @@ -124,10 +182,26 @@ class NODISCARD Configuration final struct NODISCARD MumeNativeSettings final { - /* serialized */ - bool emulatedExits = false; - bool showHiddenExitFlags = false; - bool showNotes = false; + private: + ChangeMonitor m_changeMonitor; + const std::shared_ptr> m_notifier = std::make_shared>([this]() { m_changeMonitor.notifyAll(); }); + + public: + explicit MumeNativeSettings() = default; + ~MumeNativeSettings() = default; + DELETE_CTORS_AND_ASSIGN_OPS(MumeNativeSettings); + + public: + void registerChangeCallback(const ChangeMonitor::Lifetime &lifetime, + const ChangeMonitor::Function &callback) + { + return m_changeMonitor.registerChangeCallback(lifetime, callback); + } + + public: + NamedConfig emulatedExits{"MUME_NATIVE_EMULATED_EXITS", false, m_notifier}; + NamedConfig showHiddenExitFlags{"MUME_NATIVE_SHOW_HIDDEN_EXIT_FLAGS", false, m_notifier}; + NamedConfig showNotes{"MUME_NATIVE_SHOW_NOTES", false, m_notifier}; private: SUBGROUP(); @@ -150,7 +224,7 @@ class NODISCARD Configuration final struct NODISCARD CanvasNamedColorOptions { -#define X_DECL(_id, _name) XNamedColor _id{NamedColorEnum::_name}; +#define X_DECL(_id, _name) NamedConfig _id{"CANVAS_COLOR_" #_name, Colors::white}; XFOREACH_CANVAS_NAMED_COLOR_OPTIONS(X_DECL) #undef X_DECL @@ -165,28 +239,47 @@ class NODISCARD Configuration final struct NODISCARD CanvasSettings final : public CanvasNamedColorOptions { - NamedConfig antialiasingSamples{"ANTIALIASING_SAMPLES", 0}; - NamedConfig trilinearFiltering{"TRILINEAR_FILTERING", true}; - NamedConfig showMissingMapId{"SHOW_MISSING_MAPID", false}; - NamedConfig showUnsavedChanges{"SHOW_UNSAVED_CHANGES", false}; - NamedConfig showUnmappedExits{"SHOW_UNMAPPED_EXITS", false}; - bool drawUpperLayersTextured = false; - bool drawDoorNames = false; - bool softwareOpenGL = false; - QString resourcesDirectory; + friend struct Advanced; + + private: + ChangeMonitor m_changeMonitor; + const std::shared_ptr> m_notifier = std::make_shared>([this]() { m_changeMonitor.notifyAll(); }); + + public: + explicit CanvasSettings(); + ~CanvasSettings() = default; + DELETE_CTORS_AND_ASSIGN_OPS(CanvasSettings); + + public: + void registerChangeCallback(const ChangeMonitor::Lifetime &lifetime, + const ChangeMonitor::Function &callback) + { + return m_changeMonitor.registerChangeCallback(lifetime, callback); + } + + public: + NamedConfig antialiasingSamples{"ANTIALIASING_SAMPLES", 0, m_notifier}; + NamedConfig trilinearFiltering{"TRILINEAR_FILTERING", true, m_notifier}; + NamedConfig showMissingMapId{"SHOW_MISSING_MAPID", false, m_notifier}; + NamedConfig showUnsavedChanges{"SHOW_UNSAVED_CHANGES", false, m_notifier}; + NamedConfig showUnmappedExits{"SHOW_UNMAPPED_EXITS", false, m_notifier}; + NamedConfig drawUpperLayersTextured{"CANVAS_DRAW_UPPER_LAYERS_TEXTURED", false, m_notifier}; + NamedConfig drawDoorNames{"CANVAS_DRAW_DOOR_NAMES", false, m_notifier}; + NamedConfig softwareOpenGL{"CANVAS_SOFTWARE_OPENGL", false, m_notifier}; + NamedConfig resourcesDirectory{"CANVAS_RESOURCES_DIRECTORY", "", m_notifier}; // not saved yet: - bool drawCharBeacons = true; - float charBeaconScaleCutoff = 0.4f; - float doorNameScaleCutoff = 0.4f; - float infomarkScaleCutoff = 0.25f; - float extraDetailScaleCutoff = 0.15f; + NamedConfig drawCharBeacons{"CANVAS_DRAW_CHAR_BEACONS", true, m_notifier}; + NamedConfig charBeaconScaleCutoff{"CANVAS_CHAR_BEACON_SCALE_CUTOFF", 0.4f, m_notifier}; + NamedConfig doorNameScaleCutoff{"CANVAS_DOOR_NAME_SCALE_CUTOFF", 0.4f, m_notifier}; + NamedConfig infomarkScaleCutoff{"CANVAS_INFOMARK_SCALE_CUTOFF", 0.25f, m_notifier}; + NamedConfig extraDetailScaleCutoff{"CANVAS_EXTRA_DETAIL_SCALE_CUTOFF", 0.15f, m_notifier}; MMapper::Array mapRadius{100, 100, 100}; - NamedConfig weatherAtmosphereIntensity{"WEATHER_ATMOSPHERE_INTENSITY", 50}; - NamedConfig weatherPrecipitationIntensity{"WEATHER_PRECIPITATION_INTENSITY", 50}; - NamedConfig weatherTimeOfDayIntensity{"WEATHER_TIME_OF_DAY_INTENSITY", 50}; + NamedConfig weatherAtmosphereIntensity{"WEATHER_ATMOSPHERE_INTENSITY", 50, m_notifier}; + NamedConfig weatherPrecipitationIntensity{"WEATHER_PRECIPITATION_INTENSITY", 50, m_notifier}; + NamedConfig weatherTimeOfDayIntensity{"WEATHER_TIME_OF_DAY_INTENSITY", 50, m_notifier}; struct NODISCARD Advanced final { @@ -253,9 +346,9 @@ class NODISCARD Configuration final struct NODISCARD AccountSettings final { - QString accountName; - bool accountPassword = false; - bool rememberLogin = false; + NamedConfig accountName{"ACCOUNT_NAME", ""}; + NamedConfig accountPassword{"ACCOUNT_PASSWORD", false}; + NamedConfig rememberLogin{"ACCOUNT_REMEMBER_LOGIN", false}; private: SUBGROUP(); @@ -263,9 +356,9 @@ class NODISCARD Configuration final struct NODISCARD AutoLoadSettings final { - bool autoLoadMap = false; - QString fileName; - QString lastMapDirectory; + NamedConfig autoLoadMap{"AUTO_LOAD_MAP", false}; + NamedConfig fileName{"AUTO_LOAD_FILE_NAME", ""}; + NamedConfig lastMapDirectory{"AUTO_LOAD_LAST_MAP_DIRECTORY", ""}; private: SUBGROUP(); @@ -273,13 +366,13 @@ class NODISCARD Configuration final struct NODISCARD AutoLogSettings final { - QString autoLogDirectory; - bool autoLog = false; - AutoLoggerEnum cleanupStrategy = AutoLoggerEnum::DeleteDays; - int deleteWhenLogsReachDays = 0; - int deleteWhenLogsReachBytes = 0; - bool askDelete = false; - int rotateWhenLogsReachBytes = 0; + NamedConfig autoLogDirectory{"AUTO_LOG_DIRECTORY", ""}; + NamedConfig autoLog{"AUTO_LOG_ENABLED", false}; + NamedConfig cleanupStrategy{"AUTO_LOG_CLEANUP_STRATEGY", static_cast(AutoLoggerEnum::DeleteDays)}; + NamedConfig deleteWhenLogsReachDays{"AUTO_LOG_DELETE_WHEN_REACH_DAYS", 0}; + NamedConfig deleteWhenLogsReachBytes{"AUTO_LOG_DELETE_WHEN_REACH_BYTES", 0}; + NamedConfig askDelete{"AUTO_LOG_ASK_DELETE", false}; + NamedConfig rotateWhenLogsReachBytes{"AUTO_LOG_ROTATE_WHEN_REACH_BYTES", 0}; private: SUBGROUP(); @@ -287,13 +380,30 @@ class NODISCARD Configuration final struct NODISCARD PathMachineSettings final { - double acceptBestRelative = 0.0; - double acceptBestAbsolute = 0.0; - double newRoomPenalty = 0.0; - double multipleConnectionsPenalty = 0.0; - double correctPositionBonus = 0.0; - int maxPaths = 0; - int matchingTolerance = 0; + private: + ChangeMonitor m_changeMonitor; + const std::shared_ptr> m_notifier = std::make_shared>([this]() { m_changeMonitor.notifyAll(); }); + + public: + explicit PathMachineSettings() = default; + ~PathMachineSettings() = default; + DELETE_CTORS_AND_ASSIGN_OPS(PathMachineSettings); + + public: + void registerChangeCallback(const ChangeMonitor::Lifetime &lifetime, + const ChangeMonitor::Function &callback) + { + return m_changeMonitor.registerChangeCallback(lifetime, callback); + } + + public: + NamedConfig acceptBestRelative{"PATH_MACHINE_ACCEPT_BEST_RELATIVE", 0.0, m_notifier}; + NamedConfig acceptBestAbsolute{"PATH_MACHINE_ACCEPT_BEST_ABSOLUTE", 0.0, m_notifier}; + NamedConfig newRoomPenalty{"PATH_MACHINE_NEW_ROOM_PENALTY", 0.0, m_notifier}; + NamedConfig multipleConnectionsPenalty{"PATH_MACHINE_MULTIPLE_CONNECTIONS_PENALTY", 0.0, m_notifier}; + NamedConfig correctPositionBonus{"PATH_MACHINE_CORRECT_POSITION_BONUS", 0.0, m_notifier}; + NamedConfig maxPaths{"PATH_MACHINE_MAX_PATHS", 0, m_notifier}; + NamedConfig matchingTolerance{"PATH_MACHINE_MATCHING_TOLERANCE", 0, m_notifier}; private: SUBGROUP(); @@ -301,11 +411,28 @@ class NODISCARD Configuration final struct NODISCARD GroupManagerSettings final { - QColor color; - QColor npcColor; - bool npcColorOverride = false; - bool npcSortBottom = false; - bool npcHide = false; + private: + ChangeMonitor m_changeMonitor; + const std::shared_ptr> m_notifier = std::make_shared>([this]() { m_changeMonitor.notifyAll(); }); + + public: + explicit GroupManagerSettings() = default; + ~GroupManagerSettings() = default; + DELETE_CTORS_AND_ASSIGN_OPS(GroupManagerSettings); + + public: + void registerChangeCallback(const ChangeMonitor::Lifetime &lifetime, + const ChangeMonitor::Function &callback) + { + return m_changeMonitor.registerChangeCallback(lifetime, callback); + } + + public: + NamedConfig color{"GROUP_MANAGER_COLOR", QColor(Qt::yellow), m_notifier}; + NamedConfig npcColor{"GROUP_MANAGER_NPC_COLOR", QColor(Qt::lightGray), m_notifier}; + NamedConfig npcColorOverride{"GROUP_MANAGER_NPC_COLOR_OVERRIDE", false, m_notifier}; + NamedConfig npcSortBottom{"GROUP_MANAGER_NPC_SORT_BOTTOM", false, m_notifier}; + NamedConfig npcHide{"GROUP_MANAGER_NPC_HIDE", false, m_notifier}; private: SUBGROUP(); @@ -313,8 +440,8 @@ class NODISCARD Configuration final struct NODISCARD MumeClockSettings final { - int64_t startEpoch = 0; - bool display = false; + NamedConfig startEpoch{"MUME_CLOCK_START_EPOCH", 0}; + NamedConfig display{"MUME_CLOCK_DISPLAY", false}; private: SUBGROUP(); @@ -324,7 +451,7 @@ class NODISCARD Configuration final { private: ChangeMonitor m_changeMonitor; - bool m_displayXPStatus = false; + const std::shared_ptr> m_notifier = std::make_shared>([this]() { m_changeMonitor.notifyAll(); }); public: explicit AdventurePanelSettings() = default; @@ -332,19 +459,20 @@ class NODISCARD Configuration final DELETE_CTORS_AND_ASSIGN_OPS(AdventurePanelSettings); public: - NODISCARD bool getDisplayXPStatus() const { return m_displayXPStatus; } - void setDisplayXPStatus(const bool display) - { - m_displayXPStatus = display; - m_changeMonitor.notifyAll(); - } - void registerChangeCallback(const ChangeMonitor::Lifetime &lifetime, const ChangeMonitor::Function &callback) { return m_changeMonitor.registerChangeCallback(lifetime, callback); } + void notifyChanged() { m_changeMonitor.notifyAll(); } + + NODISCARD bool getDisplayXPStatus() const { return displayXPStatus.get(); } + void setDisplayXPStatus(const bool display) { displayXPStatus.set(display); } + + public: + NamedConfig displayXPStatus{"ADVENTURE_PANEL_DISPLAY_XP_STATUS", false, m_notifier}; + private: SUBGROUP(); } adventurePanel; @@ -353,10 +481,7 @@ class NODISCARD Configuration final { private: ChangeMonitor m_changeMonitor; - int m_musicVolume = 50; - int m_soundVolume = 50; - QByteArray m_outputDeviceId; - bool m_unlocked = false; + const std::shared_ptr> m_notifier = std::make_shared>([this]() { m_changeMonitor.notifyAll(); }); public: explicit AudioSettings() = default; @@ -364,39 +489,31 @@ class NODISCARD Configuration final DELETE_CTORS_AND_ASSIGN_OPS(AudioSettings); public: - NODISCARD int getMusicVolume() const { return m_musicVolume; } - void setMusicVolume(const int volume) + void registerChangeCallback(const ChangeMonitor::Lifetime &lifetime, + const ChangeMonitor::Function &callback) { - m_musicVolume = volume; - m_changeMonitor.notifyAll(); + return m_changeMonitor.registerChangeCallback(lifetime, callback); } - NODISCARD int getSoundVolume() const { return m_soundVolume; } - void setSoundVolume(const int volume) - { - m_soundVolume = volume; - m_changeMonitor.notifyAll(); - } + void notifyChanged() { m_changeMonitor.notifyAll(); } - NODISCARD const QByteArray &getOutputDeviceId() const { return m_outputDeviceId; } - void setOutputDeviceId(const QByteArray &id) - { - m_outputDeviceId = id; - m_changeMonitor.notifyAll(); - } + NODISCARD int getMusicVolume() const { return musicVolume.get(); } + void setMusicVolume(const int volume) { musicVolume.set(volume); } - NODISCARD bool isUnlocked() const { return m_unlocked; } - void setUnlocked() - { - m_unlocked = true; - m_changeMonitor.notifyAll(); - } + NODISCARD int getSoundVolume() const { return soundVolume.get(); } + void setSoundVolume(const int volume) { soundVolume.set(volume); } - void registerChangeCallback(const ChangeMonitor::Lifetime &lifetime, - const ChangeMonitor::Function &callback) - { - return m_changeMonitor.registerChangeCallback(lifetime, callback); - } + NODISCARD const QByteArray &getOutputDeviceId() const { return outputDeviceId.get(); } + void setOutputDeviceId(const QByteArray &id) { outputDeviceId.set(id); } + + NODISCARD bool isUnlocked() const { return unlocked.get(); } + void setUnlocked() { unlocked.set(true); } + + public: + NamedConfig musicVolume{"AUDIO_MUSIC_VOLUME", 50, m_notifier}; + NamedConfig soundVolume{"AUDIO_SOUND_VOLUME", 50, m_notifier}; + NamedConfig outputDeviceId{"AUDIO_OUTPUT_DEVICE_ID", QByteArray(), m_notifier}; + NamedConfig unlocked{"AUDIO_UNLOCKED", false, m_notifier}; private: SUBGROUP(); @@ -404,21 +521,21 @@ class NODISCARD Configuration final struct NODISCARD IntegratedMudClientSettings final { - QString font; - QColor foregroundColor; - QColor backgroundColor; - QString commandSeparator; - int columns = 0; - int rows = 0; - int linesOfScrollback = 0; - int linesOfInputHistory = 0; - int tabCompletionDictionarySize = 0; - bool clearInputOnEnter = false; - bool autoResizeTerminal = false; - int linesOfPeekPreview = 0; - bool audibleBell = false; - bool visualBell = false; - bool useCommandSeparator = false; + NamedConfig font{"INTEGRATED_CLIENT_FONT", ""}; + NamedConfig foregroundColor{"INTEGRATED_CLIENT_FOREGROUND_COLOR", QColor(Qt::lightGray)}; + NamedConfig backgroundColor{"INTEGRATED_CLIENT_BACKGROUND_COLOR", QColor(Qt::black)}; + NamedConfig commandSeparator{"INTEGRATED_CLIENT_COMMAND_SEPARATOR", ""}; + NamedConfig columns{"INTEGRATED_CLIENT_COLUMNS", 0}; + NamedConfig rows{"INTEGRATED_CLIENT_ROWS", 0}; + NamedConfig linesOfScrollback{"INTEGRATED_CLIENT_LINES_OF_SCROLLBACK", 0}; + NamedConfig linesOfInputHistory{"INTEGRATED_CLIENT_LINES_OF_INPUT_HISTORY", 0}; + NamedConfig tabCompletionDictionarySize{"INTEGRATED_CLIENT_TAB_COMPLETION_DICTIONARY_SIZE", 0}; + NamedConfig clearInputOnEnter{"INTEGRATED_CLIENT_CLEAR_INPUT_ON_ENTER", false}; + NamedConfig autoResizeTerminal{"INTEGRATED_CLIENT_AUTO_RESIZE_TERMINAL", false}; + NamedConfig linesOfPeekPreview{"INTEGRATED_CLIENT_LINES_OF_PEEK_PREVIEW", 0}; + NamedConfig audibleBell{"INTEGRATED_CLIENT_AUDIBLE_BELL", false}; + NamedConfig visualBell{"INTEGRATED_CLIENT_VISUAL_BELL", false}; + NamedConfig useCommandSeparator{"INTEGRATED_CLIENT_USE_COMMAND_SEPARATOR", false}; private: SUBGROUP(); @@ -426,7 +543,7 @@ class NODISCARD Configuration final struct NODISCARD RoomPanelSettings final { - QByteArray geometry; + NamedConfig geometry{"ROOM_PANEL_GEOMETRY", QByteArray()}; private: SUBGROUP(); @@ -434,7 +551,7 @@ class NODISCARD Configuration final struct NODISCARD InfomarksDialog final { - QByteArray geometry; + NamedConfig geometry{"INFOMARKS_DIALOG_GEOMETRY", QByteArray()}; private: SUBGROUP(); @@ -442,7 +559,7 @@ class NODISCARD Configuration final struct NODISCARD RoomEditDialog final { - QByteArray geometry; + NamedConfig geometry{"ROOM_EDIT_DIALOG_GEOMETRY", QByteArray()}; private: SUBGROUP(); @@ -450,7 +567,7 @@ class NODISCARD Configuration final struct NODISCARD FindRoomsDialog final { - QByteArray geometry; + NamedConfig geometry{"FIND_ROOMS_DIALOG_GEOMETRY", QByteArray()}; private: SUBGROUP(); diff --git a/src/display/mapwindow.cpp b/src/display/mapwindow.cpp index 5e01eb4e5..bb81fa7c7 100644 --- a/src/display/mapwindow.cpp +++ b/src/display/mapwindow.cpp @@ -179,6 +179,10 @@ MapWindow::MapWindow(MapData &mapData, m_scrollTimer = mmqt::makeQPointer(this); connect(m_scrollTimer, &QTimer::timeout, this, &MapWindow::slot_scrollTimerTimeout); + + setConfig().canvas.registerChangeCallback(m_lifetime, [this]() { + slot_graphicsSettingsChanged(); + }); } void MapWindow::hideSplashImage() diff --git a/src/display/mapwindow.h b/src/display/mapwindow.h index b11929e1c..c2540d379 100644 --- a/src/display/mapwindow.h +++ b/src/display/mapwindow.h @@ -5,6 +5,7 @@ // Author: Marek Krejza (Caligor) // Author: Nils Schimmelmann (Jahara) +#include "../global/Signal2.h" #include "../map/coordinate.h" #include "mapcanvas.h" @@ -47,6 +48,7 @@ class NODISCARD_QOBJECT MapWindow final : public QWidget QPointer m_scrollTimer; int m_verticalScrollStep = 0; int m_horizontalScrollStep = 0; + Signal2Lifetime m_lifetime; private: struct NODISCARD KnownMapSize final diff --git a/src/global/FixedPoint.h b/src/global/FixedPoint.h index 221674bc1..51ba65047 100644 --- a/src/global/FixedPoint.h +++ b/src/global/FixedPoint.h @@ -54,6 +54,17 @@ class NODISCARD FixedPoint final ~FixedPoint() = default; DELETE_CTORS_AND_ASSIGN_OPS(FixedPoint); +public: + using OnAfterChange = std::function; + OnAfterChange m_onAfterChange; + + void setFromNotifier(std::function simpleCallback) + { + if (simpleCallback) { + m_onAfterChange = [cb = std::move(simpleCallback)](int) { cb(); }; + } + } + public: void reset() { set(defaultValue); } void set(const int value) @@ -89,6 +100,9 @@ class NODISCARD FixedPoint final } notification_guard{*this}; m_value = newValue; + if (m_onAfterChange) { + m_onAfterChange(m_value); + } m_changeMonitor.notifyAll(); } diff --git a/src/global/INamedConfig.h b/src/global/INamedConfig.h new file mode 100644 index 000000000..e9e8a8316 --- /dev/null +++ b/src/global/INamedConfig.h @@ -0,0 +1,18 @@ +#pragma once +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright (C) 2025 The MMapper Authors + +#include "ChangeMonitor.h" +#include + +class INamedConfig +{ +public: + virtual ~INamedConfig() = default; + virtual const std::string &getName() const = 0; + virtual std::string toString() const = 0; + virtual bool fromString(const std::string &str) = 0; + virtual void registerChangeCallback(const ChangeMonitor::Lifetime &lifetime, + ChangeMonitor::Function callback) + = 0; +}; diff --git a/src/group/mmapper2group.cpp b/src/group/mmapper2group.cpp index 9b14751a2..967a949d3 100644 --- a/src/group/mmapper2group.cpp +++ b/src/group/mmapper2group.cpp @@ -28,7 +28,11 @@ static const bool verbose_debugging = false; Mmapper2Group::Mmapper2Group(QObject *const parent) : QObject{parent} , m_groupManagerApi{std::make_unique(*this)} -{} +{ + setConfig().groupManager.registerChangeCallback(m_lifetime, [this]() { + slot_groupSettingsChanged(); + }); +} Mmapper2Group::~Mmapper2Group() {} @@ -39,7 +43,7 @@ SharedGroupChar Mmapper2Group::getSelf() m_self->setType(CharacterTypeEnum::YOU); m_charIndex.push_back(m_self); - const auto color = getConfig().groupManager.color; + const auto color = getConfig().groupManager.color.get(); m_self->setColor(color); m_colorGenerator.init(color); } @@ -246,7 +250,7 @@ void Mmapper2Group::removeChar(const GroupId id) if (character.getId() != id) { return false; } - if (!character.isYou() && character.getColor() != settings.npcColor) { + if (!character.isYou() && character.getColor() != settings.npcColor.get()) { m_colorGenerator.releaseColor(character.getColor()); } qDebug() << "removing" << id.asUint32() << character.getName().toQString(); @@ -301,7 +305,7 @@ bool Mmapper2Group::updateChar(SharedGroupChar sharedCh, const JsonObj &obj) if (ch.isYou()) { if (!m_self) { m_self = sharedCh; - m_self->setColor(getConfig().groupManager.color); + m_self->setColor(getConfig().groupManager.color.get()); } else if (m_self->getId() != sharedCh->getId()) { m_self->setId(ch.getId()); change = m_self->updateFromGmcp(obj); @@ -314,8 +318,8 @@ bool Mmapper2Group::updateChar(SharedGroupChar sharedCh, const JsonObj &obj) if (!ch.getColor().isValid()) { auto getColor = [&]() -> QColor { const auto &settings = getConfig().groupManager; - if (ch.isNpc() && settings.npcColorOverride) { - return settings.npcColor; + if (ch.isNpc() && settings.npcColorOverride.get()) { + return settings.npcColor.get(); } else { return m_colorGenerator.getNextColor(); } @@ -334,12 +338,12 @@ void Mmapper2Group::slot_groupSettingsChanged() for (const auto &pChar : m_charIndex) { auto &character = deref(pChar); if (character.isYou()) { - character.setColor(settings.color); - } else if (character.isNpc() && settings.npcColorOverride) { - if (character.getColor() != settings.npcColor) { + character.setColor(settings.color.get()); + } else if (character.isNpc() && settings.npcColorOverride.get()) { + if (character.getColor() != settings.npcColor.get()) { m_colorGenerator.releaseColor(character.getColor()); } - character.setColor(settings.npcColor); + character.setColor(settings.npcColor.get()); } } characterChanged(); diff --git a/src/group/mmapper2group.h b/src/group/mmapper2group.h index daa965226..57c5708dd 100644 --- a/src/group/mmapper2group.h +++ b/src/group/mmapper2group.h @@ -5,6 +5,7 @@ // Author: Nils Schimmelmann (Jahara) #include "../global/JsonValue.h" +#include "../global/Signal2.h" #include "CGroupChar.h" #include "ColorGenerator.h" #include "GroupManagerApi.h" @@ -25,6 +26,7 @@ class NODISCARD_QOBJECT Mmapper2Group final : public QObject private: SharedGroupChar m_self; GroupVector m_charIndex; + Signal2Lifetime m_lifetime; // deleted in destructor as member of charIndex ColorGenerator m_colorGenerator; diff --git a/src/mainwindow/mainwindow.cpp b/src/mainwindow/mainwindow.cpp index 110d7b009..98b55a6cf 100644 --- a/src/mainwindow/mainwindow.cpp +++ b/src/mainwindow/mainwindow.cpp @@ -1457,14 +1457,6 @@ void MainWindow::slot_onPreferences() if (m_configDialog == nullptr) { m_configDialog = std::make_unique(this); - connect(m_configDialog.get(), - &ConfigDialog::sig_graphicsSettingsChanged, - m_mapWindow, - &MapWindow::slot_graphicsSettingsChanged); - connect(m_configDialog.get(), - &ConfigDialog::sig_groupSettingsChanged, - m_groupManager, - &Mmapper2Group::slot_groupSettingsChanged); connect(m_configDialog.get(), &QDialog::finished, this, [this](MAYBE_UNUSED int result) { m_configDialog.reset(); }); diff --git a/src/parser/AbstractParser-Config.cpp b/src/parser/AbstractParser-Config.cpp index f021db16c..27de2bd52 100644 --- a/src/parser/AbstractParser-Config.cpp +++ b/src/parser/AbstractParser-Config.cpp @@ -8,6 +8,7 @@ #include "../display/mapcanvas.h" #include "../global/AnsiOstream.h" #include "../global/Consts.h" +#include "../global/INamedConfig.h" #include "../global/NamedColors.h" #include "../global/PrintUtils.h" #include "../mpi/remoteeditwidget.h" @@ -26,6 +27,36 @@ #include #include +class NODISCARD ArgNamedConfig final : public syntax::IArgument +{ +private: + NODISCARD syntax::MatchResult virt_match(const syntax::ParserInput &input, + syntax::IMatchErrorLogger *) const final; + + std::ostream &virt_to_stream(std::ostream &os) const final; +}; + +syntax::MatchResult ArgNamedConfig::virt_match(const syntax::ParserInput &input, + syntax::IMatchErrorLogger *) const +{ + if (input.empty()) { + return syntax::MatchResult::failure(input); + } + + auto arg = std::string_view{input.front()}; + auto it = getConfig().getRegistry().find(std::string(arg)); + if (it != getConfig().getRegistry().end()) { + return syntax::MatchResult::success(1, input, Value{it->first}); + } + + return syntax::MatchResult::failure(input); +} + +std::ostream &ArgNamedConfig::virt_to_stream(std::ostream &os) const +{ + return os << ""; +} + class NODISCARD ArgNamedColor final : public syntax::IArgument { private: @@ -102,6 +133,50 @@ NODISCARD static auto syn(Args &&...args) void AbstractParser::doConfig(const StringView cmd) { + auto listSettings = syntax::Accept( + [](User &user, const Pair *) { + auto &os = user.getOstream(); + os << "Configurable settings:\n"; + for (auto const& [name, config] : getConfig().getRegistry()) { + os << " " << name << " = " << config->toString() << AnsiOstream::endl; + } + }, + "list settings"); + + auto setSetting = syntax::Accept( + [this](User &user, const Pair *args) { + auto &os = user.getOstream(); + if (args == nullptr || args->cdr == nullptr || !args->car.isString() + || !args->cdr->car.isString()) { + throw std::runtime_error("internal error"); + } + + const std::string name = args->car.getString(); + const std::string value = args->cdr->car.getString(); + + auto *config = setConfig().lookup(name); + if (config == nullptr) { + throw std::runtime_error("invalid setting: " + name); + } + + const std::string oldValue = config->toString(); + if (oldValue == value) { + os << "Setting " << name << " is already " << value << ".\n"; + return; + } + + if (!config->fromString(value)) { + os << "Failed to set " << name << " to " << value << ".\n"; + return; + } + + os << "Setting " << name << " has been changed from " << oldValue + << " to " << config->toString() << ".\n"; + + graphicsSettingsChanged(); + }, + "set setting"); + auto listColors = syntax::Accept( [](User &user, const Pair *) { auto &os = user.getOstream(); @@ -170,60 +245,12 @@ void AbstractParser::doConfig(const StringView cmd) }, "set named color"); - auto makeSetFixedPoint = [this](FixedPoint<1> &fp, const std::string &help) -> syntax::Accept { - // - return syntax::Accept( - [this, &fp, help](User &user, const Pair *const args) -> void { - auto &os = user.getOstream(); - - if (args == nullptr || !args->car.isFloat()) { - throw std::runtime_error("internal type error"); - } - - const float value = args->car.getFloat(); - const auto min = fp.clone(fp.min).getFloat(); - const auto max = fp.clone(fp.max).getFloat(); - if (value < min || value > max) { - throw std::runtime_error("internal bounds error"); - } - - const int oldValue = fp.get(); - auto clone = fp.clone(oldValue); - clone.setFloat(value); - if (clone.get() == oldValue) { - os << "No change: " << help << " is already " << fp.getFloat() - << AnsiOstream::endl; - return; - } - - clone.set(oldValue); - fp.setFloat(value); - os << "Changed " << help << " from " << clone.getFloat() << " to " << fp.getFloat() - << AnsiOstream::endl; - this->graphicsSettingsChanged(); - }, - "set " + help); - }; - using namespace syntax; const auto argBool = TokenMatcher::alloc(); const auto argInt = TokenMatcher::alloc(); const auto optArgEquals = TokenMatcher::alloc(char_consts::C_EQUALS); - auto makeFixedPointArg = [optArgEquals, - &makeSetFixedPoint](FixedPoint<1> &fp, - const std::string &help) -> SharedConstSublist { - const auto min = fp.clone(fp.min).getFloat(); - const auto max = fp.clone(fp.max).getFloat(); - return syn(help, - optArgEquals, - TokenMatcher::alloc_copy(ArgFloat::withMinMax(min, max)), - makeSetFixedPoint(fp, help)); - }; - - auto &advanced = setConfig().canvas.advanced; - // static because it has no captures static const auto getZoom = []() -> float { if (auto primary = MapCanvas::getPrimary()) { @@ -276,31 +303,6 @@ void AbstractParser::doConfig(const StringView cmd) return syn("zoom", syn("set", argZoom, acceptZoom)); }); - const auto opt = [this, argBool, optArgEquals](const char *const name, - NamedConfig &conf, - std::string help) { - return syn(name, - optArgEquals, - argBool, - Accept( - [this, &conf](User &user, const Pair *const args) { - const auto value = deref(args).car.getBool(); - auto &os = user.getOstream(); - - if (conf.get() == value) { - os << conf.getName() << " is already " << BoolAlpha(value) - << AnsiOstream::endl; - return; - } - - conf.set(value); - os << "Set " << conf.getName() << " = " << BoolAlpha(value) - << AnsiOstream::endl; - graphicsSettingsChanged(); - }, - std::move(help))); - }; - const auto configSyntax = syn( syn("mode", syn("play", @@ -455,17 +457,13 @@ void AbstractParser::doConfig(const StringView cmd) optArgEquals, TokenMatcher::alloc(), setNamedColor))), - syn("perf-stats", - syn("set", opt("enabled", advanced.printPerfStats, "enable/disable stats"))), - zoomSyntax, - syn("3d-camera", - syn("set", - opt("enabled", advanced.use3D, "enable/disable 3d camera"), - opt("auto-tilt", advanced.autoTilt, "enable/disable 3d auto tilt"), - makeFixedPointArg(advanced.fov, "fov"), - makeFixedPointArg(advanced.verticalAngle, "pitch"), - makeFixedPointArg(advanced.horizontalAngle, "yaw"), - makeFixedPointArg(advanced.layerHeight, "layer-height"))))); + zoomSyntax), + syn("list", listSettings), + syn("set", + TokenMatcher::alloc(), + optArgEquals, + TokenMatcher::alloc(), + setSetting)); eval("config", configSyntax, cmd); } diff --git a/src/preferences/AdvancedGraphics.cpp b/src/preferences/AdvancedGraphics.cpp index de850c0cd..968120535 100644 --- a/src/preferences/AdvancedGraphics.cpp +++ b/src/preferences/AdvancedGraphics.cpp @@ -106,7 +106,6 @@ class NODISCARD SliderSpinboxButtonImpl final : public SliderSpinboxButton const SignalBlocker block_spin{m_spin}; m_fp.set(value); m_spin.setIntValue(value); - m_group.graphicsSettingsChanged(); }); QObject::connect(&m_spin, @@ -118,7 +117,6 @@ class NODISCARD SliderSpinboxButtonImpl final : public SliderSpinboxButton const int value = m_spin.getIntValue(); m_fp.set(value); m_slider.setValue(value); - m_group.graphicsSettingsChanged(); }); QObject::connect(&m_reset, &QPushButton::clicked, &group, [this](bool) { @@ -151,9 +149,6 @@ class NODISCARD SliderSpinboxButtonImpl final : public SliderSpinboxButton const auto value = m_fp.get(); m_spin.setIntValue(value); m_slider.setValue(value); - if ((false)) { - m_group.graphicsSettingsChanged(); - } } DELETE_CTORS_AND_ASSIGN_OPS(SliderSpinboxButtonImpl); @@ -220,19 +215,16 @@ AdvancedGraphicsGroupBox::AdvancedGraphicsGroupBox(QGroupBox &groupBox) MapCanvasConfig::set3dMode(is3d); enableSsbs(is3d); autoTilt->setEnabled(is3d); - graphicsSettingsChanged(); }); connect(autoTilt, &QCheckBox::stateChanged, this, [this, autoTilt](int) { const bool val = autoTilt->isChecked(); MapCanvasConfig::setAutoTilt(val); - graphicsSettingsChanged(); }); connect(checkboxDiag, &QCheckBox::stateChanged, this, [this, checkboxDiag](int) { const bool show = checkboxDiag->isChecked(); MapCanvasConfig::setShowPerfStats(show); - graphicsSettingsChanged(); }); MapCanvasConfig::registerChangeCallback(m_lifetime, @@ -251,6 +243,8 @@ AdvancedGraphicsGroupBox::AdvancedGraphicsGroupBox(QGroupBox &groupBox) checkbox3d->setChecked( MapCanvasConfig::isIn3dMode()); autoTilt->setChecked(MapCanvasConfig::isAutoTilt()); + enableSsbs(MapCanvasConfig::isIn3dMode()); + autoTilt->setEnabled(MapCanvasConfig::isIn3dMode()); }); } diff --git a/src/preferences/AdvancedGraphics.h b/src/preferences/AdvancedGraphics.h index 6764fc267..e63e58beb 100644 --- a/src/preferences/AdvancedGraphics.h +++ b/src/preferences/AdvancedGraphics.h @@ -42,9 +42,5 @@ class NODISCARD_QOBJECT AdvancedGraphicsGroupBox final : public QObject NODISCARD QGroupBox *getGroupBox() { return m_groupBox; } private: - void graphicsSettingsChanged() { emit sig_graphicsSettingsChanged(); } void enableSsbs(bool enabled); - -signals: - void sig_graphicsSettingsChanged(); }; diff --git a/src/preferences/audiopage.cpp b/src/preferences/audiopage.cpp index 7cb14d1a7..e54b2252b 100644 --- a/src/preferences/audiopage.cpp +++ b/src/preferences/audiopage.cpp @@ -44,6 +44,11 @@ AudioPage::AudioPage(QWidget *const parent) ui->musicVolumeSlider->setEnabled(false); ui->soundsVolumeSlider->setEnabled(false); } + + auto &audio = setConfig().audio; + audio.musicVolume.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + audio.soundVolume.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + audio.outputDeviceId.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); } AudioPage::~AudioPage() @@ -58,10 +63,10 @@ void AudioPage::slot_loadConfig() SignalBlocker outputBlocker(*ui->outputDeviceComboBox); const auto &settings = getConfig().audio; - ui->musicVolumeSlider->setValue(settings.getMusicVolume()); - ui->soundsVolumeSlider->setValue(settings.getSoundVolume()); + ui->musicVolumeSlider->setValue(settings.musicVolume.get()); + ui->soundsVolumeSlider->setValue(settings.soundVolume.get()); - int index = ui->outputDeviceComboBox->findData(settings.getOutputDeviceId()); + int index = ui->outputDeviceComboBox->findData(settings.outputDeviceId.get()); if (index != -1) { ui->outputDeviceComboBox->setCurrentIndex(index); } else { @@ -72,15 +77,15 @@ void AudioPage::slot_loadConfig() void AudioPage::slot_musicVolumeChanged(int value) { auto &settings = setConfig().audio; - settings.setMusicVolume(value); - settings.setUnlocked(); + settings.musicVolume.set(value); + settings.unlocked.set(true); } void AudioPage::slot_soundsVolumeChanged(int value) { auto &settings = setConfig().audio; - settings.setSoundVolume(value); - settings.setUnlocked(); + settings.soundVolume.set(value); + settings.unlocked.set(true); } void AudioPage::slot_outputDeviceChanged(int index) @@ -89,7 +94,7 @@ void AudioPage::slot_outputDeviceChanged(int index) return; } auto &settings = setConfig().audio; - settings.setOutputDeviceId(ui->outputDeviceComboBox->itemData(index).toByteArray()); + settings.outputDeviceId.set(ui->outputDeviceComboBox->itemData(index).toByteArray()); } void AudioPage::slot_updateDevices() @@ -97,7 +102,7 @@ void AudioPage::slot_updateDevices() SignalBlocker blocker(*ui->outputDeviceComboBox); QByteArray currentId = ui->outputDeviceComboBox->currentData().toByteArray(); if (currentId.isEmpty()) { - currentId = getConfig().audio.getOutputDeviceId(); + currentId = getConfig().audio.outputDeviceId.get(); } ui->outputDeviceComboBox->clear(); diff --git a/src/preferences/audiopage.h b/src/preferences/audiopage.h index 2acf9b242..1cb2f3fdb 100644 --- a/src/preferences/audiopage.h +++ b/src/preferences/audiopage.h @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later // Copyright (C) 2025 The MMapper Authors +#include "../global/Signal2.h" #include "../global/macros.h" #include @@ -16,6 +17,7 @@ class NODISCARD_QOBJECT AudioPage final : public QWidget private: Ui::AudioPage *const ui; + Signal2Lifetime m_lifetime; public: explicit AudioPage(QWidget *parent); diff --git a/src/preferences/autologpage.cpp b/src/preferences/autologpage.cpp index 86e668f8e..56fa73443 100644 --- a/src/preferences/autologpage.cpp +++ b/src/preferences/autologpage.cpp @@ -5,6 +5,7 @@ #include "autologpage.h" #include "../configuration/configuration.h" +#include "../global/SignalBlocker.h" #include "ui_autologpage.h" #include @@ -23,7 +24,7 @@ AutoLogPage::AutoLogPage(QWidget *const parent) connect(ui->autoLogCheckBox, QOverload::of(&QCheckBox::toggled), this, - [](const bool autoLog) { setConfig().autoLog.autoLog = autoLog; }); + [](const bool autoLog) { setConfig().autoLog.autoLog.set(autoLog); }); connect(ui->selectAutoLogLocationButton, &QAbstractButton::clicked, this, @@ -38,25 +39,25 @@ AutoLogPage::AutoLogPage(QWidget *const parent) this, &AutoLogPage::slot_logStrategyChanged); connect(ui->spinBoxDays, QOverload::of(&QSpinBox::valueChanged), this, [](const int size) { - setConfig().autoLog.deleteWhenLogsReachDays = size; + setConfig().autoLog.deleteWhenLogsReachDays.set(size); }); connect(ui->radioButtonDeleteSize, QOverload::of(&QRadioButton::toggled), this, &AutoLogPage::slot_logStrategyChanged); connect(ui->spinBoxSize, QOverload::of(&QSpinBox::valueChanged), this, [](const int size) { - setConfig().autoLog.deleteWhenLogsReachBytes = size * MEGABYTE_IN_BYTES; + setConfig().autoLog.deleteWhenLogsReachBytes.set(size * MEGABYTE_IN_BYTES); }); connect(ui->askDeleteCheckBox, QOverload::of(&QCheckBox::toggled), this, - [](const bool askDelete) { setConfig().autoLog.askDelete = askDelete; }); + [](const bool askDelete) { setConfig().autoLog.askDelete.set(askDelete); }); connect(ui->autoLogMaxBytes, QOverload::of(&QSpinBox::valueChanged), this, [](const int size) { - setConfig().autoLog.deleteWhenLogsReachBytes = size * MEGABYTE_IN_BYTES; + setConfig().autoLog.rotateWhenLogsReachBytes.set(size * MEGABYTE_IN_BYTES); }); if constexpr (CURRENT_PLATFORM == PlatformEnum::Wasm) { @@ -71,6 +72,15 @@ AutoLogPage::AutoLogPage(QWidget *const parent) ui->askDeleteCheckBox->setDisabled(true); ui->autoLogMaxBytes->setDisabled(true); } + + auto &autoLog = setConfig().autoLog; + autoLog.autoLog.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + autoLog.autoLogDirectory.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + autoLog.cleanupStrategy.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + autoLog.deleteWhenLogsReachDays.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + autoLog.deleteWhenLogsReachBytes.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + autoLog.askDelete.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + autoLog.rotateWhenLogsReachBytes.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); } AutoLogPage::~AutoLogPage() @@ -81,10 +91,20 @@ AutoLogPage::~AutoLogPage() void AutoLogPage::slot_loadConfig() { const auto &config = getConfig().autoLog; - ui->autoLogCheckBox->setChecked(config.autoLog); - ui->autoLogLocation->setText(config.autoLogDirectory); - ui->autoLogMaxBytes->setValue(config.rotateWhenLogsReachBytes / MEGABYTE_IN_BYTES); - switch (config.cleanupStrategy) { + + SignalBlocker b1(*ui->autoLogCheckBox); + SignalBlocker b2(*ui->radioButtonKeepForever); + SignalBlocker b3(*ui->radioButtonDeleteDays); + SignalBlocker b4(*ui->radioButtonDeleteSize); + SignalBlocker b5(*ui->spinBoxDays); + SignalBlocker b6(*ui->spinBoxSize); + SignalBlocker b7(*ui->askDeleteCheckBox); + SignalBlocker b8(*ui->autoLogMaxBytes); + + ui->autoLogCheckBox->setChecked(config.autoLog.get()); + ui->autoLogLocation->setText(config.autoLogDirectory.get()); + ui->autoLogMaxBytes->setValue(config.rotateWhenLogsReachBytes.get() / MEGABYTE_IN_BYTES); + switch (static_cast(config.cleanupStrategy.get())) { case AutoLoggerEnum::KeepForever: ui->radioButtonKeepForever->setChecked(true); break; @@ -97,9 +117,9 @@ void AutoLogPage::slot_loadConfig() default: abort(); } - ui->spinBoxDays->setValue(config.deleteWhenLogsReachDays); - ui->spinBoxSize->setValue(config.deleteWhenLogsReachBytes / MEGABYTE_IN_BYTES); - ui->askDeleteCheckBox->setChecked(config.askDelete); + ui->spinBoxDays->setValue(config.deleteWhenLogsReachDays.get()); + ui->spinBoxSize->setValue(config.deleteWhenLogsReachBytes.get() / MEGABYTE_IN_BYTES); + ui->askDeleteCheckBox->setChecked(config.askDelete.get()); } void AutoLogPage::slot_selectLogLocationButtonClicked(int /*unused*/) @@ -107,11 +127,11 @@ void AutoLogPage::slot_selectLogLocationButtonClicked(int /*unused*/) auto &config = setConfig().autoLog; QString logDirectory = QFileDialog::getExistingDirectory(this, "Choose log location ...", - config.autoLogDirectory); + config.autoLogDirectory.get()); if (!logDirectory.isEmpty()) { ui->autoLogLocation->setText(logDirectory); - config.autoLogDirectory = logDirectory; + config.autoLogDirectory.set(logDirectory); } } @@ -119,12 +139,12 @@ void AutoLogPage::slot_logStrategyChanged(int /*unused*/) { auto &strategy = setConfig().autoLog.cleanupStrategy; if (ui->radioButtonKeepForever->isChecked()) { - strategy = AutoLoggerEnum::KeepForever; + strategy.set(static_cast(AutoLoggerEnum::KeepForever)); } else if (ui->radioButtonDeleteDays->isChecked()) { - strategy = AutoLoggerEnum::DeleteDays; + strategy.set(static_cast(AutoLoggerEnum::DeleteDays)); } else if (ui->radioButtonDeleteSize->isChecked()) { - strategy = AutoLoggerEnum::DeleteSize; + strategy.set(static_cast(AutoLoggerEnum::DeleteSize)); } else { - abort(); + // can happen when toggling } } diff --git a/src/preferences/autologpage.h b/src/preferences/autologpage.h index b547625d5..a8eb82d3c 100644 --- a/src/preferences/autologpage.h +++ b/src/preferences/autologpage.h @@ -3,6 +3,7 @@ // Copyright (C) 2019 The MMapper Authors // Author: Mattias 'Mew_' Viklund (Mirnir) +#include "../global/Signal2.h" #include "../global/macros.h" #include @@ -18,6 +19,7 @@ class NODISCARD_QOBJECT AutoLogPage final : public QWidget private: Ui::AutoLogPage *const ui; + Signal2Lifetime m_lifetime; public: explicit AutoLogPage(QWidget *parent); diff --git a/src/preferences/clientpage.cpp b/src/preferences/clientpage.cpp index 7a2d46f1e..20d67fdd0 100644 --- a/src/preferences/clientpage.cpp +++ b/src/preferences/clientpage.cpp @@ -5,6 +5,7 @@ #include "clientpage.h" #include "../configuration/configuration.h" +#include "../global/SignalBlocker.h" #include "../global/macros.h" #include "ui_clientpage.h" @@ -77,7 +78,7 @@ ClientPage::ClientPage(QWidget *parent) connect(ui->previewSpinBox, QOverload::of(&QSpinBox::valueChanged), this, - [](const int value) { setConfig().integratedClient.linesOfPeekPreview = value; }); + [](const int value) { setConfig().integratedClient.linesOfPeekPreview.set(value); }); connect(ui->inputHistorySpinBox, QOverload::of(&QSpinBox::valueChanged), @@ -90,34 +91,51 @@ ClientPage::ClientPage(QWidget *parent) connect(ui->clearInputCheckBox, &QCheckBox::toggled, [](bool isChecked) { /* NOTE: This directly modifies the global setting. */ - setConfig().integratedClient.clearInputOnEnter = isChecked; + setConfig().integratedClient.clearInputOnEnter.set(isChecked); }); connect(ui->autoResizeTerminalCheckBox, &QCheckBox::toggled, [](bool isChecked) { /* NOTE: This directly modifies the global setting. */ - setConfig().integratedClient.autoResizeTerminal = isChecked; + setConfig().integratedClient.autoResizeTerminal.set(isChecked); }); connect(ui->audibleBellCheckBox, &QCheckBox::toggled, [](bool isChecked) { - setConfig().integratedClient.audibleBell = isChecked; + setConfig().integratedClient.audibleBell.set(isChecked); }); connect(ui->visualBellCheckBox, &QCheckBox::toggled, [](bool isChecked) { - setConfig().integratedClient.visualBell = isChecked; + setConfig().integratedClient.visualBell.set(isChecked); }); connect(ui->commandSeparatorCheckBox, &QCheckBox::toggled, this, [this](bool isChecked) { - setConfig().integratedClient.useCommandSeparator = isChecked; + setConfig().integratedClient.useCommandSeparator.set(isChecked); ui->commandSeparatorLineEdit->setEnabled(isChecked); }); connect(ui->commandSeparatorLineEdit, &QLineEdit::textChanged, this, [](const QString &text) { if (text.length() == 1) { - setConfig().integratedClient.commandSeparator = text; + setConfig().integratedClient.commandSeparator.set(text); } }); ui->commandSeparatorLineEdit->setValidator(new CustomSeparatorValidator(this)); + + auto &client = setConfig().integratedClient; + client.font.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + client.foregroundColor.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + client.backgroundColor.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + client.columns.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + client.rows.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + client.linesOfScrollback.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + client.linesOfInputHistory.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + client.tabCompletionDictionarySize.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + client.clearInputOnEnter.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + client.autoResizeTerminal.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + client.linesOfPeekPreview.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + client.audibleBell.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + client.visualBell.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + client.useCommandSeparator.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + client.commandSeparator.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); } ClientPage::~ClientPage() @@ -127,29 +145,42 @@ ClientPage::~ClientPage() void ClientPage::slot_loadConfig() { + SignalBlocker b1(*ui->columnsSpinBox); + SignalBlocker b2(*ui->rowsSpinBox); + SignalBlocker b3(*ui->scrollbackSpinBox); + SignalBlocker b4(*ui->previewSpinBox); + SignalBlocker b5(*ui->inputHistorySpinBox); + SignalBlocker b6(*ui->tabDictionarySpinBox); + SignalBlocker b7(*ui->clearInputCheckBox); + SignalBlocker b8(*ui->autoResizeTerminalCheckBox); + SignalBlocker b9(*ui->audibleBellCheckBox); + SignalBlocker b10(*ui->visualBellCheckBox); + SignalBlocker b11(*ui->commandSeparatorCheckBox); + SignalBlocker b12(*ui->commandSeparatorLineEdit); + updateFontAndColors(); const auto &settings = getConfig().integratedClient; - ui->columnsSpinBox->setValue(settings.columns); - ui->rowsSpinBox->setValue(settings.rows); - ui->scrollbackSpinBox->setValue(settings.linesOfScrollback); - ui->previewSpinBox->setValue(settings.linesOfPeekPreview); - ui->inputHistorySpinBox->setValue(settings.linesOfInputHistory); - ui->tabDictionarySpinBox->setValue(settings.tabCompletionDictionarySize); - ui->clearInputCheckBox->setChecked(settings.clearInputOnEnter); - ui->autoResizeTerminalCheckBox->setChecked(settings.autoResizeTerminal); - ui->audibleBellCheckBox->setChecked(settings.audibleBell); - ui->visualBellCheckBox->setChecked(settings.visualBell); - ui->commandSeparatorCheckBox->setChecked(settings.useCommandSeparator); - ui->commandSeparatorLineEdit->setText(settings.commandSeparator); - ui->commandSeparatorLineEdit->setEnabled(settings.useCommandSeparator); + ui->columnsSpinBox->setValue(settings.columns.get()); + ui->rowsSpinBox->setValue(settings.rows.get()); + ui->scrollbackSpinBox->setValue(settings.linesOfScrollback.get()); + ui->previewSpinBox->setValue(settings.linesOfPeekPreview.get()); + ui->inputHistorySpinBox->setValue(settings.linesOfInputHistory.get()); + ui->tabDictionarySpinBox->setValue(settings.tabCompletionDictionarySize.get()); + ui->clearInputCheckBox->setChecked(settings.clearInputOnEnter.get()); + ui->autoResizeTerminalCheckBox->setChecked(settings.autoResizeTerminal.get()); + ui->audibleBellCheckBox->setChecked(settings.audibleBell.get()); + ui->visualBellCheckBox->setChecked(settings.visualBell.get()); + ui->commandSeparatorCheckBox->setChecked(settings.useCommandSeparator.get()); + ui->commandSeparatorLineEdit->setText(settings.commandSeparator.get()); + ui->commandSeparatorLineEdit->setEnabled(settings.useCommandSeparator.get()); } void ClientPage::updateFontAndColors() { const auto &settings = getConfig().integratedClient; QFont font; - font.fromString(settings.font); + font.fromString(settings.font.get()); ui->exampleLabel->setFont(font); QFontInfo fi(font); @@ -157,17 +188,17 @@ void ClientPage::updateFontAndColors() QString("%1 %2, %3").arg(fi.family()).arg(fi.styleName()).arg(fi.pointSize())); QPixmap fgPix(16, 16); - fgPix.fill(settings.foregroundColor); + fgPix.fill(settings.foregroundColor.get()); ui->fgColorPushButton->setIcon(QIcon(fgPix)); QPixmap bgPix(16, 16); - bgPix.fill(settings.backgroundColor); + bgPix.fill(settings.backgroundColor.get()); ui->bgColorPushButton->setIcon(QIcon(bgPix)); QPalette palette = ui->exampleLabel->palette(); ui->exampleLabel->setAutoFillBackground(true); - palette.setColor(QPalette::WindowText, settings.foregroundColor); - palette.setColor(QPalette::Window, settings.backgroundColor); + palette.setColor(QPalette::WindowText, settings.foregroundColor.get()); + palette.setColor(QPalette::Window, settings.backgroundColor.get()); ui->exampleLabel->setPalette(palette); ui->exampleLabel->setBackgroundRole(QPalette::Window); } @@ -176,7 +207,7 @@ void ClientPage::slot_onChangeFont() { auto &fontDescription = setConfig().integratedClient.font; QFont oldFont; - oldFont.fromString(fontDescription); + oldFont.fromString(fontDescription.get()); bool ok = false; const QFont newFont = QFontDialog::getFont(&ok, @@ -185,7 +216,7 @@ void ClientPage::slot_onChangeFont() "Select Font", QFontDialog::MonospacedFonts); if (ok) { - fontDescription = newFont.toString(); + fontDescription.set(newFont.toString()); updateFontAndColors(); } } @@ -193,9 +224,9 @@ void ClientPage::slot_onChangeFont() void ClientPage::slot_onChangeBackgroundColor() { auto &backgroundColor = setConfig().integratedClient.backgroundColor; - const QColor newColor = QColorDialog::getColor(backgroundColor, this); - if (newColor.isValid() && newColor != backgroundColor) { - backgroundColor = newColor; + const QColor newColor = QColorDialog::getColor(backgroundColor.get(), this); + if (newColor.isValid() && newColor != backgroundColor.get()) { + backgroundColor.set(newColor); updateFontAndColors(); } } @@ -203,34 +234,34 @@ void ClientPage::slot_onChangeBackgroundColor() void ClientPage::slot_onChangeForegroundColor() { auto &foregroundColor = setConfig().integratedClient.foregroundColor; - const QColor newColor = QColorDialog::getColor(foregroundColor, this); - if (newColor.isValid() && newColor != foregroundColor) { - foregroundColor = newColor; + const QColor newColor = QColorDialog::getColor(foregroundColor.get(), this); + if (newColor.isValid() && newColor != foregroundColor.get()) { + foregroundColor.set(newColor); updateFontAndColors(); } } void ClientPage::slot_onChangeColumns(const int value) { - setConfig().integratedClient.columns = value; + setConfig().integratedClient.columns.set(value); } void ClientPage::slot_onChangeRows(const int value) { - setConfig().integratedClient.rows = value; + setConfig().integratedClient.rows.set(value); } void ClientPage::slot_onChangeLinesOfScrollback(const int value) { - setConfig().integratedClient.linesOfScrollback = value; + setConfig().integratedClient.linesOfScrollback.set(value); } void ClientPage::slot_onChangeLinesOfInputHistory(const int value) { - setConfig().integratedClient.linesOfInputHistory = value; + setConfig().integratedClient.linesOfInputHistory.set(value); } void ClientPage::slot_onChangeTabCompletionDictionarySize(const int value) { - setConfig().integratedClient.tabCompletionDictionarySize = value; + setConfig().integratedClient.tabCompletionDictionarySize.set(value); } diff --git a/src/preferences/clientpage.h b/src/preferences/clientpage.h index c70c579d1..f6c4be50d 100644 --- a/src/preferences/clientpage.h +++ b/src/preferences/clientpage.h @@ -3,6 +3,7 @@ // Copyright (C) 2019 The MMapper Authors // Author: Nils Schimmelmann (Jahara) +#include "../global/Signal2.h" #include "../global/macros.h" #include @@ -21,6 +22,7 @@ class NODISCARD_QOBJECT ClientPage final : public QWidget private: Ui::ClientPage *const ui; + Signal2Lifetime m_lifetime; public: explicit ClientPage(QWidget *parent); diff --git a/src/preferences/configdialog.cpp b/src/preferences/configdialog.cpp index 397e1339b..b152d863d 100644 --- a/src/preferences/configdialog.cpp +++ b/src/preferences/configdialog.cpp @@ -65,6 +65,8 @@ ConfigDialog::ConfigDialog(QWidget *const parent) &ConfigDialog::slot_changePage); connect(ui->closeButton, &QAbstractButton::clicked, this, &QWidget::close); + connect(ui->searchLineEdit, &QLineEdit::textChanged, this, &ConfigDialog::slot_filterSettings); + connect(generalPage, &GeneralPage::sig_reloadConfig, this, [this]() { emit sig_loadConfig(); }); connect(this, &ConfigDialog::sig_loadConfig, generalPage, &GeneralPage::slot_loadConfig); connect(this, &ConfigDialog::sig_loadConfig, graphicsPage, &GraphicsPage::slot_loadConfig); @@ -73,19 +75,11 @@ ConfigDialog::ConfigDialog(QWidget *const parent) connect(this, &ConfigDialog::sig_loadConfig, autoLogPage, &AutoLogPage::slot_loadConfig); connect(this, &ConfigDialog::sig_loadConfig, audioPage, &AudioPage::slot_loadConfig); connect(this, &ConfigDialog::sig_loadConfig, groupPage, &GroupPage::slot_loadConfig); - connect(groupPage, - &GroupPage::sig_groupSettingsChanged, - this, - &ConfigDialog::sig_groupSettingsChanged); connect(this, &ConfigDialog::sig_loadConfig, mumeProtocolPage, &MumeProtocolPage::slot_loadConfig); connect(this, &ConfigDialog::sig_loadConfig, pathmachinePage, &PathmachinePage::slot_loadConfig); - connect(graphicsPage, - &GraphicsPage::sig_graphicsSettingsChanged, - this, - &ConfigDialog::sig_graphicsSettingsChanged); } ConfigDialog::~ConfigDialog() @@ -144,6 +138,44 @@ void ConfigDialog::slot_changePage(QListWidgetItem *current, QListWidgetItem *co if (current == nullptr) { current = previous; } + if (current == nullptr) { + return; + } ui->pagesScrollArea->verticalScrollBar()->setSliderPosition(0); m_pagesWidget->setCurrentIndex(ui->contentsWidget->row(current)); } + +void ConfigDialog::slot_filterSettings(const QString &text) +{ + for (int i = 0; i < ui->contentsWidget->count(); ++i) { + auto *item = ui->contentsWidget->item(i); + bool match = item->text().contains(text, Qt::CaseInsensitive); + + // Also check labels in the page + if (!match) { + auto *page = m_pagesWidget->widget(i); + const auto labels = page->findChildren(); + for (auto *label : labels) { + if (label->text().contains(text, Qt::CaseInsensitive)) { + match = true; + break; + } + } + } + + item->setHidden(!match); + } + + // select first visible item if current is hidden + if (auto *current = ui->contentsWidget->currentItem()) { + if (current->isHidden()) { + for (int i = 0; i < ui->contentsWidget->count(); ++i) { + auto *item = ui->contentsWidget->item(i); + if (!item->isHidden()) { + ui->contentsWidget->setCurrentItem(item); + break; + } + } + } + } +} diff --git a/src/preferences/configdialog.h b/src/preferences/configdialog.h index 82d554926..eea6e47e4 100644 --- a/src/preferences/configdialog.h +++ b/src/preferences/configdialog.h @@ -40,10 +40,9 @@ class NODISCARD_QOBJECT ConfigDialog final : public QDialog void createIcons(); signals: - void sig_graphicsSettingsChanged(); - void sig_groupSettingsChanged(); void sig_loadConfig(); public slots: void slot_changePage(QListWidgetItem *current, QListWidgetItem *previous); + void slot_filterSettings(const QString &text); }; diff --git a/src/preferences/configdialog.ui b/src/preferences/configdialog.ui index a0828c8e7..825a3c8d0 100644 --- a/src/preferences/configdialog.ui +++ b/src/preferences/configdialog.ui @@ -1,7 +1,7 @@ ConfigDialog - + 0 @@ -10,35 +10,53 @@ 650 - + - + + + + + Search: + + + + + + + Search settings... + + + true + + + + + + + - 110 + 120 0 - 110 + 120 16777215 - 70 - 70 + 48 + 48 9 - - QListView::IconMode - @@ -53,38 +71,27 @@ true - - - 0 - 0 - 518 - 558 - - - + + + 0 + + + 0 + + + 0 + + + 0 + + - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 12 - 12 - - - - - - + diff --git a/src/preferences/generalpage.cpp b/src/preferences/generalpage.cpp index ed75b49f3..9533cda22 100644 --- a/src/preferences/generalpage.cpp +++ b/src/preferences/generalpage.cpp @@ -7,6 +7,7 @@ #include "generalpage.h" #include "../configuration/configuration.h" +#include "../global/SignalBlocker.h" #include "ui_generalpage.h" #include @@ -30,6 +31,8 @@ GeneralPage::GeneralPage(QWidget *parent) { ui->setupUi(this); + auto &cfg = setConfig(); + connect(ui->remoteName, &QLineEdit::textChanged, this, &GeneralPage::slot_remoteNameTextChanged); connect(ui->remotePort, QOverload::of(&QSpinBox::valueChanged), @@ -40,17 +43,17 @@ GeneralPage::GeneralPage(QWidget *parent) this, &GeneralPage::slot_localPortValueChanged); connect(ui->encryptionCheckBox, &QCheckBox::clicked, this, [](const bool checked) { - setConfig().connection.tlsEncryption = checked; + setConfig().connection.tlsEncryption.set(checked); }); connect(ui->proxyListensOnAnyInterfaceCheckBox, &QCheckBox::stateChanged, this, [this]() { - setConfig().connection.proxyListensOnAnyInterface = ui->proxyListensOnAnyInterfaceCheckBox - ->isChecked(); + setConfig().connection.proxyListensOnAnyInterface.set(ui->proxyListensOnAnyInterfaceCheckBox + ->isChecked()); }); connect(ui->charsetComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, [](const int index) { - setConfig().general.characterEncoding = static_cast(index); + setConfig().general.characterEncoding.set(index); }); connect(ui->themeComboBox, QOverload::of(&QComboBox::currentIndexChanged), @@ -71,7 +74,7 @@ GeneralPage::GeneralPage(QWidget *parent) &GeneralPage::slot_showNotesStateChanged); connect(ui->checkForUpdateCheckBox, &QCheckBox::stateChanged, this, [this]() { - setConfig().general.checkForUpdate = ui->checkForUpdateCheckBox->isChecked(); + setConfig().general.checkForUpdate.set(ui->checkForUpdateCheckBox->isChecked()); }); connect(ui->autoLoadFileName, &QLineEdit::textChanged, @@ -97,7 +100,7 @@ GeneralPage::GeneralPage(QWidget *parent) &GeneralPage::slot_displayXPStatusStateChanged); connect(ui->proxyConnectionStatusCheckBox, &QCheckBox::stateChanged, this, [this]() { - setConfig().connection.proxyConnectionStatus = ui->proxyConnectionStatusCheckBox->isChecked(); + setConfig().connection.proxyConnectionStatus.set(ui->proxyConnectionStatusCheckBox->isChecked()); }); connect(ui->configurationResetButton, &QAbstractButton::clicked, this, [this]() { @@ -171,11 +174,11 @@ GeneralPage::GeneralPage(QWidget *parent) }); connect(ui->autoLogin, &QCheckBox::stateChanged, this, [this]() { - setConfig().account.rememberLogin = ui->autoLogin->isChecked(); + setConfig().account.rememberLogin.set(ui->autoLogin->isChecked()); }); connect(ui->accountName, &QLineEdit::textChanged, this, [](const QString &account) { - setConfig().account.accountName = account; + setConfig().account.accountName.set(account); }); connect(&passCfg, &PasswordConfig::sig_error, this, [this](const QString &msg) { @@ -190,7 +193,7 @@ GeneralPage::GeneralPage(QWidget *parent) }); connect(ui->accountPassword, &QLineEdit::textEdited, this, [this](const QString &password) { - setConfig().account.accountPassword = !password.isEmpty(); + setConfig().account.accountPassword.set(!password.isEmpty()); passCfg.setPassword(password); }); @@ -199,23 +202,23 @@ GeneralPage::GeneralPage(QWidget *parent) ui->showPassword->setText("Show Password"); ui->accountPassword->clear(); ui->accountPassword->setEchoMode(QLineEdit::Password); - } else if (getConfig().account.accountPassword && ui->accountPassword->text().isEmpty()) { + } else if (getConfig().account.accountPassword.get() && ui->accountPassword->text().isEmpty()) { ui->showPassword->setText("Request Password"); passCfg.getPassword(); } }); connect(ui->resourceLineEdit, &QLineEdit::textChanged, this, [](const QString &text) { - setConfig().canvas.resourcesDirectory = text; + setConfig().canvas.resourcesDirectory.set(text); }); connect(ui->resourcePushButton, &QAbstractButton::clicked, this, [this](bool /*unused*/) { - const auto &resourceDir = getConfig().canvas.resourcesDirectory; + const auto resourceDir = getConfig().canvas.resourcesDirectory.get(); QString directory = QFileDialog::getExistingDirectory(ui->resourcePushButton, "Choose resource location ...", resourceDir); if (!directory.isEmpty()) { ui->resourceLineEdit->setText(directory); - setConfig().canvas.resourcesDirectory = directory; + setConfig().canvas.resourcesDirectory.set(directory); } }); @@ -228,6 +231,26 @@ GeneralPage::GeneralPage(QWidget *parent) ui->resourceLineEdit->setDisabled(true); ui->resourcePushButton->setDisabled(true); } + + cfg.connection.remoteServerName.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + cfg.connection.remotePort.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + cfg.connection.localPort.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + cfg.connection.tlsEncryption.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + cfg.connection.proxyListensOnAnyInterface.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + cfg.connection.proxyConnectionStatus.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + cfg.general.characterEncoding.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + cfg.general.theme.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + cfg.general.checkForUpdate.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + cfg.mumeNative.emulatedExits.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + cfg.mumeNative.showHiddenExitFlags.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + cfg.mumeNative.showNotes.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + cfg.autoLoad.fileName.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + cfg.autoLoad.autoLoadMap.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + cfg.mumeClock.display.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + cfg.adventurePanel.displayXPStatus.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + cfg.account.rememberLogin.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + cfg.account.accountName.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + cfg.canvas.resourcesDirectory.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); } GeneralPage::~GeneralPage() @@ -244,9 +267,29 @@ void GeneralPage::slot_loadConfig() const auto &general = config.general; const auto &account = config.account; - ui->remoteName->setText(connection.remoteServerName); - ui->remotePort->setValue(connection.remotePort); - ui->localPort->setValue(connection.localPort); + SignalBlocker b1(*ui->remoteName); + SignalBlocker b2(*ui->remotePort); + SignalBlocker b3(*ui->localPort); + SignalBlocker b4(*ui->encryptionCheckBox); + SignalBlocker b5(*ui->proxyListensOnAnyInterfaceCheckBox); + SignalBlocker b6(*ui->charsetComboBox); + SignalBlocker b7(*ui->themeComboBox); + SignalBlocker b8(*ui->emulatedExitsCheckBox); + SignalBlocker b9(*ui->showHiddenExitFlagsCheckBox); + SignalBlocker b10(*ui->showNotesCheckBox); + SignalBlocker b11(*ui->checkForUpdateCheckBox); + SignalBlocker b12(*ui->autoLoadFileName); + SignalBlocker b13(*ui->autoLoadCheck); + SignalBlocker b14(*ui->displayMumeClockCheckBox); + SignalBlocker b15(*ui->displayXPStatusCheckBox); + SignalBlocker b16(*ui->proxyConnectionStatusCheckBox); + SignalBlocker b17(*ui->resourceLineEdit); + SignalBlocker b18(*ui->autoLogin); + SignalBlocker b19(*ui->accountName); + + ui->remoteName->setText(connection.remoteServerName.get()); + ui->remotePort->setValue(connection.remotePort.get()); + ui->localPort->setValue(connection.localPort.get()); #ifdef Q_OS_WASM ui->encryptionCheckBox->setDisabled(true); ui->encryptionCheckBox->setChecked(true); @@ -255,36 +298,36 @@ void GeneralPage::slot_loadConfig() ui->encryptionCheckBox->setEnabled(false); ui->encryptionCheckBox->setChecked(false); } else { - ui->encryptionCheckBox->setChecked(connection.tlsEncryption); + ui->encryptionCheckBox->setChecked(connection.tlsEncryption.get()); } #endif - ui->proxyListensOnAnyInterfaceCheckBox->setChecked(connection.proxyListensOnAnyInterface); - ui->charsetComboBox->setCurrentIndex(static_cast(general.characterEncoding)); - ui->themeComboBox->setCurrentIndex(static_cast(general.getTheme())); + ui->proxyListensOnAnyInterfaceCheckBox->setChecked(connection.proxyListensOnAnyInterface.get()); + ui->charsetComboBox->setCurrentIndex(general.characterEncoding.get()); + ui->themeComboBox->setCurrentIndex(general.theme.get()); - ui->emulatedExitsCheckBox->setChecked(mumeNative.emulatedExits); - ui->showHiddenExitFlagsCheckBox->setChecked(mumeNative.showHiddenExitFlags); - ui->showNotesCheckBox->setChecked(mumeNative.showNotes); + ui->emulatedExitsCheckBox->setChecked(mumeNative.emulatedExits.get()); + ui->showHiddenExitFlagsCheckBox->setChecked(mumeNative.showHiddenExitFlags.get()); + ui->showNotesCheckBox->setChecked(mumeNative.showNotes.get()); - ui->checkForUpdateCheckBox->setChecked(config.general.checkForUpdate); + ui->checkForUpdateCheckBox->setChecked(config.general.checkForUpdate.get()); ui->checkForUpdateCheckBox->setDisabled(NO_UPDATER); - ui->autoLoadFileName->setText(autoLoad.fileName); - ui->autoLoadCheck->setChecked(autoLoad.autoLoadMap); + ui->autoLoadFileName->setText(autoLoad.fileName.get()); + ui->autoLoadCheck->setChecked(autoLoad.autoLoadMap.get()); if constexpr (CURRENT_PLATFORM == PlatformEnum::Wasm) { ui->autoLoadFileName->setDisabled(true); ui->selectWorldFileButton->setDisabled(true); } else { - ui->autoLoadFileName->setEnabled(autoLoad.autoLoadMap); - ui->selectWorldFileButton->setEnabled(autoLoad.autoLoadMap); + ui->autoLoadFileName->setEnabled(autoLoad.autoLoadMap.get()); + ui->selectWorldFileButton->setEnabled(autoLoad.autoLoadMap.get()); } - ui->displayMumeClockCheckBox->setChecked(config.mumeClock.display); + ui->displayMumeClockCheckBox->setChecked(config.mumeClock.display.get()); - ui->displayXPStatusCheckBox->setChecked(config.adventurePanel.getDisplayXPStatus()); + ui->displayXPStatusCheckBox->setChecked(config.adventurePanel.displayXPStatus.get()); - ui->proxyConnectionStatusCheckBox->setChecked(connection.proxyConnectionStatus); + ui->proxyConnectionStatusCheckBox->setChecked(connection.proxyConnectionStatus.get()); - ui->resourceLineEdit->setText(config.canvas.resourcesDirectory); + ui->resourceLineEdit->setText(config.canvas.resourcesDirectory.get()); if constexpr (NO_QTKEYCHAIN) { ui->autoLogin->setEnabled(false); @@ -292,9 +335,9 @@ void GeneralPage::slot_loadConfig() ui->accountPassword->setEnabled(false); ui->showPassword->setEnabled(false); } else { - ui->autoLogin->setChecked(account.rememberLogin); - ui->accountName->setText(account.accountName); - if (!account.accountPassword) { + ui->autoLogin->setChecked(account.rememberLogin.get()); + ui->accountName->setText(account.accountName.get()); + if (!account.accountPassword.get()) { ui->accountPassword->setPlaceholderText(""); } } @@ -303,7 +346,7 @@ void GeneralPage::slot_loadConfig() void GeneralPage::slot_selectWorldFileButtonClicked(bool /*unused*/) { // FIXME: code duplication - const auto &savedLastMapDir = getConfig().autoLoad.lastMapDirectory; + const auto savedLastMapDir = getConfig().autoLoad.lastMapDirectory.get(); QString fileName = QFileDialog::getOpenFileName(this, "Choose map file ...", savedLastMapDir, @@ -312,49 +355,49 @@ void GeneralPage::slot_selectWorldFileButtonClicked(bool /*unused*/) ui->autoLoadFileName->setText(fileName); ui->autoLoadCheck->setChecked(true); auto &savedAutoLoad = setConfig().autoLoad; - savedAutoLoad.fileName = fileName; - savedAutoLoad.autoLoadMap = true; + savedAutoLoad.fileName.set(fileName); + savedAutoLoad.autoLoadMap.set(true); } } void GeneralPage::slot_remoteNameTextChanged(const QString & /*unused*/) { - setConfig().connection.remoteServerName = ui->remoteName->text(); + setConfig().connection.remoteServerName.set(ui->remoteName->text()); } void GeneralPage::slot_remotePortValueChanged(int /*unused*/) { - setConfig().connection.remotePort = static_cast(ui->remotePort->value()); + setConfig().connection.remotePort.set(ui->remotePort->value()); } void GeneralPage::slot_localPortValueChanged(int /*unused*/) { - setConfig().connection.localPort = static_cast(ui->localPort->value()); + setConfig().connection.localPort.set(ui->localPort->value()); } void GeneralPage::slot_emulatedExitsStateChanged(int /*unused*/) { - setConfig().mumeNative.emulatedExits = ui->emulatedExitsCheckBox->isChecked(); + setConfig().mumeNative.emulatedExits.set(ui->emulatedExitsCheckBox->isChecked()); } void GeneralPage::slot_showHiddenExitFlagsStateChanged(int /*unused*/) { - setConfig().mumeNative.showHiddenExitFlags = ui->showHiddenExitFlagsCheckBox->isChecked(); + setConfig().mumeNative.showHiddenExitFlags.set(ui->showHiddenExitFlagsCheckBox->isChecked()); } void GeneralPage::slot_showNotesStateChanged(int /*unused*/) { - setConfig().mumeNative.showNotes = ui->showNotesCheckBox->isChecked(); + setConfig().mumeNative.showNotes.set(ui->showNotesCheckBox->isChecked()); } void GeneralPage::slot_autoLoadFileNameTextChanged(const QString & /*unused*/) { - setConfig().autoLoad.fileName = ui->autoLoadFileName->text(); + setConfig().autoLoad.fileName.set(ui->autoLoadFileName->text()); } void GeneralPage::slot_autoLoadCheckStateChanged(int /*unused*/) { - setConfig().autoLoad.autoLoadMap = ui->autoLoadCheck->isChecked(); + setConfig().autoLoad.autoLoadMap.set(ui->autoLoadCheck->isChecked()); if (CURRENT_PLATFORM != PlatformEnum::Wasm) { ui->autoLoadFileName->setEnabled(ui->autoLoadCheck->isChecked()); ui->selectWorldFileButton->setEnabled(ui->autoLoadCheck->isChecked()); @@ -363,15 +406,15 @@ void GeneralPage::slot_autoLoadCheckStateChanged(int /*unused*/) void GeneralPage::slot_displayMumeClockStateChanged(int /*unused*/) { - setConfig().mumeClock.display = ui->displayMumeClockCheckBox->isChecked(); + setConfig().mumeClock.display.set(ui->displayMumeClockCheckBox->isChecked()); } void GeneralPage::slot_displayXPStatusStateChanged([[maybe_unused]] int) { - setConfig().adventurePanel.setDisplayXPStatus(ui->displayXPStatusCheckBox->isChecked()); + setConfig().adventurePanel.displayXPStatus.set(ui->displayXPStatusCheckBox->isChecked()); } void GeneralPage::slot_themeComboBoxChanged(int index) { - setConfig().general.setTheme(static_cast(index)); + setConfig().general.theme.set(index); } diff --git a/src/preferences/generalpage.h b/src/preferences/generalpage.h index 2a1e9fe50..dcd254166 100644 --- a/src/preferences/generalpage.h +++ b/src/preferences/generalpage.h @@ -6,6 +6,7 @@ // Author: Nils Schimmelmann (Jahara) #include "../configuration/PasswordConfig.h" +#include "../global/Signal2.h" #include "../global/macros.h" #include @@ -23,6 +24,7 @@ class NODISCARD_QOBJECT GeneralPage final : public QWidget private: Ui::GeneralPage *const ui; PasswordConfig passCfg; + Signal2Lifetime m_lifetime; public: explicit GeneralPage(QWidget *parent); diff --git a/src/preferences/graphicspage.cpp b/src/preferences/graphicspage.cpp index 4305087c5..dca429f0f 100644 --- a/src/preferences/graphicspage.cpp +++ b/src/preferences/graphicspage.cpp @@ -5,6 +5,7 @@ #include "graphicspage.h" #include "../configuration/configuration.h" +#include "../global/SignalBlocker.h" #include "../global/utils.h" #include "../opengl/OpenGLConfig.h" #include "AdvancedGraphics.h" @@ -33,19 +34,15 @@ GraphicsPage::GraphicsPage(QWidget *parent) connect(ui->bgChangeColor, &QAbstractButton::clicked, this, [this]() { changeColorClicked(setConfig().canvas.backgroundColor, ui->bgChangeColor); - graphicsSettingsChanged(); }); connect(ui->darkPushButton, &QAbstractButton::clicked, this, [this]() { changeColorClicked(setConfig().canvas.roomDarkColor, ui->darkPushButton); - graphicsSettingsChanged(); }); connect(ui->darkLitPushButton, &QAbstractButton::clicked, this, [this]() { changeColorClicked(setConfig().canvas.roomDarkLitColor, ui->darkLitPushButton); - graphicsSettingsChanged(); }); connect(ui->connectionNormalPushButton, &QAbstractButton::clicked, this, [this]() { changeColorClicked(setConfig().canvas.connectionNormalColor, ui->connectionNormalPushButton); - graphicsSettingsChanged(); }); connect(ui->antialiasingSamplesComboBox, &QComboBox::currentTextChanged, @@ -56,17 +53,14 @@ GraphicsPage::GraphicsPage(QWidget *parent) ui->antialiasingSamplesComboBox ->itemData(ui->antialiasingSamplesComboBox->currentIndex()) .toInt()); - graphicsSettingsChanged(); } }); connect(ui->trilinearFilteringCheckBox, &QCheckBox::stateChanged, this, [this](int /*unused*/) { setConfig().canvas.trilinearFiltering.set(ui->trilinearFilteringCheckBox->isChecked()); - graphicsSettingsChanged(); }); connect(ui->drawUnsavedChanges, &QCheckBox::stateChanged, this, [this](int /*unused*/) { setConfig().canvas.showUnsavedChanges.set(ui->drawUnsavedChanges->isChecked()); - graphicsSettingsChanged(); }); connect(ui->drawNeedsUpdate, &QCheckBox::stateChanged, @@ -87,23 +81,27 @@ GraphicsPage::GraphicsPage(QWidget *parent) connect(ui->weatherAtmosphereSlider, &QSlider::valueChanged, this, [this](int value) { setConfig().canvas.weatherAtmosphereIntensity.set(value); - graphicsSettingsChanged(); }); connect(ui->weatherPrecipitationSlider, &QSlider::valueChanged, this, [this](int value) { setConfig().canvas.weatherPrecipitationIntensity.set(value); - graphicsSettingsChanged(); }); connect(ui->weatherTimeOfDaySlider, &QSlider::valueChanged, this, [this](int value) { setConfig().canvas.weatherTimeOfDayIntensity.set(value); - graphicsSettingsChanged(); }); - connect(m_advanced.get(), - &AdvancedGraphicsGroupBox::sig_graphicsSettingsChanged, - this, - &GraphicsPage::slot_graphicsSettingsChanged); + auto &canvas = setConfig().canvas; + canvas.antialiasingSamples.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + canvas.trilinearFiltering.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + canvas.showMissingMapId.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + canvas.showUnsavedChanges.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + canvas.showUnmappedExits.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + canvas.drawUpperLayersTextured.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + canvas.drawDoorNames.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + canvas.weatherAtmosphereIntensity.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + canvas.weatherPrecipitationIntensity.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + canvas.weatherTimeOfDayIntensity.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); } GraphicsPage::~GraphicsPage() @@ -114,6 +112,18 @@ GraphicsPage::~GraphicsPage() void GraphicsPage::slot_loadConfig() { const auto &settings = getConfig().canvas; + + SignalBlocker b1(*ui->antialiasingSamplesComboBox); + SignalBlocker b2(*ui->trilinearFilteringCheckBox); + SignalBlocker b3(*ui->drawUnsavedChanges); + SignalBlocker b4(*ui->drawNeedsUpdate); + SignalBlocker b5(*ui->drawNotMappedExits); + SignalBlocker b6(*ui->drawDoorNames); + SignalBlocker b7(*ui->drawUpperLayersTextured); + SignalBlocker b8(*ui->weatherAtmosphereSlider); + SignalBlocker b9(*ui->weatherPrecipitationSlider); + SignalBlocker b10(*ui->weatherTimeOfDaySlider); + setIconColor(ui->bgChangeColor, settings.backgroundColor); setIconColor(ui->darkPushButton, settings.roomDarkColor); setIconColor(ui->darkLitPushButton, settings.roomDarkLitColor); @@ -140,7 +150,8 @@ void GraphicsPage::slot_loadConfig() ui->drawUnsavedChanges->setChecked(settings.showUnsavedChanges.get()); ui->drawNeedsUpdate->setChecked(settings.showMissingMapId.get()); ui->drawNotMappedExits->setChecked(settings.showUnmappedExits.get()); - ui->drawDoorNames->setChecked(settings.drawDoorNames); + ui->drawDoorNames->setChecked(settings.drawDoorNames.get()); + ui->drawUpperLayersTextured->setChecked(settings.drawUpperLayersTextured.get()); ui->weatherAtmosphereSlider->setValue(settings.weatherAtmosphereIntensity.get()); ui->weatherPrecipitationSlider->setValue(settings.weatherPrecipitationIntensity.get()); @@ -160,23 +171,19 @@ void GraphicsPage::changeColorClicked(XNamedColor &namedColor, QPushButton *cons void GraphicsPage::slot_drawNeedsUpdateStateChanged(int /*unused*/) { setConfig().canvas.showMissingMapId.set(ui->drawNeedsUpdate->isChecked()); - graphicsSettingsChanged(); } void GraphicsPage::slot_drawNotMappedExitsStateChanged(int /*unused*/) { setConfig().canvas.showUnmappedExits.set(ui->drawNotMappedExits->isChecked()); - graphicsSettingsChanged(); } void GraphicsPage::slot_drawDoorNamesStateChanged(int /*unused*/) { - setConfig().canvas.drawDoorNames = ui->drawDoorNames->isChecked(); - graphicsSettingsChanged(); + setConfig().canvas.drawDoorNames.set(ui->drawDoorNames->isChecked()); } void GraphicsPage::slot_drawUpperLayersTexturedStateChanged(int /*unused*/) { - setConfig().canvas.drawUpperLayersTextured = ui->drawUpperLayersTextured->isChecked(); - graphicsSettingsChanged(); + setConfig().canvas.drawUpperLayersTextured.set(ui->drawUpperLayersTextured->isChecked()); } diff --git a/src/preferences/graphicspage.h b/src/preferences/graphicspage.h index 1135cce98..4b6acf25b 100644 --- a/src/preferences/graphicspage.h +++ b/src/preferences/graphicspage.h @@ -3,6 +3,7 @@ // Copyright (C) 2019 The MMapper Authors // Author: Nils Schimmelmann (Jahara) +#include "../global/Signal2.h" #include "../global/macros.h" #include "ui_graphicspage.h" @@ -31,12 +32,9 @@ class NODISCARD_QOBJECT GraphicsPage final : public QWidget private: void changeColorClicked(XNamedColor &color, QPushButton *pushButton); - void graphicsSettingsChanged() { emit sig_graphicsSettingsChanged(); } Ui::GraphicsPage *const ui; std::unique_ptr m_advanced; - -signals: - void sig_graphicsSettingsChanged(); + Signal2Lifetime m_lifetime; public slots: void slot_loadConfig(); @@ -44,6 +42,4 @@ public slots: void slot_drawNotMappedExitsStateChanged(int); void slot_drawDoorNamesStateChanged(int); void slot_drawUpperLayersTexturedStateChanged(int); - // this slot just calls the signal... not useful - void slot_graphicsSettingsChanged() { graphicsSettingsChanged(); } }; diff --git a/src/preferences/grouppage.cpp b/src/preferences/grouppage.cpp index f9f3a988a..447eb0282 100644 --- a/src/preferences/grouppage.cpp +++ b/src/preferences/grouppage.cpp @@ -4,6 +4,7 @@ #include "grouppage.h" #include "../configuration/configuration.h" +#include "../global/SignalBlocker.h" #include "ui_grouppage.h" #include @@ -20,8 +21,7 @@ GroupPage::GroupPage(QWidget *const parent) connect(ui->yourColorPushButton, &QPushButton::clicked, this, &GroupPage::slot_chooseColor); connect(ui->npcOverrideColorCheckBox, &QCheckBox::stateChanged, this, [this](int checked) { - setConfig().groupManager.npcColorOverride = checked; - emit sig_groupSettingsChanged(); + setConfig().groupManager.npcColorOverride.set(checked); }); connect(ui->npcOverrideColorPushButton, &QPushButton::clicked, @@ -29,14 +29,19 @@ GroupPage::GroupPage(QWidget *const parent) &GroupPage::slot_chooseNpcOverrideColor); connect(ui->npcSortBottomCheckbox, &QCheckBox::stateChanged, this, [this](int checked) { - setConfig().groupManager.npcSortBottom = checked; - emit sig_groupSettingsChanged(); + setConfig().groupManager.npcSortBottom.set(checked); }); connect(ui->npcHideCheckbox, &QCheckBox::stateChanged, this, [this](int checked) { - setConfig().groupManager.npcHide = checked; - emit sig_groupSettingsChanged(); + setConfig().groupManager.npcHide.set(checked); }); + auto &group = setConfig().groupManager; + group.color.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + group.npcColor.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + group.npcColorOverride.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + group.npcSortBottom.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + group.npcHide.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + slot_loadConfig(); } @@ -49,41 +54,43 @@ void GroupPage::slot_loadConfig() { const auto &settings = getConfig().groupManager; + SignalBlocker b1(*ui->npcOverrideColorCheckBox); + SignalBlocker b2(*ui->npcSortBottomCheckbox); + SignalBlocker b3(*ui->npcHideCheckbox); + QPixmap yourPix(16, 16); - yourPix.fill(settings.color); + yourPix.fill(settings.color.get()); ui->yourColorPushButton->setIcon(QIcon(yourPix)); - ui->npcOverrideColorCheckBox->setChecked(settings.npcColorOverride); + ui->npcOverrideColorCheckBox->setChecked(settings.npcColorOverride.get()); QPixmap npcOverridePix(16, 16); - npcOverridePix.fill(settings.npcColor); + npcOverridePix.fill(settings.npcColor.get()); ui->npcOverrideColorPushButton->setIcon(QIcon(npcOverridePix)); - ui->npcSortBottomCheckbox->setChecked(settings.npcSortBottom); - ui->npcHideCheckbox->setChecked(settings.npcHide); + ui->npcSortBottomCheckbox->setChecked(settings.npcSortBottom.get()); + ui->npcHideCheckbox->setChecked(settings.npcHide.get()); } void GroupPage::slot_chooseColor() { - const QColor color = QColorDialog::getColor(getConfig().groupManager.color, + const QColor color = QColorDialog::getColor(getConfig().groupManager.color.get(), this, tr("Select Your Color")); if (color.isValid()) { - setConfig().groupManager.color = color; + setConfig().groupManager.color.set(color); slot_loadConfig(); - emit sig_groupSettingsChanged(); } } void GroupPage::slot_chooseNpcOverrideColor() { - const QColor color = QColorDialog::getColor(getConfig().groupManager.npcColor, + const QColor color = QColorDialog::getColor(getConfig().groupManager.npcColor.get(), this, tr("Select NPC Override Color")); if (color.isValid()) { - setConfig().groupManager.npcColor = color; + setConfig().groupManager.npcColor.set(color); slot_loadConfig(); - emit sig_groupSettingsChanged(); } } diff --git a/src/preferences/grouppage.h b/src/preferences/grouppage.h index 866ff13be..9e4e1c975 100644 --- a/src/preferences/grouppage.h +++ b/src/preferences/grouppage.h @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later // Copyright (C) 2025 The MMapper Authors +#include "../global/Signal2.h" #include "../global/macros.h" #include @@ -22,14 +23,12 @@ class NODISCARD_QOBJECT GroupPage final : public QWidget private: Ui::GroupPage *const ui; + Signal2Lifetime m_lifetime; public: explicit GroupPage(QWidget *parent = nullptr); ~GroupPage() final; -signals: - void sig_groupSettingsChanged(); - public slots: void slot_loadConfig(); diff --git a/src/preferences/mumeprotocolpage.cpp b/src/preferences/mumeprotocolpage.cpp index b1016b09c..c0da96eeb 100644 --- a/src/preferences/mumeprotocolpage.cpp +++ b/src/preferences/mumeprotocolpage.cpp @@ -5,6 +5,7 @@ #include "mumeprotocolpage.h" #include "../configuration/configuration.h" +#include "../global/SignalBlocker.h" #include "ui_mumeprotocolpage.h" #include @@ -30,6 +31,10 @@ MumeProtocolPage::MumeProtocolPage(QWidget *parent) &QAbstractButton::clicked, this, &MumeProtocolPage::slot_externalEditorBrowseButtonClicked); + + auto &proto = setConfig().mumeClientProtocol; + proto.internalRemoteEditor.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + proto.externalRemoteEditorCommand.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); } MumeProtocolPage::~MumeProtocolPage() @@ -40,11 +45,16 @@ MumeProtocolPage::~MumeProtocolPage() void MumeProtocolPage::slot_loadConfig() { const auto &settings = getConfig().mumeClientProtocol; - ui->internalEditorRadioButton->setChecked(settings.internalRemoteEditor); - ui->externalEditorRadioButton->setChecked(!settings.internalRemoteEditor); - ui->externalEditorCommand->setText(settings.externalRemoteEditorCommand); - ui->externalEditorCommand->setEnabled(!settings.internalRemoteEditor); - ui->externalEditorBrowseButton->setEnabled(!settings.internalRemoteEditor); + + SignalBlocker b1(*ui->internalEditorRadioButton); + SignalBlocker b2(*ui->externalEditorRadioButton); + SignalBlocker b3(*ui->externalEditorCommand); + + ui->internalEditorRadioButton->setChecked(settings.internalRemoteEditor.get()); + ui->externalEditorRadioButton->setChecked(!settings.internalRemoteEditor.get()); + ui->externalEditorCommand->setText(settings.externalRemoteEditorCommand.get()); + ui->externalEditorCommand->setEnabled(!settings.internalRemoteEditor.get()); + ui->externalEditorBrowseButton->setEnabled(!settings.internalRemoteEditor.get()); if constexpr (CURRENT_PLATFORM == PlatformEnum::Wasm) { ui->externalEditorRadioButton->setDisabled(true); @@ -55,7 +65,7 @@ void MumeProtocolPage::slot_internalEditorRadioButtonChanged(bool /*unused*/) { const bool useInternalEditor = ui->internalEditorRadioButton->isChecked(); - setConfig().mumeClientProtocol.internalRemoteEditor = useInternalEditor; + setConfig().mumeClientProtocol.internalRemoteEditor.set(useInternalEditor); ui->externalEditorCommand->setEnabled(!useInternalEditor); ui->externalEditorBrowseButton->setEnabled(!useInternalEditor); @@ -63,18 +73,18 @@ void MumeProtocolPage::slot_internalEditorRadioButtonChanged(bool /*unused*/) void MumeProtocolPage::slot_externalEditorCommandTextChanged(QString text) { - setConfig().mumeClientProtocol.externalRemoteEditorCommand = std::move(text); + setConfig().mumeClientProtocol.externalRemoteEditorCommand.set(std::move(text)); } void MumeProtocolPage::slot_externalEditorBrowseButtonClicked(bool /*unused*/) { - auto &command = setConfig().mumeClientProtocol.externalRemoteEditorCommand; - QFileInfo dirInfo(command); + auto &proto = setConfig().mumeClientProtocol; + QFileInfo dirInfo(proto.externalRemoteEditorCommand.get()); const auto dir = dirInfo.exists() ? dirInfo.absoluteDir().absolutePath() : QDir::homePath(); QString fileName = QFileDialog::getOpenFileName(this, "Choose editor...", dir, "Editor (*)"); if (!fileName.isEmpty()) { QString quotedFileName = QString(R"("%1")").arg(fileName.replace(R"(")", R"(\")")); ui->externalEditorCommand->setText(quotedFileName); - command = quotedFileName; + proto.externalRemoteEditorCommand.set(quotedFileName); } } diff --git a/src/preferences/mumeprotocolpage.h b/src/preferences/mumeprotocolpage.h index 583d528d9..fcdea3768 100644 --- a/src/preferences/mumeprotocolpage.h +++ b/src/preferences/mumeprotocolpage.h @@ -3,6 +3,7 @@ // Copyright (C) 2019 The MMapper Authors // Author: Nils Schimmelmann (Jahara) +#include "../global/Signal2.h" #include "../global/macros.h" #include @@ -21,6 +22,7 @@ class NODISCARD_QOBJECT MumeProtocolPage final : public QWidget private: Ui::MumeProtocolPage *const ui; + Signal2Lifetime m_lifetime; public: explicit MumeProtocolPage(QWidget *parent); diff --git a/src/preferences/parserpage.cpp b/src/preferences/parserpage.cpp index 8288ac74f..65894598d 100644 --- a/src/preferences/parserpage.cpp +++ b/src/preferences/parserpage.cpp @@ -7,6 +7,7 @@ #include "../configuration/configuration.h" #include "../global/Charset.h" +#include "../global/SignalBlocker.h" #include "../parser/AbstractParser-Utils.h" #include "AnsiColorDialog.h" #include "ansicombo.h" @@ -60,43 +61,58 @@ ParserPage::ParserPage(QWidget *const parent) &ParserPage::slot_roomDescColorClicked); connect(charPrefixLineEdit, &QLineEdit::editingFinished, this, [this]() { - setConfig().parser.prefixChar = mmqt::toLatin1(charPrefixLineEdit->text().front()); + if (!charPrefixLineEdit->text().isEmpty()) { + setConfig().parser.prefixChar.set(mmqt::toLatin1(charPrefixLineEdit->text().front())); + } }); connect(encodeEmoji, &QCheckBox::clicked, this, [](bool checked) { - setConfig().parser.encodeEmoji = checked; + setConfig().parser.encodeEmoji.set(checked); }); connect(decodeEmoji, &QCheckBox::clicked, this, [](bool checked) { - setConfig().parser.decodeEmoji = checked; + setConfig().parser.decodeEmoji.set(checked); }); + + auto &parser = setConfig().parser; + parser.roomNameColor.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + parser.roomDescColor.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + parser.prefixChar.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + parser.encodeEmoji.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + parser.decodeEmoji.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); } void ParserPage::slot_loadConfig() { const auto &settings = getConfig().parser; - AnsiCombo::makeWidgetColoured(roomNameColorLabel, settings.roomNameColor); - AnsiCombo::makeWidgetColoured(roomDescColorLabel, settings.roomDescColor); + SignalBlocker b1(*charPrefixLineEdit); + SignalBlocker b2(*encodeEmoji); + SignalBlocker b3(*decodeEmoji); - charPrefixLineEdit->setText(QString(settings.prefixChar)); - charPrefixLineEdit->setValidator(new CommandPrefixValidator(this)); + AnsiCombo::makeWidgetColoured(roomNameColorLabel, settings.roomNameColor.get()); + AnsiCombo::makeWidgetColoured(roomDescColorLabel, settings.roomDescColor.get()); + + charPrefixLineEdit->setText(QString(static_cast(settings.prefixChar.get()))); + if (charPrefixLineEdit->validator() == nullptr) { + charPrefixLineEdit->setValidator(new CommandPrefixValidator(this)); + } - encodeEmoji->setChecked(settings.encodeEmoji); - decodeEmoji->setChecked(settings.decodeEmoji); + encodeEmoji->setChecked(settings.encodeEmoji.get()); + decodeEmoji->setChecked(settings.decodeEmoji.get()); } void ParserPage::slot_roomNameColorClicked() { - AnsiColorDialog::getColor(getConfig().parser.roomNameColor, this, [this](QString ansiString) { + AnsiColorDialog::getColor(getConfig().parser.roomNameColor.get(), this, [this](QString ansiString) { AnsiCombo::makeWidgetColoured(roomNameColorLabel, ansiString); - setConfig().parser.roomNameColor = ansiString; + setConfig().parser.roomNameColor.set(ansiString); }); } void ParserPage::slot_roomDescColorClicked() { - AnsiColorDialog::getColor(getConfig().parser.roomDescColor, this, [this](QString ansiString) { + AnsiColorDialog::getColor(getConfig().parser.roomDescColor.get(), this, [this](QString ansiString) { AnsiCombo::makeWidgetColoured(roomDescColorLabel, ansiString); - setConfig().parser.roomDescColor = ansiString; + setConfig().parser.roomDescColor.set(ansiString); }); } diff --git a/src/preferences/parserpage.h b/src/preferences/parserpage.h index 1776c355a..3fdd770ee 100644 --- a/src/preferences/parserpage.h +++ b/src/preferences/parserpage.h @@ -4,6 +4,7 @@ // Author: Ulf Hermann (Alve) // Author: Marek Krejza (Caligor) +#include "../global/Signal2.h" #include "../global/macros.h" #include "ui_parserpage.h" @@ -19,6 +20,9 @@ class NODISCARD_QOBJECT ParserPage : public QWidget, private Ui::ParserPage { Q_OBJECT +private: + Signal2Lifetime m_lifetime; + public: explicit ParserPage(QWidget *parent); diff --git a/src/preferences/pathmachinepage.cpp b/src/preferences/pathmachinepage.cpp index 9bb020f9a..1ff0b9296 100644 --- a/src/preferences/pathmachinepage.cpp +++ b/src/preferences/pathmachinepage.cpp @@ -6,6 +6,7 @@ #include "pathmachinepage.h" #include "../configuration/configuration.h" +#include "../global/SignalBlocker.h" #include @@ -43,51 +44,68 @@ PathmachinePage::PathmachinePage(QWidget *parent) QOverload::of(&QSpinBox::valueChanged), this, &PathmachinePage::slot_matchingToleranceSpinBoxValueChanged); + + auto &pm = setConfig().pathMachine; + pm.acceptBestRelative.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + pm.acceptBestAbsolute.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + pm.newRoomPenalty.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + pm.correctPositionBonus.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + pm.multipleConnectionsPenalty.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + pm.maxPaths.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); + pm.matchingTolerance.registerChangeCallback(m_lifetime, [this]() { slot_loadConfig(); }); } void PathmachinePage::slot_loadConfig() { + SignalBlocker b1(*acceptBestRelativeDoubleSpinBox); + SignalBlocker b2(*acceptBestAbsoluteDoubleSpinBox); + SignalBlocker b3(*newRoomPenaltyDoubleSpinBox); + SignalBlocker b4(*correctPositionBonusDoubleSpinBox); + SignalBlocker b5(*multipleConnectionsPenaltyDoubleSpinBox); + SignalBlocker b6(*maxPaths); + SignalBlocker b7(*matchingToleranceSpinBox); + const auto &settings = getConfig().pathMachine; - acceptBestRelativeDoubleSpinBox->setValue(settings.acceptBestRelative); - acceptBestAbsoluteDoubleSpinBox->setValue(settings.acceptBestAbsolute); - newRoomPenaltyDoubleSpinBox->setValue(settings.newRoomPenalty); - correctPositionBonusDoubleSpinBox->setValue(settings.correctPositionBonus); - maxPaths->setValue(settings.maxPaths); - matchingToleranceSpinBox->setValue(settings.matchingTolerance); - multipleConnectionsPenaltyDoubleSpinBox->setValue(settings.multipleConnectionsPenalty); + acceptBestRelativeDoubleSpinBox->setValue(settings.acceptBestRelative.get()); + acceptBestAbsoluteDoubleSpinBox->setValue(settings.acceptBestAbsolute.get()); + newRoomPenaltyDoubleSpinBox->setValue(settings.newRoomPenalty.get()); + correctPositionBonusDoubleSpinBox->setValue(settings.correctPositionBonus.get()); + maxPaths->setValue(settings.maxPaths.get()); + matchingToleranceSpinBox->setValue(settings.matchingTolerance.get()); + multipleConnectionsPenaltyDoubleSpinBox->setValue(settings.multipleConnectionsPenalty.get()); } void PathmachinePage::slot_acceptBestRelativeDoubleSpinBoxValueChanged(const double val) { - setConfig().pathMachine.acceptBestRelative = val; + setConfig().pathMachine.acceptBestRelative.set(val); } void PathmachinePage::slot_acceptBestAbsoluteDoubleSpinBoxValueChanged(const double val) { - setConfig().pathMachine.acceptBestAbsolute = val; + setConfig().pathMachine.acceptBestAbsolute.set(val); } void PathmachinePage::slot_newRoomPenaltyDoubleSpinBoxValueChanged(const double val) { - setConfig().pathMachine.newRoomPenalty = val; + setConfig().pathMachine.newRoomPenalty.set(val); } void PathmachinePage::slot_correctPositionBonusDoubleSpinBoxValueChanged(const double val) { - setConfig().pathMachine.correctPositionBonus = val; + setConfig().pathMachine.correctPositionBonus.set(val); } void PathmachinePage::slot_multipleConnectionsPenaltyDoubleSpinBoxValueChanged(const double val) { - setConfig().pathMachine.multipleConnectionsPenalty = val; + setConfig().pathMachine.multipleConnectionsPenalty.set(val); } void PathmachinePage::slot_maxPathsValueChanged(const int val) { - setConfig().pathMachine.maxPaths = utils::clampNonNegative(val); + setConfig().pathMachine.maxPaths.set(utils::clampNonNegative(val)); } void PathmachinePage::slot_matchingToleranceSpinBoxValueChanged(const int val) { - setConfig().pathMachine.matchingTolerance = utils::clampNonNegative(val); + setConfig().pathMachine.matchingTolerance.set(utils::clampNonNegative(val)); } diff --git a/src/preferences/pathmachinepage.h b/src/preferences/pathmachinepage.h index 53c693fa0..fa62683d6 100644 --- a/src/preferences/pathmachinepage.h +++ b/src/preferences/pathmachinepage.h @@ -4,6 +4,7 @@ // Author: Ulf Hermann (Alve) // Author: Marek Krejza (Caligor) +#include "../global/Signal2.h" #include "../global/macros.h" #include "ui_pathmachinepage.h" @@ -17,6 +18,9 @@ class NODISCARD_QOBJECT PathmachinePage : public QWidget, private Ui::Pathmachin { Q_OBJECT +private: + Signal2Lifetime m_lifetime; + public: explicit PathmachinePage(QWidget *parent);