diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5395c0a..759d7c7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,16 +13,29 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Install dependencies + - name: Install non-Qt dependencies run: | sudo apt-get update - sudo apt-get install -y qt6-base-dev qt6-svg-dev libxkbcommon-dev liblo-dev libasound2-dev build-essential cmake + sudo apt-get install -y libxkbcommon-dev liblo-dev libasound2-dev build-essential cmake + + - name: Install Qt + uses: jurplel/install-qt-action@v4 + with: + version: "6.11.0" + host: "linux" + target: "desktop" + arch: "linux_gcc_64" + + - name: ccache + uses: hendrikmuhs/ccache-action@v1 + with: + key: ${{ runner.os }}-${{ hashFiles('**/CMakeLists.txt') }} - name: Configure run: cmake -B build -DCMAKE_BUILD_TYPE=Release - name: Build - run: cmake --build build --config Release + run: cmake --build build --config Release --parallel - name: Upload artifact uses: actions/upload-artifact@v4 @@ -35,33 +48,51 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Install Qt - uses: jurplel/install-qt-action@v4 + - name: Cache Qt + id: cache-qt + uses: actions/cache@v4 with: - version: "6.6.0" - host: "windows" - target: "desktop" - arch: "win64_msvc2019_64" + path: C:\Qt\6.11.0\msvc2022_64 + key: qt-6.11.0-msvc2022_64 + + - name: Download Qt + if: steps.cache-qt.outputs.cache-hit != 'true' + run: | + gh release download qt-deps --pattern "msvc2022_64.zip" --dir $env:TEMP + Expand-Archive -Path "$env:TEMP\msvc2022_64.zip" -DestinationPath C:\Qt\6.11.0 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Install liblo via vcpkg run: vcpkg install liblo:x64-windows + - name: Set up MSVC + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: x64 + + - name: ccache + uses: hendrikmuhs/ccache-action@v1 + with: + key: ${{ runner.os }}-${{ hashFiles('**/CMakeLists.txt') }} + - name: Configure - run: cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" + run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=cl -DCMAKE_CXX_COMPILER=cl -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" -DCMAKE_PREFIX_PATH="C:\Qt\6.11.0\msvc2022_64" - name: Build - run: cmake --build build --config Release + run: cmake --build build --parallel - name: Deploy Qt run: | - cd build/Release - windeployqt --release OpenMix.exe + mkdir build\deploy + copy build\OpenMix.exe build\deploy\ + C:\Qt\6.11.0\msvc2022_64\bin\windeployqt --release build\deploy\OpenMix.exe - name: Upload artifact uses: actions/upload-artifact@v4 with: name: OpenMix-windows - path: build/Release/ + path: build/deploy/ build-macos: runs-on: macos-latest @@ -71,6 +102,11 @@ jobs: - name: Install dependencies run: brew install qt@6 liblo + - name: ccache + uses: hendrikmuhs/ccache-action@v1 + with: + key: ${{ runner.os }}-${{ hashFiles('**/CMakeLists.txt') }} + - name: Configure run: | export PATH="/opt/homebrew/opt/qt@6/bin:$PATH" @@ -79,7 +115,7 @@ jobs: -DCMAKE_PREFIX_PATH=/opt/homebrew/opt/qt@6 - name: Build - run: cmake --build build --config Release + run: cmake --build build --config Release --parallel - name: Deploy, sign, & package run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index 335058f..e65c85d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,18 @@ set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_AUTOUIC ON) +find_program(CCACHE_PROGRAM ccache) +if(CCACHE_PROGRAM) + set(CMAKE_C_COMPILER_LAUNCHER "${CCACHE_PROGRAM}") + set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}") + if(MSVC) + foreach(config DEBUG RELWITHDEBINFO) + string(REPLACE "/Zi" "/Z7" CMAKE_CXX_FLAGS_${config} "${CMAKE_CXX_FLAGS_${config}}") + string(REPLACE "/Zi" "/Z7" CMAKE_C_FLAGS_${config} "${CMAKE_C_FLAGS_${config}}") + endforeach() + endif() +endif() + # find Qt6 find_package(Qt6 REQUIRED COMPONENTS Core @@ -40,8 +52,27 @@ else() endif() # RtMidi -set(RTMIDI_SOURCES libs/RtMidi/RtMidi.cpp) -set(RTMIDI_HEADERS libs/RtMidi/RtMidi.h) +add_library(RtMidi STATIC libs/RtMidi/RtMidi.cpp libs/RtMidi/RtMidi.h) +target_include_directories(RtMidi PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/libs/RtMidi) +if(WIN32) + target_compile_definitions(RtMidi PUBLIC __WINDOWS_MM__) + target_compile_options(RtMidi PRIVATE /FIwindows.h) + target_link_libraries(RtMidi PUBLIC winmm) +elseif(APPLE) + target_compile_definitions(RtMidi PUBLIC __MACOSX_CORE__) + find_library(COREMIDI_LIBRARY CoreMIDI REQUIRED) + find_library(COREAUDIO_LIBRARY CoreAudio REQUIRED) + find_library(COREFOUNDATION_LIBRARY CoreFoundation REQUIRED) + target_link_libraries(RtMidi PUBLIC + ${COREMIDI_LIBRARY} + ${COREAUDIO_LIBRARY} + ${COREFOUNDATION_LIBRARY} + ) +else() + target_compile_definitions(RtMidi PUBLIC __LINUX_ALSA__) + find_package(ALSA REQUIRED) + target_link_libraries(RtMidi PUBLIC ALSA::ALSA) +endif() # source files set(SOURCES @@ -214,8 +245,6 @@ set(RESOURCES add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS} - ${RTMIDI_SOURCES} - ${RTMIDI_HEADERS} ${RESOURCES} ) @@ -224,7 +253,6 @@ target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src ${CMAKE_CURRENT_SOURCE_DIR}/src/ui ${CMAKE_CURRENT_SOURCE_DIR}/include - ${CMAKE_CURRENT_SOURCE_DIR}/libs/RtMidi ) # link libraries @@ -244,31 +272,28 @@ else() target_link_libraries(${PROJECT_NAME} PRIVATE PkgConfig::LIBLO) endif() -# RtMidi config -if(WIN32) - target_compile_definitions(${PROJECT_NAME} PRIVATE __WINDOWS_MM__) - target_link_libraries(${PROJECT_NAME} PRIVATE winmm) - set_source_files_properties(libs/RtMidi/RtMidi.cpp PROPERTIES - COMPILE_FLAGS "/FIwindows.h" - ) -elseif(APPLE) - target_compile_definitions(${PROJECT_NAME} PRIVATE __MACOSX_CORE__) - find_library(COREMIDI_LIBRARY CoreMIDI) - find_library(COREAUDIO_LIBRARY CoreAudio) - find_library(COREFOUNDATION_LIBRARY CoreFoundation) - target_link_libraries(${PROJECT_NAME} PRIVATE - ${COREMIDI_LIBRARY} - ${COREAUDIO_LIBRARY} - ${COREFOUNDATION_LIBRARY} - ) -else() - target_compile_definitions(${PROJECT_NAME} PRIVATE __LINUX_ALSA__) - find_package(ALSA REQUIRED) - target_link_libraries(${PROJECT_NAME} PRIVATE ALSA::ALSA) -endif() +target_link_libraries(${PROJECT_NAME} PRIVATE RtMidi) + +target_precompile_headers(${PROJECT_NAME} PRIVATE + + + + + + + + + + + + + + +) # platform-specific settings if(WIN32) + target_compile_definitions(${PROJECT_NAME} PRIVATE NOMINMAX) set_target_properties(${PROJECT_NAME} PROPERTIES WIN32_EXECUTABLE TRUE ) diff --git a/src/app/Application.cpp b/src/app/Application.cpp index 622b370..78d42d0 100644 --- a/src/app/Application.cpp +++ b/src/app/Application.cpp @@ -1,4 +1,5 @@ #include "Application.h" +#include "ui/MainWindow.h" #include "core/AppLogger.h" #include "core/ConnectionLogBridge.h" #include "core/Cue.h" @@ -212,4 +213,8 @@ void Application::startupScan() { m_discoveryService->startScan(3000); } +void Application::setMainWindow(MainWindow* window) { m_mainWindow = window; } + +MainWindow* Application::mainWindow() { return m_mainWindow; } + } // namespace OpenMix diff --git a/src/app/Application.h b/src/app/Application.h index ad51a21..98fd84e 100644 --- a/src/app/Application.h +++ b/src/app/Application.h @@ -1,12 +1,12 @@ #pragma once -#include "ui/MainWindow.h" #include #include #include namespace OpenMix { +class MainWindow; class Show; class PlaybackEngine; class MixerProtocol; @@ -32,36 +32,36 @@ class Application : public QObject { ~Application() override; // singleton access - static Application* instance() { return s_instance; } + [[nodiscard]] static Application* instance() { return s_instance; } // core components - Show* show() { return m_show; } - PlaybackEngine* playbackEngine() { return m_playbackEngine; } - MixerProtocol* mixer() { return m_mixer; } - QUndoStack* undoStack() { return m_undoStack; } - AutosaveManager* autosaveManager() { return m_autosaveManager; } + [[nodiscard]] Show* show() { return m_show; } + [[nodiscard]] PlaybackEngine* playbackEngine() { return m_playbackEngine; } + [[nodiscard]] MixerProtocol* mixer() { return m_mixer; } + [[nodiscard]] QUndoStack* undoStack() { return m_undoStack; } + [[nodiscard]] AutosaveManager* autosaveManager() { return m_autosaveManager; } // safety & validation - CueValidator* cueValidator() { return m_cueValidator; } - PlaybackGuard* playbackGuard() { return m_playbackGuard; } - PlaybackLogger* playbackLogger() { return m_playbackLogger; } - DryRunEngine* dryRunEngine() { return m_dryRunEngine; } + [[nodiscard]] CueValidator* cueValidator() { return m_cueValidator; } + [[nodiscard]] PlaybackGuard* playbackGuard() { return m_playbackGuard; } + [[nodiscard]] PlaybackLogger* playbackLogger() { return m_playbackLogger; } + [[nodiscard]] DryRunEngine* dryRunEngine() { return m_dryRunEngine; } // operator experience - ShortcutManager* shortcutManager() { return m_shortcutManager; } - OperationModeManager* operationModeManager() { return m_operationModeManager; } + [[nodiscard]] ShortcutManager* shortcutManager() { return m_shortcutManager; } + [[nodiscard]] OperationModeManager* operationModeManager() { return m_operationModeManager; } // recovery - CrashRecovery* crashRecovery() { return m_crashRecovery; } + [[nodiscard]] CrashRecovery* crashRecovery() { return m_crashRecovery; } // MIDI input - MidiInputManager* midiInputManager() { return m_midiInputManager; } + [[nodiscard]] MidiInputManager* midiInputManager() { return m_midiInputManager; } // console discovery - ConsoleDiscoveryService* discoveryService() { return m_discoveryService; } + [[nodiscard]] ConsoleDiscoveryService* discoveryService() { return m_discoveryService; } // application logging - AppLogger* appLogger() { return m_appLogger; } + [[nodiscard]] AppLogger* appLogger() { return m_appLogger; } // mixer connection void connectToMixer(const QString& type, const QString& host, int port); @@ -69,8 +69,8 @@ class Application : public QObject { void disconnectFromMixer(); // main window - void setMainWindow(MainWindow* window) { m_mainWindow = window; } - MainWindow* mainWindow() { return m_mainWindow; } + void setMainWindow(MainWindow* window); + [[nodiscard]] MainWindow* mainWindow(); // initialization void initialize(); @@ -87,6 +87,8 @@ class Application : public QObject { static Application* s_instance; + // All pointer members below are owned by Qt's parent-child system (parent = this). + // m_mixer is the exception: created by ProtocolFactory with this as parent, swapped on reconnect. Show* m_show; PlaybackEngine* m_playbackEngine; MixerProtocol* m_mixer = nullptr; diff --git a/src/core/AppLogger.cpp b/src/core/AppLogger.cpp index 4a9e248..f3f5b11 100644 --- a/src/core/AppLogger.cpp +++ b/src/core/AppLogger.cpp @@ -10,12 +10,16 @@ QString LogEntry::levelString() const { switch (level) { case LogLevel::Debug: return "Debug"; + case LogLevel::Info: return "Info"; + case LogLevel::Warning: return "Warning"; + case LogLevel::Error: return "Error"; + case LogLevel::Critical: return "Critical"; } @@ -26,16 +30,22 @@ QString LogEntry::sourceString() const { switch (source) { case LogSource::Connection: return "Connection"; + case LogSource::Protocol: return "Protocol"; + case LogSource::Playback: return "Playback"; + case LogSource::UI: return "UI"; + case LogSource::System: return "System"; + case LogSource::MIDI: return "MIDI"; + case LogSource::Discovery: return "Discovery"; } @@ -161,13 +171,18 @@ void AppLogger::critical(LogSource source, const QString& message, const QJsonOb log(LogLevel::Critical, source, message, metadata); } +void AppLogger::logConnectionEvent(LogLevel level, const QString& event, + const QJsonObject& metadata) { + log(level, LogSource::Connection, event, metadata); +} + void AppLogger::logConnectionAttempt(const QString& protocol, const QString& host, int port) { QJsonObject meta; meta["protocol"] = protocol; meta["host"] = host; meta["port"] = port; - info(LogSource::Connection, - QString("Connecting to %1:%2 (%3)").arg(host).arg(port).arg(protocol), meta); + logConnectionEvent(LogLevel::Info, + QString("Connecting to %1:%2 (%3)").arg(host).arg(port).arg(protocol), meta); } void AppLogger::logConnectionSuccess(const QString& protocol, const QString& host, int port) { @@ -175,8 +190,8 @@ void AppLogger::logConnectionSuccess(const QString& protocol, const QString& hos meta["protocol"] = protocol; meta["host"] = host; meta["port"] = port; - info(LogSource::Connection, - QString("Connected to %1:%2 (%3)").arg(host).arg(port).arg(protocol), meta); + logConnectionEvent(LogLevel::Info, + QString("Connected to %1:%2 (%3)").arg(host).arg(port).arg(protocol), meta); } void AppLogger::logConnectionFailed(const QString& protocol, const QString& host, int port, @@ -186,8 +201,8 @@ void AppLogger::logConnectionFailed(const QString& protocol, const QString& host meta["host"] = host; meta["port"] = port; meta["error"] = error; - this->error( - LogSource::Connection, + logConnectionEvent( + LogLevel::Error, QString("Connection failed to %1:%2 (%3): %4").arg(host).arg(port).arg(protocol).arg(error), meta); } @@ -197,8 +212,9 @@ void AppLogger::logConnectionLost(const QString& protocol, const QString& host, meta["protocol"] = protocol; meta["host"] = host; meta["port"] = port; - warning(LogSource::Connection, - QString("Connection lost to %1:%2 (%3)").arg(host).arg(port).arg(protocol), meta); + logConnectionEvent(LogLevel::Warning, + QString("Connection lost to %1:%2 (%3)").arg(host).arg(port).arg(protocol), + meta); } void AppLogger::logReconnectAttempt(const QString& protocol, const QString& host, int port, @@ -209,14 +225,14 @@ void AppLogger::logReconnectAttempt(const QString& protocol, const QString& host meta["port"] = port; meta["attempt"] = attempt; meta["maxAttempts"] = maxAttempts; - info(LogSource::Connection, - QString("Reconnecting to %1:%2 (%3) - attempt %4/%5") - .arg(host) - .arg(port) - .arg(protocol) - .arg(attempt) - .arg(maxAttempts), - meta); + logConnectionEvent(LogLevel::Info, + QString("Reconnecting to %1:%2 (%3) - attempt %4/%5") + .arg(host) + .arg(port) + .arg(protocol) + .arg(attempt) + .arg(maxAttempts), + meta); } void AppLogger::logDisconnected(const QString& protocol, const QString& host, int port) { @@ -224,8 +240,9 @@ void AppLogger::logDisconnected(const QString& protocol, const QString& host, in meta["protocol"] = protocol; meta["host"] = host; meta["port"] = port; - info(LogSource::Connection, - QString("Disconnected from %1:%2 (%3)").arg(host).arg(port).arg(protocol), meta); + logConnectionEvent(LogLevel::Info, + QString("Disconnected from %1:%2 (%3)").arg(host).arg(port).arg(protocol), + meta); } QVector AppLogger::allEntries() const { diff --git a/src/core/AppLogger.h b/src/core/AppLogger.h index 522479f..9dc9cf1 100644 --- a/src/core/AppLogger.h +++ b/src/core/AppLogger.h @@ -25,7 +25,7 @@ struct LogEntry { QString levelString() const; QString sourceString() const; QJsonObject toJson() const; - static LogEntry fromJson(const QJsonObject& json); + [[nodiscard]] static LogEntry fromJson(const QJsonObject& json); static LogLevel levelFromString(const QString& str); static LogSource sourceFromString(const QString& str); }; @@ -39,17 +39,17 @@ class AppLogger : public QObject { // file logging void setLogFile(const QString& path); - QString logFilePath() const { return m_logFilePath; } - bool isFileLoggingEnabled() const { return m_logFile.isOpen(); } + [[nodiscard]] QString logFilePath() const { return m_logFilePath; } + [[nodiscard]] bool isFileLoggingEnabled() const { return m_logFile.isOpen(); } void closeLogFile(); // memory management void setMaxMemoryEntries(int max) { m_maxMemoryEntries = max; } - int maxMemoryEntries() const { return m_maxMemoryEntries; } + [[nodiscard]] int maxMemoryEntries() const noexcept { return m_maxMemoryEntries; } // batch emission interval for UI updates (ms) void setBatchInterval(int ms); - int batchInterval() const { return m_batchInterval; } + [[nodiscard]] int batchInterval() const noexcept { return m_batchInterval; } // core logging method void log(LogLevel level, LogSource source, const QString& message, @@ -73,16 +73,16 @@ class AppLogger : public QObject { void logDisconnected(const QString& protocol, const QString& host, int port); // retrieve entries - QVector allEntries() const; - QVector recentEntries(int count = 100) const; - QVector entriesSince(const QDateTime& since) const; - QVector entriesFiltered(LogLevel minLevel = LogLevel::Debug, - LogSource source = LogSource::System, - bool filterBySource = false) const; + [[nodiscard]] QVector allEntries() const; + [[nodiscard]] QVector recentEntries(int count = 100) const; + [[nodiscard]] QVector entriesSince(const QDateTime& since) const; + [[nodiscard]] QVector entriesFiltered(LogLevel minLevel = LogLevel::Debug, + LogSource source = LogSource::System, + bool filterBySource = false) const; // export - bool exportToFile(const QString& path) const; - bool exportToCSV(const QString& path) const; + [[nodiscard]] bool exportToFile(const QString& path) const; + [[nodiscard]] bool exportToCSV(const QString& path) const; // clear logs void clear(); @@ -96,13 +96,17 @@ class AppLogger : public QObject { void flushBatch(); private: + void logConnectionEvent(LogLevel level, const QString& event, const QJsonObject& metadata); void addEntry(const LogEntry& entry); void writeToFile(const LogEntry& entry); void pruneOldEntries(); + static constexpr int DEFAULT_MAX_MEMORY_ENTRIES = 5000; + static constexpr int DEFAULT_BATCH_INTERVAL_MS = 100; + mutable QMutex m_mutex; QVector m_entries; - int m_maxMemoryEntries = 5000; + int m_maxMemoryEntries = DEFAULT_MAX_MEMORY_ENTRIES; QString m_logFilePath; QFile m_logFile; @@ -110,7 +114,7 @@ class AppLogger : public QObject { // batched emission for UI performance QTimer* m_batchTimer; QVector m_pendingBatch; - int m_batchInterval = 100; // ms + int m_batchInterval = DEFAULT_BATCH_INTERVAL_MS; }; } // namespace OpenMix diff --git a/src/core/Cue.cpp b/src/core/Cue.cpp index 99faab0..f696a76 100644 --- a/src/core/Cue.cpp +++ b/src/core/Cue.cpp @@ -317,6 +317,7 @@ Cue Cue::fromJson(const QJsonObject& json) { } cue.m_dcaChannelMapping = channelMapping; } + if (dcaMappingObj.contains("buses")) { QMap> busMapping; QJsonObject busesObj = dcaMappingObj["buses"].toObject(); diff --git a/src/core/Cue.h b/src/core/Cue.h index f5fc73c..5b2557d 100644 --- a/src/core/Cue.h +++ b/src/core/Cue.h @@ -22,10 +22,10 @@ struct DCAOverride { std::optional mute; // mute state override (nullopt = don't change) std::optional label; // label override (nullopt = don't change) - bool hasOverrides() const { return mute.has_value() || label.has_value(); } + [[nodiscard]] bool hasOverrides() const noexcept { return mute.has_value() || label.has_value(); } QJsonObject toJson() const; - static DCAOverride fromJson(const QJsonObject& json); + [[nodiscard]] static DCAOverride fromJson(const QJsonObject& json); }; enum class MacroExecutionMode { @@ -52,83 +52,87 @@ class Cue { Cue(); explicit Cue(double number, const QString& name = QString()); - QString id() const { return m_id; } + [[nodiscard]] QString id() const { return m_id; } void regenerateId(); - double number() const { return m_number; } + [[nodiscard]] double number() const noexcept { return m_number; } void setNumber(double number) { m_number = number; } - QString name() const { return m_name; } + [[nodiscard]] QString name() const { return m_name; } void setName(const QString& name) { m_name = name; } - QString notes() const { return m_notes; } + [[nodiscard]] QString notes() const { return m_notes; } void setNotes(const QString& notes) { m_notes = notes; } - CueType type() const { return m_type; } + [[nodiscard]] CueType type() const noexcept { return m_type; } void setType(CueType type) { m_type = type; } - bool autoFollow() const { return m_autoFollow; } + [[nodiscard]] bool autoFollow() const noexcept { return m_autoFollow; } void setAutoFollow(bool autoFollow) { m_autoFollow = autoFollow; } - double autoFollowDelay() const { return m_autoFollowDelay; } + [[nodiscard]] double autoFollowDelay() const noexcept { return m_autoFollowDelay; } void setAutoFollowDelay(double seconds) { m_autoFollowDelay = seconds; } - AutoFollowCondition autoFollowCondition() const { return m_autoFollowCondition; } + [[nodiscard]] AutoFollowCondition autoFollowCondition() const noexcept { + return m_autoFollowCondition; + } void setAutoFollowCondition(AutoFollowCondition condition) { m_autoFollowCondition = condition; } // DCA targeting - QSet targetedDCAs() const { return m_targetedDCAs; } + [[nodiscard]] QSet targetedDCAs() const { return m_targetedDCAs; } void setTargetedDCAs(const QSet& dcas) { m_targetedDCAs = dcas; } void addTargetedDCA(int dca) { m_targetedDCAs.insert(dca); } void removeTargetedDCA(int dca) { m_targetedDCAs.remove(dca); } void clearTargetedDCAs() { m_targetedDCAs.clear(); } - bool targetsAllDCAs() const { return m_targetedDCAs.isEmpty(); } - bool targetsDCA(int dca) const { + [[nodiscard]] bool targetsAllDCAs() const noexcept { return m_targetedDCAs.isEmpty(); } + [[nodiscard]] bool targetsDCA(int dca) const { return m_targetedDCAs.isEmpty() || m_targetedDCAs.contains(dca); } // per-DCA overrides - QMap dcaOverrides() const { return m_dcaOverrides; } + [[nodiscard]] QMap dcaOverrides() const { return m_dcaOverrides; } void setDCAOverrides(const QMap& overrides) { m_dcaOverrides = overrides; } void setDCAOverride(int dca, const DCAOverride& override); - DCAOverride dcaOverride(int dca) const; + [[nodiscard]] DCAOverride dcaOverride(int dca) const; void clearDCAOverride(int dca); void clearAllDCAOverrides() { m_dcaOverrides.clear(); } // per-cue DCA mapping (overrides show-level mapping during playback) - bool hasCustomDCAMapping() const; + [[nodiscard]] bool hasCustomDCAMapping() const; void setDCAChannelMapping(const QMap>& mapping); void setDCABusMapping(const QMap>& mapping); - QMap> dcaChannelMapping() const; - QMap> dcaBusMapping() const; + [[nodiscard]] QMap> dcaChannelMapping() const; + [[nodiscard]] QMap> dcaBusMapping() const; void clearCustomDCAMapping(); void copyDCAMappingFrom(const class DCAMapping* showMapping); - bool isMacro() const { return m_type == CueType::Macro; } - QStringList childCueIds() const { return m_childCueIds; } + [[nodiscard]] bool isMacro() const noexcept { return m_type == CueType::Macro; } + [[nodiscard]] QStringList childCueIds() const { return m_childCueIds; } void setChildCueIds(const QStringList& ids) { m_childCueIds = ids; } void addChildCueId(const QString& id) { m_childCueIds.append(id); } void removeChildCueId(const QString& id) { m_childCueIds.removeAll(id); } void clearChildCueIds() { m_childCueIds.clear(); } - MacroExecutionMode macroExecutionMode() const { return m_macroExecutionMode; } + [[nodiscard]] MacroExecutionMode macroExecutionMode() const noexcept { + return m_macroExecutionMode; + } void setMacroExecutionMode(MacroExecutionMode mode) { m_macroExecutionMode = mode; } - QString gotoTarget() const { return m_gotoTarget; } + [[nodiscard]] QString gotoTarget() const { return m_gotoTarget; } void setGotoTarget(const QString& target) { m_gotoTarget = target; } - bool gotoAutoExecute() const { return m_gotoAutoExecute; } + [[nodiscard]] bool gotoAutoExecute() const noexcept { return m_gotoAutoExecute; } void setGotoAutoExecute(bool autoExec) { m_gotoAutoExecute = autoExec; } - StopBehavior stopBehavior() const { return m_stopBehavior; } + [[nodiscard]] StopBehavior stopBehavior() const noexcept { return m_stopBehavior; } void setStopBehavior(StopBehavior behavior) { m_stopBehavior = behavior; } - QString group() const { return m_group; } + [[nodiscard]] QString group() const { return m_group; } void setGroup(const QString& group) { m_group = group; } - QStringList tags() const { return m_tags; } + [[nodiscard]] QStringList tags() const { return m_tags; } void setTags(const QStringList& tags) { m_tags = tags; } void addTag(const QString& tag) { if (!m_tags.contains(tag)) @@ -136,17 +140,17 @@ class Cue { } void removeTag(const QString& tag) { m_tags.removeAll(tag); } - QJsonObject parameters() const { return m_parameters; } + [[nodiscard]] QJsonObject parameters() const { return m_parameters; } void setParameters(const QJsonObject& params) { m_parameters = params; } void setParameter(const QString& path, const QVariant& value); - QVariant parameter(const QString& path) const; + [[nodiscard]] QVariant parameter(const QString& path) const; void clearParameters() { m_parameters = QJsonObject(); } QJsonObject toJson() const; - static Cue fromJson(const QJsonObject& json); + [[nodiscard]] static Cue fromJson(const QJsonObject& json); - bool operator<(const Cue& other) const { return m_number < other.m_number; } - bool operator==(const Cue& other) const { return m_id == other.m_id; } + bool operator<(const Cue& other) const noexcept { return m_number < other.m_number; } + bool operator==(const Cue& other) const noexcept { return m_id == other.m_id; } private: QString m_id; diff --git a/src/core/CueList.cpp b/src/core/CueList.cpp index 5165421..d6fcc66 100644 --- a/src/core/CueList.cpp +++ b/src/core/CueList.cpp @@ -1,41 +1,44 @@ #include "CueList.h" #include +#include namespace OpenMix { +namespace { +constexpr double FIRST_CUE_NUMBER = 1.0; +} // namespace + CueList::CueList(QObject* parent) : QObject(parent) {} -int CueList::indexOf(const QString& id) const { - for (int i = 0; i < m_cues.size(); ++i) { - if (m_cues[i].id() == id) { - return i; - } - } - return -1; +std::optional CueList::indexOf(const QString& id) const { + auto it = std::find_if(m_cues.cbegin(), m_cues.cend(), + [&id](const Cue& c) { return c.id() == id; }); + if (it == m_cues.cend()) + return std::nullopt; + return static_cast(std::distance(m_cues.cbegin(), it)); } -int CueList::indexOfNumber(double number) const { - for (int i = 0; i < m_cues.size(); ++i) { - if (qFuzzyCompare(m_cues[i].number(), number)) { - return i; - } - } - return -1; +std::optional CueList::indexOfNumber(double number) const { + auto it = std::find_if(m_cues.cbegin(), m_cues.cend(), + [number](const Cue& c) { return qFuzzyCompare(c.number(), number); }); + if (it == m_cues.cend()) + return std::nullopt; + return static_cast(std::distance(m_cues.cbegin(), it)); } Cue* CueList::findById(const QString& id) { - int idx = indexOf(id); - return idx >= 0 ? &m_cues[idx] : nullptr; + const auto idx = indexOf(id); + return idx ? &m_cues[*idx] : nullptr; } const Cue* CueList::findById(const QString& id) const { - int idx = indexOf(id); - return idx >= 0 ? &m_cues[idx] : nullptr; + const auto idx = indexOf(id); + return idx ? &m_cues[*idx] : nullptr; } Cue* CueList::findByNumber(double number) { - int idx = indexOfNumber(number); - return idx >= 0 ? &m_cues[idx] : nullptr; + const auto idx = indexOfNumber(number); + return idx ? &m_cues[*idx] : nullptr; } void CueList::addCue(const Cue& cue) { @@ -67,9 +70,8 @@ void CueList::removeCue(int index) { } void CueList::removeCueById(const QString& id) { - int idx = indexOf(id); - if (idx >= 0) { - removeCue(idx); + if (const auto idx = indexOf(id)) { + removeCue(*idx); } } @@ -94,16 +96,11 @@ void CueList::clear() { void CueList::sortByNumber() { std::sort(m_cues.begin(), m_cues.end()); } double CueList::nextCueNumber() const { - if (m_cues.isEmpty()) { - return 1.0; - } - double maxNum = 0.0; - for (const auto& cue : m_cues) { - if (cue.number() > maxNum) { - maxNum = cue.number(); - } - } - return std::floor(maxNum) + 1.0; + if (m_cues.isEmpty()) + return FIRST_CUE_NUMBER; + auto it = std::max_element(m_cues.cbegin(), m_cues.cend(), + [](const Cue& a, const Cue& b) { return a.number() < b.number(); }); + return std::floor(it->number()) + FIRST_CUE_NUMBER; } QJsonArray CueList::toJson() const { diff --git a/src/core/CueList.h b/src/core/CueList.h index 0934acb..c16d3b7 100644 --- a/src/core/CueList.h +++ b/src/core/CueList.h @@ -4,6 +4,7 @@ #include #include #include +#include namespace OpenMix { @@ -13,18 +14,18 @@ class CueList : public QObject { public: explicit CueList(QObject* parent = nullptr); - int count() const { return m_cues.size(); } - bool isEmpty() const { return m_cues.isEmpty(); } + [[nodiscard]] int count() const noexcept { return m_cues.size(); } + [[nodiscard]] bool isEmpty() const noexcept { return m_cues.isEmpty(); } - const Cue& at(int index) const { return m_cues.at(index); } - Cue& operator[](int index) { return m_cues[index]; } - const Cue& operator[](int index) const { return m_cues[index]; } + [[nodiscard]] const Cue& at(int index) const { return m_cues.at(index); } + [[nodiscard]] Cue& operator[](int index) { return m_cues[index]; } + [[nodiscard]] const Cue& operator[](int index) const { return m_cues[index]; } - int indexOf(const QString& id) const; - int indexOfNumber(double number) const; - Cue* findById(const QString& id); - const Cue* findById(const QString& id) const; - Cue* findByNumber(double number); + [[nodiscard]] std::optional indexOf(const QString& id) const; + [[nodiscard]] std::optional indexOfNumber(double number) const; + [[nodiscard]] Cue* findById(const QString& id); + [[nodiscard]] const Cue* findById(const QString& id) const; + [[nodiscard]] Cue* findByNumber(double number); void addCue(const Cue& cue); void insertCue(int index, const Cue& cue); @@ -36,7 +37,7 @@ class CueList : public QObject { void sortByNumber(); - double nextCueNumber() const; + [[nodiscard]] double nextCueNumber() const; QVector::iterator begin() { return m_cues.begin(); } QVector::iterator end() { return m_cues.end(); } diff --git a/src/core/CueValidator.cpp b/src/core/CueValidator.cpp index 78a6e16..250767a 100644 --- a/src/core/CueValidator.cpp +++ b/src/core/CueValidator.cpp @@ -21,7 +21,9 @@ ValidationResult CueValidator::validate(const Cue& cue, const CueList* cueList) } if (cue.macroExecutionMode() == MacroExecutionMode::Parallel) { - detectConflictingFadeTargets(cue, cueList, result.issues); + if (!detectConflictingFadeTargets(cue, cueList, result.issues)) { + result.valid = false; + } } } @@ -39,7 +41,7 @@ ValidationResult CueValidator::validateAll(const CueList* cueList) { if (!cueList) { result.valid = false; - result.issues.append({ValidationIssue::Error, tr("Cue list is null"), QString()}); + result.issues.append({ValidationIssue::Severity::Error, tr("Cue list is null"), QString()}); return result; } @@ -74,12 +76,12 @@ bool CueValidator::validateMacroIds(const Cue& cue, const CueList* cueList, QStringList childIds = cue.childCueIds(); if (childIds.isEmpty()) { - issues.append({ValidationIssue::Warning, tr("Macro cue has no child cues"), QString()}); + issues.append({ValidationIssue::Severity::Warning, tr("Macro cue has no child cues"), QString()}); } for (const QString& childId : childIds) { if (!cueList || !cueList->findById(childId)) { - issues.append({ValidationIssue::Error, + issues.append({ValidationIssue::Severity::Error, tr("Macro references non-existent cue ID: %1").arg(childId), QString()}); valid = false; } @@ -94,7 +96,7 @@ bool CueValidator::validateParameters(const Cue& cue, QList& is if (cue.type() == CueType::Snapshot) { QJsonObject params = cue.parameters(); if (params.isEmpty()) { - issues.append({ValidationIssue::Warning, tr("Snapshot cue has no parameters defined"), + issues.append({ValidationIssue::Severity::Warning, tr("Snapshot cue has no parameters defined"), QString()}); } } @@ -104,7 +106,7 @@ bool CueValidator::validateParameters(const Cue& cue, QList& is for (auto it = params.begin(); it != params.end(); ++it) { QString path = it.key(); if (!path.startsWith('/')) { - issues.append({ValidationIssue::Warning, + issues.append({ValidationIssue::Severity::Warning, tr("Parameter path '%1' does not start with '/'").arg(path), path}); } } @@ -122,7 +124,7 @@ bool CueValidator::detectCircularMacroReferences(const Cue& cue, const CueList* QSet recursionStack; if (hasCircularReference(cue.id(), cueList, visited, recursionStack)) { - issues.append({ValidationIssue::Error, tr("Circular macro reference detected"), QString()}); + issues.append({ValidationIssue::Severity::Error, tr("Circular macro reference detected"), QString()}); return false; } @@ -199,7 +201,7 @@ bool CueValidator::detectConflictingFadeTargets(const Cue& cue, const CueList* c bool hasConflicts = false; for (auto it = parameterToCues.begin(); it != parameterToCues.end(); ++it) { if (it.value().size() > 1) { - issues.append({ValidationIssue::Warning, + issues.append({ValidationIssue::Severity::Error, tr("Parameter '%1' is targeted by multiple parallel cues: %2") .arg(it.key()) .arg(it.value().join(", ")), diff --git a/src/core/CueValidator.h b/src/core/CueValidator.h index 6c6d859..64fc870 100644 --- a/src/core/CueValidator.h +++ b/src/core/CueValidator.h @@ -12,13 +12,13 @@ class Cue; class CueList; struct ValidationIssue { - enum Severity { Warning, Error }; + enum class Severity { Warning, Error }; Severity severity; QString message; QString parameterPath; - bool isError() const { return severity == Error; } - bool isWarning() const { return severity == Warning; } + bool isError() const { return severity == Severity::Error; } + bool isWarning() const { return severity == Severity::Warning; } }; struct ValidationResult { @@ -52,22 +52,23 @@ class CueValidator : public QObject { public: explicit CueValidator(QObject* parent = nullptr); - ValidationResult validate(const Cue& cue, const CueList* cueList); - ValidationResult validateAll(const CueList* cueList); + [[nodiscard]] ValidationResult validate(const Cue& cue, const CueList* cueList); + [[nodiscard]] ValidationResult validateAll(const CueList* cueList); signals: void validationCompleted(const ValidationResult& result); private: - bool validateMacroIds(const Cue& cue, const CueList* cueList, - QList& issues) const; - bool validateParameters(const Cue& cue, QList& issues) const; - bool detectCircularMacroReferences(const Cue& cue, const CueList* cueList, - QList& issues) const; - bool detectConflictingFadeTargets(const Cue& cue, const CueList* cueList, - QList& issues) const; - bool hasCircularReference(const QString& cueId, const CueList* cueList, QSet& visited, - QSet& recursionStack) const; + [[nodiscard]] bool validateMacroIds(const Cue& cue, const CueList* cueList, + QList& issues) const; + [[nodiscard]] bool validateParameters(const Cue& cue, QList& issues) const; + [[nodiscard]] bool detectCircularMacroReferences(const Cue& cue, const CueList* cueList, + QList& issues) const; + [[nodiscard]] bool detectConflictingFadeTargets(const Cue& cue, const CueList* cueList, + QList& issues) const; + [[nodiscard]] bool hasCircularReference(const QString& cueId, const CueList* cueList, + QSet& visited, + QSet& recursionStack) const; QSet collectMacroParameters(const Cue& cue, const CueList* cueList) const; }; diff --git a/src/core/DCAMapping.cpp b/src/core/DCAMapping.cpp index 4d06c75..4809c45 100644 --- a/src/core/DCAMapping.cpp +++ b/src/core/DCAMapping.cpp @@ -25,11 +25,11 @@ void DCAMapping::removeChannelFromDCA(int channel, int dca) { } } -void DCAMapping::clearChannelFromAllDCAs(int channel) { - for (auto it = m_channelAssignments.begin(); it != m_channelAssignments.end();) { - if (it.value().removeAll(channel) > 0) { +void DCAMapping::clearFromAllDCAs(QMap>& map, int id) { + for (auto it = map.begin(); it != map.end();) { + if (it.value().removeAll(id) > 0) { if (it.value().isEmpty()) { - it = m_channelAssignments.erase(it); + it = map.erase(it); continue; } } @@ -37,18 +37,22 @@ void DCAMapping::clearChannelFromAllDCAs(int channel) { } } +void DCAMapping::clearChannelFromAllDCAs(int channel) { + clearFromAllDCAs(m_channelAssignments, channel); +} + QList DCAMapping::channelsForDCA(int dca) const { return m_channelAssignments.value(dca); } -int DCAMapping::dcaForChannel(int channel) const { - for (auto it = m_channelAssignments.constBegin(); it != m_channelAssignments.constEnd(); ++it) { - if (it.value().contains(channel)) { - return it.key(); +std::optional DCAMapping::dcaForChannel(int channel) const { + for (const auto& [dca, channels] : m_channelAssignments.asKeyValueRange()) { + if (channels.contains(channel)) { + return dca; } } - return -1; + return std::nullopt; } -bool DCAMapping::isChannelAssigned(int channel) const { return dcaForChannel(channel) >= 0; } +bool DCAMapping::isChannelAssigned(int channel) const { return dcaForChannel(channel).has_value(); } void DCAMapping::assignBusToDCA(int bus, int dca) { clearBusFromAllDCAs(bus); @@ -71,29 +75,21 @@ void DCAMapping::removeBusFromDCA(int bus, int dca) { } void DCAMapping::clearBusFromAllDCAs(int bus) { - for (auto it = m_busAssignments.begin(); it != m_busAssignments.end();) { - if (it.value().removeAll(bus) > 0) { - if (it.value().isEmpty()) { - it = m_busAssignments.erase(it); - continue; - } - } - ++it; - } + clearFromAllDCAs(m_busAssignments, bus); } QList DCAMapping::busesForDCA(int dca) const { return m_busAssignments.value(dca); } -int DCAMapping::dcaForBus(int bus) const { - for (auto it = m_busAssignments.constBegin(); it != m_busAssignments.constEnd(); ++it) { - if (it.value().contains(bus)) { - return it.key(); +std::optional DCAMapping::dcaForBus(int bus) const { + for (const auto& [dca, buses] : m_busAssignments.asKeyValueRange()) { + if (buses.contains(bus)) { + return dca; } } - return -1; + return std::nullopt; } -bool DCAMapping::isBusAssigned(int bus) const { return dcaForBus(bus) >= 0; } +bool DCAMapping::isBusAssigned(int bus) const { return dcaForBus(bus).has_value(); } void DCAMapping::setBusName(int bus, const QString& name) { if (name.isEmpty()) { @@ -122,46 +118,49 @@ void DCAMapping::setBusAssignments(const QMap>& assignments) { QSet DCAMapping::assignedDCAs() const { QSet dcas; - for (auto it = m_channelAssignments.constBegin(); it != m_channelAssignments.constEnd(); ++it) { - if (!it.value().isEmpty()) { - dcas.insert(it.key()); - } + for (const auto& [dca, channels] : m_channelAssignments.asKeyValueRange()) { + if (!channels.isEmpty()) + dcas.insert(dca); } - for (auto it = m_busAssignments.constBegin(); it != m_busAssignments.constEnd(); ++it) { - if (!it.value().isEmpty()) { - dcas.insert(it.key()); - } + for (const auto& [dca, buses] : m_busAssignments.asKeyValueRange()) { + if (!buses.isEmpty()) + dcas.insert(dca); } return dcas; } -QJsonObject DCAMapping::toJson() const { - QJsonObject json; - - QJsonObject channelsObj; - for (auto it = m_channelAssignments.constBegin(); it != m_channelAssignments.constEnd(); ++it) { - QJsonArray channels; - for (int ch : it.value()) { - channels.append(ch); - } - channelsObj[QString::number(it.key())] = channels; +QJsonObject DCAMapping::mappingToJsonObject(const QMap>& map) { + QJsonObject obj; + for (const auto& [dca, items] : map.asKeyValueRange()) { + QJsonArray arr; + for (int item : items) + arr.append(item); + obj[QString::number(dca)] = arr; } - json["channels"] = channelsObj; + return obj; +} - QJsonObject busesObj; - for (auto it = m_busAssignments.constBegin(); it != m_busAssignments.constEnd(); ++it) { - QJsonArray buses; - for (int bus : it.value()) { - buses.append(bus); - } - busesObj[QString::number(it.key())] = buses; +QMap> DCAMapping::jsonObjectToMapping(const QJsonObject& obj) { + QMap> result; + for (const auto& [key, val] : obj.asKeyValueRange()) { + QList items; + for (const QJsonValue& v : val.toArray()) + items.append(v.toInt()); + if (!items.isEmpty()) + result[key.toString().toInt()] = items; } - json["buses"] = busesObj; + return result; +} + +QJsonObject DCAMapping::toJson() const { + QJsonObject json; + json["channels"] = mappingToJsonObject(m_channelAssignments); + json["buses"] = mappingToJsonObject(m_busAssignments); if (!m_busNames.isEmpty()) { QJsonObject busNamesObj; - for (auto it = m_busNames.constBegin(); it != m_busNames.constEnd(); ++it) { - busNamesObj[QString::number(it.key())] = it.value(); + for (const auto& [bus, name] : m_busNames.asKeyValueRange()) { + busNamesObj[QString::number(bus)] = name; } json["busNames"] = busNamesObj; } @@ -180,39 +179,14 @@ void DCAMapping::loadFromJson(const QJsonObject& json) { m_busAssignments.clear(); m_busNames.clear(); - QJsonObject channelsObj = json["channels"].toObject(); - for (auto it = channelsObj.constBegin(); it != channelsObj.constEnd(); ++it) { - int dca = it.key().toInt(); - QJsonArray channels = it.value().toArray(); - QList channelList; - for (const QJsonValue& val : channels) { - channelList.append(val.toInt()); - } - if (!channelList.isEmpty()) { - m_channelAssignments[dca] = channelList; - } - } + m_channelAssignments = jsonObjectToMapping(json["channels"].toObject()); + m_busAssignments = jsonObjectToMapping(json["buses"].toObject()); - QJsonObject busesObj = json["buses"].toObject(); - for (auto it = busesObj.constBegin(); it != busesObj.constEnd(); ++it) { - int dca = it.key().toInt(); - QJsonArray buses = it.value().toArray(); - QList busList; - for (const QJsonValue& val : buses) { - busList.append(val.toInt()); - } - if (!busList.isEmpty()) { - m_busAssignments[dca] = busList; - } - } - - QJsonObject busNamesObj = json["busNames"].toObject(); - for (auto it = busNamesObj.constBegin(); it != busNamesObj.constEnd(); ++it) { - int bus = it.key().toInt(); - QString name = it.value().toString(); - if (bus > 0 && !name.isEmpty()) { + for (const auto& [key, val] : json["busNames"].toObject().asKeyValueRange()) { + int bus = key.toString().toInt(); + QString name = val.toString(); + if (bus > 0 && !name.isEmpty()) m_busNames[bus] = name; - } } } diff --git a/src/core/DCAMapping.h b/src/core/DCAMapping.h index 94f506a..9072366 100644 --- a/src/core/DCAMapping.h +++ b/src/core/DCAMapping.h @@ -5,6 +5,7 @@ #include #include #include +#include namespace OpenMix { @@ -18,35 +19,35 @@ class DCAMapping : public QObject { void assignChannelToDCA(int channel, int dca); void removeChannelFromDCA(int channel, int dca); void clearChannelFromAllDCAs(int channel); - QList channelsForDCA(int dca) const; - int dcaForChannel(int channel) const; // -1 = unassigned - bool isChannelAssigned(int channel) const; + [[nodiscard]] QList channelsForDCA(int dca) const; + [[nodiscard]] std::optional dcaForChannel(int channel) const; + [[nodiscard]] bool isChannelAssigned(int channel) const; // bus assignments void assignBusToDCA(int bus, int dca); void removeBusFromDCA(int bus, int dca); void clearBusFromAllDCAs(int bus); - QList busesForDCA(int dca) const; - int dcaForBus(int bus) const; // -1 = unassigned - bool isBusAssigned(int bus) const; + [[nodiscard]] QList busesForDCA(int dca) const; + [[nodiscard]] std::optional dcaForBus(int bus) const; + [[nodiscard]] bool isBusAssigned(int bus) const; // bus names void setBusName(int bus, const QString& name); - QString busName(int bus) const; - QMap busNames() const { return m_busNames; } + [[nodiscard]] QString busName(int bus) const; + [[nodiscard]] QMap busNames() const { return m_busNames; } // bulk operations void clear(); void setChannelAssignments(const QMap>& assignments); void setBusAssignments(const QMap>& assignments); - QMap> channelAssignments() const { return m_channelAssignments; } - QMap> busAssignments() const { return m_busAssignments; } + [[nodiscard]] QMap> channelAssignments() const { return m_channelAssignments; } + [[nodiscard]] QMap> busAssignments() const { return m_busAssignments; } - QSet assignedDCAs() const; + [[nodiscard]] QSet assignedDCAs() const; // serialization QJsonObject toJson() const; - static DCAMapping* fromJson(const QJsonObject& json, QObject* parent = nullptr); + [[nodiscard]] static DCAMapping* fromJson(const QJsonObject& json, QObject* parent = nullptr); void loadFromJson(const QJsonObject& json); signals: @@ -62,6 +63,10 @@ class DCAMapping : public QObject { QMap> m_busAssignments; // bus# -> user-defined name QMap m_busNames; + + static void clearFromAllDCAs(QMap>& map, int id); + static QJsonObject mappingToJsonObject(const QMap>& map); + static QMap> jsonObjectToMapping(const QJsonObject& obj); }; } // namespace OpenMix diff --git a/src/core/DryRunEngine.cpp b/src/core/DryRunEngine.cpp index 7b1ffea..e67e7d9 100644 --- a/src/core/DryRunEngine.cpp +++ b/src/core/DryRunEngine.cpp @@ -55,10 +55,10 @@ DryRunResult DryRunEngine::executeDryRun(int cueIndex) { switch (cue.type()) { case CueType::Snapshot: { QJsonObject targetParams = cue.parameters(); - for (auto it = targetParams.begin(); it != targetParams.end(); ++it) { - currentState[it.key()] = it.value(); + for (const auto& [path, value] : targetParams.asKeyValueRange()) { + currentState[path.toString()] = value; } - result.timeline.append(qMakePair(currentTime, currentState)); + result.timeline.append({currentTime, currentState}); result.finalState = currentState; result.wouldSucceed = true; break; @@ -85,11 +85,10 @@ DryRunResult DryRunEngine::executeDryRun(int cueIndex) { } case CueType::Stop: { - QJsonObject targetParams = cue.parameters(); - for (auto it = targetParams.begin(); it != targetParams.end(); ++it) { - currentState[it.key()] = it.value(); + for (const auto& [path, value] : cue.parameters().asKeyValueRange()) { + currentState[path.toString()] = value; } - result.timeline.append(qMakePair(currentTime, currentState)); + result.timeline.append({currentTime, currentState}); result.finalState = currentState; result.wouldSucceed = true; break; @@ -118,8 +117,7 @@ DryRunResult DryRunEngine::executeDryRunById(const QString& cueId) { return result; } - int index = m_cueList->indexOf(cueId); - return executeDryRun(index); + return executeDryRun(m_cueList->indexOf(cueId).value_or(-1)); } QVector DryRunEngine::executeDryRunSequence(int startIndex, int maxCues) { @@ -168,9 +166,9 @@ QJsonObject DryRunEngine::predictedStateAtTime(int cueIndex, double secondsAfter qint64 targetTime = static_cast(secondsAfterStart * 1000); QJsonObject state = m_initialState; - for (const auto& entry : result.timeline) { - if (entry.first <= targetTime) { - state = entry.second; + for (const auto& [time, snapshot] : result.timeline) { + if (time <= targetTime) { + state = snapshot; } else { break; } diff --git a/src/core/OperationMode.h b/src/core/OperationMode.h index 44fd8ba..e22be24 100644 --- a/src/core/OperationMode.h +++ b/src/core/OperationMode.h @@ -17,31 +17,31 @@ class OperationModeManager : public QObject { public: explicit OperationModeManager(QObject* parent = nullptr); - AppMode currentMode() const { return m_mode; } + [[nodiscard]] AppMode currentMode() const noexcept { return m_mode; } void setMode(AppMode mode); - QString modeString() const; - static QString modeString(AppMode mode); - - bool canEditCues(); // programmer only - bool canDeleteCues(); // programmer only - bool canModifyShow(); // programmer only - bool canAddCues(); // programmer only - bool canRenumberCues(); // programmer only - bool canOpenShow(); // programmer only - bool canNewShow(); // programmer only - bool canSaveShow() const; // always (emergency save) - bool canGo() const; // always - bool canStop() const; // always - bool canNavigateCues() const; // always - bool canUsePanic() const; // always - bool canViewTimeline() const; // always - bool canViewMixerFeedback() const; // always + [[nodiscard]] QString modeString() const; + [[nodiscard]] static QString modeString(AppMode mode); + + [[nodiscard]] bool canEditCues(); // programmer only + [[nodiscard]] bool canDeleteCues(); // programmer only + [[nodiscard]] bool canModifyShow(); // programmer only + [[nodiscard]] bool canAddCues(); // programmer only + [[nodiscard]] bool canRenumberCues(); // programmer only + [[nodiscard]] bool canOpenShow(); // programmer only + [[nodiscard]] bool canNewShow(); // programmer only + [[nodiscard]] bool canSaveShow() const; // always (emergency save) + [[nodiscard]] bool canGo() const; // always + [[nodiscard]] bool canStop() const; // always + [[nodiscard]] bool canNavigateCues() const; // always + [[nodiscard]] bool canUsePanic() const; // always + [[nodiscard]] bool canViewTimeline() const; // always + [[nodiscard]] bool canViewMixerFeedback() const; // always void setShowModePassword(const QString& password); void clearPassword(); - bool hasPassword() const { return !m_passwordHash.isEmpty(); } - bool validatePassword(const QString& password) const; + [[nodiscard]] bool hasPassword() const noexcept { return !m_passwordHash.isEmpty(); } + [[nodiscard]] bool validatePassword(const QString& password) const; bool switchToProgrammerMode(const QString& password = QString()); void switchToShowMode(); @@ -50,7 +50,7 @@ class OperationModeManager : public QObject { void loadFromSettings(); void setSaveMode(bool enabled) { m_saveMode = enabled; } - bool saveMode() const { return m_saveMode; } + [[nodiscard]] bool saveMode() const noexcept { return m_saveMode; } signals: void modeChanged(AppMode mode); diff --git a/src/core/PlaybackEngine.cpp b/src/core/PlaybackEngine.cpp index 38a1f99..da66f80 100644 --- a/src/core/PlaybackEngine.cpp +++ b/src/core/PlaybackEngine.cpp @@ -1,4 +1,6 @@ #include "PlaybackEngine.h" +#include "CueList.h" +#include "DCAMapping.h" #include "PlaybackGuard.h" #include "protocol/MixerCapabilities.h" #include "protocol/MixerProtocol.h" @@ -114,9 +116,9 @@ void PlaybackEngine::goToIndex(int index) { void PlaybackEngine::goToNumber(double num) { if (!m_cueList) return; - int idx = m_cueList->indexOfNumber(num); - if (idx >= 0) { - setStandbyIndex(idx); + auto idx = m_cueList->indexOfNumber(num); + if (idx.has_value()) { + setStandbyIndex(idx.value()); } } @@ -149,9 +151,9 @@ void PlaybackEngine::executeCue(int index) { void PlaybackEngine::executeCueById(const QString& id) { if (!m_cueList) return; - int index = m_cueList->indexOf(id); - if (index >= 0) { - executeCue(index); + auto index = m_cueList->indexOf(id); + if (index.has_value()) { + executeCue(index.value()); } } @@ -290,6 +292,7 @@ QJsonObject PlaybackEngine::filterParametersForDCAs(const Cue& cue, if (!hasMapping || targetDCAs.isEmpty()) { QJsonObject filtered; static QRegularExpression dcaFaderRegex("^/dca/\\d+/fader$"); + for (auto it = cue.parameters().begin(); it != cue.parameters().end(); ++it) { if (!dcaFaderRegex.match(it.key()).hasMatch()) { filtered[it.key()] = it.value(); @@ -315,7 +318,9 @@ QJsonObject PlaybackEngine::filterParametersForDCAs(const Cue& cue, // filter params to only include targeted channels/buses QJsonObject filtered; static QRegularExpression channelRegex("^/ch/(\\d+)/"); + static QRegularExpression busRegex("^/bus/(\\d+)/"); + static QRegularExpression dcaFaderRegex("^/dca/\\d+/fader$"); const QJsonObject& params = cue.parameters(); @@ -403,9 +408,11 @@ void PlaybackEngine::executeMacroCue(const Cue& cue) { if (m_macroPendingChildren.isEmpty()) { const Cue* macroCue = m_cueList->findById(m_currentMacroId); + m_currentMacroId.clear(); m_macroPendingChildren.clear(); m_macroChildIndex = 0; + emit cueCompleted(m_currentIndex); if (macroCue) { handleAutoFollow(*macroCue); @@ -446,9 +453,11 @@ void PlaybackEngine::executeNextMacroChild() { executeNextMacroChild(); } else { const Cue* macroCue = m_cueList->findById(m_currentMacroId); + m_currentMacroId.clear(); m_macroPendingChildren.clear(); m_macroChildIndex = 0; + emit cueCompleted(m_currentIndex); if (macroCue) { handleAutoFollow(*macroCue); @@ -474,11 +483,15 @@ void PlaybackEngine::executeGoToCue(const Cue& cue) { bool isNumber = false; double cueNumber = target.toDouble(&isNumber); if (isNumber) { - targetIndex = m_cueList->indexOfNumber(cueNumber); + auto result = m_cueList->indexOfNumber(cueNumber); + if (result.has_value()) + targetIndex = result.value(); } if (targetIndex < 0) { - targetIndex = m_cueList->indexOf(target); + auto result = m_cueList->indexOf(target); + if (result.has_value()) + targetIndex = result.value(); } if (targetIndex < 0) { diff --git a/src/core/PlaybackEngine.h b/src/core/PlaybackEngine.h index 37ec134..63469cc 100644 --- a/src/core/PlaybackEngine.h +++ b/src/core/PlaybackEngine.h @@ -1,9 +1,6 @@ #pragma once -#include "Cue.h" -#include "CueList.h" #include "CueValidator.h" -#include "DCAMapping.h" #include #include #include @@ -11,6 +8,9 @@ namespace OpenMix { +class Cue; +class CueList; +class DCAMapping; class MixerProtocol; class PlaybackGuard; class PlaybackLogger; @@ -28,26 +28,26 @@ class PlaybackEngine : public QObject { void setDCAMapping(DCAMapping* mapping); void setValidator(CueValidator* validator); - CueValidator* validator() const { return m_validator; } + [[nodiscard]] CueValidator* validator() const noexcept { return m_validator; } void setGuard(PlaybackGuard* guard); - PlaybackGuard* guard() const { return m_guard; } + [[nodiscard]] PlaybackGuard* guard() const noexcept { return m_guard; } void setLogger(PlaybackLogger* logger); - PlaybackLogger* logger() const { return m_logger; } + [[nodiscard]] PlaybackLogger* logger() const noexcept { return m_logger; } void setDryRunMode(bool enabled) { m_dryRunMode = enabled; } - bool isDryRunMode() const { return m_dryRunMode; } + [[nodiscard]] bool isDryRunMode() const noexcept { return m_dryRunMode; } - PlaybackState state() const { return m_state; } + [[nodiscard]] PlaybackState state() const noexcept { return m_state; } - int currentCueIndex() const { return m_currentIndex; } - int standbyCueIndex() const { return m_standbyIndex; } + [[nodiscard]] int currentCueIndex() const noexcept { return m_currentIndex; } + [[nodiscard]] int standbyCueIndex() const noexcept { return m_standbyIndex; } - const Cue* currentCue() const; - const Cue* standbyCue() const; + [[nodiscard]] const Cue* currentCue() const; + [[nodiscard]] const Cue* standbyCue() const; - bool isAutoFollowArmed() const { return m_autoFollowArmed; } + [[nodiscard]] bool isAutoFollowArmed() const noexcept { return m_autoFollowArmed; } public slots: void go(); diff --git a/src/core/PlaybackGuard.cpp b/src/core/PlaybackGuard.cpp index b05d514..12853b0 100644 --- a/src/core/PlaybackGuard.cpp +++ b/src/core/PlaybackGuard.cpp @@ -68,8 +68,8 @@ void PlaybackGuard::sendSafeValues() { if (!m_mixer) return; - for (auto it = m_defaultSafeValues.begin(); it != m_defaultSafeValues.end(); ++it) { - m_mixer->sendParameter(it.key(), it.value().toVariant()); + for (const auto& [path, value] : m_defaultSafeValues.asKeyValueRange()) { + m_mixer->sendParameter(path.toString(), value.toVariant()); } } diff --git a/src/core/PlaybackLogger.cpp b/src/core/PlaybackLogger.cpp index 628822e..59ed3c7 100644 --- a/src/core/PlaybackLogger.cpp +++ b/src/core/PlaybackLogger.cpp @@ -8,25 +8,25 @@ namespace OpenMix { QString PlaybackLogEntry::typeString() const { switch (type) { - case CueExecuted: + case Type::CueExecuted: return "CueExecuted"; - case MacroExpanded: + case Type::MacroExpanded: return "MacroExpanded"; - case AutoFollowTriggered: + case Type::AutoFollowTriggered: return "AutoFollowTriggered"; - case ValidationWarning: + case Type::ValidationWarning: return "ValidationWarning"; - case ValidationError: + case Type::ValidationError: return "ValidationError"; - case EmergencyStop: + case Type::EmergencyStop: return "EmergencyStop"; - case GoLockout: + case Type::GoLockout: return "GoLockout"; - case ParameterSent: + case Type::ParameterSent: return "ParameterSent"; - case StateCapture: + case Type::StateCapture: return "StateCapture"; - case Custom: + case Type::Custom: return "Custom"; } return "Unknown"; @@ -50,25 +50,25 @@ PlaybackLogEntry PlaybackLogEntry::fromJson(const QJsonObject& json) { QString typeStr = json["type"].toString(); if (typeStr == "CueExecuted") - entry.type = CueExecuted; + entry.type = Type::CueExecuted; else if (typeStr == "MacroExpanded") - entry.type = MacroExpanded; + entry.type = Type::MacroExpanded; else if (typeStr == "AutoFollowTriggered") - entry.type = AutoFollowTriggered; + entry.type = Type::AutoFollowTriggered; else if (typeStr == "ValidationWarning") - entry.type = ValidationWarning; + entry.type = Type::ValidationWarning; else if (typeStr == "ValidationError") - entry.type = ValidationError; + entry.type = Type::ValidationError; else if (typeStr == "EmergencyStop") - entry.type = EmergencyStop; + entry.type = Type::EmergencyStop; else if (typeStr == "GoLockout") - entry.type = GoLockout; + entry.type = Type::GoLockout; else if (typeStr == "ParameterSent") - entry.type = ParameterSent; + entry.type = Type::ParameterSent; else if (typeStr == "StateCapture") - entry.type = StateCapture; + entry.type = Type::StateCapture; else - entry.type = Custom; + entry.type = Type::Custom; entry.cueId = json["cueId"].toString(); entry.details = json["details"].toString(); @@ -116,45 +116,45 @@ void PlaybackLogger::log(PlaybackLogEntry::Type type, const QString& cueId, cons void PlaybackLogger::logCueExecuted(const QString& cueId, const QString& cueName, double cueNumber) { QString details = QString("Executed cue %1: %2").arg(cueNumber, 0, 'f', 1).arg(cueName); - log(PlaybackLogEntry::CueExecuted, cueId, details); + log(PlaybackLogEntry::Type::CueExecuted, cueId, details); } void PlaybackLogger::logMacroExpanded(const QString& parentId, const QString& childId) { QString details = QString("Macro expanded: child %1").arg(childId); - log(PlaybackLogEntry::MacroExpanded, parentId, details); + log(PlaybackLogEntry::Type::MacroExpanded, parentId, details); } void PlaybackLogger::logAutoFollowTriggered(const QString& cueId, double delay) { QString details = QString("Auto-follow triggered after %1s delay").arg(delay, 0, 'f', 2); - log(PlaybackLogEntry::AutoFollowTriggered, cueId, details); + log(PlaybackLogEntry::Type::AutoFollowTriggered, cueId, details); } void PlaybackLogger::logValidationWarning(const QString& cueId, const QString& message) { - log(PlaybackLogEntry::ValidationWarning, cueId, message); + log(PlaybackLogEntry::Type::ValidationWarning, cueId, message); } void PlaybackLogger::logValidationError(const QString& cueId, const QString& message) { - log(PlaybackLogEntry::ValidationError, cueId, message); + log(PlaybackLogEntry::Type::ValidationError, cueId, message); } void PlaybackLogger::logEmergencyStop(const QString& reason) { - log(PlaybackLogEntry::EmergencyStop, QString(), reason); + log(PlaybackLogEntry::Type::EmergencyStop, QString(), reason); } void PlaybackLogger::logGoLockout(const QString& reason) { - log(PlaybackLogEntry::GoLockout, QString(), reason); + log(PlaybackLogEntry::Type::GoLockout, QString(), reason); } void PlaybackLogger::logParameterSent(const QString& path, const QVariant& value) { QJsonObject params; params["path"] = path; params["value"] = QJsonValue::fromVariant(value); - log(PlaybackLogEntry::ParameterSent, QString(), + log(PlaybackLogEntry::Type::ParameterSent, QString(), QString("Sent %1 = %2").arg(path).arg(value.toString()), params); } void PlaybackLogger::logStateCapture(const QString& description, const QJsonObject& state) { - log(PlaybackLogEntry::StateCapture, QString(), description, state); + log(PlaybackLogEntry::Type::StateCapture, QString(), description, state); } QVector PlaybackLogger::allEntries() const { @@ -173,23 +173,20 @@ QVector PlaybackLogger::recentEntries(int count) const { } QVector PlaybackLogger::entriesSince(const QDateTime& since) const { - QMutexLocker locker(&m_mutex); - - QVector result; - for (const PlaybackLogEntry& entry : m_entries) { - if (entry.timestamp >= since) { - result.append(entry); - } - } - return result; + return entriesMatching([&](const PlaybackLogEntry& e) { return e.timestamp >= since; }); } QVector PlaybackLogger::entriesForCue(const QString& cueId) const { + return entriesMatching([&](const PlaybackLogEntry& e) { return e.cueId == cueId; }); +} + +QVector +PlaybackLogger::entriesMatching(std::function predicate) const { QMutexLocker locker(&m_mutex); QVector result; for (const PlaybackLogEntry& entry : m_entries) { - if (entry.cueId == cueId) { + if (predicate(entry)) { result.append(entry); } } diff --git a/src/core/PlaybackLogger.h b/src/core/PlaybackLogger.h index 441907c..b0605cb 100644 --- a/src/core/PlaybackLogger.h +++ b/src/core/PlaybackLogger.h @@ -7,13 +7,14 @@ #include #include #include +#include namespace OpenMix { struct PlaybackLogEntry { QDateTime timestamp; - enum Type { + enum class Type { CueExecuted, MacroExpanded, AutoFollowTriggered, @@ -36,7 +37,7 @@ struct PlaybackLogEntry { // serialize to JSON for file export QJsonObject toJson() const; - static PlaybackLogEntry fromJson(const QJsonObject& json); + [[nodiscard]] static PlaybackLogEntry fromJson(const QJsonObject& json); }; class PlaybackLogger : public QObject { @@ -48,13 +49,13 @@ class PlaybackLogger : public QObject { // file logging void setLogFile(const QString& path); - QString logFilePath() const { return m_logFilePath; } - bool isFileLoggingEnabled() const { return m_logFile.isOpen(); } + [[nodiscard]] QString logFilePath() const { return m_logFilePath; } + [[nodiscard]] bool isFileLoggingEnabled() const { return m_logFile.isOpen(); } void closeLogFile(); // maximum entries to keep in memory (0 = unlimited) void setMaxMemoryEntries(int max) { m_maxMemoryEntries = max; } - int maxMemoryEntries() const { return m_maxMemoryEntries; } + [[nodiscard]] int maxMemoryEntries() const noexcept { return m_maxMemoryEntries; } // log entries void log(PlaybackLogEntry::Type type, const QString& cueId, const QString& details, @@ -71,14 +72,14 @@ class PlaybackLogger : public QObject { void logStateCapture(const QString& description, const QJsonObject& state); // retrieve logged entries - QVector allEntries() const; - QVector recentEntries(int count = 100) const; - QVector entriesSince(const QDateTime& since) const; - QVector entriesForCue(const QString& cueId) const; + [[nodiscard]] QVector allEntries() const; + [[nodiscard]] QVector recentEntries(int count = 100) const; + [[nodiscard]] QVector entriesSince(const QDateTime& since) const; + [[nodiscard]] QVector entriesForCue(const QString& cueId) const; // export - bool exportToFile(const QString& path) const; - bool exportToCSV(const QString& path) const; + [[nodiscard]] bool exportToFile(const QString& path) const; + [[nodiscard]] bool exportToCSV(const QString& path) const; // clear logs void clear(); @@ -91,10 +92,14 @@ class PlaybackLogger : public QObject { void addEntry(const PlaybackLogEntry& entry); void writeToFile(const PlaybackLogEntry& entry); void pruneOldEntries(); + [[nodiscard]] QVector + entriesMatching(std::function predicate) const; + + static constexpr int DEFAULT_MAX_MEMORY_ENTRIES = 10000; mutable QMutex m_mutex; QVector m_entries; - int m_maxMemoryEntries = 10000; // default 10k entries in memory + int m_maxMemoryEntries = DEFAULT_MAX_MEMORY_ENTRIES; QString m_logFilePath; QFile m_logFile; diff --git a/src/core/ShortcutManager.cpp b/src/core/ShortcutManager.cpp index 4632509..b9e54d5 100644 --- a/src/core/ShortcutManager.cpp +++ b/src/core/ShortcutManager.cpp @@ -121,6 +121,7 @@ bool ShortcutManager::handleKeyPress(QKeyEvent* event) { } } + // handle decimal point if (key == Qt::Key_Period || key == Qt::Key_Comma) { if (!m_keypadBuffer.contains('.')) { @@ -134,6 +135,7 @@ bool ShortcutManager::handleKeyPress(QKeyEvent* event) { } } + // handle Enter key to confirm cue number if (key == Qt::Key_Return || key == Qt::Key_Enter) { if (!m_keypadBuffer.isEmpty()) { @@ -147,6 +149,7 @@ bool ShortcutManager::handleKeyPress(QKeyEvent* event) { } } + // handle Escape to clear buffer if (key == Qt::Key_Escape) { if (!m_keypadBuffer.isEmpty()) { @@ -155,6 +158,7 @@ bool ShortcutManager::handleKeyPress(QKeyEvent* event) { } } + // handle Backspace to remove last digit if (key == Qt::Key_Backspace) { if (!m_keypadBuffer.isEmpty()) { diff --git a/src/core/Show.h b/src/core/Show.h index fe3201b..049b45f 100644 --- a/src/core/Show.h +++ b/src/core/Show.h @@ -9,13 +9,16 @@ namespace OpenMix { struct MixerConfig { + static constexpr int DEFAULT_PORT = 10023; + static constexpr int DEFAULT_DCA_COUNT = 8; + QString type; // protocol ID: "x32", "wing", "sq7", "cl5", etc. QString host; // IP address or hostname - int port = 10023; // default X32 port (will be updated per console type) - int dcaCount = 8; // number of DCAs (varies by console) + int port = DEFAULT_PORT; + int dcaCount = DEFAULT_DCA_COUNT; QJsonObject toJson() const; - static MixerConfig fromJson(const QJsonObject& json); + [[nodiscard]] static MixerConfig fromJson(const QJsonObject& json); }; class Show : public QObject { @@ -24,29 +27,29 @@ class Show : public QObject { public: explicit Show(QObject* parent = nullptr); - QString name() const { return m_name; } + [[nodiscard]] QString name() const { return m_name; } void setName(const QString& name); - QString author() const { return m_author; } + [[nodiscard]] QString author() const { return m_author; } void setAuthor(const QString& author) { m_author = author; } - QString notes() const { return m_notes; } + [[nodiscard]] QString notes() const { return m_notes; } void setNotes(const QString& notes) { m_notes = notes; } - QString filePath() const { return m_filePath; } + [[nodiscard]] QString filePath() const { return m_filePath; } void setFilePath(const QString& path) { m_filePath = path; } - bool isModified() const; + [[nodiscard]] bool isModified() const; void setModified(bool modified); void checkModifiedState(); - CueList* cueList() { return &m_cueList; } - const CueList* cueList() const { return &m_cueList; } + [[nodiscard]] CueList* cueList() { return &m_cueList; } + [[nodiscard]] const CueList* cueList() const { return &m_cueList; } - DCAMapping* dcaMapping() { return &m_dcaMapping; } - const DCAMapping* dcaMapping() const { return &m_dcaMapping; } + [[nodiscard]] DCAMapping* dcaMapping() { return &m_dcaMapping; } + [[nodiscard]] const DCAMapping* dcaMapping() const { return &m_dcaMapping; } - MixerConfig mixerConfig() const { return m_mixerConfig; } + [[nodiscard]] MixerConfig mixerConfig() const { return m_mixerConfig; } void setMixerConfig(const MixerConfig& config) { m_mixerConfig = config; } QJsonObject toJson() const; diff --git a/src/core/UndoCommands.h b/src/core/UndoCommands.h index 108863a..e7e3bbd 100644 --- a/src/core/UndoCommands.h +++ b/src/core/UndoCommands.h @@ -10,7 +10,7 @@ namespace OpenMix { class CueList; // command IDs for merging -enum CommandId { EditCueId = 1, BatchEditId = 2 }; +enum class CommandId { EditCueId = 1, BatchEditId = 2 }; // command for editing a single cue class EditCueCommand : public QUndoCommand { @@ -20,7 +20,7 @@ class EditCueCommand : public QUndoCommand { void undo() override; void redo() override; - int id() const override { return EditCueId; } + int id() const override { return static_cast(CommandId::EditCueId); } bool mergeWith(const QUndoCommand* other) override; private: diff --git a/src/io/AutosaveManager.cpp b/src/io/AutosaveManager.cpp index 826be86..27d6cec 100644 --- a/src/io/AutosaveManager.cpp +++ b/src/io/AutosaveManager.cpp @@ -35,14 +35,14 @@ void AutosaveManager::setEnabled(bool enabled) { } void AutosaveManager::setIntervalMinutes(int minutes) { - m_intervalMinutes = qMax(1, minutes); + m_intervalMinutes = std::max(1, minutes); if (m_enabled) { m_timer.start(m_intervalMinutes * 60 * 1000); } } void AutosaveManager::setMaxBackups(int count) { - m_maxBackups = qMax(1, count); + m_maxBackups = std::max(1, count); cleanupOldBackups(); } @@ -56,55 +56,40 @@ QString AutosaveManager::backupDir() { return dir + "/backups"; } -QString AutosaveManager::autosavePath() const { +QString AutosaveManager::showBaseName() const { if (!m_app || !m_app->show()) return QString(); Show* show = m_app->show(); - QString filename; - if (!show->filePath().isEmpty()) { QFileInfo fi(show->filePath()); - filename = fi.completeBaseName(); - } else { - filename = "untitled"; + return fi.completeBaseName(); } + return QStringLiteral("untitled"); +} + +QString AutosaveManager::autosavePath() const { + QString filename = showBaseName(); + if (filename.isEmpty()) + return QString(); return autosaveDir() + "/" + filename + "_autosave.omproj"; } QString AutosaveManager::generateBackupFilename() const { - if (!m_app || !m_app->show()) + QString baseName = showBaseName(); + if (baseName.isEmpty()) return QString(); - Show* show = m_app->show(); - QString baseName; - - if (!show->filePath().isEmpty()) { - QFileInfo fi(show->filePath()); - baseName = fi.completeBaseName(); - } else { - baseName = "untitled"; - } - QString timestamp = QDateTime::currentDateTime().toString("yyyyMMdd_HHmmss"); return QString("%1_%2.omproj.bak").arg(baseName, timestamp); } QString AutosaveManager::generateAutosaveFilename() const { - if (!m_app || !m_app->show()) + QString baseName = showBaseName(); + if (baseName.isEmpty()) return QString(); - Show* show = m_app->show(); - QString baseName; - - if (!show->filePath().isEmpty()) { - QFileInfo fi(show->filePath()); - baseName = fi.completeBaseName(); - } else { - baseName = "untitled"; - } - QString timestamp = QDateTime::currentDateTime().toString("yyyyMMdd_HHmmss"); return QString("%1_autosave_%2.omproj").arg(baseName, timestamp); } @@ -244,34 +229,27 @@ QString AutosaveManager::recoverableAutosavePath() const { return path; } -void AutosaveManager::cleanupOldBackups() { - QDir dir(backupDir()); +void AutosaveManager::cleanupOldFiles(const QString& dirPath, const QString& nameFilter, + int maxCount) { + QDir dir(dirPath); if (!dir.exists()) return; - // get all backup files sorted by modification time (newest first) - QFileInfoList files = - dir.entryInfoList(QStringList() << "*.omproj.bak", QDir::Files, QDir::Time); + // get all matching files sorted by modification time (newest first) + QFileInfoList files = dir.entryInfoList(QStringList() << nameFilter, QDir::Files, QDir::Time); // remove files beyond max count - for (int i = m_maxBackups; i < files.size(); ++i) { + for (int i = maxCount; i < files.size(); ++i) { QFile::remove(files[i].absoluteFilePath()); } } -void AutosaveManager::cleanupOldAutosaves() { - QDir dir(autosaveDir()); - if (!dir.exists()) - return; - - // get all autosave files sorted by modification time (newest first) - QFileInfoList files = - dir.entryInfoList(QStringList() << "*_autosave*.omproj", QDir::Files, QDir::Time); +void AutosaveManager::cleanupOldBackups() { + cleanupOldFiles(backupDir(), "*.omproj.bak", m_maxBackups); +} - // remove files beyond max count - for (int i = m_maxAutosaves; i < files.size(); ++i) { - QFile::remove(files[i].absoluteFilePath()); - } +void AutosaveManager::cleanupOldAutosaves() { + cleanupOldFiles(autosaveDir(), "*_autosave*.omproj", m_maxAutosaves); } } // namespace OpenMix diff --git a/src/io/AutosaveManager.h b/src/io/AutosaveManager.h index fc4269b..1c5c56e 100644 --- a/src/io/AutosaveManager.h +++ b/src/io/AutosaveManager.h @@ -44,6 +44,7 @@ class AutosaveManager : public QObject { signals: void autosaveCompleted(const QString& path); void autosaveFailed(const QString& error); + void backupCreated(const QString& path); public slots: @@ -58,6 +59,8 @@ class AutosaveManager : public QObject { void cleanupOldAutosaves(); QString generateBackupFilename() const; QString generateAutosaveFilename() const; + QString showBaseName() const; + void cleanupOldFiles(const QString& dirPath, const QString& nameFilter, int maxCount); Application* m_app; QTimer m_timer; diff --git a/src/io/CrashRecovery.cpp b/src/io/CrashRecovery.cpp index 3dc36a1..c6366f2 100644 --- a/src/io/CrashRecovery.cpp +++ b/src/io/CrashRecovery.cpp @@ -3,6 +3,9 @@ #include "core/Show.h" #include "protocol/MixerProtocol.h" +#include +#include + #include #include #include @@ -20,6 +23,29 @@ #include #endif +namespace { + +bool writeJsonToFile(const QString& path, const QJsonObject& obj) { + QFile file(path); + if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) + return false; + file.write(QJsonDocument(obj).toJson(QJsonDocument::Compact)); + return true; +} + +std::optional readJsonFromFile(const QString& path) { + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) + return std::nullopt; + QJsonParseError error; + auto doc = QJsonDocument::fromJson(file.readAll(), &error); + if (error.error != QJsonParseError::NoError || !doc.isObject()) + return std::nullopt; + return doc.object(); +} + +} // anonymous namespace + namespace OpenMix { CrashRecovery::CrashRecovery(QObject* parent) : QObject(parent) { @@ -84,23 +110,15 @@ void CrashRecovery::saveState() { QDir dir; dir.mkpath(configDir()); - QFile file(crashStatePath()); - if (file.open(QIODevice::WriteOnly)) { - QJsonDocument doc(state); - file.write(doc.toJson(QJsonDocument::Compact)); - file.close(); + if (writeJsonToFile(crashStatePath(), state)) { emit stateSaved(); } - QFile lockFile(lockFilePath()); - if (lockFile.exists()) { - lockFile.open(QIODevice::WriteOnly); + if (QFile::exists(lockFilePath())) { QJsonObject lockData; lockData["timestamp"] = QDateTime::currentMSecsSinceEpoch(); lockData["pid"] = QCoreApplication::applicationPid(); - QJsonDocument doc(lockData); - lockFile.write(doc.toJson(QJsonDocument::Compact)); - lockFile.close(); + writeJsonToFile(lockFilePath(), lockData); } } @@ -111,7 +129,7 @@ void CrashRecovery::markCleanExit() { } void CrashRecovery::setAutoSaveInterval(int seconds) { - m_autoSaveIntervalSec = qMax(1, seconds); + m_autoSaveIntervalSec = std::max(1, seconds); if (m_autoSaveTimer.isActive()) { m_autoSaveTimer.setInterval(m_autoSaveIntervalSec * 1000); } @@ -144,17 +162,10 @@ bool CrashRecovery::createLockFile() { QDir dir; dir.mkpath(configDir()); - QFile file(lockFilePath()); - if (file.open(QIODevice::WriteOnly)) { - QJsonObject lockData; - lockData["timestamp"] = QDateTime::currentMSecsSinceEpoch(); - lockData["pid"] = QCoreApplication::applicationPid(); - QJsonDocument doc(lockData); - file.write(doc.toJson(QJsonDocument::Compact)); - file.close(); - return true; - } - return false; + QJsonObject lockData; + lockData["timestamp"] = QDateTime::currentMSecsSinceEpoch(); + lockData["pid"] = QCoreApplication::applicationPid(); + return writeJsonToFile(lockFilePath(), lockData); } void CrashRecovery::removeLockFile() { QFile::remove(lockFilePath()); } @@ -165,37 +176,19 @@ bool CrashRecovery::isLockFileStale() const { return false; } - QFile file(lockFilePath()); - if (file.open(QIODevice::ReadOnly)) { - QByteArray data = file.readAll(); - file.close(); - - QJsonParseError error; - QJsonDocument doc = QJsonDocument::fromJson(data, &error); - if (error.error == QJsonParseError::NoError && doc.isObject()) { - QJsonObject obj = doc.object(); - qint64 timestamp = obj["timestamp"].toVariant().toLongLong(); - qint64 now = QDateTime::currentMSecsSinceEpoch(); - qint64 age = (now - timestamp) / 1000; - return age > LOCK_FILE_STALE_SECONDS; - } + if (auto obj = readJsonFromFile(lockFilePath())) { + qint64 timestamp = (*obj)["timestamp"].toVariant().toLongLong(); + qint64 now = QDateTime::currentMSecsSinceEpoch(); + qint64 age = (now - timestamp) / 1000; + return age > LOCK_FILE_STALE_SECONDS; } return true; } qint64 CrashRecovery::readPidFromLockFile() const { - QFile file(lockFilePath()); - if (file.open(QIODevice::ReadOnly)) { - QByteArray data = file.readAll(); - file.close(); - - QJsonParseError error; - QJsonDocument doc = QJsonDocument::fromJson(data, &error); - if (error.error == QJsonParseError::NoError && doc.isObject()) { - return doc.object()["pid"].toVariant().toLongLong(); - } - } + if (auto obj = readJsonFromFile(lockFilePath())) + return (*obj)["pid"].toVariant().toLongLong(); return 0; } @@ -242,22 +235,13 @@ void CrashRecovery::loadCrashState() { } } - QFile file(statePath); - if (!file.open(QIODevice::ReadOnly)) { - m_crashStateLoaded = false; - return; - } - - QJsonParseError error; - QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &error); - file.close(); - - if (error.error != QJsonParseError::NoError || !doc.isObject()) { + auto optObj = readJsonFromFile(statePath); + if (!optObj) { m_crashStateLoaded = false; return; } - QJsonObject obj = doc.object(); + QJsonObject obj = *optObj; m_crashState.version = obj["version"].toString(); m_crashState.timestamp = obj["timestamp"].toVariant().toLongLong(); diff --git a/src/io/ProjectFile.h b/src/io/ProjectFile.h index afaae36..cad4254 100644 --- a/src/io/ProjectFile.h +++ b/src/io/ProjectFile.h @@ -15,11 +15,11 @@ class ProjectFile { static bool save(const Show* show, const QString& filePath, QString* errorMsg = nullptr); static bool load(Show* show, const QString& filePath, QString* errorMsg = nullptr); - static QStringList recentProjects(); + [[nodiscard]] static QStringList recentProjects(); static void addRecentProject(const QString& filePath); static void removeRecentProject(const QString& filePath); static void clearRecentProjects(); - static int maxRecentProjects() { return 10; } + [[nodiscard]] static int maxRecentProjects() noexcept { return 10; } private: static QString recentProjectsKey(); diff --git a/src/midi/MidiInputManager.cpp b/src/midi/MidiInputManager.cpp index 038b025..b20d53c 100644 --- a/src/midi/MidiInputManager.cpp +++ b/src/midi/MidiInputManager.cpp @@ -6,9 +6,14 @@ #include #include #include +#include namespace OpenMix { +namespace { +constexpr int DEVICE_POLL_INTERVAL_MS = 2000; +} // namespace + MidiInputManager::MidiInputManager(QObject* parent) : QObject(parent) { try { m_midiIn = std::make_unique(); @@ -17,7 +22,7 @@ MidiInputManager::MidiInputManager(QObject* parent) : QObject(parent) { } // device polling timer for hot-plug detection - m_devicePollTimer.setInterval(2000); + m_devicePollTimer.setInterval(DEVICE_POLL_INTERVAL_MS); connect(&m_devicePollTimer, &QTimer::timeout, this, &MidiInputManager::onDevicePollTimer); m_devicePollTimer.start(); } @@ -73,13 +78,8 @@ bool MidiInputManager::openDevice(int deviceIndex) { } bool MidiInputManager::openDevice(const QString& deviceName) { - QVector devices = availableDevices(); - for (const MidiDeviceInfo& dev : devices) { - if (dev.name == deviceName) { - return openDevice(dev.index); - } - } - return false; + const int idx = findDeviceIndex(deviceName); + return idx >= 0 ? openDevice(idx) : false; } void MidiInputManager::closeDevice() { @@ -210,14 +210,15 @@ void MidiInputManager::onDevicePollTimer() { bool MidiInputManager::tryReconnect() { if (m_savedDeviceName.isEmpty()) return false; + const int idx = findDeviceIndex(m_savedDeviceName); + return idx >= 0 ? openDevice(idx) : false; +} - QVector devices = availableDevices(); - for (const MidiDeviceInfo& dev : devices) { - if (dev.name == m_savedDeviceName) { - return openDevice(dev.index); - } - } - return false; +int MidiInputManager::findDeviceIndex(const QString& deviceName) const { + const auto devices = availableDevices(); + auto it = std::find_if(devices.cbegin(), devices.cend(), + [&deviceName](const MidiDeviceInfo& dev) { return dev.name == deviceName; }); + return it != devices.cend() ? it->index : -1; } void MidiInputManager::midiCallback(double /*timeStamp*/, std::vector* message, diff --git a/src/midi/MidiInputManager.h b/src/midi/MidiInputManager.h index 0f479cb..7444a7c 100644 --- a/src/midi/MidiInputManager.h +++ b/src/midi/MidiInputManager.h @@ -75,9 +75,11 @@ class MidiInputManager : public QObject { void onDevicePollTimer(); private: + void processMidiMessage(const std::vector& message); void executeAction(MidiAction action); bool tryReconnect(); + int findDeviceIndex(const QString& deviceName) const; static void midiCallback(double timeStamp, std::vector* message, void* userData); diff --git a/src/protocol/LoopbackProtocol.cpp b/src/protocol/LoopbackProtocol.cpp index 31fb82f..6b3faa2 100644 --- a/src/protocol/LoopbackProtocol.cpp +++ b/src/protocol/LoopbackProtocol.cpp @@ -2,6 +2,8 @@ #include "../core/Cue.h" #include #include +#include +#include namespace OpenMix { @@ -11,10 +13,22 @@ LoopbackProtocol::LoopbackProtocol(const MixerCapabilities& caps, QObject* paren } void LoopbackProtocol::initializeDefaultState() { - // default EQ frequencies (6-band setup) - static const double defaultFreqs[6] = {80.0, 220.0, 1000.0, 2500.0, 6000.0, 12000.0}; - // default EQ types: 0 = LCut, 1 = LShv, 2 = PEQ, 3 = VEQ, 4 = HShv, 5 = HCut - static const int defaultTypes[6] = {1, 2, 2, 2, 2, 4}; + // 0=LCut 1=LShv 2=PEQ 3=VEQ 4=HShv 5=HCut + static constexpr std::array defaultFreqs = {80.0, 220.0, 1000.0, 2500.0, 6000.0, 12000.0}; + static constexpr std::array defaultTypes = {1, 2, 2, 2, 2, 4}; + + auto initEQ = [&](const QString& prefix) { + if (!m_capabilities.supportsChannelEQ) + return; + m_parameterState[prefix + "/eq/on"] = 1; + for (int band = 1; band <= m_capabilities.eqBandsPerChannel && band <= 6; ++band) { + const QString bandPrefix = QStringLiteral("%1/eq/%2").arg(prefix).arg(band); + m_parameterState[bandPrefix + "/type"] = defaultTypes[band - 1]; + m_parameterState[bandPrefix + "/f"] = defaultFreqs[band - 1]; + m_parameterState[bandPrefix + "/g"] = 0.0f; + m_parameterState[bandPrefix + "/q"] = 2.0f; + } + }; // init DCAs with default values for (int i = 1; i <= m_capabilities.dcaCount; ++i) { @@ -25,7 +39,7 @@ void LoopbackProtocol::initializeDefaultState() { } // init input channels for testing - for (int i = 1; i <= qMin(m_capabilities.inputChannels, 32); ++i) { + for (int i = 1; i <= std::min(m_capabilities.inputChannels, 32); ++i) { QString chPrefix = QString("/ch/%1").arg(i, 2, 10, QChar('0')); m_parameterState[chPrefix + "/mix/fader"] = 0.75f; m_parameterState[chPrefix + "/mix/on"] = 1; @@ -35,22 +49,11 @@ void LoopbackProtocol::initializeDefaultState() { int dcaIndex = ((i - 1) / 4) % m_capabilities.dcaCount; m_parameterState[chPrefix + "/grp/dca"] = (1 << dcaIndex); - // EQ parameters - if (m_capabilities.supportsChannelEQ) { - m_parameterState[chPrefix + "/eq/on"] = 1; // EQ enabled - - for (int band = 1; band <= m_capabilities.eqBandsPerChannel && band <= 6; ++band) { - QString bandPrefix = QString("%1/eq/%2").arg(chPrefix).arg(band); - m_parameterState[bandPrefix + "/type"] = defaultTypes[band - 1]; - m_parameterState[bandPrefix + "/f"] = defaultFreqs[band - 1]; - m_parameterState[bandPrefix + "/g"] = 0.0f; // 0dB (unity) - m_parameterState[bandPrefix + "/q"] = 2.0f; // moderate Q - } - } + initEQ(chPrefix); // effect send parameters if (m_capabilities.supportsEffectSends) { - int sends = qMin(m_capabilities.effectSendBuses, 16); + int sends = std::min(m_capabilities.effectSendBuses, 16); for (int send = 1; send <= sends; ++send) { QString sendPrefix = QString("%1/mix/%2").arg(chPrefix).arg(send, 2, 10, QChar('0')); @@ -61,7 +64,7 @@ void LoopbackProtocol::initializeDefaultState() { } // init mix buses with EQ - for (int i = 1; i <= qMin(m_capabilities.mixBuses, 16); ++i) { + for (int i = 1; i <= std::min(m_capabilities.mixBuses, 16); ++i) { QString busPrefix = QString("/bus/%1").arg(i, 2, 10, QChar('0')); m_parameterState[busPrefix + "/mix/fader"] = 0.75f; m_parameterState[busPrefix + "/mix/on"] = 1; @@ -71,42 +74,18 @@ void LoopbackProtocol::initializeDefaultState() { int dcaIndex = ((i - 1) / 2) % m_capabilities.dcaCount; m_parameterState[busPrefix + "/grp/dca"] = (1 << dcaIndex); - // bus EQ parameters - if (m_capabilities.supportsChannelEQ) { - m_parameterState[busPrefix + "/eq/on"] = 1; - - for (int band = 1; band <= m_capabilities.eqBandsPerChannel && band <= 6; ++band) { - QString bandPrefix = QString("%1/eq/%2").arg(busPrefix).arg(band); - m_parameterState[bandPrefix + "/type"] = defaultTypes[band - 1]; - m_parameterState[bandPrefix + "/f"] = defaultFreqs[band - 1]; - m_parameterState[bandPrefix + "/g"] = 0.0f; - m_parameterState[bandPrefix + "/q"] = 2.0f; - } - } + initEQ(busPrefix); } // init main stereo bus m_parameterState["/main/st/mix/fader"] = 0.75f; m_parameterState["/main/st/mix/on"] = 1; - if (m_capabilities.supportsChannelEQ) { - m_parameterState["/main/st/eq/on"] = 1; - - for (int band = 1; band <= m_capabilities.eqBandsPerChannel && band <= 6; ++band) { - QString bandPrefix = QString("/main/st/eq/%1").arg(band); - m_parameterState[bandPrefix + "/type"] = defaultTypes[band - 1]; - m_parameterState[bandPrefix + "/f"] = defaultFreqs[band - 1]; - m_parameterState[bandPrefix + "/g"] = 0.0f; - m_parameterState[bandPrefix + "/q"] = 2.0f; - } - } + initEQ("/main/st"); } -bool LoopbackProtocol::connect(const QString& host, int port) { - Q_UNUSED(host); - Q_UNUSED(port); - - m_connected = true; +bool LoopbackProtocol::connect([[maybe_unused]] const QString& host, [[maybe_unused]] int port) { + m_connectionState = ConnectionState::Connected; m_statusMessage = "Connected (Offline)"; // emit connection signal on next event loop @@ -116,8 +95,8 @@ bool LoopbackProtocol::connect(const QString& host, int port) { emit connected(); // emit initial parameter values - for (auto it = m_parameterState.begin(); it != m_parameterState.end(); ++it) { - emit parameterChanged(it.key(), it.value()); + for (const auto& [path, value] : m_parameterState.asKeyValueRange()) { + emit parameterChanged(path, value); } }); @@ -125,7 +104,7 @@ bool LoopbackProtocol::connect(const QString& host, int port) { } void LoopbackProtocol::disconnect() { - m_connected = false; + m_connectionState = ConnectionState::Disconnected; m_statusMessage = "Disconnected"; emit connectionStateChanged(ConnectionState::Disconnected); @@ -134,14 +113,14 @@ void LoopbackProtocol::disconnect() { } void LoopbackProtocol::sendParameter(const QString& path, const QVariant& value) { - if (!m_connected) { + if (m_connectionState != ConnectionState::Connected) { return; } m_parameterState[path] = value; // echo back the change - QTimer::singleShot(5, this, [this, path, value]() { emit parameterChanged(path, value); }); + emitParameterChangedAsync(path, value); } QVariant LoopbackProtocol::getParameter(const QString& path) { @@ -149,18 +128,18 @@ QVariant LoopbackProtocol::getParameter(const QString& path) { } void LoopbackProtocol::requestParameter(const QString& path) { - if (!m_connected) { + if (m_connectionState != ConnectionState::Connected) { return; } QVariant value = m_parameterState.value(path); // respond asynchronously - QTimer::singleShot(5, this, [this, path, value]() { emit parameterChanged(path, value); }); + emitParameterChangedAsync(path, value); } void LoopbackProtocol::requestParameterAsync(const QString& path, ParameterCallback callback) { - if (!m_connected) { + if (m_connectionState != ConnectionState::Connected) { if (callback) { callback(path, QVariant(), false); } @@ -177,28 +156,20 @@ void LoopbackProtocol::requestParameterAsync(const QString& path, ParameterCallb }); } +void LoopbackProtocol::emitParameterChangedAsync(const QString& addr, const QVariant& val) { + QTimer::singleShot(5, this, [this, addr, val]() { emit parameterChanged(addr, val); }); +} + void LoopbackProtocol::recallSnapshot(const Cue& cue) { - QJsonObject params = cue.parameters(); - - // recall DCA parameters - if (params.contains("dca")) { - QJsonObject dcaObj = params["dca"].toObject(); - for (auto it = dcaObj.begin(); it != dcaObj.end(); ++it) { - int dcaNum = it.key().toInt(); - QJsonObject dcaParams = it.value().toObject(); - QString prefix = QString("/dca/%1").arg(dcaNum); - - for (auto pit = dcaParams.begin(); pit != dcaParams.end(); ++pit) { - QString path = prefix + "/" + pit.key(); - QVariant value = pit.value().toVariant(); - sendParameter(path, value); - } - } + if (m_connectionState != ConnectionState::Connected) + return; + + for (const auto& [path, value] : cue.parameters().asKeyValueRange()) { + sendParameter(path.toString(), value.toVariant()); } } void LoopbackProtocol::recallScene(int sceneNumber) { - Q_UNUSED(sceneNumber); emit sceneChanged(sceneNumber); } diff --git a/src/protocol/LoopbackProtocol.h b/src/protocol/LoopbackProtocol.h index 8cfeb60..d4fccb4 100644 --- a/src/protocol/LoopbackProtocol.h +++ b/src/protocol/LoopbackProtocol.h @@ -3,7 +3,6 @@ #include "MixerCapabilities.h" #include "MixerProtocol.h" #include -#include namespace OpenMix { @@ -16,21 +15,23 @@ class LoopbackProtocol : public MixerProtocol { ~LoopbackProtocol() override = default; // protocol identification - QString protocolName() const override { return m_capabilities.displayName; } - QString protocolDescription() const override { return "Virtual loopback for testing"; } + [[nodiscard]] QString protocolName() const override { return m_capabilities.displayName; } + [[nodiscard]] QString protocolDescription() const override { return "Virtual loopback for testing"; } // connection management - bool connect(const QString& host, int port) override; + [[nodiscard]] bool connect(const QString& host, int port) override; void disconnect() override; - bool isConnected() const override { return m_connected; } - QString connectionStatus() const override { return m_statusMessage; } - ConnectionState connectionState() const override { - return m_connected ? ConnectionState::Connected : ConnectionState::Disconnected; + [[nodiscard]] bool isConnected() const noexcept override { + return m_connectionState == ConnectionState::Connected; + } + [[nodiscard]] QString connectionStatus() const override { return m_statusMessage; } + [[nodiscard]] ConnectionState connectionState() const noexcept override { + return m_connectionState; } // parameter operations void sendParameter(const QString& path, const QVariant& value) override; - QVariant getParameter(const QString& path) override; + [[nodiscard]] QVariant getParameter(const QString& path) override; void requestParameter(const QString& path) override; void requestParameterAsync(const QString& path, ParameterCallback callback) override; @@ -44,16 +45,17 @@ class LoopbackProtocol : public MixerProtocol { void refresh() override; // latency - int latencyMs() const override { return 1; } + [[nodiscard]] int latencyMs() const noexcept override { return 1; } // capabilities - const MixerCapabilities& capabilities() const override { return m_capabilities; } + [[nodiscard]] const MixerCapabilities& capabilities() const override { return m_capabilities; } private: void initializeDefaultState(); + void emitParameterChangedAsync(const QString& addr, const QVariant& val); MixerCapabilities m_capabilities; - bool m_connected = false; + ConnectionState m_connectionState = ConnectionState::Disconnected; QString m_statusMessage; QMap m_parameterState; }; diff --git a/src/protocol/MixerCapabilities.cpp b/src/protocol/MixerCapabilities.cpp index 666a795..76dda7d 100644 --- a/src/protocol/MixerCapabilities.cpp +++ b/src/protocol/MixerCapabilities.cpp @@ -67,6 +67,7 @@ MixerCapabilities MixerCapabilities::forConsole(ConsoleType type) { caps.supportsEffectSends = true; break; + // Allen & Heath case ConsoleType::SQ5: caps.manufacturer = Manufacturer::AllenHeath; caps.protocol = ProtocolType::MidiTcp; @@ -207,6 +208,7 @@ MixerCapabilities MixerCapabilities::forConsole(ConsoleType type) { caps.supportsEffectSends = true; break; + // Yamaha case ConsoleType::TF1: caps.manufacturer = Manufacturer::Yamaha; caps.protocol = ProtocolType::OscUdp; @@ -380,6 +382,7 @@ MixerCapabilities MixerCapabilities::forConsole(ConsoleType type) { caps.supportsEffectSends = true; break; + // Loopback case ConsoleType::Loopback: caps.manufacturer = Manufacturer::Unknown; caps.protocol = ProtocolType::Internal; @@ -420,6 +423,7 @@ MixerCapabilities MixerCapabilities::forProtocolId(const QString& protocolId) { if (id == "wing") return forConsole(ConsoleType::Wing); + // Allen & Heath SQ if (id == "sq5" || id == "sq-5") return forConsole(ConsoleType::SQ5); if (id == "sq6" || id == "sq-6") @@ -427,16 +431,19 @@ MixerCapabilities MixerCapabilities::forProtocolId(const QString& protocolId) { if (id == "sq7" || id == "sq-7" || id == "sq") return forConsole(ConsoleType::SQ7); + // Allen & Heath GLD if (id == "gld80" || id == "gld-80") return forConsole(ConsoleType::GLD80); if (id == "gld112" || id == "gld-112" || id == "gld") return forConsole(ConsoleType::GLD112); + // Allen & Heath Avantis/dLive if (id == "avantis") return forConsole(ConsoleType::Avantis); if (id == "dlive" || id == "d-live") return forConsole(ConsoleType::DLive); + // Yamaha TF if (id == "tf1") return forConsole(ConsoleType::TF1); if (id == "tf3") @@ -444,11 +451,13 @@ MixerCapabilities MixerCapabilities::forProtocolId(const QString& protocolId) { if (id == "tf5" || id == "tf") return forConsole(ConsoleType::TF5); + // Yamaha QL if (id == "ql1") return forConsole(ConsoleType::QL1); if (id == "ql5" || id == "ql") return forConsole(ConsoleType::QL5); + // Yamaha CL if (id == "cl1") return forConsole(ConsoleType::CL1); if (id == "cl3") @@ -456,9 +465,11 @@ MixerCapabilities MixerCapabilities::forProtocolId(const QString& protocolId) { if (id == "cl5" || id == "cl") return forConsole(ConsoleType::CL5); + // Yamaha DM7 if (id == "dm7") return forConsole(ConsoleType::DM7); + // Loopback if (id == "loopback" || id == "test") return forConsole(ConsoleType::Loopback); @@ -472,6 +483,7 @@ QVector MixerCapabilities::allSupported() { all.append(forConsole(ConsoleType::M32)); all.append(forConsole(ConsoleType::Wing)); + // Allen & Heath all.append(forConsole(ConsoleType::SQ5)); all.append(forConsole(ConsoleType::SQ6)); all.append(forConsole(ConsoleType::SQ7)); @@ -481,6 +493,7 @@ QVector MixerCapabilities::allSupported() { all.append(forConsole(ConsoleType::Avantis)); all.append(forConsole(ConsoleType::DLive)); + // Yamaha all.append(forConsole(ConsoleType::TF1)); all.append(forConsole(ConsoleType::TF3)); all.append(forConsole(ConsoleType::TF5)); diff --git a/src/protocol/MixerCapabilities.h b/src/protocol/MixerCapabilities.h index 85bf59f..b96fb84 100644 --- a/src/protocol/MixerCapabilities.h +++ b/src/protocol/MixerCapabilities.h @@ -48,6 +48,13 @@ enum class Manufacturer { Behringer, Midas, AllenHeath, Yamaha, Unknown }; // capabilities descriptor for each console type struct MixerCapabilities { + static constexpr int DEFAULT_PORT = 10023; + static constexpr int DEFAULT_DCA_COUNT = 8; + static constexpr int DEFAULT_INPUT_CHANNELS = 32; + static constexpr int DEFAULT_MIX_BUSES = 16; + static constexpr int DEFAULT_SCENES = 100; + static constexpr int DEFAULT_MAX_DCA_NAME_LENGTH = 6; + ConsoleType type = ConsoleType::Unknown; Manufacturer manufacturer = Manufacturer::Unknown; ProtocolType protocol = ProtocolType::OscUdp; @@ -55,13 +62,13 @@ struct MixerCapabilities { QString displayName; // human-readable name QString protocolId; // ID for factory lookup ("x32", "wing", "sq", etc.) - int defaultPort = 10023; // default port - int dcaCount = 8; // # of DCAs - int inputChannels = 32; // # of input channels - int mixBuses = 16; // # of mix buses - int matrixOutputs = 0; // # of matrix outputs - int scenes = 100; // # of scene/snapshot slots - int maxDCANameLength = 6; // default to safe minimum + int defaultPort = DEFAULT_PORT; + int dcaCount = DEFAULT_DCA_COUNT; + int inputChannels = DEFAULT_INPUT_CHANNELS; + int mixBuses = DEFAULT_MIX_BUSES; + int matrixOutputs = 0; + int scenes = DEFAULT_SCENES; + int maxDCANameLength = DEFAULT_MAX_DCA_NAME_LENGTH; bool supportsSceneRecall = true; bool supportsDCAMute = true; @@ -76,13 +83,13 @@ struct MixerCapabilities { int effectSendBuses = 0; // # of effect send buses bool supportsEffectSends = false; - static MixerCapabilities forConsole(ConsoleType type); - static MixerCapabilities forProtocolId(const QString& protocolId); - static QVector allSupported(); - static QVector forManufacturer(Manufacturer manufacturer); + [[nodiscard]] static MixerCapabilities forConsole(ConsoleType type); + [[nodiscard]] static MixerCapabilities forProtocolId(const QString& protocolId); + [[nodiscard]] static QVector allSupported(); + [[nodiscard]] static QVector forManufacturer(Manufacturer manufacturer); - bool isSupported() const; - QString manufacturerName() const; + [[nodiscard]] bool isSupported() const; + [[nodiscard]] QString manufacturerName() const; }; // convert between types & strings diff --git a/src/protocol/MixerProtocol.h b/src/protocol/MixerProtocol.h index c5d71e3..f23b1f5 100644 --- a/src/protocol/MixerProtocol.h +++ b/src/protocol/MixerProtocol.h @@ -50,8 +50,10 @@ class MixerProtocol : public QObject { void connectionStatusChanged(const QString& status); void connectionStateChanged(ConnectionState state); void connectionLost(); + void parameterChanged(const QString& path, const QVariant& value); void requestTimeout(const QString& path); + void latencyChanged(int ms); void sceneChanged(int sceneNumber); }; diff --git a/src/protocol/ProtocolFactory.h b/src/protocol/ProtocolFactory.h index 8f9b5fb..cd39292 100644 --- a/src/protocol/ProtocolFactory.h +++ b/src/protocol/ProtocolFactory.h @@ -10,15 +10,15 @@ class MixerProtocol; class ProtocolFactory { public: - static MixerProtocol* create(const QString& type, QObject* parent = nullptr); - static MixerProtocol* create(ConsoleType type, QObject* parent = nullptr); - static MixerProtocol* create(const MixerCapabilities& caps, QObject* parent = nullptr); - static MixerProtocol* create(const DiscoveredConsole& console, QObject* parent = nullptr); + [[nodiscard]] static MixerProtocol* create(const QString& type, QObject* parent = nullptr); + [[nodiscard]] static MixerProtocol* create(ConsoleType type, QObject* parent = nullptr); + [[nodiscard]] static MixerProtocol* create(const MixerCapabilities& caps, QObject* parent = nullptr); + [[nodiscard]] static MixerProtocol* create(const DiscoveredConsole& console, QObject* parent = nullptr); - static bool isImplemented(const QString& type); - static bool isImplemented(ConsoleType type); + [[nodiscard]] static bool isImplemented(const QString& type); + [[nodiscard]] static bool isImplemented(ConsoleType type); - static MixerCapabilities capabilities(const QString& type); + [[nodiscard]] static MixerCapabilities capabilities(const QString& type); private: ProtocolFactory() = default; diff --git a/src/protocol/allenheath/AllenHeathMidiProtocol.cpp b/src/protocol/allenheath/AllenHeathMidiProtocol.cpp index 300168c..fb4d220 100644 --- a/src/protocol/allenheath/AllenHeathMidiProtocol.cpp +++ b/src/protocol/allenheath/AllenHeathMidiProtocol.cpp @@ -165,6 +165,7 @@ QByteArray AllenHeathMidiProtocol::buildNRPNMessage(int channel, int nrpnMsb, in return msg; } +// SysEx builders QByteArray AllenHeathMidiProtocol::buildSysExSceneRecall(int sceneNumber) { QByteArray msg; // Allen & Heath SysEx header diff --git a/src/protocol/allenheath/AllenHeathMidiProtocol.h b/src/protocol/allenheath/AllenHeathMidiProtocol.h index 51de3d1..075928a 100644 --- a/src/protocol/allenheath/AllenHeathMidiProtocol.h +++ b/src/protocol/allenheath/AllenHeathMidiProtocol.h @@ -36,21 +36,21 @@ class AllenHeathMidiProtocol : public MixerProtocol { ~AllenHeathMidiProtocol() override; // protocol identification - QString protocolName() const override { return m_capabilities.displayName; } - QString protocolDescription() const override { + [[nodiscard]] QString protocolName() const override { return m_capabilities.displayName; } + [[nodiscard]] QString protocolDescription() const override { return m_capabilities.displayName + " MIDI/TCP Protocol"; } // connection management - bool connect(const QString& host, int port) override; + [[nodiscard]] bool connect(const QString& host, int port) override; void disconnect() override; - bool isConnected() const override { return m_connectionState == ConnectionState::Connected; } - QString connectionStatus() const override { return m_statusMessage; } - ConnectionState connectionState() const override { return m_connectionState; } + [[nodiscard]] bool isConnected() const override { return m_connectionState == ConnectionState::Connected; } + [[nodiscard]] QString connectionStatus() const override { return m_statusMessage; } + [[nodiscard]] ConnectionState connectionState() const override { return m_connectionState; } // parameter operations void sendParameter(const QString& path, const QVariant& value) override; - QVariant getParameter(const QString& path) override; + [[nodiscard]] QVariant getParameter(const QString& path) override; void requestParameter(const QString& path) override; void requestParameterAsync(const QString& path, ParameterCallback callback) override; @@ -64,10 +64,10 @@ class AllenHeathMidiProtocol : public MixerProtocol { void refresh() override; // latency monitoring - int latencyMs() const override { return m_latencyMs; } + [[nodiscard]] int latencyMs() const override { return m_latencyMs; } // capabilities - const MixerCapabilities& capabilities() const override { return m_capabilities; } + [[nodiscard]] const MixerCapabilities& capabilities() const override { return m_capabilities; } protected: // MIDI message builders used by subclasses diff --git a/src/protocol/allenheath/AllenHeathTcpProtocol.cpp b/src/protocol/allenheath/AllenHeathTcpProtocol.cpp index 32bb649..87d3896 100644 --- a/src/protocol/allenheath/AllenHeathTcpProtocol.cpp +++ b/src/protocol/allenheath/AllenHeathTcpProtocol.cpp @@ -154,6 +154,7 @@ QByteArray AllenHeathTcpProtocol::buildDCAFaderMessage(int dca, float level) { return msg; } +// buildDCAMuteMessage QByteArray AllenHeathTcpProtocol::buildDCAMuteMessage(int dca, bool muted) { QByteArray msg; @@ -168,6 +169,7 @@ QByteArray AllenHeathTcpProtocol::buildDCAMuteMessage(int dca, bool muted) { return msg; } +// buildSceneRecallMessage QByteArray AllenHeathTcpProtocol::buildSceneRecallMessage(int sceneNumber) { QByteArray msg; diff --git a/src/protocol/allenheath/AllenHeathTcpProtocol.h b/src/protocol/allenheath/AllenHeathTcpProtocol.h index 92eb319..af5a466 100644 --- a/src/protocol/allenheath/AllenHeathTcpProtocol.h +++ b/src/protocol/allenheath/AllenHeathTcpProtocol.h @@ -20,21 +20,21 @@ class AllenHeathTcpProtocol : public MixerProtocol { ~AllenHeathTcpProtocol() override; // protocol identification - QString protocolName() const override { return m_capabilities.displayName; } - QString protocolDescription() const override { + [[nodiscard]] QString protocolName() const override { return m_capabilities.displayName; } + [[nodiscard]] QString protocolDescription() const override { return m_capabilities.displayName + " TCP Protocol"; } // connection management - bool connect(const QString& host, int port) override; + [[nodiscard]] bool connect(const QString& host, int port) override; void disconnect() override; - bool isConnected() const override { return m_connectionState == ConnectionState::Connected; } - QString connectionStatus() const override { return m_statusMessage; } - ConnectionState connectionState() const override { return m_connectionState; } + [[nodiscard]] bool isConnected() const override { return m_connectionState == ConnectionState::Connected; } + [[nodiscard]] QString connectionStatus() const override { return m_statusMessage; } + [[nodiscard]] ConnectionState connectionState() const override { return m_connectionState; } // parameter operations void sendParameter(const QString& path, const QVariant& value) override; - QVariant getParameter(const QString& path) override; + [[nodiscard]] QVariant getParameter(const QString& path) override; void requestParameter(const QString& path) override; void requestParameterAsync(const QString& path, ParameterCallback callback) override; @@ -48,10 +48,10 @@ class AllenHeathTcpProtocol : public MixerProtocol { void refresh() override; // latency monitoring - int latencyMs() const override { return m_latencyMs; } + [[nodiscard]] int latencyMs() const override { return m_latencyMs; } // capabilities - const MixerCapabilities& capabilities() const override { return m_capabilities; } + [[nodiscard]] const MixerCapabilities& capabilities() const override { return m_capabilities; } protected: // binary message builders diff --git a/src/protocol/behringer/WingProtocol.cpp b/src/protocol/behringer/WingProtocol.cpp index 26ff7aa..369b770 100644 --- a/src/protocol/behringer/WingProtocol.cpp +++ b/src/protocol/behringer/WingProtocol.cpp @@ -1,6 +1,7 @@ #include "WingProtocol.h" #include "../../core/Cue.h" #include +#include namespace OpenMix { @@ -64,7 +65,7 @@ void WingProtocol::initializeSnapshotParams() { // effect send params if (m_capabilities.supportsEffectSends) { - int sends = qMin(m_capabilities.effectSendBuses, 16); + int sends = std::min(m_capabilities.effectSendBuses, 16); for (int send = 1; send <= sends; ++send) { QString sendPrefix = QString("%1/send/%2").arg(chPrefix).arg(send); m_snapshotParams.append(sendPrefix + "/level"); @@ -211,8 +212,8 @@ void WingProtocol::recallSnapshot(const Cue& cue) { return; QJsonObject params = cue.parameters(); - for (auto it = params.begin(); it != params.end(); ++it) { - sendParameter(it.key(), it.value().toVariant()); + for (const auto& [path, value] : params.asKeyValueRange()) { + sendParameter(path.toString(), value.toVariant()); } } @@ -304,9 +305,9 @@ void WingProtocol::onRequestTimeoutCheck() { QDateTime now = QDateTime::currentDateTime(); QStringList timedOut; - for (auto it = m_pendingRequests.begin(); it != m_pendingRequests.end(); ++it) { - if (it->timestamp.msecsTo(now) > m_requestTimeoutMs) { - timedOut.append(it.key()); + for (const auto& [path, req] : m_pendingRequests.asKeyValueRange()) { + if (req.timestamp.msecsTo(now) > m_requestTimeoutMs) { + timedOut.append(path); } } @@ -370,8 +371,7 @@ void WingProtocol::processResponse(const QString& path, const QVariant& value) { } } -void WingProtocol::handleInfoResponse(const QVariant& value) { - Q_UNUSED(value); +void WingProtocol::handleInfoResponse([[maybe_unused]] const QVariant& value) { m_connectionTimer.stop(); m_waitingForInfo = false; diff --git a/src/protocol/behringer/WingProtocol.h b/src/protocol/behringer/WingProtocol.h index 09ffd27..4f65539 100644 --- a/src/protocol/behringer/WingProtocol.h +++ b/src/protocol/behringer/WingProtocol.h @@ -30,19 +30,19 @@ class WingProtocol : public MixerProtocol { ~WingProtocol() override; // protocol identification - QString protocolName() const override { return m_capabilities.displayName; } - QString protocolDescription() const override { return "Behringer WING OSC Protocol"; } + [[nodiscard]] QString protocolName() const override { return m_capabilities.displayName; } + [[nodiscard]] QString protocolDescription() const override { return "Behringer WING OSC Protocol"; } // connection management - bool connect(const QString& host, int port) override; + [[nodiscard]] bool connect(const QString& host, int port) override; void disconnect() override; - bool isConnected() const override { return m_connectionState == ConnectionState::Connected; } - QString connectionStatus() const override { return m_statusMessage; } - ConnectionState connectionState() const override { return m_connectionState; } + [[nodiscard]] bool isConnected() const override { return m_connectionState == ConnectionState::Connected; } + [[nodiscard]] QString connectionStatus() const override { return m_statusMessage; } + [[nodiscard]] ConnectionState connectionState() const override { return m_connectionState; } // parameter operations void sendParameter(const QString& path, const QVariant& value) override; - QVariant getParameter(const QString& path) override; + [[nodiscard]] QVariant getParameter(const QString& path) override; void requestParameter(const QString& path) override; void requestParameterAsync(const QString& path, ParameterCallback callback) override; @@ -56,10 +56,10 @@ class WingProtocol : public MixerProtocol { void refresh() override; // latency monitoring - int latencyMs() const override { return m_latencyMs; } + [[nodiscard]] int latencyMs() const override { return m_latencyMs; } // capabilities - const MixerCapabilities& capabilities() const override { return m_capabilities; } + [[nodiscard]] const MixerCapabilities& capabilities() const override { return m_capabilities; } private slots: void onTransportConnected(); diff --git a/src/protocol/behringer/X32Protocol.cpp b/src/protocol/behringer/X32Protocol.cpp index 5e39427..a8cd78d 100644 --- a/src/protocol/behringer/X32Protocol.cpp +++ b/src/protocol/behringer/X32Protocol.cpp @@ -1,9 +1,16 @@ #include "X32Protocol.h" #include "../../core/Cue.h" #include +#include namespace OpenMix { +namespace { +constexpr int MAX_X32_INPUT_CHANNELS = 32; +constexpr int MAX_X32_EFFECT_SENDS = 16; +constexpr int MAX_X32_MIX_BUSES = 16; +} // namespace + X32Protocol::X32Protocol(const MixerCapabilities& caps, QObject* parent) : MixerProtocol(parent), m_capabilities(caps), m_transport(this) { @@ -40,7 +47,7 @@ void X32Protocol::rebuildSnapshotParams() { m_snapshotParams.clear(); // basic fader/mute parameters - for (int i = 1; i <= m_capabilities.inputChannels && i <= 32; ++i) { + for (int i = 1; i <= m_capabilities.inputChannels && i <= MAX_X32_INPUT_CHANNELS; ++i) { QString chPrefix = QString("/ch/%1").arg(i, 2, 10, QChar('0')); m_snapshotParams.append(chPrefix + "/mix/fader"); m_snapshotParams.append(chPrefix + "/mix/on"); @@ -59,7 +66,7 @@ void X32Protocol::rebuildSnapshotParams() { // effect send parameters (mix bus sends) if (m_capabilities.supportsEffectSends) { - int sends = qMin(m_capabilities.effectSendBuses, 16); + int sends = std::min(m_capabilities.effectSendBuses, MAX_X32_EFFECT_SENDS); for (int send = 1; send <= sends; ++send) { QString sendPrefix = QString("%1/mix/%2").arg(chPrefix).arg(send, 2, 10, QChar('0')); @@ -70,7 +77,7 @@ void X32Protocol::rebuildSnapshotParams() { } // mix bus parameters - for (int i = 1; i <= m_capabilities.mixBuses && i <= 16; ++i) { + for (int i = 1; i <= m_capabilities.mixBuses && i <= MAX_X32_MIX_BUSES; ++i) { QString busPrefix = QString("/bus/%1").arg(i, 2, 10, QChar('0')); m_snapshotParams.append(busPrefix + "/mix/fader"); m_snapshotParams.append(busPrefix + "/mix/on"); @@ -205,8 +212,8 @@ void X32Protocol::recallSnapshot(const Cue& cue) { return; QJsonObject params = cue.parameters(); - for (auto it = params.begin(); it != params.end(); ++it) { - sendParameter(it.key(), it.value().toVariant()); + for (const auto& [path, value] : params.asKeyValueRange()) { + sendParameter(path.toString(), value.toVariant()); } } @@ -297,9 +304,9 @@ void X32Protocol::onRequestTimeoutCheck() { QDateTime now = QDateTime::currentDateTime(); QStringList timedOut; - for (auto it = m_pendingRequests.begin(); it != m_pendingRequests.end(); ++it) { - if (it->timestamp.msecsTo(now) > m_requestTimeoutMs) { - timedOut.append(it.key()); + for (const auto& [path, req] : m_pendingRequests.asKeyValueRange()) { + if (req.timestamp.msecsTo(now) > m_requestTimeoutMs) { + timedOut.append(path); } } @@ -363,8 +370,7 @@ void X32Protocol::processResponse(const QString& path, const QVariant& value) { } } -void X32Protocol::handleXinfoResponse(const QVariant& value) { - Q_UNUSED(value); +void X32Protocol::handleXinfoResponse([[maybe_unused]] const QVariant& value) { m_connectionTimer.stop(); m_waitingForXinfo = false; diff --git a/src/protocol/behringer/X32Protocol.h b/src/protocol/behringer/X32Protocol.h index 982b1ac..462e783 100644 --- a/src/protocol/behringer/X32Protocol.h +++ b/src/protocol/behringer/X32Protocol.h @@ -26,21 +26,21 @@ class X32Protocol : public MixerProtocol { ~X32Protocol() override; // protocol identification - QString protocolName() const override { return m_capabilities.displayName; } - QString protocolDescription() const override { + [[nodiscard]] QString protocolName() const override { return m_capabilities.displayName; } + [[nodiscard]] QString protocolDescription() const override { return m_capabilities.displayName + " OSC Protocol"; } // connection management - bool connect(const QString& host, int port) override; + [[nodiscard]] bool connect(const QString& host, int port) override; void disconnect() override; - bool isConnected() const override { return m_connectionState == ConnectionState::Connected; } - QString connectionStatus() const override { return m_statusMessage; } - ConnectionState connectionState() const override { return m_connectionState; } + [[nodiscard]] bool isConnected() const override { return m_connectionState == ConnectionState::Connected; } + [[nodiscard]] QString connectionStatus() const override { return m_statusMessage; } + [[nodiscard]] ConnectionState connectionState() const override { return m_connectionState; } // parameter operations void sendParameter(const QString& path, const QVariant& value) override; - QVariant getParameter(const QString& path) override; + [[nodiscard]] QVariant getParameter(const QString& path) override; void requestParameter(const QString& path) override; void requestParameterAsync(const QString& path, ParameterCallback callback) override; @@ -54,13 +54,13 @@ class X32Protocol : public MixerProtocol { void refresh() override; // latency monitoring - int latencyMs() const override { return m_latencyMs; } + [[nodiscard]] int latencyMs() const override { return m_latencyMs; } // capabilities - const MixerCapabilities& capabilities() const override { return m_capabilities; } + [[nodiscard]] const MixerCapabilities& capabilities() const override { return m_capabilities; } // X32-specific: list of parameters to recall - QStringList snapshotParameters() const { return m_snapshotParams; } + [[nodiscard]] QStringList snapshotParameters() const { return m_snapshotParams; } void setSnapshotParameters(const QStringList& params) { m_snapshotParams = params; } // configuration diff --git a/src/protocol/discovery/probes/BehringerWingProbeStrategy.cpp b/src/protocol/discovery/probes/BehringerWingProbeStrategy.cpp index 13d600b..cd83b61 100644 --- a/src/protocol/discovery/probes/BehringerWingProbeStrategy.cpp +++ b/src/protocol/discovery/probes/BehringerWingProbeStrategy.cpp @@ -2,12 +2,10 @@ namespace OpenMix { -DiscoveredConsole BehringerWingProbeStrategy::parseResponse(const QString& path, +DiscoveredConsole BehringerWingProbeStrategy::parseResponse([[maybe_unused]] const QString& path, const QVariant& value, const QHostAddress& sender, - int senderPort) { - Q_UNUSED(path); - Q_UNUSED(senderPort); + [[maybe_unused]] int senderPort) { DiscoveredConsole console; console.address = sender; diff --git a/src/protocol/discovery/probes/BehringerX32ProbeStrategy.cpp b/src/protocol/discovery/probes/BehringerX32ProbeStrategy.cpp index 0319010..ba91365 100644 --- a/src/protocol/discovery/probes/BehringerX32ProbeStrategy.cpp +++ b/src/protocol/discovery/probes/BehringerX32ProbeStrategy.cpp @@ -2,12 +2,10 @@ namespace OpenMix { -DiscoveredConsole BehringerX32ProbeStrategy::parseResponse(const QString& path, +DiscoveredConsole BehringerX32ProbeStrategy::parseResponse([[maybe_unused]] const QString& path, const QVariant& value, const QHostAddress& sender, - int senderPort) { - Q_UNUSED(path); - Q_UNUSED(senderPort); + [[maybe_unused]] int senderPort) { DiscoveredConsole console; console.address = sender; diff --git a/src/protocol/discovery/probes/YamahaOscProbeStrategy.cpp b/src/protocol/discovery/probes/YamahaOscProbeStrategy.cpp index e7c6495..0841a35 100644 --- a/src/protocol/discovery/probes/YamahaOscProbeStrategy.cpp +++ b/src/protocol/discovery/probes/YamahaOscProbeStrategy.cpp @@ -2,11 +2,10 @@ namespace OpenMix { -DiscoveredConsole YamahaOscProbeStrategy::parseResponse(const QString& path, const QVariant& value, +DiscoveredConsole YamahaOscProbeStrategy::parseResponse([[maybe_unused]] const QString& path, + const QVariant& value, const QHostAddress& sender, - int senderPort) { - Q_UNUSED(path); - Q_UNUSED(senderPort); + [[maybe_unused]] int senderPort) { DiscoveredConsole console; console.address = sender; diff --git a/src/protocol/transport/OscTransport.h b/src/protocol/transport/OscTransport.h index 1ce8923..d4be171 100644 --- a/src/protocol/transport/OscTransport.h +++ b/src/protocol/transport/OscTransport.h @@ -15,9 +15,9 @@ class OscTransport : public QObject { explicit OscTransport(QObject* parent = nullptr); ~OscTransport() override; - bool connect(const QString& host, int port); + [[nodiscard]] bool connect(const QString& host, int port); void disconnect(); - bool isConnected() const { return m_connected; } + [[nodiscard]] bool isConnected() const noexcept { return m_connected; } void send(const QString& path); void send(const QString& path, float value); @@ -25,8 +25,8 @@ class OscTransport : public QObject { void send(const QString& path, const QString& value); void send(const QString& path, const QVariant& value); - QString host() const { return m_host; } - int port() const { return m_port; } + [[nodiscard]] QString host() const { return m_host; } + [[nodiscard]] int port() const noexcept { return m_port; } signals: void connected(); diff --git a/src/protocol/transport/TcpTransport.h b/src/protocol/transport/TcpTransport.h index fa5fe75..9b2db5b 100644 --- a/src/protocol/transport/TcpTransport.h +++ b/src/protocol/transport/TcpTransport.h @@ -13,14 +13,14 @@ class TcpTransport : public QObject { explicit TcpTransport(QObject* parent = nullptr); ~TcpTransport() override; - bool connect(const QString& host, int port); + [[nodiscard]] bool connect(const QString& host, int port); void disconnect(); - bool isConnected() const; + [[nodiscard]] bool isConnected() const; bool send(const QByteArray& data); - QString host() const { return m_host; } - int port() const { return m_port; } + [[nodiscard]] QString host() const { return m_host; } + [[nodiscard]] int port() const noexcept { return m_port; } void setConnectionTimeout(int ms) { m_connectionTimeoutMs = ms; } void setReconnectEnabled(bool enabled) { m_reconnectEnabled = enabled; } diff --git a/src/protocol/yamaha/YamahaCLProtocol.cpp b/src/protocol/yamaha/YamahaCLProtocol.cpp index 86196f7..88eb5ad 100644 --- a/src/protocol/yamaha/YamahaCLProtocol.cpp +++ b/src/protocol/yamaha/YamahaCLProtocol.cpp @@ -10,35 +10,8 @@ YamahaCLProtocol::YamahaCLProtocol(const MixerCapabilities& caps, QObject* paren void YamahaCLProtocol::initializeSnapshotParams() { m_snapshotParams.clear(); - // CL series input channels (up to 72) - for (int ch = 1; ch <= m_capabilities.inputChannels && ch <= 72; ++ch) { - QString chPrefix = QString("/ch/%1").arg(ch, 2, 10, QChar('0')); - m_snapshotParams.append(chPrefix + "/mix/fader"); - m_snapshotParams.append(chPrefix + "/mix/on"); - - // EQ parameters - if (m_capabilities.supportsChannelEQ) { - m_snapshotParams.append(chPrefix + "/eq/on"); - for (int band = 1; band <= m_capabilities.eqBandsPerChannel; ++band) { - QString bandPrefix = QString("%1/eq/%2").arg(chPrefix).arg(band); - m_snapshotParams.append(bandPrefix + "/type"); - m_snapshotParams.append(bandPrefix + "/f"); - m_snapshotParams.append(bandPrefix + "/g"); - m_snapshotParams.append(bandPrefix + "/q"); - } - } - - // effect send parameters - if (m_capabilities.supportsEffectSends) { - int sends = qMin(m_capabilities.effectSendBuses, 16); - for (int send = 1; send <= sends; ++send) { - QString sendPrefix = - QString("%1/mix/%2").arg(chPrefix).arg(send, 2, 10, QChar('0')); - m_snapshotParams.append(sendPrefix + "/level"); - m_snapshotParams.append(sendPrefix + "/on"); - } - } - } + // CL series input channels (up to 72), 16 effect-send buses max + appendEqSnapshotParams(m_snapshotParams, "/ch/", 72, 2, 16); // CL series has 16 DCAs for (int i = 1; i <= m_capabilities.dcaCount && i <= 16; ++i) { diff --git a/src/protocol/yamaha/YamahaCLProtocol.h b/src/protocol/yamaha/YamahaCLProtocol.h index bb8254e..ca40eb1 100644 --- a/src/protocol/yamaha/YamahaCLProtocol.h +++ b/src/protocol/yamaha/YamahaCLProtocol.h @@ -12,7 +12,7 @@ class YamahaCLProtocol : public YamahaProtocol { public: explicit YamahaCLProtocol(const MixerCapabilities& caps, QObject* parent = nullptr); - QString protocolDescription() const override { return "Yamaha CL Protocol"; } + [[nodiscard]] QString protocolDescription() const override { return "Yamaha CL Protocol"; } protected: void initializeSnapshotParams() override; diff --git a/src/protocol/yamaha/YamahaDM7Protocol.cpp b/src/protocol/yamaha/YamahaDM7Protocol.cpp index 9bcd875..f86a1e2 100644 --- a/src/protocol/yamaha/YamahaDM7Protocol.cpp +++ b/src/protocol/yamaha/YamahaDM7Protocol.cpp @@ -10,35 +10,8 @@ YamahaDM7Protocol::YamahaDM7Protocol(const MixerCapabilities& caps, QObject* par void YamahaDM7Protocol::initializeSnapshotParams() { m_snapshotParams.clear(); - // DM7 input channels (up to 120) - for (int ch = 1; ch <= m_capabilities.inputChannels && ch <= 120; ++ch) { - QString chPrefix = QString("/ch/%1").arg(ch, 3, 10, QChar('0')); - m_snapshotParams.append(chPrefix + "/mix/fader"); - m_snapshotParams.append(chPrefix + "/mix/on"); - - // EQ parameters - DM7 has 6-band EQ - if (m_capabilities.supportsChannelEQ) { - m_snapshotParams.append(chPrefix + "/eq/on"); - for (int band = 1; band <= m_capabilities.eqBandsPerChannel; ++band) { - QString bandPrefix = QString("%1/eq/%2").arg(chPrefix).arg(band); - m_snapshotParams.append(bandPrefix + "/type"); - m_snapshotParams.append(bandPrefix + "/f"); - m_snapshotParams.append(bandPrefix + "/g"); - m_snapshotParams.append(bandPrefix + "/q"); - } - } - - // effect send parameters - if (m_capabilities.supportsEffectSends) { - int sends = qMin(m_capabilities.effectSendBuses, 24); - for (int send = 1; send <= sends; ++send) { - QString sendPrefix = - QString("%1/mix/%2").arg(chPrefix).arg(send, 2, 10, QChar('0')); - m_snapshotParams.append(sendPrefix + "/level"); - m_snapshotParams.append(sendPrefix + "/on"); - } - } - } + // DM7 input channels (up to 120), 3-digit channel field, 24 effect-send buses max + appendEqSnapshotParams(m_snapshotParams, "/ch/", 120, 3, 24); // DM7 has 24 DCAs for (int i = 1; i <= m_capabilities.dcaCount && i <= 24; ++i) { diff --git a/src/protocol/yamaha/YamahaDM7Protocol.h b/src/protocol/yamaha/YamahaDM7Protocol.h index ec0d91a..2f54310 100644 --- a/src/protocol/yamaha/YamahaDM7Protocol.h +++ b/src/protocol/yamaha/YamahaDM7Protocol.h @@ -12,7 +12,7 @@ class YamahaDM7Protocol : public YamahaProtocol { public: explicit YamahaDM7Protocol(const MixerCapabilities& caps, QObject* parent = nullptr); - QString protocolDescription() const override { return "Yamaha DM7 Protocol"; } + [[nodiscard]] QString protocolDescription() const override { return "Yamaha DM7 Protocol"; } protected: void initializeSnapshotParams() override; diff --git a/src/protocol/yamaha/YamahaProtocol.cpp b/src/protocol/yamaha/YamahaProtocol.cpp index 25b5e01..e03e10f 100644 --- a/src/protocol/yamaha/YamahaProtocol.cpp +++ b/src/protocol/yamaha/YamahaProtocol.cpp @@ -1,6 +1,7 @@ #include "YamahaProtocol.h" #include "../../core/Cue.h" #include +#include #include #include @@ -39,41 +40,50 @@ void YamahaProtocol::initializeSnapshotParams() { rebuildSnapshotParams(); } void YamahaProtocol::rebuildSnapshotParams() { m_snapshotParams.clear(); - // input channel fader/on parameters - for (int i = 1; i <= m_capabilities.inputChannels; ++i) { - QString chPrefix = QString("/ch/%1").arg(i, 2, 10, QChar('0')); - m_snapshotParams.append(chPrefix + "/mix/fader"); - m_snapshotParams.append(chPrefix + "/mix/on"); + // input channel fader/on, EQ, and effect-send parameters + appendEqSnapshotParams(m_snapshotParams, "/ch/", m_capabilities.inputChannels); + + // DCA parameters, Yamaha uses /dca/X/fader and /dca/X/on (NOT /mute) + for (int i = 1; i <= m_capabilities.dcaCount; ++i) { + m_snapshotParams.append(QString("/dca/%1/fader").arg(i)); + m_snapshotParams.append(QString("/dca/%1/on").arg(i)); + } +} + +void YamahaProtocol::appendEqSnapshotParams(QStringList& params, + const QString& channelPrefix, + int channelCount, + int channelFieldWidth, + int maxEffectSends) const { + for (int ch = 1; ch <= m_capabilities.inputChannels && ch <= channelCount; ++ch) { + QString chPrefix = + QString("%1%2").arg(channelPrefix).arg(ch, channelFieldWidth, 10, QChar('0')); + params.append(chPrefix + "/mix/fader"); + params.append(chPrefix + "/mix/on"); // EQ parameters if (m_capabilities.supportsChannelEQ) { - m_snapshotParams.append(chPrefix + "/eq/on"); + params.append(chPrefix + "/eq/on"); for (int band = 1; band <= m_capabilities.eqBandsPerChannel; ++band) { QString bandPrefix = QString("%1/eq/%2").arg(chPrefix).arg(band); - m_snapshotParams.append(bandPrefix + "/type"); - m_snapshotParams.append(bandPrefix + "/f"); - m_snapshotParams.append(bandPrefix + "/g"); - m_snapshotParams.append(bandPrefix + "/q"); + params.append(bandPrefix + "/type"); + params.append(bandPrefix + "/f"); + params.append(bandPrefix + "/g"); + params.append(bandPrefix + "/q"); } } // effect send parameters if (m_capabilities.supportsEffectSends) { - int sends = qMin(m_capabilities.effectSendBuses, 16); + int sends = std::min(m_capabilities.effectSendBuses, maxEffectSends); for (int send = 1; send <= sends; ++send) { QString sendPrefix = QString("%1/mix/%2").arg(chPrefix).arg(send, 2, 10, QChar('0')); - m_snapshotParams.append(sendPrefix + "/level"); - m_snapshotParams.append(sendPrefix + "/on"); + params.append(sendPrefix + "/level"); + params.append(sendPrefix + "/on"); } } } - - // DCA parameters, Yamaha uses /dca/X/fader and /dca/X/on (NOT /mute) - for (int i = 1; i <= m_capabilities.dcaCount; ++i) { - m_snapshotParams.append(QString("/dca/%1/fader").arg(i)); - m_snapshotParams.append(QString("/dca/%1/on").arg(i)); - } } bool YamahaProtocol::connect(const QString& host, int port) { @@ -208,8 +218,8 @@ void YamahaProtocol::recallSnapshot(const Cue& cue) { } QJsonObject params = cue.parameters(); - for (auto it = params.begin(); it != params.end(); ++it) { - sendParameter(it.key(), it.value().toVariant()); + for (const auto& [path, value] : params.asKeyValueRange()) { + sendParameter(path.toString(), value.toVariant()); } } @@ -279,9 +289,9 @@ void YamahaProtocol::onRequestTimeoutCheck() { QDateTime now = QDateTime::currentDateTime(); QStringList timedOut; - for (auto it = m_pendingRequests.begin(); it != m_pendingRequests.end(); ++it) { - if (it->timestamp.msecsTo(now) > m_requestTimeoutMs) { - timedOut.append(it.key()); + for (const auto& [path, req] : m_pendingRequests.asKeyValueRange()) { + if (req.timestamp.msecsTo(now) > m_requestTimeoutMs) { + timedOut.append(path); } } @@ -485,8 +495,7 @@ void YamahaProtocol::processResponse(const QString& path, const QVariant& value) } } -void YamahaProtocol::handleModelResponse(const QVariant& value) { - Q_UNUSED(value); +void YamahaProtocol::handleModelResponse([[maybe_unused]] const QVariant& value) { m_connectionTimer.stop(); m_waitingForModel = false; diff --git a/src/protocol/yamaha/YamahaProtocol.h b/src/protocol/yamaha/YamahaProtocol.h index 560ebb8..811b83a 100644 --- a/src/protocol/yamaha/YamahaProtocol.h +++ b/src/protocol/yamaha/YamahaProtocol.h @@ -25,17 +25,17 @@ class YamahaProtocol : public MixerProtocol { explicit YamahaProtocol(const MixerCapabilities& caps, QObject* parent = nullptr); ~YamahaProtocol() override; - QString protocolName() const override { return "Yamaha OSC"; } - QString protocolDescription() const override { return "Yamaha OSC/UDP Protocol"; } + [[nodiscard]] QString protocolName() const override { return "Yamaha OSC"; } + [[nodiscard]] QString protocolDescription() const override { return "Yamaha OSC/UDP Protocol"; } - bool connect(const QString& host, int port) override; + [[nodiscard]] bool connect(const QString& host, int port) override; void disconnect() override; - bool isConnected() const override { return m_connectionState == ConnectionState::Connected; } - QString connectionStatus() const override { return m_statusMessage; } - ConnectionState connectionState() const override { return m_connectionState; } + [[nodiscard]] bool isConnected() const override { return m_connectionState == ConnectionState::Connected; } + [[nodiscard]] QString connectionStatus() const override { return m_statusMessage; } + [[nodiscard]] ConnectionState connectionState() const override { return m_connectionState; } void sendParameter(const QString& path, const QVariant& value) override; - QVariant getParameter(const QString& path) override; + [[nodiscard]] QVariant getParameter(const QString& path) override; void requestParameter(const QString& path) override; void requestParameterAsync(const QString& path, ParameterCallback callback) override; @@ -43,13 +43,22 @@ class YamahaProtocol : public MixerProtocol { void recallScene(int sceneNumber) override; void refresh() override; - int latencyMs() const override { return m_latencyMs; } - const MixerCapabilities& capabilities() const override { return m_capabilities; } + [[nodiscard]] int latencyMs() const override { return m_latencyMs; } + [[nodiscard]] const MixerCapabilities& capabilities() const override { return m_capabilities; } protected: virtual void initializeSnapshotParams(); void rebuildSnapshotParams(); + // appends per-channel fader/on, EQ, and effect-send snapshot parameters for + // `channelCount` channels. `channelPrefix` is the path stem (e.g. "/ch/"), + // `channelFieldWidth` controls zero-padding (2 for CL/QL/TF, 3 for DM7), and + // `maxEffectSends` is the model-specific send-bus ceiling applied on top of + // m_capabilities.effectSendBuses. + void appendEqSnapshotParams(QStringList& params, const QString& channelPrefix, + int channelCount, int channelFieldWidth = 2, + int maxEffectSends = 16) const; + MixerCapabilities m_capabilities; QStringList m_snapshotParams; @@ -71,10 +80,6 @@ class YamahaProtocol : public MixerProtocol { void sendOscMessage(const QString& path, const QString& value); void parseOscMessage(const QByteArray& data); QVariant parseOscArgument(const QByteArray& data, int& offset, char type); - QByteArray buildOscMessage(const QString& path); - QByteArray buildOscMessage(const QString& path, float value); - QByteArray buildOscMessage(const QString& path, int value); - QByteArray buildOscMessage(const QString& path, const QString& value); void processResponse(const QString& path, const QVariant& value); void handleModelResponse(const QVariant& value); diff --git a/src/protocol/yamaha/YamahaQLProtocol.cpp b/src/protocol/yamaha/YamahaQLProtocol.cpp index 39d18a5..e98876c 100644 --- a/src/protocol/yamaha/YamahaQLProtocol.cpp +++ b/src/protocol/yamaha/YamahaQLProtocol.cpp @@ -10,35 +10,8 @@ YamahaQLProtocol::YamahaQLProtocol(const MixerCapabilities& caps, QObject* paren void YamahaQLProtocol::initializeSnapshotParams() { m_snapshotParams.clear(); - // QL series input channels (up to 64) - for (int ch = 1; ch <= m_capabilities.inputChannels && ch <= 64; ++ch) { - QString chPrefix = QString("/ch/%1").arg(ch, 2, 10, QChar('0')); - m_snapshotParams.append(chPrefix + "/mix/fader"); - m_snapshotParams.append(chPrefix + "/mix/on"); - - // EQ parameters - if (m_capabilities.supportsChannelEQ) { - m_snapshotParams.append(chPrefix + "/eq/on"); - for (int band = 1; band <= m_capabilities.eqBandsPerChannel; ++band) { - QString bandPrefix = QString("%1/eq/%2").arg(chPrefix).arg(band); - m_snapshotParams.append(bandPrefix + "/type"); - m_snapshotParams.append(bandPrefix + "/f"); - m_snapshotParams.append(bandPrefix + "/g"); - m_snapshotParams.append(bandPrefix + "/q"); - } - } - - // effect send parameters - if (m_capabilities.supportsEffectSends) { - int sends = qMin(m_capabilities.effectSendBuses, 16); - for (int send = 1; send <= sends; ++send) { - QString sendPrefix = - QString("%1/mix/%2").arg(chPrefix).arg(send, 2, 10, QChar('0')); - m_snapshotParams.append(sendPrefix + "/level"); - m_snapshotParams.append(sendPrefix + "/on"); - } - } - } + // QL series input channels (up to 64), 16 effect-send buses max + appendEqSnapshotParams(m_snapshotParams, "/ch/", 64, 2, 16); // QL series has 8 DCAs for (int i = 1; i <= m_capabilities.dcaCount && i <= 8; ++i) { diff --git a/src/protocol/yamaha/YamahaQLProtocol.h b/src/protocol/yamaha/YamahaQLProtocol.h index d5f401f..d316c2d 100644 --- a/src/protocol/yamaha/YamahaQLProtocol.h +++ b/src/protocol/yamaha/YamahaQLProtocol.h @@ -12,7 +12,7 @@ class YamahaQLProtocol : public YamahaProtocol { public: explicit YamahaQLProtocol(const MixerCapabilities& caps, QObject* parent = nullptr); - QString protocolDescription() const override { return "Yamaha QL Protocol"; } + [[nodiscard]] QString protocolDescription() const override { return "Yamaha QL Protocol"; } protected: void initializeSnapshotParams() override; diff --git a/src/protocol/yamaha/YamahaTFProtocol.cpp b/src/protocol/yamaha/YamahaTFProtocol.cpp index 7e79f45..480c34a 100644 --- a/src/protocol/yamaha/YamahaTFProtocol.cpp +++ b/src/protocol/yamaha/YamahaTFProtocol.cpp @@ -10,35 +10,8 @@ YamahaTFProtocol::YamahaTFProtocol(const MixerCapabilities& caps, QObject* paren void YamahaTFProtocol::initializeSnapshotParams() { m_snapshotParams.clear(); - // TF series input channels - for (int ch = 1; ch <= m_capabilities.inputChannels && ch <= 40; ++ch) { - QString chPrefix = QString("/ch/%1").arg(ch, 2, 10, QChar('0')); - m_snapshotParams.append(chPrefix + "/mix/fader"); - m_snapshotParams.append(chPrefix + "/mix/on"); - - // EQ parameters - if (m_capabilities.supportsChannelEQ) { - m_snapshotParams.append(chPrefix + "/eq/on"); - for (int band = 1; band <= m_capabilities.eqBandsPerChannel; ++band) { - QString bandPrefix = QString("%1/eq/%2").arg(chPrefix).arg(band); - m_snapshotParams.append(bandPrefix + "/type"); - m_snapshotParams.append(bandPrefix + "/f"); - m_snapshotParams.append(bandPrefix + "/g"); - m_snapshotParams.append(bandPrefix + "/q"); - } - } - - // effect send parameters - if (m_capabilities.supportsEffectSends) { - int sends = qMin(m_capabilities.effectSendBuses, 8); - for (int send = 1; send <= sends; ++send) { - QString sendPrefix = - QString("%1/mix/%2").arg(chPrefix).arg(send, 2, 10, QChar('0')); - m_snapshotParams.append(sendPrefix + "/level"); - m_snapshotParams.append(sendPrefix + "/on"); - } - } - } + // TF series input channels (up to 40), 8 effect-send buses max + appendEqSnapshotParams(m_snapshotParams, "/ch/", 40, 2, 8); // TF series has 8 DCAs for (int i = 1; i <= m_capabilities.dcaCount && i <= 8; ++i) { diff --git a/src/protocol/yamaha/YamahaTFProtocol.h b/src/protocol/yamaha/YamahaTFProtocol.h index df91f8d..6a1981a 100644 --- a/src/protocol/yamaha/YamahaTFProtocol.h +++ b/src/protocol/yamaha/YamahaTFProtocol.h @@ -12,7 +12,7 @@ class YamahaTFProtocol : public YamahaProtocol { public: explicit YamahaTFProtocol(const MixerCapabilities& caps, QObject* parent = nullptr); - QString protocolDescription() const override { return "Yamaha TF Protocol"; } + [[nodiscard]] QString protocolDescription() const override { return "Yamaha TF Protocol"; } protected: void initializeSnapshotParams() override; diff --git a/src/ui/BubbleButton.cpp b/src/ui/BubbleButton.cpp index cff91da..d719802 100644 --- a/src/ui/BubbleButton.cpp +++ b/src/ui/BubbleButton.cpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace OpenMix { @@ -77,7 +78,7 @@ void BubbleButton::paintEvent(QPaintEvent* event) { QFontMetrics fm(badgeFont); int textWidth = fm.horizontalAdvance(m_badgeText); - int badgeWidth = qMax(14, textWidth + 6); + int badgeWidth = std::max(14, textWidth + 6); int badgeHeight = 14; int x = width() - badgeWidth - 2; diff --git a/src/ui/ConnectionStateWidget.cpp b/src/ui/ConnectionStateWidget.cpp index ad89ffe..b5363c7 100644 --- a/src/ui/ConnectionStateWidget.cpp +++ b/src/ui/ConnectionStateWidget.cpp @@ -38,8 +38,7 @@ QSize ConnectionStateWidget::sizeHint() const { return QSize(200, 28); } QSize ConnectionStateWidget::minimumSizeHint() const { return QSize(100, 24); } -void ConnectionStateWidget::paintEvent(QPaintEvent* event) { - Q_UNUSED(event); +void ConnectionStateWidget::paintEvent([[maybe_unused]] QPaintEvent* event) { QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); diff --git a/src/ui/ConsoleDiscoveryWidget.cpp b/src/ui/ConsoleDiscoveryWidget.cpp index f345bba..2f82583 100644 --- a/src/ui/ConsoleDiscoveryWidget.cpp +++ b/src/ui/ConsoleDiscoveryWidget.cpp @@ -142,8 +142,7 @@ void ConsoleDiscoveryWidget::onItemSelectionChanged() { updateUiState(); } -void ConsoleDiscoveryWidget::onItemDoubleClicked(QListWidgetItem* item) { - Q_UNUSED(item); +void ConsoleDiscoveryWidget::onItemDoubleClicked([[maybe_unused]] QListWidgetItem* item) { DiscoveredConsole console = selectedConsole(); if (console.isValid()) { emit consoleDoubleClicked(console); diff --git a/src/ui/CueConfidenceIndicator.cpp b/src/ui/CueConfidenceIndicator.cpp index 0f72915..65ffd7f 100644 --- a/src/ui/CueConfidenceIndicator.cpp +++ b/src/ui/CueConfidenceIndicator.cpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace OpenMix { @@ -89,8 +90,7 @@ QString CueConfidenceIndicator::iconForLevel(ConfidenceLevel level) { return "?"; } -void CueConfidenceIndicator::paintEvent(QPaintEvent* event) { - Q_UNUSED(event); +void CueConfidenceIndicator::paintEvent([[maybe_unused]] QPaintEvent* event) { QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); @@ -98,7 +98,7 @@ void CueConfidenceIndicator::paintEvent(QPaintEvent* event) { QColor color = colorForLevel(m_level); // draw filled circle - int size = qMin(width(), height()); + int size = std::min(width(), height()); int margin = 2; QRect circleRect(margin, margin, size - margin * 2, size - margin * 2); diff --git a/src/ui/CueFilterProxyModel.cpp b/src/ui/CueFilterProxyModel.cpp index 2f8af6c..1d772c4 100644 --- a/src/ui/CueFilterProxyModel.cpp +++ b/src/ui/CueFilterProxyModel.cpp @@ -10,52 +10,61 @@ CueFilterProxyModel::CueFilterProxyModel(QObject* parent) : QSortFilterProxyMode } void CueFilterProxyModel::setTypeFilter(CueType type) { + beginFilterChange(); m_filterByType = true; m_typeFilter = type; - invalidateFilter(); + endFilterChange(); } void CueFilterProxyModel::clearTypeFilter() { + beginFilterChange(); m_filterByType = false; - invalidateFilter(); + endFilterChange(); } void CueFilterProxyModel::setGroupFilter(const QString& group) { + beginFilterChange(); m_groupFilter = group; - invalidateFilter(); + endFilterChange(); } void CueFilterProxyModel::clearGroupFilter() { + beginFilterChange(); m_groupFilter.clear(); - invalidateFilter(); + endFilterChange(); } void CueFilterProxyModel::setTagFilter(const QString& tag) { + beginFilterChange(); m_tagFilter = tag; - invalidateFilter(); + endFilterChange(); } void CueFilterProxyModel::clearTagFilter() { + beginFilterChange(); m_tagFilter.clear(); - invalidateFilter(); + endFilterChange(); } void CueFilterProxyModel::setTextFilter(const QString& text) { + beginFilterChange(); m_textFilter = text; - invalidateFilter(); + endFilterChange(); } void CueFilterProxyModel::clearTextFilter() { + beginFilterChange(); m_textFilter.clear(); - invalidateFilter(); + endFilterChange(); } void CueFilterProxyModel::clearAllFilters() { + beginFilterChange(); m_filterByType = false; m_groupFilter.clear(); m_tagFilter.clear(); m_textFilter.clear(); - invalidateFilter(); + endFilterChange(); } QStringList CueFilterProxyModel::availableGroups() const { @@ -66,8 +75,8 @@ QStringList CueFilterProxyModel::availableGroups() const { return QStringList(); CueList* cueList = model->cueList(); - for (int i = 0; i < cueList->count(); ++i) { - const QString& group = cueList->at(i).group(); + for (const Cue& cue : *cueList) { + const QString& group = cue.group(); if (!group.isEmpty()) { groups.insert(group); } @@ -86,8 +95,8 @@ QStringList CueFilterProxyModel::availableTags() const { return QStringList(); CueList* cueList = model->cueList(); - for (int i = 0; i < cueList->count(); ++i) { - const QStringList& cueTags = cueList->at(i).tags(); + for (const Cue& cue : *cueList) { + const QStringList& cueTags = cue.tags(); for (const QString& tag : cueTags) { if (!tag.isEmpty()) { tags.insert(tag); @@ -100,8 +109,7 @@ QStringList CueFilterProxyModel::availableTags() const { return result; } -bool CueFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const { - Q_UNUSED(sourceParent); +bool CueFilterProxyModel::filterAcceptsRow(int sourceRow, [[maybe_unused]] const QModelIndex& sourceParent) const { CueTableModel* model = qobject_cast(sourceModel()); if (!model) diff --git a/src/ui/CueItemDelegates.cpp b/src/ui/CueItemDelegates.cpp index 616945a..6113432 100644 --- a/src/ui/CueItemDelegates.cpp +++ b/src/ui/CueItemDelegates.cpp @@ -31,9 +31,8 @@ void CueNumberDelegate::paint(QPainter* painter, const QStyleOptionViewItem& opt QStyledItemDelegate::paint(painter, opt, index); } -QWidget* CueNumberDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, +QWidget* CueNumberDelegate::createEditor(QWidget* parent, [[maybe_unused]] const QStyleOptionViewItem& option, const QModelIndex& index) const { - Q_UNUSED(option); m_currentEditIndex = index; @@ -81,8 +80,8 @@ void CueNumberDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, // check for conflicts with other cues if (m_cueList) { - int existingIndex = m_cueList->indexOfNumber(newNumber); - if (existingIndex >= 0 && existingIndex != index.row()) { + const auto existingIndex = m_cueList->indexOfNumber(newNumber); + if (existingIndex && *existingIndex != index.row()) { QMessageBox::warning( qobject_cast(editor->parent()), QObject::tr("Cue Number Conflict"), QObject::tr("Cue %1 already exists. Please choose a different number.") @@ -95,8 +94,7 @@ void CueNumberDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, } void CueNumberDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, - const QModelIndex& index) const { - Q_UNUSED(index); + [[maybe_unused]] const QModelIndex& index) const { editor->setGeometry(option.rect); } @@ -104,8 +102,7 @@ bool CueNumberDelegate::eventFilter(QObject* object, QEvent* event) { if (event->type() == QEvent::KeyPress) { QKeyEvent* keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Tab || keyEvent->key() == Qt::Key_Backtab) { - QWidget* editor = qobject_cast(object); - if (editor && m_currentEditIndex.isValid()) { + if (auto* editor = qobject_cast(object); editor && m_currentEditIndex.isValid()) { bool forward = (keyEvent->key() == Qt::Key_Tab); emit tabNavigationRequested(m_currentEditIndex, forward); return true; @@ -132,9 +129,8 @@ void CueTypeDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio QStyledItemDelegate::paint(painter, opt, index); } -QWidget* CueTypeDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, +QWidget* CueTypeDelegate::createEditor(QWidget* parent, [[maybe_unused]] const QStyleOptionViewItem& option, const QModelIndex& index) const { - Q_UNUSED(option); m_currentEditIndex = index; @@ -177,15 +173,12 @@ void CueTypeDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, } void CueTypeDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, - const QModelIndex& index) const { - Q_UNUSED(index); + [[maybe_unused]] const QModelIndex& index) const { editor->setGeometry(option.rect); } bool CueTypeDelegate::eventFilter(QObject* object, QEvent* event) { - QComboBox* comboBox = qobject_cast(object); - - if (comboBox && event->type() == QEvent::KeyPress) { + if (auto* comboBox = qobject_cast(object); comboBox && event->type() == QEvent::KeyPress) { QKeyEvent* keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) { @@ -225,9 +218,8 @@ void CueTextDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio QStyledItemDelegate::paint(painter, opt, index); } -QWidget* CueTextDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, +QWidget* CueTextDelegate::createEditor(QWidget* parent, [[maybe_unused]] const QStyleOptionViewItem& option, const QModelIndex& index) const { - Q_UNUSED(option); m_currentEditIndex = index; @@ -253,8 +245,7 @@ void CueTextDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, } void CueTextDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, - const QModelIndex& index) const { - Q_UNUSED(index); + [[maybe_unused]] const QModelIndex& index) const { editor->setGeometry(option.rect); } @@ -262,8 +253,7 @@ bool CueTextDelegate::eventFilter(QObject* object, QEvent* event) { if (event->type() == QEvent::KeyPress) { QKeyEvent* keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Tab || keyEvent->key() == Qt::Key_Backtab) { - QWidget* editor = qobject_cast(object); - if (editor && m_currentEditIndex.isValid()) { + if (auto* editor = qobject_cast(object); editor && m_currentEditIndex.isValid()) { bool forward = (keyEvent->key() == Qt::Key_Tab); emit tabNavigationRequested(m_currentEditIndex, forward); return true; diff --git a/src/ui/CueListView.cpp b/src/ui/CueListView.cpp index b60f6d0..3ea9aa7 100644 --- a/src/ui/CueListView.cpp +++ b/src/ui/CueListView.cpp @@ -17,6 +17,7 @@ #include #include #include +#include namespace OpenMix { @@ -398,22 +399,17 @@ QModelIndex CueListView::nextEditableIndex(const QModelIndex& current, bool forw int col = current.column(); // order of editable columns - static const int editableColumns[] = {CueTableModel::ColNumber, CueTableModel::ColName, - CueTableModel::ColType, CueTableModel::ColGroup, - CueTableModel::ColTags, CueTableModel::ColNotes}; + static constexpr int editableColumns[] = {CueTableModel::ColNumber, CueTableModel::ColName, + CueTableModel::ColType, CueTableModel::ColGroup, + CueTableModel::ColTags, CueTableModel::ColNotes}; - static const int editableCount = sizeof(editableColumns) / sizeof(editableColumns[0]); + static constexpr int editableCount = sizeof(editableColumns) / sizeof(editableColumns[0]); // find column's position in list - int colPos = -1; - for (int i = 0; i < editableCount; ++i) { - if (editableColumns[i] == col) { - colPos = i; - break; - } - } - if (colPos < 0) - colPos = 0; + const auto* found = std::find(std::begin(editableColumns), std::end(editableColumns), col); + int colPos = (found != std::end(editableColumns)) + ? static_cast(std::distance(std::begin(editableColumns), found)) + : 0; // move to next position if (forward) { diff --git a/src/ui/CueTableModel.cpp b/src/ui/CueTableModel.cpp index 1c59bef..754a2d6 100644 --- a/src/ui/CueTableModel.cpp +++ b/src/ui/CueTableModel.cpp @@ -237,9 +237,7 @@ QMimeData* CueTableModel::mimeData(const QModelIndexList& indexes) const { } bool CueTableModel::canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, - int column, const QModelIndex& parent) const { - Q_UNUSED(column); - Q_UNUSED(parent); + [[maybe_unused]] int column, [[maybe_unused]] const QModelIndex& parent) const { if (!data->hasFormat(s_mimeType)) return false; @@ -251,10 +249,8 @@ bool CueTableModel::canDropMimeData(const QMimeData* data, Qt::DropAction action return true; } -bool CueTableModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, - const QModelIndex& parent) { - Q_UNUSED(column); - Q_UNUSED(parent); +bool CueTableModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, + [[maybe_unused]] int column, [[maybe_unused]] const QModelIndex& parent) { if (!canDropMimeData(data, action, row, column, parent)) return false; diff --git a/src/ui/DCAMappingPanel.cpp b/src/ui/DCAMappingPanel.cpp index 332915a..6e9bed3 100644 --- a/src/ui/DCAMappingPanel.cpp +++ b/src/ui/DCAMappingPanel.cpp @@ -1,5 +1,6 @@ #include "DCAMappingPanel.h" #include "app/Application.h" +#include "MainWindow.h" #include "core/Cue.h" #include "core/DCAMapping.h" #include "core/ShortcutManager.h" @@ -10,9 +11,14 @@ #include "theme/Theme.h" #include +#include +#include +#include +#include #include #include #include +#include #include #include #include @@ -27,6 +33,37 @@ namespace OpenMix { +class NoScrollComboBox : public QComboBox { + public: + using QComboBox::QComboBox; + + protected: + void wheelEvent(QWheelEvent* event) override { event->ignore(); } + + void showPopup() override { + m_popupOpen = true; + QComboBox::showPopup(); + } + + void hidePopup() override { + QComboBox::hidePopup(); + QTimer::singleShot(100, this, [this]() { m_popupOpen = false; }); + } + + void keyPressEvent(QKeyEvent* event) override { + int key = event->key(); + if ((key == Qt::Key_Return || key == Qt::Key_Enter) && !m_popupOpen) { + showPopup(); + event->accept(); + } else { + QComboBox::keyPressEvent(event); + } + } + + private: + bool m_popupOpen = false; +}; + DCAMappingPanel::DCAMappingPanel(Application* app, QWidget* parent) : QWidget(parent), m_app(app), m_mapping(nullptr) { setupUi(); @@ -328,18 +365,16 @@ void DCAMappingPanel::populateFromMapping() { // build reverse lookup from channel -> DCA QMap channelToDCA; - for (auto it = channelMap.constBegin(); it != channelMap.constEnd(); ++it) { - int dca = it.key(); - for (int ch : it.value()) { + for (const auto& [dca, channels] : channelMap.asKeyValueRange()) { + for (int ch : channels) { channelToDCA[ch] = dca; } } // build reverse lookup from bus -> DCA QMap busToDCA; - for (auto it = busMap.constBegin(); it != busMap.constEnd(); ++it) { - int dca = it.key(); - for (int bus : it.value()) { + for (const auto& [dca, buses] : busMap.asKeyValueRange()) { + for (int bus : buses) { busToDCA[bus] = dca; } } @@ -387,18 +422,16 @@ void DCAMappingPanel::updateComboItemStates() { // build reverse lookup from channel -> DCA QMap channelToDCA; - for (auto it = channelMap.constBegin(); it != channelMap.constEnd(); ++it) { - int dca = it.key(); - for (int ch : it.value()) { + for (const auto& [dca, channels] : channelMap.asKeyValueRange()) { + for (int ch : channels) { channelToDCA[ch] = dca; } } // build reverse lookup from bus -> DCA QMap busToDCA; - for (auto it = busMap.constBegin(); it != busMap.constEnd(); ++it) { - int dca = it.key(); - for (int bus : it.value()) { + for (const auto& [dca, buses] : busMap.asKeyValueRange()) { + for (int bus : buses) { busToDCA[bus] = dca; } } @@ -508,8 +541,8 @@ void DCAMappingPanel::syncFromMixer() { // X32/M32/loopback use zero-padded paths, WING uses non-padded bool useZeroPadding = (caps.type != ConsoleType::Wing); - int channelCount = qMin(caps.inputChannels, 32); - int busCount = qMin(caps.mixBuses, 16); + int channelCount = std::min(caps.inputChannels, 32); + int busCount = std::min(caps.mixBuses, 16); state->pendingCount = channelCount + busCount; // capture references for callbacks @@ -523,10 +556,7 @@ void DCAMappingPanel::syncFromMixer() { return; // requests completed, populate mapping from bitmasks - for (auto it = state->channelDCAMasks.constBegin(); it != state->channelDCAMasks.constEnd(); - ++it) { - int channel = it.key(); - int mask = it.value(); + for (const auto& [channel, mask] : state->channelDCAMasks.asKeyValueRange()) { for (int d = 1; d <= dcaCount; ++d) { if (mask & (1 << (d - 1))) { mapping->assignChannelToDCA(channel, d); @@ -534,9 +564,7 @@ void DCAMappingPanel::syncFromMixer() { } } - for (auto it = state->busDCAMasks.constBegin(); it != state->busDCAMasks.constEnd(); ++it) { - int bus = it.key(); - int mask = it.value(); + for (const auto& [bus, mask] : state->busDCAMasks.asKeyValueRange()) { for (int d = 1; d <= dcaCount; ++d) { if (mask & (1 << (d - 1))) { mapping->assignBusToDCA(bus, d); @@ -653,8 +681,8 @@ void DCAMappingPanel::onChannelDCAChanged(int channel, int dca) { QMap> channelMap = m_currentCue->dcaChannelMapping(); // remove from any existing DCA - for (auto it = channelMap.begin(); it != channelMap.end(); ++it) { - it.value().removeAll(channel); + for (auto& channels : channelMap) { + channels.removeAll(channel); } // add to new DCA @@ -681,8 +709,8 @@ void DCAMappingPanel::onBusDCAChanged(int bus, int dca) { QMap> busMap = m_currentCue->dcaBusMapping(); // remove from any existing DCA - for (auto it = busMap.begin(); it != busMap.end(); ++it) { - it.value().removeAll(bus); + for (auto& buses : busMap) { + buses.removeAll(bus); } // add to new DCA @@ -812,16 +840,14 @@ QString DCAMappingPanel::busDisplayName(int bus) const { bool DCAMappingPanel::eventFilter(QObject* obj, QEvent* event) { if (event->type() == QEvent::MouseButtonDblClick) { - QLabel* label = qobject_cast(obj); - if (label && label->property("busIndex").isValid()) { + if (auto* label = qobject_cast(obj); label && label->property("busIndex").isValid()) { int bus = label->property("busIndex").toInt(); startBusNameEdit(bus); return true; } } if (event->type() == QEvent::KeyPress) { - QLineEdit* edit = qobject_cast(obj); - if (edit && edit->property("busIndex").isValid()) { + if (auto* edit = qobject_cast(obj); edit && edit->property("busIndex").isValid()) { QKeyEvent* ke = static_cast(event); if (ke->key() == Qt::Key_Escape) { int bus = edit->property("busIndex").toInt(); @@ -831,8 +857,7 @@ bool DCAMappingPanel::eventFilter(QObject* obj, QEvent* event) { } } if (event->type() == QEvent::FocusOut) { - QLineEdit* edit = qobject_cast(obj); - if (edit && edit->property("busIndex").isValid()) { + if (auto* edit = qobject_cast(obj); edit && edit->property("busIndex").isValid()) { int bus = edit->property("busIndex").toInt(); finishBusNameEdit(bus); return false; diff --git a/src/ui/DCAMappingPanel.h b/src/ui/DCAMappingPanel.h index 92dad72..18682bd 100644 --- a/src/ui/DCAMappingPanel.h +++ b/src/ui/DCAMappingPanel.h @@ -1,55 +1,20 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include #include +class QAction; class QCheckBox; +class QComboBox; +class QEvent; class QGridLayout; class QGroupBox; class QLabel; +class QLineEdit; class QPushButton; class QScrollArea; namespace OpenMix { -class NoScrollComboBox : public QComboBox { - Q_OBJECT - public: - using QComboBox::QComboBox; - - protected: - void wheelEvent(QWheelEvent* event) override { event->ignore(); } - - void showPopup() override { - m_popupOpen = true; - QComboBox::showPopup(); - } - - void hidePopup() override { - QComboBox::hidePopup(); - QTimer::singleShot(100, this, [this]() { m_popupOpen = false; }); - } - - void keyPressEvent(QKeyEvent* event) override { - int key = event->key(); - if ((key == Qt::Key_Return || key == Qt::Key_Enter) && !m_popupOpen) { - showPopup(); - event->accept(); - } else { - QComboBox::keyPressEvent(event); - } - } - - private: - bool m_popupOpen = false; -}; - class Application; class Cue; class DCAMapping; diff --git a/src/ui/DCAWidget.cpp b/src/ui/DCAWidget.cpp index a203c7c..8644b2a 100644 --- a/src/ui/DCAWidget.cpp +++ b/src/ui/DCAWidget.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include namespace OpenMix { @@ -70,7 +71,7 @@ void DCAWidget::setupUi() { } void DCAWidget::setLevel(float level) { - m_level = qBound(0.0f, level, 1.0f); + m_level = std::clamp(level, 0.0f, 1.0f); m_faderSlider->setValue(static_cast(m_level * 1000)); updateDisplay(); } @@ -192,7 +193,7 @@ void DCAWidget::setPreviewMode(bool preview) { } void DCAWidget::setOriginalLevel(float level) { - m_originalLevel = qBound(0.0f, level, 1.0f); + m_originalLevel = std::clamp(level, 0.0f, 1.0f); update(); } diff --git a/src/ui/KeyboardShortcutsDialog.cpp b/src/ui/KeyboardShortcutsDialog.cpp index 6d67a9f..e26ad29 100644 --- a/src/ui/KeyboardShortcutsDialog.cpp +++ b/src/ui/KeyboardShortcutsDialog.cpp @@ -401,8 +401,8 @@ QString KeyboardShortcutsDialog::getCategoryName(const QString& actionId) const void KeyboardShortcutsDialog::accept() { // apply all pending changes - for (auto it = m_pendingChanges.begin(); it != m_pendingChanges.end(); ++it) { - m_manager->setShortcut(it.key(), it.value()); + for (const auto& [id, shortcut] : m_pendingChanges.asKeyValueRange()) { + m_manager->setShortcut(id, shortcut); } // save to settings diff --git a/src/ui/LogItemDelegate.cpp b/src/ui/LogItemDelegate.cpp index 3255e2d..b65e374 100644 --- a/src/ui/LogItemDelegate.cpp +++ b/src/ui/LogItemDelegate.cpp @@ -94,8 +94,7 @@ void LogItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio } QSize LogItemDelegate::sizeHint(const QStyleOptionViewItem& option, - const QModelIndex& index) const { - Q_UNUSED(index) + [[maybe_unused]] const QModelIndex& index) const { return QSize(option.rect.width(), 36); } diff --git a/src/ui/LogViewerDialog.cpp b/src/ui/LogViewerDialog.cpp index b364c36..89f0aa9 100644 --- a/src/ui/LogViewerDialog.cpp +++ b/src/ui/LogViewerDialog.cpp @@ -75,7 +75,7 @@ void LogViewerDialog::setupUi() { // auto scroll m_autoScrollCheck = new QCheckBox(tr("Auto-scroll"), this); m_autoScrollCheck->setChecked(true); - connect(m_autoScrollCheck, &QCheckBox::stateChanged, this, + connect(m_autoScrollCheck, &QCheckBox::checkStateChanged, this, &LogViewerDialog::onAutoScrollChanged); filterLayout->addWidget(levelLabel); @@ -141,7 +141,7 @@ void LogViewerDialog::onSourceFilterChanged(int index) { void LogViewerDialog::onSearchTextChanged(const QString& text) { m_model->setSearchText(text); } -void LogViewerDialog::onAutoScrollChanged(int state) { Q_UNUSED(state) } +void LogViewerDialog::onAutoScrollChanged([[maybe_unused]] Qt::CheckState state) {} void LogViewerDialog::onClearClicked() { QMessageBox::StandardButton reply = QMessageBox::question( diff --git a/src/ui/LogViewerDialog.h b/src/ui/LogViewerDialog.h index 990511f..b058d76 100644 --- a/src/ui/LogViewerDialog.h +++ b/src/ui/LogViewerDialog.h @@ -24,7 +24,7 @@ class LogViewerDialog : public QDialog { void onLevelFilterChanged(int index); void onSourceFilterChanged(int index); void onSearchTextChanged(const QString& text); - void onAutoScrollChanged(int state); + void onAutoScrollChanged(Qt::CheckState state); void onClearClicked(); void onExportClicked(); void onEntryAdded(); diff --git a/src/ui/MacroPreviewWidget.cpp b/src/ui/MacroPreviewWidget.cpp index 7615596..772fddb 100644 --- a/src/ui/MacroPreviewWidget.cpp +++ b/src/ui/MacroPreviewWidget.cpp @@ -109,8 +109,7 @@ void MacroPreviewWidget::clear() { m_headerLabel->setText(tr("Macro Preview")); } -void MacroPreviewWidget::onItemClicked(QTreeWidgetItem* item, int column) { - Q_UNUSED(column); +void MacroPreviewWidget::onItemClicked(QTreeWidgetItem* item, [[maybe_unused]] int column) { if (item) { QString cueId = item->data(0, Qt::UserRole).toString(); if (!cueId.isEmpty()) { @@ -119,8 +118,7 @@ void MacroPreviewWidget::onItemClicked(QTreeWidgetItem* item, int column) { } } -void MacroPreviewWidget::onItemDoubleClicked(QTreeWidgetItem* item, int column) { - Q_UNUSED(column); +void MacroPreviewWidget::onItemDoubleClicked(QTreeWidgetItem* item, [[maybe_unused]] int column) { if (item) { QString cueId = item->data(0, Qt::UserRole).toString(); if (!cueId.isEmpty()) { diff --git a/src/ui/MainWindow.cpp b/src/ui/MainWindow.cpp index 83ae59f..33e3f00 100644 --- a/src/ui/MainWindow.cpp +++ b/src/ui/MainWindow.cpp @@ -381,8 +381,7 @@ void MainWindow::createBubbleBar() { m_cueEditor->addBottomWidget(m_bubbleBar); } -void MainWindow::onBubbleButtonClicked(const QString& id, bool checked) { - Q_UNUSED(checked) +void MainWindow::onBubbleButtonClicked(const QString& id, [[maybe_unused]] bool checked) { if (id == "dcaMapping") { toggleDCAMappingPanel(); diff --git a/src/ui/MainWindow.h b/src/ui/MainWindow.h index 66bb466..7f38862 100644 --- a/src/ui/MainWindow.h +++ b/src/ui/MainWindow.h @@ -95,7 +95,6 @@ class MainWindow : public QMainWindow { void saveSettings(); bool maybeSave(); void updateRecentProjectsMenu(); - void updateBubbleBarPosition(); Application* m_app; diff --git a/src/ui/MidiConfigDialog.cpp b/src/ui/MidiConfigDialog.cpp index 5fd14fd..15b5e1e 100644 --- a/src/ui/MidiConfigDialog.cpp +++ b/src/ui/MidiConfigDialog.cpp @@ -63,13 +63,13 @@ void MidiConfigDialog::setupUi() { QHBoxLayout* optionsLayout = new QHBoxLayout(); m_enabledCheck = new QCheckBox(tr("Enable MIDI input"), this); m_enabledCheck->setChecked(m_pendingEnabled); - connect(m_enabledCheck, &QCheckBox::stateChanged, this, &MidiConfigDialog::onEnabledChanged); + connect(m_enabledCheck, &QCheckBox::checkStateChanged, this, &MidiConfigDialog::onEnabledChanged); optionsLayout->addWidget(m_enabledCheck); m_autoReconnectCheck = new QCheckBox(tr("Auto-reconnect"), this); m_autoReconnectCheck->setChecked(m_pendingAutoReconnect); m_autoReconnectCheck->setToolTip(tr("Automatically reconnect when device is plugged in")); - connect(m_autoReconnectCheck, &QCheckBox::stateChanged, this, + connect(m_autoReconnectCheck, &QCheckBox::checkStateChanged, this, &MidiConfigDialog::onAutoReconnectChanged); optionsLayout->addWidget(m_autoReconnectCheck); optionsLayout->addStretch(); @@ -212,9 +212,9 @@ void MidiConfigDialog::onDeviceChanged(int index) { void MidiConfigDialog::onRefreshDevicesClicked() { populateDeviceList(); } -void MidiConfigDialog::onEnabledChanged(int state) { m_pendingEnabled = (state == Qt::Checked); } +void MidiConfigDialog::onEnabledChanged(Qt::CheckState state) { m_pendingEnabled = (state == Qt::Checked); } -void MidiConfigDialog::onAutoReconnectChanged(int state) { +void MidiConfigDialog::onAutoReconnectChanged(Qt::CheckState state) { m_pendingAutoReconnect = (state == Qt::Checked); } diff --git a/src/ui/MidiConfigDialog.h b/src/ui/MidiConfigDialog.h index 40a6372..b6af228 100644 --- a/src/ui/MidiConfigDialog.h +++ b/src/ui/MidiConfigDialog.h @@ -24,8 +24,8 @@ class MidiConfigDialog : public QDialog { void onDeviceListChanged(); void onDeviceChanged(int index); void onRefreshDevicesClicked(); - void onEnabledChanged(int state); - void onAutoReconnectChanged(int state); + void onEnabledChanged(Qt::CheckState state); + void onAutoReconnectChanged(Qt::CheckState state); void onAddMappingClicked(); void onMidiLearnClicked(); void onRemoveMappingClicked(); diff --git a/src/ui/MixerFeedbackPanel.cpp b/src/ui/MixerFeedbackPanel.cpp index ccfad51..0b91ccb 100644 --- a/src/ui/MixerFeedbackPanel.cpp +++ b/src/ui/MixerFeedbackPanel.cpp @@ -12,6 +12,7 @@ #include #include #include +#include namespace OpenMix { @@ -57,7 +58,7 @@ void MixerFeedbackPanel::connectDCASignals(DCAWidget* dca) { } void MixerFeedbackPanel::setDCACount(int count) { - count = qBound(1, count, 24); // support up to 24 DCAs (WING max) + count = std::clamp(count, 1, 24); // support up to 24 DCAs (WING max) QHBoxLayout* layout = qobject_cast(this->layout()); if (!layout) @@ -213,9 +214,8 @@ void MixerFeedbackPanel::onDCALabelEdited(int dcaNumber, const QString& newLabel cue->setParameter(path, newLabel); // update cue in list - int cueIndex = cueList->indexOf(m_activeCueId); - if (cueIndex >= 0) { - cueList->updateCue(cueIndex, *cue); + if (const auto cueIndex = cueList->indexOf(m_activeCueId)) { + cueList->updateCue(*cueIndex, *cue); } // update widget immediately @@ -266,13 +266,13 @@ void MixerFeedbackPanel::loadCueSettings(const QString& cueId) { } CueList* cueList = m_app->show()->cueList(); - int index = cueList->indexOf(cueId); - if (index < 0) { + const auto index = cueList->indexOf(cueId); + if (!index) { clearCueSettings(); return; } - const Cue& cue = cueList->at(index); + const Cue& cue = cueList->at(*index); // load /dca/N/label values from cue for (int i = 1; i <= m_dcaWidgets.size(); ++i) { diff --git a/src/ui/theme/Theme.cpp b/src/ui/theme/Theme.cpp index 7cdc2a0..46ba389 100644 --- a/src/ui/theme/Theme.cpp +++ b/src/ui/theme/Theme.cpp @@ -44,9 +44,8 @@ QFont monoFont(int size) { QStringList monoFonts = {"JetBrains Mono", "Cascadia Code", "SF Mono", "Consolas", "Monaco", "Menlo", "DejaVu Sans Mono"}; - QFontDatabase fontDb; for (const QString& fontName : monoFonts) { - if (fontDb.families().contains(fontName)) { + if (QFontDatabase::families().contains(fontName)) { QFont font(fontName); font.setPointSize(size); font.setStyleHint(QFont::Monospace);