diff --git a/CMakeLists.txt b/CMakeLists.txt index a3f849873b1..0ecc247eb3d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -217,6 +217,7 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/controllers/keyboard/keyboardeventfilter.cpp src/controllers/learningutils.cpp src/controllers/midi/midicontroller.cpp + src/controllers/midi/midicontrollerpreset.cpp src/controllers/midi/midicontrollerpresetfilehandler.cpp src/controllers/midi/midienumerator.cpp src/controllers/midi/midimessage.cpp @@ -1979,6 +1980,7 @@ if(HID) target_sources(mixxx-lib PRIVATE src/controllers/hid/hidcontroller.cpp src/controllers/hid/hidenumerator.cpp + src/controllers/hid/hidcontrollerpreset.cpp src/controllers/hid/hidcontrollerpresetfilehandler.cpp ) target_compile_definitions(mixxx-lib PUBLIC __HID__) diff --git a/build/depends.py b/build/depends.py index b68c7f85cdf..f8794c9a680 100644 --- a/build/depends.py +++ b/build/depends.py @@ -924,6 +924,7 @@ def sources(self, build): "src/controllers/midi/midimessage.cpp", "src/controllers/midi/midiutils.cpp", "src/controllers/midi/midicontroller.cpp", + "src/controllers/midi/midicontrollerpreset.cpp", "src/controllers/midi/midicontrollerpresetfilehandler.cpp", "src/controllers/midi/midienumerator.cpp", "src/controllers/midi/midioutputhandler.cpp", diff --git a/build/features.py b/build/features.py index b701b9c4fcf..0209740979e 100644 --- a/build/features.py +++ b/build/features.py @@ -100,6 +100,7 @@ def configure(self, build, conf): def sources(self, build): sources = ['src/controllers/hid/hidcontroller.cpp', + 'src/controllers/hid/hidcontrollerpreset.cpp', 'src/controllers/hid/hidenumerator.cpp', 'src/controllers/hid/hidcontrollerpresetfilehandler.cpp'] diff --git a/res/images/ic_custom.svg b/res/images/ic_custom.svg new file mode 100644 index 00000000000..44a0fe3f680 --- /dev/null +++ b/res/images/ic_custom.svg @@ -0,0 +1,67 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/res/images/ic_mixxx_symbolic.svg b/res/images/ic_mixxx_symbolic.svg new file mode 100644 index 00000000000..c51004a4556 --- /dev/null +++ b/res/images/ic_mixxx_symbolic.svg @@ -0,0 +1,1308 @@ + + + + + + + + image/svg+xml + + Mixxx application icon + + + + + + + Mixxx application icon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/images/ic_none.svg b/res/images/ic_none.svg new file mode 100644 index 00000000000..1d4ae648d3a --- /dev/null +++ b/res/images/ic_none.svg @@ -0,0 +1,77 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/res/mixxx.qrc b/res/mixxx.qrc index c863791eee5..02f2f053c66 100644 --- a/res/mixxx.qrc +++ b/res/mixxx.qrc @@ -33,7 +33,10 @@ images/mixxx-icon-logo-symbolic.svg images/skin_preview_placeholder.png images/ic_checkmark.svg + images/ic_custom.svg images/ic_delete.svg + images/ic_mixxx_symbolic.svg + images/ic_none.svg images/preferences/ic_preferences_autodj.svg images/preferences/ic_preferences_bpmdetect.svg images/preferences/ic_preferences_broadcast.svg diff --git a/src/controllers/bulk/bulkcontroller.cpp b/src/controllers/bulk/bulkcontroller.cpp index 49468b1ceab..4280cc75bee 100644 --- a/src/controllers/bulk/bulkcontroller.cpp +++ b/src/controllers/bulk/bulkcontroller.cpp @@ -116,11 +116,6 @@ void BulkController::visit(const HidControllerPreset* preset) { emit presetLoaded(getPreset()); } -bool BulkController::savePreset(const QString fileName) const { - HidControllerPresetFileHandler handler; - return handler.save(m_preset, getName(), fileName); -} - bool BulkController::matchPreset(const PresetInfo& preset) { const QList& products = preset.getProducts(); for (const auto& product : products) { diff --git a/src/controllers/bulk/bulkcontroller.h b/src/controllers/bulk/bulkcontroller.h index 9d67c1041ea..94e32869ba1 100644 --- a/src/controllers/bulk/bulkcontroller.h +++ b/src/controllers/bulk/bulkcontroller.h @@ -56,8 +56,6 @@ class BulkController : public Controller { return ControllerPresetPointer(pClone); } - bool savePreset(const QString fileName) const override; - void visit(const MidiControllerPreset* preset) override; void visit(const HidControllerPreset* preset) override; diff --git a/src/controllers/controller.cpp b/src/controllers/controller.cpp index 15a1e1dd5ec..fb16eea5a70 100644 --- a/src/controllers/controller.cpp +++ b/src/controllers/controller.cpp @@ -50,7 +50,7 @@ void Controller::stopEngine() { m_pEngine = NULL; } -bool Controller::applyPreset(QList scriptPaths, bool initializeScripts) { +bool Controller::applyPreset(bool initializeScripts) { qDebug() << "Applying controller preset..."; const ControllerPreset* pPreset = preset(); @@ -61,14 +61,15 @@ bool Controller::applyPreset(QList scriptPaths, bool initializeScripts) return false; } - if (pPreset->scripts.isEmpty()) { + QList scriptFiles = pPreset->getScriptFiles(); + if (scriptFiles.isEmpty()) { qWarning() << "No script functions available! Did the XML file(s) load successfully? See above for any errors."; return true; } - bool success = m_pEngine->loadScriptFiles(scriptPaths, pPreset->scripts); + bool success = m_pEngine->loadScriptFiles(scriptFiles); if (initializeScripts) { - m_pEngine->initializeScripts(pPreset->scripts); + m_pEngine->initializeScripts(scriptFiles); } return success; } diff --git a/src/controllers/controller.h b/src/controllers/controller.h index 09a20d8181b..06fed55a482 100644 --- a/src/controllers/controller.h +++ b/src/controllers/controller.h @@ -26,9 +26,9 @@ class Controller : public QObject, ConstControllerPresetVisitor { explicit Controller(UserSettingsPointer pConfig); ~Controller() override; // Subclass should call close() at minimum. - // Returns the extension for the controller (type) preset files. This is - // used by the ControllerManager to display only relevant preset files for - // the controller (type.) + /// Returns the extension for the controller (type) preset files. This is + /// used by the ControllerManager to display only relevant preset files for + /// the controller (type.) virtual QString presetExtension() = 0; void setPreset(const ControllerPreset& preset) { @@ -39,8 +39,6 @@ class Controller : public QObject, ConstControllerPresetVisitor { virtual void accept(ControllerVisitor* visitor) = 0; - virtual bool savePreset(const QString filename) const = 0; - // Returns a clone of the Controller's loaded preset. virtual ControllerPresetPointer getPreset() const = 0; @@ -71,8 +69,11 @@ class Controller : public QObject, ConstControllerPresetVisitor { // preset, not a pointer to the preset itself. void presetLoaded(ControllerPresetPointer pPreset); - // Making these slots protected/private ensures that other parts of Mixxx can - // only signal them which allows us to use no locks. + /// Emitted when the controller is opened or closed. + void openChanged(bool bOpen); + + // Making these slots protected/private ensures that other parts of Mixxx can + // only signal them which allows us to use no locks. protected slots: // TODO(XXX) move this into the inherited classes since is not called here // (via Controller) and re-implemented anyway in most cases. @@ -82,8 +83,13 @@ class Controller : public QObject, ConstControllerPresetVisitor { // this if they have an alternate way of handling such data.) virtual void receive(const QByteArray data, mixxx::Duration timestamp); - // Initializes the controller engine and returns whether it was successful. - virtual bool applyPreset(QList scriptPaths, bool initializeScripts); + /// Apply the preset to the controller. + /// @brief Initializes both controller engine and static output mappings. + /// + /// @param initializeScripts Can be set to false to skip script + /// initialization for unit tests. + /// @return Returns whether it was successful. + virtual bool applyPreset(bool initializeScripts = true); // Puts the controller in and out of learning mode. void startLearning(); @@ -122,6 +128,7 @@ class Controller : public QObject, ConstControllerPresetVisitor { } inline void setOpen(bool open) { m_bIsOpen = open; + emit openChanged(m_bIsOpen); } private: // but used by ControllerManager diff --git a/src/controllers/controllerengine.cpp b/src/controllers/controllerengine.cpp index 1488353b5d4..7739b742d4a 100644 --- a/src/controllers/controllerengine.cpp +++ b/src/controllers/controllerengine.cpp @@ -227,14 +227,10 @@ void ControllerEngine::initializeScriptEngine() { Input: List of script paths and file names to load Output: Returns true if no errors occurred. -------- ------------------------------------------------------ */ -bool ControllerEngine::loadScriptFiles(const QList& scriptPaths, - const QList& scripts) { - m_lastScriptPaths = scriptPaths; - - // scriptPaths holds the paths to search in when we're looking for scripts +bool ControllerEngine::loadScriptFiles(const QList& scripts) { bool result = true; - for (const ControllerPreset::ScriptFileInfo& script : scripts) { - if (!evaluate(script.name, scriptPaths)) { + for (const auto& script : scripts) { + if (!evaluate(script.file)) { result = false; } @@ -243,6 +239,8 @@ bool ControllerEngine::loadScriptFiles(const QList& scriptPaths, } } + m_lastScriptFiles = scripts; + connect(&m_scriptWatcher, SIGNAL(fileChanged(QString)), this, SLOT(scriptHasChanged(QString))); @@ -271,10 +269,10 @@ void ControllerEngine::scriptHasChanged(const QString& scriptFilename) { } initializeScriptEngine(); - loadScriptFiles(m_lastScriptPaths, pPreset->scripts); + loadScriptFiles(m_lastScriptFiles); qDebug() << "Re-initializing scripts"; - initializeScripts(pPreset->scripts); + initializeScripts(m_lastScriptFiles); } /* -------- ------------------------------------------------------ @@ -310,10 +308,7 @@ void ControllerEngine::initializeScripts(const QList dummy; - bool ret = evaluate(filepath, dummy); - - return ret; + return evaluate(QFileInfo(filepath)); } bool ControllerEngine::syntaxIsValid(const QString& scriptCode) { @@ -933,35 +928,22 @@ void ControllerEngine::trigger(QString group, QString name) { Input: Script filename Output: false if the script file has errors or doesn't exist -------- ------------------------------------------------------ */ -bool ControllerEngine::evaluate(const QString& scriptName, QList scriptPaths) { +bool ControllerEngine::evaluate(const QFileInfo& scriptFile) { if (m_pEngine == nullptr) { return false; } - QString filename = ""; - QFile input; - - if (scriptPaths.length() == 0) { - // If we aren't given any paths to search, assume that scriptName - // contains the full file name - filename = scriptName; - input.setFileName(filename); - } else { - for (const QString& scriptPath : scriptPaths) { - QDir scriptPathDir(scriptPath); - filename = scriptPathDir.absoluteFilePath(scriptName); - input.setFileName(filename); - if (input.exists()) { - qDebug() << "ControllerEngine: Watching JS File:" << filename; - m_scriptWatcher.addPath(filename); - break; - } - } + if (!scriptFile.exists()) { + qWarning() << "ControllerEngine: File does not exist:" << scriptFile.absoluteFilePath(); + return false; } + m_scriptWatcher.addPath(scriptFile.absoluteFilePath()); - qDebug() << "ControllerEngine: Loading" << filename; + qDebug() << "ControllerEngine: Loading" << scriptFile.absoluteFilePath(); // Read in the script file + QString filename = scriptFile.absoluteFilePath(); + QFile input(filename); if (!input.open(QIODevice::ReadOnly)) { qWarning() << QString("ControllerEngine: Problem opening the script file: %1, error # %2, %3") .arg(filename, QString::number(input.error()), input.errorString()); diff --git a/src/controllers/controllerengine.h b/src/controllers/controllerengine.h index 87a75abd730..ecc1c4a4761 100644 --- a/src/controllers/controllerengine.h +++ b/src/controllers/controllerengine.h @@ -158,8 +158,7 @@ class ControllerEngine : public QObject { // Evaluates all provided script files and returns true if no script errors // occurred while evaluating them. - bool loadScriptFiles(const QList& scriptPaths, - const QList& scripts); + bool loadScriptFiles(const QList& scripts); void initializeScripts(const QList& scripts); void gracefulShutdown(); void scriptHasChanged(const QString&); @@ -173,7 +172,7 @@ class ControllerEngine : public QObject { private: bool syntaxIsValid(const QString& scriptCode); - bool evaluate(const QString& scriptName, QList scriptPaths); + bool evaluate(const QFileInfo& scriptFile); bool internalExecute(QScriptValue thisObject, const QString& scriptCode); bool internalExecute(QScriptValue thisObject, QScriptValue functionObject, QScriptValueList arguments); @@ -221,7 +220,7 @@ class ControllerEngine : public QObject { QHash m_scriptWrappedFunctionCache; // Filesystem watcher for script auto-reload QFileSystemWatcher m_scriptWatcher; - QList m_lastScriptPaths; + QList m_lastScriptFiles; friend class ControllerEngineTest; }; diff --git a/src/controllers/controllerinputmappingtablemodel.cpp b/src/controllers/controllerinputmappingtablemodel.cpp index 456e0556307..237cda87745 100644 --- a/src/controllers/controllerinputmappingtablemodel.cpp +++ b/src/controllers/controllerinputmappingtablemodel.cpp @@ -18,12 +18,13 @@ void ControllerInputMappingTableModel::apply() { if (m_pMidiPreset != NULL) { // Clear existing input mappings and insert all the input mappings in // the table into the preset. - m_pMidiPreset->inputMappings.clear(); + QHash mappings; foreach (const MidiInputMapping& mapping, m_midiInputMappings) { // Use insertMulti because we support multiple inputs mappings for // the same input MidiKey. - m_pMidiPreset->inputMappings.insertMulti(mapping.key.key, mapping); + mappings.insertMulti(mapping.key.key, mapping); } + m_pMidiPreset->setInputMappings(mappings); } } @@ -39,9 +40,9 @@ void ControllerInputMappingTableModel::onPresetLoaded() { setHeaderData(MIDI_COLUMN_ACTION, Qt::Horizontal, tr("Action")); setHeaderData(MIDI_COLUMN_COMMENT, Qt::Horizontal, tr("Comment")); - if (!m_pMidiPreset->inputMappings.isEmpty()) { - beginInsertRows(QModelIndex(), 0, m_pMidiPreset->inputMappings.size() - 1); - m_midiInputMappings = m_pMidiPreset->inputMappings.values(); + if (!m_pMidiPreset->getInputMappings().isEmpty()) { + beginInsertRows(QModelIndex(), 0, m_pMidiPreset->getInputMappings().size() - 1); + m_midiInputMappings = m_pMidiPreset->getInputMappings().values(); endInsertRows(); } } diff --git a/src/controllers/controllerinputmappingtablemodel.h b/src/controllers/controllerinputmappingtablemodel.h index 96b6134f2a7..40d51d08c63 100644 --- a/src/controllers/controllerinputmappingtablemodel.h +++ b/src/controllers/controllerinputmappingtablemodel.h @@ -1,5 +1,4 @@ -#ifndef CONTROLLERINPUTMAPPINGTABLEMODEL_H -#define CONTROLLERINPUTMAPPINGTABLEMODEL_H +#pragma once #include #include @@ -9,6 +8,9 @@ #include "controllers/controllermappingtablemodel.h" #include "controllers/midi/midimessage.h" +/// Table Model for the "Inputs" table view in the preferences dialog. +/// +/// This allows editing the input mappings for a MIDI preset. class ControllerInputMappingTableModel : public ControllerMappingTableModel { Q_OBJECT public: @@ -64,5 +66,3 @@ class ControllerInputMappingTableModel : public ControllerMappingTableModel { QList m_midiInputMappings; }; - -#endif /* CONTROLLERINPUTMAPPINGTABLEMODEL_H */ diff --git a/src/controllers/controllermanager.cpp b/src/controllers/controllermanager.cpp index ec74976f7f5..a232fbfa294 100644 --- a/src/controllers/controllermanager.cpp +++ b/src/controllers/controllermanager.cpp @@ -39,6 +39,28 @@ const int kPollIntervalMillis = 5; const int kPollIntervalMillis = 1; #endif +/// Strip slashes and spaces from device name, so that it can be used as config +/// key or a filename. +QString sanitizeDeviceName(QString name) { + return name.replace(" ", "_").replace("/", "_").replace("\\", "_"); +} + +QFileInfo findPresetFile(const QString& pathOrFilename, const QStringList& paths) { + QFileInfo fileInfo(pathOrFilename); + if (fileInfo.isAbsolute()) { + return fileInfo; + } + + for (const QString& path : paths) { + fileInfo = QFileInfo(QDir(path).absoluteFilePath(pathOrFilename)); + if (fileInfo.exists()) { + return fileInfo; + } + } + + return QFileInfo(); +} + } // anonymous namespace QString firstAvailableFilename(QSet& filenames, @@ -95,8 +117,6 @@ ControllerManager::ControllerManager(UserSettingsPointer pConfig) this, SLOT(slotSetUpDevices())); connect(this, SIGNAL(requestShutdown()), this, SLOT(slotShutdown())); - connect(this, SIGNAL(requestSave(bool)), - this, SLOT(slotSavePresets(bool))); // Signal that we should run slotInitialize once our event loop has started // up. @@ -119,11 +139,10 @@ void ControllerManager::slotInitialize() { // Initialize preset info parsers. This object is only for use in the main // thread. Do not touch it from within ControllerManager. - QStringList presetSearchPaths; - presetSearchPaths << userPresetsPath(m_pConfig) - << resourcePresetsPath(m_pConfig); - m_pMainThreadPresetEnumerator = QSharedPointer( - new PresetInfoEnumerator(presetSearchPaths)); + m_pMainThreadUserPresetEnumerator = QSharedPointer( + new PresetInfoEnumerator(userPresetsPath(m_pConfig))); + m_pMainThreadSystemPresetEnumerator = QSharedPointer( + new PresetInfoEnumerator(resourcePresetsPath(m_pConfig))); // Instantiate all enumerators. Enumerators can take a long time to // construct since they interact with host MIDI APIs. @@ -150,7 +169,7 @@ void ControllerManager::slotShutdown() { locker.unlock(); // Delete enumerators and they'll delete their Devices - foreach (ControllerEnumerator* pEnumerator, enumerators) { + for (ControllerEnumerator* pEnumerator : enumerators) { delete pEnumerator; } @@ -168,7 +187,7 @@ void ControllerManager::updateControllerList() { locker.unlock(); QList newDeviceList; - foreach (ControllerEnumerator* pEnumerator, enumerators) { + for (ControllerEnumerator* pEnumerator : enumerators) { newDeviceList.append(pEnumerator->queryDevices()); } @@ -196,7 +215,7 @@ QList ControllerManager::getControllerList(bool bOutputDevices, boo // options. QList filteredDeviceList; - foreach (Controller* device, controllers) { + for (Controller* device : controllers) { if ((bOutputDevices == device->isOutputDevice()) || (bInputDevices == device->isInputDevice())) { filteredDeviceList.push_back(device); @@ -205,15 +224,18 @@ QList ControllerManager::getControllerList(bool bOutputDevices, boo return filteredDeviceList; } +QString ControllerManager::getConfiguredPresetFileForDevice(QString name) { + return m_pConfig->getValueString(ConfigKey("[ControllerPreset]", sanitizeDeviceName(name))); +} + void ControllerManager::slotSetUpDevices() { qDebug() << "ControllerManager: Setting up devices"; updateControllerList(); QList deviceList = getControllerList(false, true); + QStringList presetPaths(getPresetPaths(m_pConfig)); - QSet filenames; - - foreach (Controller* pController, deviceList) { + for (Controller* pController : deviceList) { QString name = pController->getName(); if (pController->isOpen()) { @@ -221,27 +243,36 @@ void ControllerManager::slotSetUpDevices() { } // The filename for this device name. - QString presetBaseName = presetFilenameFromName(name); + QString deviceName = sanitizeDeviceName(name); - // The first unique filename for this device (appends numbers at the end - // if we have already seen a controller by this name on this run of - // Mixxx. - presetBaseName = firstAvailableFilename(filenames, presetBaseName); + // Check if device is enabled + if (!m_pConfig->getValue(ConfigKey("[Controller]", deviceName), 0)) { + continue; + } - ControllerPresetPointer pPreset = - ControllerPresetFileHandler::loadPreset( - presetBaseName + pController->presetExtension(), - getPresetPaths(m_pConfig)); + // Check if device has a configured preset + QString presetFilePath = getConfiguredPresetFileForDevice(deviceName); + if (presetFilePath.isEmpty()) { + continue; + } - if (!loadPreset(pController, pPreset)) { - // TODO(XXX) : auto load midi preset here. + qDebug() << "Searching for controller preset" << presetFilePath + << "in paths:" << presetPaths.join(","); + QFileInfo presetFile = findPresetFile(presetFilePath, presetPaths); + if (!presetFile.exists()) { + qDebug() << "Could not find" << presetFilePath << "in any preset path."; continue; } - if (m_pConfig->getValueString(ConfigKey("[Controller]", presetBaseName)) != "1") { + ControllerPresetPointer pPreset = ControllerPresetFileHandler::loadPreset( + presetFile, resourcePresetsPath(m_pConfig)); + + if (!pPreset) { continue; } + pController->setPreset(*pPreset); + // If we are in safe mode, skip opening controllers. if (CmdlineArgs::Instance().getSafeMode()) { qDebug() << "We are in safe mode -- skipping opening controller."; @@ -255,7 +286,7 @@ void ControllerManager::slotSetUpDevices() { qWarning() << "There was a problem opening" << name; continue; } - pController->applyPreset(getPresetPaths(m_pConfig), true); + pController->applyPreset(); } maybeStartOrStopPolling(); @@ -267,7 +298,7 @@ void ControllerManager::maybeStartOrStopPolling() { locker.unlock(); bool shouldPoll = false; - foreach (Controller* pController, controllers) { + for (Controller* pController : controllers) { if (pController->isOpen() && pController->isPolling()) { shouldPoll = true; } @@ -321,7 +352,7 @@ void ControllerManager::pollDevices() { } mixxx::Duration start = mixxx::Time::elapsed(); - foreach (Controller* pDevice, m_controllers) { + for (Controller* pDevice : m_controllers) { if (pDevice->isOpen() && pDevice->isPolling()) { pDevice->poll(); } @@ -347,11 +378,11 @@ void ControllerManager::openController(Controller* pController) { // If successfully opened the device, apply the preset and save the // preference setting. if (result == 0) { - pController->applyPreset(getPresetPaths(m_pConfig), true); + pController->applyPreset(); // Update configuration to reflect controller is enabled. - m_pConfig->setValue(ConfigKey( - "[Controller]", presetFilenameFromName(pController->getName())), 1); + m_pConfig->setValue( + ConfigKey("[Controller]", sanitizeDeviceName(pController->getName())), 1); } } @@ -362,45 +393,40 @@ void ControllerManager::closeController(Controller* pController) { pController->close(); maybeStartOrStopPolling(); // Update configuration to reflect controller is disabled. - m_pConfig->setValue(ConfigKey( - "[Controller]", presetFilenameFromName(pController->getName())), 0); + m_pConfig->setValue( + ConfigKey("[Controller]", sanitizeDeviceName(pController->getName())), 0); } -bool ControllerManager::loadPreset(Controller* pController, - ControllerPresetPointer preset) { - if (!preset) { - return false; +void ControllerManager::slotApplyPreset(Controller* pController, + ControllerPresetPointer pPreset, + bool bEnabled) { + VERIFY_OR_DEBUG_ASSERT(pController) { + qWarning() << "slotApplyPreset got invalid controller!"; + return; } - pController->setPreset(*preset.data()); + + ConfigKey key("[ControllerPreset]", sanitizeDeviceName(pController->getName())); + if (!pPreset) { + closeController(pController); + // Unset the controller preset for this controller + m_pConfig->remove(key); + return; + } + + VERIFY_OR_DEBUG_ASSERT(!pPreset->isDirty()) { + qWarning() << "Preset is dirty, changes might be lost on restart!"; + } + + pController->setPreset(*pPreset); + // Save the file path/name in the config so it can be auto-loaded at // startup next time - m_pConfig->set( - ConfigKey("[ControllerPreset]", - presetFilenameFromName(pController->getName())), - preset->filePath()); - return true; -} - -void ControllerManager::slotSavePresets(bool onlyActive) { - QList deviceList = getControllerList(false, true); - QSet filenames; + m_pConfig->set(key, pPreset->filePath()); - // TODO(rryan): This should be split up somehow but the filename selection - // is dependent on all of the controllers to prevent over-writing each - // other. We need a better solution. - foreach (Controller* pController, deviceList) { - if (onlyActive && !pController->isOpen()) { - continue; - } - QString name = pController->getName(); - QString filename = firstAvailableFilename( - filenames, presetFilenameFromName(name)); - QString presetPath = userPresetsPath(m_pConfig) + filename - + pController->presetExtension(); - if (!pController->savePreset(presetPath)) { - qWarning() << "Failed to write preset for device" - << name << "to" << presetPath; - } + if (bEnabled) { + openController(pController); + } else { + closeController(pController); } } @@ -411,111 +437,3 @@ QList ControllerManager::getPresetPaths(UserSettingsPointer pConfig) { scriptPaths.append(resourcePresetsPath(pConfig)); return scriptPaths; } - -// static -bool ControllerManager::checksumFile(const QString& filename, - quint16* pChecksum) { - QFile file(filename); - if (!file.open(QIODevice::ReadOnly)) { - return false; - } - - qint64 fileSize = file.size(); - const char* pFile = reinterpret_cast(file.map(0, fileSize)); - - if (pFile == NULL) { - file.close(); - return false; - } - - *pChecksum = qChecksum(pFile, fileSize); - file.close(); - return true; -} - -// static -QString ControllerManager::getAbsolutePath(const QString& pathOrFilename, - const QStringList& paths) { - QFileInfo fileInfo(pathOrFilename); - if (fileInfo.isAbsolute()) { - return pathOrFilename; - } - - foreach (const QString& path, paths) { - QDir pathDir(path); - - if (pathDir.exists(pathOrFilename)) { - return pathDir.absoluteFilePath(pathOrFilename); - } - } - - return QString(); -} - -bool ControllerManager::importScript(const QString& scriptPath, - QString* newScriptFileName) { - QDir userPresets(userPresetsPath(m_pConfig)); - - qDebug() << "ControllerManager::importScript importing script" << scriptPath - << "to" << userPresets.absolutePath(); - - QFile scriptFile(scriptPath); - QFileInfo script(scriptFile); - - if (!script.exists() || !script.isReadable()) { - qWarning() << "ControllerManager::importScript script does not exist" - << "or is unreadable:" << scriptPath; - return false; - } - - // Not fatal if we can't checksum but still warn about it. - quint16 scriptChecksum = 0; - bool scriptChecksumGood = checksumFile(scriptPath, &scriptChecksum); - if (!scriptChecksumGood) { - qWarning() << "ControllerManager::importScript could not checksum file:" - << scriptPath; - } - - // The name we will save this file as in our local script mixxxdb. The - // conflict resolution logic below will mutate this variable if the name is - // already taken. - QString scriptFileName = script.fileName(); - - // For a file like "myfile.foo.bar.js", scriptBaseName is "myfile.foo.bar" - // and scriptSuffix is "js". - QString scriptBaseName = script.completeBaseName(); - QString scriptSuffix = script.suffix(); - int conflictNumber = 1; - - // This script exists. - while (userPresets.exists(scriptFileName)) { - // If the two files are identical. We're done. - quint16 localScriptChecksum = 0; - if (checksumFile(userPresets.filePath(scriptFileName), &localScriptChecksum) && - scriptChecksumGood && scriptChecksum == localScriptChecksum) { - *newScriptFileName = scriptFileName; - qDebug() << "ControllerManager::importScript" << scriptFileName - << "had identical checksum to a file of the same name." - << "Skipping import."; - return true; - } - - // Otherwise, we need to rename the file to a non-conflicting - // name. Insert a .X where X is a counter that we count up until we find - // a filename that does not exist. - scriptFileName = QString("%1.%2.%3").arg( - scriptBaseName, - QString::number(conflictNumber++), - scriptSuffix); - } - - QString destinationPath = userPresets.filePath(scriptFileName); - if (!scriptFile.copy(destinationPath)) { - qDebug() << "ControllerManager::importScript could not copy script to" - << "local preset path:" << destinationPath; - return false; - } - - *newScriptFileName = scriptFileName; - return true; -} diff --git a/src/controllers/controllermanager.h b/src/controllers/controllermanager.h index 1fa873ba4bc..8837d903580 100644 --- a/src/controllers/controllermanager.h +++ b/src/controllers/controllermanager.h @@ -33,41 +33,32 @@ class ControllerManager : public QObject { QList getControllers() const; QList getControllerList(bool outputDevices=true, bool inputDevices=true); ControllerLearningEventFilter* getControllerLearningEventFilter() const; - QSharedPointer getMainThreadPresetEnumerator() { - return m_pMainThreadPresetEnumerator; + QSharedPointer getMainThreadUserPresetEnumerator() { + return m_pMainThreadUserPresetEnumerator; } + QSharedPointer getMainThreadSystemPresetEnumerator() { + return m_pMainThreadSystemPresetEnumerator; + } + QString getConfiguredPresetFileForDevice(QString name); // Prevent other parts of Mixxx from having to manually connect to our slots void setUpDevices() { emit requestSetUpDevices(); }; - void savePresets(bool onlyActive=false) { emit requestSave(onlyActive); }; static QList getPresetPaths(UserSettingsPointer pConfig); - // If pathOrFilename is an absolute path, returns it. If it is a relative - // path and it is contained within any of the directories in presetPaths, - // returns the path to the first file in the path that exists. - static QString getAbsolutePath(const QString& pathOrFilename, - const QStringList& presetPaths); - - bool importScript(const QString& scriptPath, QString* newScriptFileName); - static bool checksumFile(const QString& filename, quint16* pChecksum); - signals: void devicesChanged(); void requestSetUpDevices(); void requestShutdown(); - void requestSave(bool onlyActive); void requestInitialize(); public slots: void updateControllerList(); + void slotApplyPreset(Controller* pController, ControllerPresetPointer pPreset, bool bEnabled); void openController(Controller* pController); void closeController(Controller* pController); - // Writes out presets for currently connected input devices - void slotSavePresets(bool onlyActive=false); - private slots: // Perform initialization that should be delayed until the ControllerManager // thread is started. @@ -77,18 +68,12 @@ class ControllerManager : public QObject { // preferences dialog on apply, and only open/close changed devices void slotSetUpDevices(); void slotShutdown(); - bool loadPreset(Controller* pController, - ControllerPresetPointer preset); // Calls poll() on all devices that have isPolling() true. void pollDevices(); void startPolling(); void stopPolling(); void maybeStartOrStopPolling(); - static QString presetFilenameFromName(QString name) { - return name.replace(" ", "_").replace("/", "_").replace("\\", "_"); - } - private: UserSettingsPointer m_pConfig; ControllerLearningEventFilter* m_pControllerLearningEventFilter; @@ -97,7 +82,8 @@ class ControllerManager : public QObject { QList m_enumerators; QList m_controllers; QThread* m_pThread; - QSharedPointer m_pMainThreadPresetEnumerator; + QSharedPointer m_pMainThreadUserPresetEnumerator; + QSharedPointer m_pMainThreadSystemPresetEnumerator; bool m_skipPoll; }; diff --git a/src/controllers/controlleroutputmappingtablemodel.cpp b/src/controllers/controlleroutputmappingtablemodel.cpp index 7dc3756e414..253aa412d6d 100644 --- a/src/controllers/controlleroutputmappingtablemodel.cpp +++ b/src/controllers/controlleroutputmappingtablemodel.cpp @@ -18,12 +18,13 @@ void ControllerOutputMappingTableModel::apply() { if (m_pMidiPreset != NULL) { // Clear existing output mappings and insert all the output mappings in // the table into the preset. - m_pMidiPreset->outputMappings.clear(); + QHash mappings; foreach (const MidiOutputMapping& mapping, m_midiOutputMappings) { // Use insertMulti because we support multiple outputs from the same // control. - m_pMidiPreset->outputMappings.insertMulti(mapping.controlKey, mapping); + mappings.insertMulti(mapping.controlKey, mapping); } + m_pMidiPreset->setOutputMappings(mappings); } } @@ -42,9 +43,9 @@ void ControllerOutputMappingTableModel::onPresetLoaded() { setHeaderData(MIDI_COLUMN_MAX, Qt::Horizontal, tr("On Range Max")); setHeaderData(MIDI_COLUMN_COMMENT, Qt::Horizontal, tr("Comment")); - if (!m_pMidiPreset->outputMappings.isEmpty()) { - beginInsertRows(QModelIndex(), 0, m_pMidiPreset->outputMappings.size() - 1); - m_midiOutputMappings = m_pMidiPreset->outputMappings.values(); + if (!m_pMidiPreset->getOutputMappings().isEmpty()) { + beginInsertRows(QModelIndex(), 0, m_pMidiPreset->getOutputMappings().size() - 1); + m_midiOutputMappings = m_pMidiPreset->getOutputMappings().values(); endInsertRows(); } } diff --git a/src/controllers/controlleroutputmappingtablemodel.h b/src/controllers/controlleroutputmappingtablemodel.h index 781338c0e7a..36be40f69a6 100644 --- a/src/controllers/controlleroutputmappingtablemodel.h +++ b/src/controllers/controlleroutputmappingtablemodel.h @@ -1,5 +1,4 @@ -#ifndef CONTROLLEROUTPUTMAPPINGTABLEMODEL_H -#define CONTROLLEROUTPUTMAPPINGTABLEMODEL_H +#pragma once #include #include @@ -9,6 +8,9 @@ #include "controllers/controllermappingtablemodel.h" #include "controllers/midi/midimessage.h" +/// Table Model for the "Outputs" table view in the preferences dialog. +/// +/// This allows editing the output mappings for a MIDI preset. class ControllerOutputMappingTableModel : public ControllerMappingTableModel { Q_OBJECT public: @@ -58,5 +60,3 @@ class ControllerOutputMappingTableModel : public ControllerMappingTableModel { QList m_midiOutputMappings; }; - -#endif /* CONTROLLEROUTPUTMAPPINGTABLEMODEL_H */ diff --git a/src/controllers/controllerpreset.h b/src/controllers/controllerpreset.h index ad4019632e1..e51092961de 100644 --- a/src/controllers/controllerpreset.h +++ b/src/controllers/controllerpreset.h @@ -1,27 +1,26 @@ -/** -* @file controllerpreset.h -* @author Sean Pappalardo spappalardo@mixxx.org -* @date Mon 9 Apr 2012 -* @brief Controller preset -* -* This class represents a controller preset, containing the data elements that -* make it up. -*/ - -#ifndef CONTROLLERPRESET_H -#define CONTROLLERPRESET_H - +#pragma once +/// @file controllerpreset.h +/// @author Sean Pappalardo spappalardo@mixxx.org +/// @date Mon 9 Apr 2012 +/// @brief Controller Preset + +#include +#include #include +#include #include #include -#include class ControllerPresetVisitor; class ConstControllerPresetVisitor; +/// This class represents a controller preset, containing the data elements that +/// make it up. class ControllerPreset { public: - ControllerPreset() {} + ControllerPreset() + : m_bDirty(false) { + } virtual ~ControllerPreset() {} struct ScriptFileInfo { @@ -32,25 +31,43 @@ class ControllerPreset { QString name; QString functionPrefix; + QFileInfo file; bool builtin; }; - /** addScriptFile(QString,QString) - * Adds an entry to the list of script file names & associated list of function prefixes - * @param filename Name of the XML file to add - * @param functionprefix Function prefix to add - */ - void addScriptFile(QString filename, QString functionprefix, - bool builtin=false) { + /// Adds a script file to the list of controller scripts for this preset. + /// @param filename Name of the script file to add + /// @param functionprefix The script's function prefix (or empty string) + /// @param file A FileInfo object pointing to the script file + /// @param builtin If this is true, the script won't be written to the XML + void addScriptFile(const QString& name, + const QString& functionprefix, + const QFileInfo& file, + bool builtin = false) { ScriptFileInfo info; - info.name = filename; + info.name = name; info.functionPrefix = functionprefix; + info.file = file; info.builtin = builtin; - scripts.append(info); + m_scripts.append(info); + setDirty(true); + } + + const QList& getScriptFiles() const { + return m_scripts; + } + + inline void setDirty(bool bDirty) { + m_bDirty = bDirty; + } + + inline bool isDirty() const { + return m_bDirty; } inline void setDeviceId(const QString id) { m_deviceId = id; + setDirty(true); } inline QString deviceId() const { @@ -59,14 +76,20 @@ class ControllerPreset { inline void setFilePath(const QString filePath) { m_filePath = filePath; + setDirty(true); } inline QString filePath() const { return m_filePath; } + inline QDir dirPath() const { + return QFileInfo(filePath()).absoluteDir(); + } + inline void setName(const QString name) { m_name = name; + setDirty(true); } inline QString name() const { @@ -75,6 +98,7 @@ class ControllerPreset { inline void setAuthor(const QString author) { m_author = author; + setDirty(true); } inline QString author() const { @@ -83,6 +107,7 @@ class ControllerPreset { inline void setDescription(const QString description) { m_description = description; + setDirty(true); } inline QString description() const { @@ -91,6 +116,7 @@ class ControllerPreset { inline void setForumLink(const QString forumlink) { m_forumlink = forumlink; + setDirty(true); } inline QString forumlink() const { @@ -99,6 +125,7 @@ class ControllerPreset { inline void setWikiLink(const QString wikilink) { m_wikilink = wikilink; + setDirty(true); } inline QString wikilink() const { @@ -107,6 +134,7 @@ class ControllerPreset { inline void setSchemaVersion(const QString schemaVersion) { m_schemaVersion = schemaVersion; + setDirty(true); } inline QString schemaVersion() const { @@ -115,6 +143,7 @@ class ControllerPreset { inline void setMixxxVersion(const QString mixxxVersion) { m_mixxxVersion = mixxxVersion; + setDirty(true); } inline QString mixxxVersion() const { @@ -123,8 +152,11 @@ class ControllerPreset { inline void addProductMatch(QHash match) { m_productMatches.append(match); + setDirty(true); } + virtual bool savePreset(const QString& filename) const = 0; + virtual void accept(ControllerPresetVisitor* visitor) = 0; virtual void accept(ConstControllerPresetVisitor* visitor) const = 0; virtual bool isMappable() const = 0; @@ -134,6 +166,8 @@ class ControllerPreset { QList< QHash > m_productMatches; private: + bool m_bDirty; + QString m_deviceId; QString m_filePath; QString m_name; @@ -143,8 +177,8 @@ class ControllerPreset { QString m_wikilink; QString m_schemaVersion; QString m_mixxxVersion; + + QList m_scripts; }; typedef QSharedPointer ControllerPresetPointer; - -#endif diff --git a/src/controllers/controllerpresetfilehandler.cpp b/src/controllers/controllerpresetfilehandler.cpp index f50b8bd1b23..6d147a6e885 100644 --- a/src/controllers/controllerpresetfilehandler.cpp +++ b/src/controllers/controllerpresetfilehandler.cpp @@ -1,10 +1,7 @@ -/** -* @file controllerpresetfilehandler.cpp -* @author Sean Pappalardo spappalardo@mixxx.org -* @date Mon 9 Apr 2012 -* @brief Handles loading and saving of Controller presets. -* -*/ +/// @file controllerpresetfilehandler.cpp +/// @author Sean Pappalardo spappalardo@mixxx.org +/// @date Mon 9 Apr 2012 +/// @brief Handles loading and saving of Controller presets. #include "controllers/controllerpresetfilehandler.h" #include "controllers/controllermanager.h" @@ -12,57 +9,75 @@ #include "controllers/midi/midicontrollerpresetfilehandler.h" #include "controllers/hid/hidcontrollerpresetfilehandler.h" -// static -ControllerPresetPointer ControllerPresetFileHandler::loadPreset(const QString& pathOrFilename, - const QStringList& presetPaths) { - qDebug() << "Searching for controller preset" << pathOrFilename - << "in paths:" << presetPaths.join(","); - QString scriptPath = ControllerManager::getAbsolutePath(pathOrFilename, - presetPaths); - - if (scriptPath.isEmpty()) { - qDebug() << "Could not find" << pathOrFilename - << "in any preset path."; - return ControllerPresetPointer(); - } +namespace { + +/// Find script file in the preset or system path. +/// +/// @param preset The controller preset the script belongs to. +/// @param filename The script filename. +/// @param systemPresetsPath The system presets path to use as fallback. +/// @return Returns a QFileInfo object. If the script was not found in either +/// of the search directories, the QFileInfo object might point to a +/// non-existing file. +QFileInfo findScriptFile(ControllerPreset* preset, + const QString& filename, + const QDir& systemPresetsPath) { + // Always try to load script from the mapping's directory first + QFileInfo file = QFileInfo(preset->dirPath().absoluteFilePath(filename)); + + // If the script does not exist, try to find it in the fallback dir + if (!file.exists()) { + file = QFileInfo(systemPresetsPath.absoluteFilePath(filename)); + } + return file; +} + +} // namespace - QFileInfo scriptPathInfo(scriptPath); - if (!scriptPathInfo.exists() || !scriptPathInfo.isReadable()) { - qDebug() << "Preset" << scriptPath << "does not exist or is unreadable."; +// static +ControllerPresetPointer ControllerPresetFileHandler::loadPreset( + const QFileInfo& presetFile, const QDir& systemPresetsPath) { + if (!presetFile.exists() || !presetFile.isReadable()) { + qDebug() << "Preset" << presetFile.absoluteFilePath() + << "does not exist or is unreadable."; return ControllerPresetPointer(); } - ControllerPresetFileHandler* pHandler = NULL; - if (scriptPath.endsWith(MIDI_PRESET_EXTENSION, Qt::CaseInsensitive)) { + ControllerPresetFileHandler* pHandler = nullptr; + if (presetFile.fileName().endsWith( + MIDI_PRESET_EXTENSION, Qt::CaseInsensitive)) { pHandler = new MidiControllerPresetFileHandler(); - } else if (scriptPath.endsWith(HID_PRESET_EXTENSION, Qt::CaseInsensitive) || - scriptPath.endsWith(BULK_PRESET_EXTENSION, Qt::CaseInsensitive)) { + } else if (presetFile.fileName().endsWith( + HID_PRESET_EXTENSION, Qt::CaseInsensitive) || + presetFile.fileName().endsWith( + BULK_PRESET_EXTENSION, Qt::CaseInsensitive)) { pHandler = new HidControllerPresetFileHandler(); } - if (pHandler == NULL) { - qDebug() << "Preset" << scriptPath << "has an unrecognized extension."; + if (pHandler == nullptr) { + qDebug() << "Preset" << presetFile.absoluteFilePath() + << "has an unrecognized extension."; return ControllerPresetPointer(); } - // NOTE(rryan): We don't provide a device name. It's unused currently. - // TODO(rryan): Delete pHandler. - return pHandler->load(scriptPath, QString()); + ControllerPresetPointer pPreset = pHandler->load( + presetFile.absoluteFilePath(), systemPresetsPath); + if (pPreset) { + pPreset->setDirty(false); + } + return pPreset; } -ControllerPresetPointer ControllerPresetFileHandler::load(const QString path, - const QString deviceName) { +ControllerPresetPointer ControllerPresetFileHandler::load( + const QString& path, const QDir& systemPresetsPath) { qDebug() << "Loading controller preset from" << path; - ControllerPresetPointer pPreset = load(XmlParse::openXMLFile(path, "controller"), - deviceName); - if (pPreset) { - pPreset->setFilePath(path); - } + ControllerPresetPointer pPreset = load( + XmlParse::openXMLFile(path, "controller"), path, systemPresetsPath); return pPreset; } -void ControllerPresetFileHandler::parsePresetInfo(const QDomElement& root, - ControllerPreset* preset) const { +void ControllerPresetFileHandler::parsePresetInfo( + const QDomElement& root, ControllerPreset* preset) const { if (root.isNull() || !preset) { return; } @@ -88,9 +103,8 @@ void ControllerPresetFileHandler::parsePresetInfo(const QDomElement& root, preset->setWikiLink(wiki.isNull() ? "" : wiki.text()); } -QDomElement ControllerPresetFileHandler::getControllerNode(const QDomElement& root, - const QString deviceName) { - Q_UNUSED(deviceName); +QDomElement ControllerPresetFileHandler::getControllerNode( + const QDomElement& root) { if (root.isNull()) { return QDomElement(); } @@ -102,7 +116,9 @@ QDomElement ControllerPresetFileHandler::getControllerNode(const QDomElement& ro } void ControllerPresetFileHandler::addScriptFilesToPreset( - const QDomElement& controller, ControllerPreset* preset) const { + const QDomElement& controller, + ControllerPreset* preset, + const QDir& systemPresetsPath) const { if (controller.isNull()) return; @@ -111,22 +127,27 @@ void ControllerPresetFileHandler::addScriptFilesToPreset( // Build a list of script files to load QDomElement scriptFile = controller.firstChildElement("scriptfiles") - .firstChildElement("file"); + .firstChildElement("file"); // Default currently required file - preset->addScriptFile(REQUIRED_SCRIPT_FILE, "", true); + preset->addScriptFile(REQUIRED_SCRIPT_FILE, + "", + findScriptFile(preset, REQUIRED_SCRIPT_FILE, systemPresetsPath), + true); // Look for additional ones while (!scriptFile.isNull()) { - QString functionPrefix = scriptFile.attribute("functionprefix",""); - QString filename = scriptFile.attribute("filename",""); - preset->addScriptFile(filename, functionPrefix); + QString functionPrefix = scriptFile.attribute("functionprefix", ""); + QString filename = scriptFile.attribute("filename", ""); + QFileInfo file = findScriptFile(preset, filename, systemPresetsPath); + + preset->addScriptFile(filename, functionPrefix, file); scriptFile = scriptFile.nextSiblingElement("file"); } } -bool ControllerPresetFileHandler::writeDocument(QDomDocument root, - const QString fileName) const { +bool ControllerPresetFileHandler::writeDocument( + QDomDocument root, const QString fileName) const { // Need to do this on Windows QDir directory; if (!directory.mkpath(fileName.left(fileName.lastIndexOf("/")))) { @@ -150,16 +171,18 @@ bool ControllerPresetFileHandler::writeDocument(QDomDocument root, return true; } -void addTextTag(QDomDocument& doc, QDomElement& holder, - QString tagName, QString tagText) { +void addTextTag(QDomDocument& doc, + QDomElement& holder, + QString tagName, + QString tagText) { QDomElement tag = doc.createElement(tagName); QDomText textNode = doc.createTextNode(tagText); tag.appendChild(textNode); holder.appendChild(tag); } -QDomDocument ControllerPresetFileHandler::buildRootWithScripts(const ControllerPreset& preset, - const QString deviceName) const { +QDomDocument ControllerPresetFileHandler::buildRootWithScripts( + const ControllerPreset& preset) const { QDomDocument doc("Preset"); QString blank = "\n" "\n" @@ -190,13 +213,13 @@ QDomDocument ControllerPresetFileHandler::buildRootWithScripts(const ControllerP QDomElement controller = doc.createElement("controller"); // Strip off the serial number - controller.setAttribute("id", rootDeviceName(deviceName)); + controller.setAttribute("id", rootDeviceName(preset.deviceId())); rootNode.appendChild(controller); QDomElement scriptFiles = doc.createElement("scriptfiles"); controller.appendChild(scriptFiles); - foreach (const ControllerPreset::ScriptFileInfo& script, preset.scripts) { + for (const ControllerPreset::ScriptFileInfo& script : preset.getScriptFiles()) { QString filename = script.name; // Don't need to write anything for built-in files. if (script.builtin) { diff --git a/src/controllers/controllerpresetfilehandler.h b/src/controllers/controllerpresetfilehandler.h index 904a8721960..2ee8c34e37d 100644 --- a/src/controllers/controllerpresetfilehandler.h +++ b/src/controllers/controllerpresetfilehandler.h @@ -1,30 +1,31 @@ -/** -* @file controllerpresetfilehandler.h -* @author Sean Pappalardo spappalardo@mixxx.org -* @date Mon 9 Apr 2012 -* @brief Handles loading and saving of Controller presets. -* -*/ -#ifndef CONTROLLERPRESETFILEHANDLER_H -#define CONTROLLERPRESETFILEHANDLER_H +#pragma once +/// @file controllerpresetfilehandler.h +/// @author Sean Pappalardo spappalardo@mixxx.org +/// @date Mon 9 Apr 2012 +/// @brief Handles loading and saving of Controller presets. #include "util/xml.h" #include "controllers/controllerpreset.h" +/// The ControllerPresetFileHandler is used for serializing/deserializing the +/// ControllerPreset objects to/from XML files and is also responsible +/// finding the script files that belong to a preset in the file system. +/// +/// Subclasses can implement the private load function to add support for XML +/// elements that are only useful for certain mapping types. class ControllerPresetFileHandler { public: ControllerPresetFileHandler() {}; virtual ~ControllerPresetFileHandler() {}; - static ControllerPresetPointer loadPreset(const QString& path, - const QStringList& presetPaths); + static ControllerPresetPointer loadPreset(const QFileInfo& presetFile, + const QDir& systemPresetsPath); - /** load(QString,QString,bool) - * Overloaded function for convenience - * @param path The path to a controller preset XML file. - * @param deviceName The name/id of the controller - */ - ControllerPresetPointer load(const QString path, const QString deviceName); + /// Overloaded function for convenience + /// + /// @param path The path to a controller preset XML file. + /// @param systemPresetsPath Fallback directory for searching script files. + ControllerPresetPointer load(const QString& path, const QDir& systemPresetsPath); // Returns just the name of a given device (everything before the first // space) @@ -33,32 +34,33 @@ class ControllerPresetFileHandler { } protected: - QDomElement getControllerNode(const QDomElement& root, - const QString deviceName); + QDomElement getControllerNode(const QDomElement& root); void parsePresetInfo(const QDomElement& root, ControllerPreset* preset) const; - /** addScriptFilesToPreset(QDomElement,QString,bool) - * Loads script files specified in a QDomElement structure into the supplied - * ControllerPreset. - * @param root The root node of the XML document for the preset. - * @param deviceName The name/id of the controller - * @param preset The ControllerPreset into which the scripts should be placed. - */ + /// Adds script files from XML to the ControllerPreset. + /// + /// This function parses the supplied QDomElement structure, finds the + /// matching script files inside the search paths and adds them to + /// ControllerPreset. + /// + /// @param root The root node of the XML document for the preset. + /// @param preset The ControllerPreset these scripts belong to. + /// @param systemPresetsPath Fallback directory for searching script files. void addScriptFilesToPreset(const QDomElement& root, - ControllerPreset* preset) const; + ControllerPreset* preset, + const QDir& systemPresetsPath) const; - // Creates the XML document and includes what script files are currently - // loaded. Sub-classes need to call this before adding any other items. - QDomDocument buildRootWithScripts(const ControllerPreset& preset, - const QString deviceName) const; + /// Creates the XML document and includes what script files are currently + /// loaded. Sub-classes need to call this before adding any other items. + QDomDocument buildRootWithScripts(const ControllerPreset& preset) const; bool writeDocument(QDomDocument root, const QString fileName) const; private: // Sub-classes implement this. - virtual ControllerPresetPointer load(const QDomElement root, const QString deviceName) = 0; + virtual ControllerPresetPointer load(const QDomElement& root, + const QString& filePath, + const QDir& systemPresetPath) = 0; }; - -#endif diff --git a/src/controllers/controllerpresetinfo.cpp b/src/controllers/controllerpresetinfo.cpp index 3e8c1d581f4..bf0051209af 100644 --- a/src/controllers/controllerpresetinfo.cpp +++ b/src/controllers/controllerpresetinfo.cpp @@ -10,7 +10,6 @@ */ #include "controllers/controllerpresetinfo.h" -#include "controllers/controllerpresetinfoenumerator.h" #include "controllers/defs_controllers.h" #include "util/xml.h" diff --git a/src/controllers/controllerpresetinfoenumerator.cpp b/src/controllers/controllerpresetinfoenumerator.cpp index 60e5d6e0408..becbad20a4d 100644 --- a/src/controllers/controllerpresetinfoenumerator.cpp +++ b/src/controllers/controllerpresetinfoenumerator.cpp @@ -29,6 +29,10 @@ bool presetInfoNameComparator(const PresetInfo &a, const PresetInfo &b) { } } +PresetInfoEnumerator::PresetInfoEnumerator(const QString& searchPath) + : PresetInfoEnumerator(QList{searchPath}) { +} + PresetInfoEnumerator::PresetInfoEnumerator(const QStringList& searchPaths) : m_controllerDirPaths(searchPaths) { loadSupportedPresets(); @@ -48,6 +52,10 @@ QList PresetInfoEnumerator::getPresetsByExtension(const QString& ext } void PresetInfoEnumerator::loadSupportedPresets() { + m_midiPresets.clear(); + m_hidPresets.clear(); + m_bulkPresets.clear(); + for (const QString& dirPath : m_controllerDirPaths) { QDirIterator it(dirPath); while (it.hasNext()) { diff --git a/src/controllers/controllerpresetinfoenumerator.h b/src/controllers/controllerpresetinfoenumerator.h index d948aadfb61..07a71dfd10d 100644 --- a/src/controllers/controllerpresetinfoenumerator.h +++ b/src/controllers/controllerpresetinfoenumerator.h @@ -15,12 +15,11 @@ class PresetInfoEnumerator { public: + PresetInfoEnumerator(const QString& searchPath); PresetInfoEnumerator(const QStringList& searchPaths); // Return cached list of presets for this extension QList getPresetsByExtension(const QString& extension); - - protected: void loadSupportedPresets(); private: diff --git a/src/controllers/dlgprefcontroller.cpp b/src/controllers/dlgprefcontroller.cpp index b470fad540b..e281bbb5011 100644 --- a/src/controllers/dlgprefcontroller.cpp +++ b/src/controllers/dlgprefcontroller.cpp @@ -39,15 +39,14 @@ DlgPrefController::DlgPrefController(QWidget* parent, Controller* controller, initTableView(m_ui.m_pInputMappingTableView); initTableView(m_ui.m_pOutputMappingTableView); - connect(m_pController, SIGNAL(presetLoaded(ControllerPresetPointer)), - this, SLOT(slotPresetLoaded(ControllerPresetPointer))); + connect(m_pController, &Controller::presetLoaded, this, &DlgPrefController::slotShowPreset); // TODO(rryan): Eh, this really isn't thread safe but it's the way it's been // since 1.11.0. We shouldn't be calling Controller methods because it lives // in a different thread. Booleans (like isOpen()) are fine but a complex // object like a preset involves QHash's and other data structures that // really don't like concurrent access. ControllerPresetPointer pPreset = m_pController->getPreset(); - slotPresetLoaded(pPreset); + slotShowPreset(pPreset); m_ui.labelDeviceName->setText(m_pController->getName()); QString category = m_pController->getCategory(); @@ -58,44 +57,55 @@ DlgPrefController::DlgPrefController(QWidget* parent, Controller* controller, } // When the user picks a preset, load it. - connect(m_ui.comboBoxPreset, SIGNAL(activated(int)), - this, SLOT(slotLoadPreset(int))); + connect(m_ui.comboBoxPreset, SIGNAL(activated(int)), this, SLOT(slotPresetSelected(int))); - // When the user toggles the Enabled checkbox, toggle. - connect(m_ui.chkEnabledDevice, SIGNAL(clicked(bool)), - this, SLOT(slotEnableDevice(bool))); + // When the user toggles the Enabled checkbox, mark as dirty + connect(m_ui.chkEnabledDevice, &QCheckBox::clicked, [this] { setDirty(true); }); // Connect our signals to controller manager. - connect(this, SIGNAL(openController(Controller*)), - m_pControllerManager, SLOT(openController(Controller*))); - connect(this, SIGNAL(closeController(Controller*)), - m_pControllerManager, SLOT(closeController(Controller*))); - connect(this, SIGNAL(loadPreset(Controller*, ControllerPresetPointer)), - m_pControllerManager, SLOT(loadPreset(Controller*, ControllerPresetPointer))); + connect(this, + &DlgPrefController::applyPreset, + m_pControllerManager, + &ControllerManager::slotApplyPreset); // Open script file links connect(m_ui.labelLoadedPresetScriptFileLinks, &QLabel::linkActivated, - [](const QString & path) { - QDesktopServices::openUrl(QUrl::fromLocalFile(path)); }); + [](const QString& path) { + QDesktopServices::openUrl(QUrl::fromLocalFile(path)); + }); // Input mappings - connect(m_ui.btnAddInputMapping, SIGNAL(clicked()), - this, SLOT(addInputMapping())); - connect(m_ui.btnRemoveInputMappings, SIGNAL(clicked()), - this, SLOT(removeInputMappings())); - connect(m_ui.btnLearningWizard, SIGNAL(clicked()), - this, SLOT(showLearningWizard())); - connect(m_ui.btnClearAllInputMappings, SIGNAL(clicked()), - this, SLOT(clearAllInputMappings())); + connect(m_ui.btnAddInputMapping, + SIGNAL(clicked()), + this, + SLOT(addInputMapping())); + connect(m_ui.btnRemoveInputMappings, + SIGNAL(clicked()), + this, + SLOT(removeInputMappings())); + connect(m_ui.btnLearningWizard, + SIGNAL(clicked()), + this, + SLOT(showLearningWizard())); + connect(m_ui.btnClearAllInputMappings, + SIGNAL(clicked()), + this, + SLOT(clearAllInputMappings())); // Output mappings - connect(m_ui.btnAddOutputMapping, SIGNAL(clicked()), - this, SLOT(addOutputMapping())); - connect(m_ui.btnRemoveOutputMappings, SIGNAL(clicked()), - this, SLOT(removeOutputMappings())); - connect(m_ui.btnClearAllOutputMappings, SIGNAL(clicked()), - this, SLOT(clearAllOutputMappings())); + connect(m_ui.btnAddOutputMapping, + SIGNAL(clicked()), + this, + SLOT(addOutputMapping())); + connect(m_ui.btnRemoveOutputMappings, + SIGNAL(clicked()), + this, + SLOT(removeOutputMappings())); + connect(m_ui.btnClearAllOutputMappings, + SIGNAL(clicked()), + this, + SLOT(clearAllOutputMappings())); } DlgPrefController::~DlgPrefController() { @@ -107,13 +117,14 @@ void DlgPrefController::showLearningWizard() { // learning dialog. If we don't apply the settings first and open the // device, the dialog won't react to controller messages. if (m_ui.chkEnabledDevice->isChecked() && !m_pController->isOpen()) { - QMessageBox::StandardButton result = QMessageBox::question( - this, - tr("Apply device settings?"), - tr("Your settings must be applied before starting the learning wizard.\n" - "Apply settings and continue?"), - QMessageBox::Ok | QMessageBox::Cancel, // Buttons to be displayed - QMessageBox::Ok); // Default button + QMessageBox::StandardButton result = QMessageBox::question(this, + tr("Apply device settings?"), + tr("Your settings must be applied before starting the learning " + "wizard.\n" + "Apply settings and continue?"), + QMessageBox::Ok | + QMessageBox::Cancel, // Buttons to be displayed + QMessageBox::Ok); // Default button // Stop if the user has not pressed the Ok button, // which could be the Cancel or the Close Button. if (result != QMessageBox::Ok) { @@ -122,9 +133,6 @@ void DlgPrefController::showLearningWizard() { } slotApply(); - // After this point we consider the mapping wizard as dirtying the preset. - slotDirty(); - // Note that DlgControllerLearning is set to delete itself on close using // the Qt::WA_DeleteOnClose attribute (so this "new" doesn't leak memory) m_pDlgControllerLearning = new DlgControllerLearning(this, m_pController); @@ -132,23 +140,36 @@ void DlgPrefController::showLearningWizard() { ControllerLearningEventFilter* pControllerLearning = m_pControllerManager->getControllerLearningEventFilter(); pControllerLearning->startListening(); - connect(pControllerLearning, SIGNAL(controlClicked(ControlObject*)), - m_pDlgControllerLearning, SLOT(controlClicked(ControlObject*))); - connect(m_pDlgControllerLearning, SIGNAL(listenForClicks()), - pControllerLearning, SLOT(startListening())); - connect(m_pDlgControllerLearning, SIGNAL(stopListeningForClicks()), - pControllerLearning, SLOT(stopListening())); - connect(m_pDlgControllerLearning, SIGNAL(stopLearning()), - this, SLOT(show())); - connect(m_pDlgControllerLearning, SIGNAL(inputMappingsLearned(MidiInputMappings)), - this, SLOT(midiInputMappingsLearned(MidiInputMappings))); + connect(pControllerLearning, + SIGNAL(controlClicked(ControlObject*)), + m_pDlgControllerLearning, + SLOT(controlClicked(ControlObject*))); + connect(m_pDlgControllerLearning, + SIGNAL(listenForClicks()), + pControllerLearning, + SLOT(startListening())); + connect(m_pDlgControllerLearning, + SIGNAL(stopListeningForClicks()), + pControllerLearning, + SLOT(stopListening())); + connect(m_pDlgControllerLearning, + SIGNAL(stopLearning()), + this, + SLOT(show())); + connect(m_pDlgControllerLearning, + SIGNAL(inputMappingsLearned(MidiInputMappings)), + this, + SLOT(midiInputMappingsLearned(MidiInputMappings))); emit mappingStarted(); - connect(m_pDlgControllerLearning, SIGNAL(stopLearning()), - this, SIGNAL(mappingEnded())); + connect(m_pDlgControllerLearning, + SIGNAL(stopLearning()), + this, + SIGNAL(mappingEnded())); } -void DlgPrefController::midiInputMappingsLearned(const MidiInputMappings& mappings) { +void DlgPrefController::midiInputMappingsLearned( + const MidiInputMappings& mappings) { // This is just a shortcut since doing a round-trip from Learning -> // Controller -> slotPresetLoaded -> setPreset is too heavyweight. if (m_pInputTableModel != NULL) { @@ -156,7 +177,8 @@ void DlgPrefController::midiInputMappingsLearned(const MidiInputMappings& mappin } } -QString DlgPrefController::presetShortName(const ControllerPresetPointer pPreset) const { +QString DlgPrefController::presetShortName( + const ControllerPresetPointer pPreset) const { QString presetName = tr("None"); if (pPreset) { QString name = pPreset->name(); @@ -173,7 +195,8 @@ QString DlgPrefController::presetShortName(const ControllerPresetPointer pPreset return presetName; } -QString DlgPrefController::presetName(const ControllerPresetPointer pPreset) const { +QString DlgPrefController::presetName( + const ControllerPresetPointer pPreset) const { if (pPreset) { QString name = pPreset->name(); if (name.length() > 0) @@ -182,7 +205,8 @@ QString DlgPrefController::presetName(const ControllerPresetPointer pPreset) con return tr("No Name"); } -QString DlgPrefController::presetDescription(const ControllerPresetPointer pPreset) const { +QString DlgPrefController::presetDescription( + const ControllerPresetPointer pPreset) const { if (pPreset) { QString description = pPreset->description(); if (description.length() > 0) @@ -191,7 +215,8 @@ QString DlgPrefController::presetDescription(const ControllerPresetPointer pPres return tr("No Description"); } -QString DlgPrefController::presetAuthor(const ControllerPresetPointer pPreset) const { +QString DlgPrefController::presetAuthor( + const ControllerPresetPointer pPreset) const { if (pPreset) { QString author = pPreset->author(); if (author.length() > 0) @@ -200,7 +225,8 @@ QString DlgPrefController::presetAuthor(const ControllerPresetPointer pPreset) c return tr("No Author"); } -QString DlgPrefController::presetForumLink(const ControllerPresetPointer pPreset) const { +QString DlgPrefController::presetForumLink( + const ControllerPresetPointer pPreset) const { QString url; if (pPreset) { QString link = pPreset->forumlink(); @@ -210,7 +236,8 @@ QString DlgPrefController::presetForumLink(const ControllerPresetPointer pPreset return url; } -QString DlgPrefController::presetWikiLink(const ControllerPresetPointer pPreset) const { +QString DlgPrefController::presetWikiLink( + const ControllerPresetPointer pPreset) const { QString url; if (pPreset) { QString link = pPreset->wikilink(); @@ -220,74 +247,112 @@ QString DlgPrefController::presetWikiLink(const ControllerPresetPointer pPreset) return url; } -QString DlgPrefController::presetScriptFileLinks(const ControllerPresetPointer pPreset) const { - QString scriptFileLinks; +QString DlgPrefController::presetScriptFileLinks( + const ControllerPresetPointer pPreset) const { + if (!pPreset || pPreset->getScriptFiles().empty()) { + return tr("No Scripts"); + } - if (pPreset) { - QList presetDirs; - presetDirs.append(userPresetsPath(m_pConfig)); - presetDirs.append(resourcePresetsPath(m_pConfig)); - QStringList linkList; - for (QList::iterator it = - pPreset->scripts.begin(); it != pPreset->scripts.end(); ++it) { - QString name = it->name; - QString path = ControllerManager::getAbsolutePath( - name, presetDirs); - QString scriptFileLink = "" + name + ""; - linkList << scriptFileLink; + QString systemPresetPath = resourcePresetsPath(m_pConfig); + QStringList linkList; + for (const auto& script : pPreset->getScriptFiles()) { + QString scriptFileLink = QStringLiteral("") + + script.name + QStringLiteral(""); + + if (!script.file.exists()) { + scriptFileLink += + QStringLiteral(" (") + tr("missing") + QStringLiteral(")"); + } else if (script.file.absoluteFilePath().startsWith( + systemPresetPath)) { + scriptFileLink += + QStringLiteral(" (") + tr("built-in") + QStringLiteral(")"); } - scriptFileLinks = linkList.join("
"); - } - return scriptFileLinks; -} -void DlgPrefController::slotDirty() { - m_bDirty = true; + linkList << scriptFileLink; + } + return linkList.join("
"); } -void DlgPrefController::enumeratePresets() { +void DlgPrefController::enumeratePresets(const QString& selectedPresetPath) { m_ui.comboBoxPreset->clear(); // qDebug() << "Enumerating presets for controller" << m_pController->getName(); - // Insert a dummy "..." item at the top to try to make it less confusing. + // Insert a dummy item at the top to try to make it less confusing. // (We don't want the first found file showing up as the default item when a // user has their controller plugged in) - m_ui.comboBoxPreset->addItem("..."); + QIcon noPresetIcon(":/images/ic_none.svg"); + m_ui.comboBoxPreset->addItem(noPresetIcon, "No Preset"); - // Ask the controller manager for a list of applicable presets - QSharedPointer pie = - m_pControllerManager->getMainThreadPresetEnumerator(); + PresetInfo match; + // Enumerate user presets + QIcon userPresetIcon(":/images/ic_custom.svg"); - // Not ready yet. Should be rare. We will re-enumerate on the next open of - // the preferences. - if (pie.isNull()) { - return; + // Reload user presets to detect added, changed or removed mappings + m_pControllerManager->getMainThreadUserPresetEnumerator()->loadSupportedPresets(); + + PresetInfo userPresetsMatch = enumeratePresetsFromEnumerator( + m_pControllerManager->getMainThreadUserPresetEnumerator(), + userPresetIcon); + if (userPresetsMatch.isValid()) { + match = userPresetsMatch; } - // Making the list of presets in the alphabetical order - QList presets = pie->getPresetsByExtension( - m_pController->presetExtension()); + // Insert a separator between user presets (+ dummy item) and system presets + m_ui.comboBoxPreset->insertSeparator(m_ui.comboBoxPreset->count()); - PresetInfo match; - for (const PresetInfo& preset : presets) { - m_ui.comboBoxPreset->addItem(preset.getName(), preset.getPath()); - if (m_pController->matchPreset(preset)) { - match = preset; - } + // Enumerate system presets + QIcon systemPresetIcon(":/images/ic_mixxx_symbolic.svg"); + PresetInfo systemPresetsMatch = enumeratePresetsFromEnumerator( + m_pControllerManager->getMainThreadSystemPresetEnumerator(), + systemPresetIcon); + if (systemPresetsMatch.isValid()) { + match = systemPresetsMatch; + } + + // Preselect configured or matching preset + int index = -1; + if (!selectedPresetPath.isEmpty()) { + index = m_ui.comboBoxPreset->findData(selectedPresetPath); + } else if (match.isValid()) { + index = m_ui.comboBoxPreset->findText(match.getName()); } + if (index == -1) { + m_ui.chkEnabledDevice->setEnabled(false); + } else { + m_ui.comboBoxPreset->setCurrentIndex(index); + m_ui.chkEnabledDevice->setEnabled(true); + } +} + +PresetInfo DlgPrefController::enumeratePresetsFromEnumerator( + QSharedPointer pPresetEnumerator, QIcon icon) { + PresetInfo match; - // Jump to matching device in list if it was found. - if (match.isValid()) { - int index = m_ui.comboBoxPreset->findText(match.getName()); - if (index != -1) { - m_ui.comboBoxPreset->setCurrentIndex(index); + // Check if enumerator is ready. Should be rare that it isn't. We will + // re-enumerate on the next open of the preferences. + if (!pPresetEnumerator.isNull()) { + // Get a list of presets in alphabetical order + QList systemPresets = + pPresetEnumerator->getPresetsByExtension( + m_pController->presetExtension()); + + for (const PresetInfo& preset : systemPresets) { + m_ui.comboBoxPreset->addItem( + icon, preset.getName(), preset.getPath()); + if (m_pController->matchPreset(preset)) { + match = preset; + } } } + + return match; } void DlgPrefController::slotUpdate() { - enumeratePresets(); + enumeratePresets(m_pControllerManager->getConfiguredPresetFileForDevice( + m_pController->getName())); // Check if the controller is open. bool deviceOpen = m_pController->isOpen(); @@ -303,94 +368,136 @@ void DlgPrefController::slotUpdate() { } void DlgPrefController::slotCancel() { - if (m_pInputTableModel != NULL) { - m_pInputTableModel->cancel(); + slotShowPreset(m_pController->getPreset()); +} + +void DlgPrefController::applyPresetChanges() { + if (m_pInputTableModel) { + m_pInputTableModel->apply(); } - if (m_pOutputTableModel != NULL) { - m_pOutputTableModel->cancel(); + if (m_pOutputTableModel) { + m_pOutputTableModel->apply(); } } void DlgPrefController::slotApply() { - if (m_bDirty) { - // Apply the presets and load the resulting preset. - if (m_pInputTableModel != NULL) { - m_pInputTableModel->apply(); - } + applyPresetChanges(); - if (m_pOutputTableModel != NULL) { - m_pOutputTableModel->apply(); - } - - // Load the resulting preset (which has been mutated by the input/output - // table models). The controller clones the preset so we aren't touching - // the same preset. - emit loadPreset(m_pController, m_pPreset); + // If no changes were made, do nothing + if (!(isDirty() || (m_pPreset && m_pPreset->isDirty()))) { + return; + } - //Select the "..." item again in the combobox. - m_ui.comboBoxPreset->setCurrentIndex(0); + bool bEnabled = false; + if (m_pPreset) { + bEnabled = m_ui.chkEnabledDevice->isChecked(); - bool wantEnabled = m_ui.chkEnabledDevice->isChecked(); - bool enabled = m_pController->isOpen(); - if (wantEnabled && !enabled) { - enableDevice(); - } else if (!wantEnabled && enabled) { - disableDevice(); + if (m_pPreset->isDirty()) { + savePreset(); } + } + m_ui.chkEnabledDevice->setChecked(bEnabled); - m_bDirty = false; + // The shouldn't be dirty at this pint because we already tried to save + // it. If that failed, don't apply the preset. + if (m_pPreset && m_pPreset->isDirty()) { + return; } + + // Load the resulting preset (which has been mutated by the input/output + // table models). The controller clones the preset so we aren't touching + // the same preset. + emit applyPreset(m_pController, m_pPreset, bEnabled); + + // Mark the dialog as not dirty + setDirty(false); } -void DlgPrefController::slotLoadPreset(int chosenIndex) { +void DlgPrefController::slotPresetSelected(int chosenIndex) { + QString presetPath; if (chosenIndex == 0) { - // User picked ... - return; + // User picked "No Preset" item + m_ui.chkEnabledDevice->setEnabled(false); + + if (m_ui.chkEnabledDevice->isChecked()) { + m_ui.chkEnabledDevice->setChecked(false); + setDirty(true); + } + } else { + // User picked a preset + m_ui.chkEnabledDevice->setEnabled(true); + + if (!m_ui.chkEnabledDevice->isChecked()) { + m_ui.chkEnabledDevice->setChecked(true); + setDirty(true); + } + + presetPath = m_ui.comboBoxPreset->itemData(chosenIndex).toString(); } - const QString presetPath = m_ui.comboBoxPreset->itemData(chosenIndex).toString(); - // When loading the preset, we only want to load from the same dir as the - // preset itself, otherwise when loading from the system-wide dir we'll - // start the search in the user's dir find the existing script, - // and do nothing. - const QFileInfo presetFileInfo(presetPath); - QList presetDirs; - presetDirs.append(presetFileInfo.canonicalPath()); + // Check if the preset is different from the configured preset + if (m_pControllerManager->getConfiguredPresetFileForDevice( + m_pController->getName()) != presetPath) { + setDirty(true); + } + + applyPresetChanges(); + if (m_pPreset && m_pPreset->isDirty()) { + if (QMessageBox::question(this, + tr("Preset has been edited"), + tr("Do you want to save the changes?")) == + QMessageBox::Yes) { + savePreset(); + } + } ControllerPresetPointer pPreset = ControllerPresetFileHandler::loadPreset( - presetPath, ControllerManager::getPresetPaths(m_pConfig)); + presetPath, QDir(resourcePresetsPath(m_pConfig))); - if (!pPreset) { + if (pPreset) { + DEBUG_ASSERT(!pPreset->isDirty()); + } + + slotShowPreset(pPreset); +} + +void DlgPrefController::savePreset() { + VERIFY_OR_DEBUG_ASSERT(m_pPreset) { return; } - // Import the preset scripts to the user scripts folder. - for (QList::iterator it = - pPreset->scripts.begin(); it != pPreset->scripts.end(); ++it) { - // No need to import builtin scripts. - if (it->builtin) { - continue; - } + if (!m_pPreset->isDirty()) { + qDebug() << "Preset is not dirty, no need to save it."; + return; + } - QString scriptPath = ControllerManager::getAbsolutePath( - it->name, presetDirs); + QFileInfo fileInfo(m_pPreset->filePath()); + QString fileName = fileInfo.fileName(); + // Add " (edited)" to preset name (if it's not already present) + QString editedSuffix = QStringLiteral(" (") + tr("edited") + QStringLiteral(")"); + if (!m_pPreset->name().endsWith(editedSuffix)) { + m_pPreset->setName(m_pPreset->name() + editedSuffix); + qDebug() << "Renamed preset to " << m_pPreset->name(); - QString importedScriptFileName; - // If a conflict exists then importScript will provide a new filename to - // use. If importing fails then load the preset anyway without the - // import. - if (m_pControllerManager->importScript(scriptPath, &importedScriptFileName)) { - it->name = importedScriptFileName; + // Add " (edited)" to file name (if it's not already present) + QString baseName = fileInfo.baseName(); + if (baseName.endsWith(editedSuffix)) { + baseName.chop(editedSuffix.size()); } + fileName = baseName + editedSuffix + QStringLiteral(".") + fileInfo.completeSuffix(); + } + QString filePath = QDir(userPresetsPath(m_pConfig)).absoluteFilePath(fileName); + + if (!m_pPreset->savePreset(filePath)) { + qDebug() << "Failed to save preset!"; } - // TODO(rryan): We really should not load the preset here. We should load it - // into the preferences GUI and then load it to the actual controller once - // the user hits apply. - emit loadPreset(m_pController, pPreset); - slotDirty(); + m_pPreset->setFilePath(filePath); + m_pPreset->setDirty(false); + + enumeratePresets(m_pPreset->filePath()); } void DlgPrefController::initTableView(QTableView* pTable) { @@ -413,7 +520,7 @@ void DlgPrefController::initTableView(QTableView* pTable) { pTable->setAlternatingRowColors(true); } -void DlgPrefController::slotPresetLoaded(ControllerPresetPointer preset) { +void DlgPrefController::slotShowPreset(ControllerPresetPointer preset) { m_ui.labelLoadedPreset->setText(presetName(preset)); m_ui.labelLoadedPresetDescription->setText(presetDescription(preset)); m_ui.labelLoadedPresetAuthor->setText(presetAuthor(preset)); @@ -449,13 +556,6 @@ void DlgPrefController::slotPresetLoaded(ControllerPresetPointer preset) { ControllerInputMappingTableModel* pInputModel = new ControllerInputMappingTableModel(this); - // If the model reports changes, mark ourselves as dirty. - connect(pInputModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), - this, SLOT(slotDirty())); - connect(pInputModel, SIGNAL(rowsInserted(QModelIndex, int, int)), - this, SLOT(slotDirty())); - connect(pInputModel, SIGNAL(rowsRemoved(QModelIndex, int, int)), - this, SLOT(slotDirty())); pInputModel->setPreset(preset); QSortFilterProxyModel* pInputProxyModel = new QSortFilterProxyModel(this); @@ -503,23 +603,6 @@ void DlgPrefController::slotPresetLoaded(ControllerPresetPointer preset) { m_pOutputTableModel = pOutputModel; } -void DlgPrefController::slotEnableDevice(bool enable) { - slotDirty(); - - // Set tree item text to normal/bold. - emit controllerEnabled(this, enable); -} - -void DlgPrefController::enableDevice() { - emit openController(m_pController); - //TODO: Should probably check if open() actually succeeded. -} - -void DlgPrefController::disableDevice() { - emit closeController(m_pController); - //TODO: Should probably check if close() actually succeeded. -} - void DlgPrefController::addInputMapping() { if (m_pInputTableModel) { m_pInputTableModel->addEmptyMapping(); @@ -532,7 +615,6 @@ void DlgPrefController::addInputMapping() { m_ui.m_pInputMappingTableView->selectionModel()->select( QItemSelection(left, right), QItemSelectionModel::Clear | QItemSelectionModel::Select); m_ui.m_pInputMappingTableView->scrollTo(left); - slotDirty(); } } @@ -543,7 +625,6 @@ void DlgPrefController::removeInputMappings() { QModelIndexList selectedIndices = selection.indexes(); if (selectedIndices.size() > 0 && m_pInputTableModel) { m_pInputTableModel->removeMappings(selectedIndices); - slotDirty(); } } } @@ -557,7 +638,6 @@ void DlgPrefController::clearAllInputMappings() { } if (m_pInputTableModel) { m_pInputTableModel->clear(); - slotDirty(); } } @@ -573,7 +653,6 @@ void DlgPrefController::addOutputMapping() { m_ui.m_pOutputMappingTableView->selectionModel()->select( QItemSelection(left, right), QItemSelectionModel::Clear | QItemSelectionModel::Select); m_ui.m_pOutputMappingTableView->scrollTo(left); - slotDirty(); } } @@ -584,7 +663,6 @@ void DlgPrefController::removeOutputMappings() { QModelIndexList selectedIndices = selection.indexes(); if (selectedIndices.size() > 0 && m_pOutputTableModel) { m_pOutputTableModel->removeMappings(selectedIndices); - slotDirty(); } } } @@ -598,6 +676,5 @@ void DlgPrefController::clearAllOutputMappings() { } if (m_pOutputTableModel) { m_pOutputTableModel->clear(); - slotDirty(); } } diff --git a/src/controllers/dlgprefcontroller.h b/src/controllers/dlgprefcontroller.h index 58a0c81849d..45774383310 100644 --- a/src/controllers/dlgprefcontroller.h +++ b/src/controllers/dlgprefcontroller.h @@ -1,9 +1,7 @@ -/** -* @file dlgprefcontroller.h -* @author Sean M. Pappalardo spappalardo@mixxx.org -* @date Mon May 2 2011 -* @brief Configuration dialog for a DJ controller -*/ +/// @file dlgprefcontroller.h +/// @author Sean M. Pappalardo spappalardo@mixxx.org +/// @date Mon May 2 2011 +/// @brief Configuration dialog for a single DJ controller #ifndef DLGPREFCONTROLLER_H #define DLGPREFCONTROLLER_H @@ -23,6 +21,7 @@ // Forward declarations class Controller; class ControllerManager; +class PresetInfoEnumerator; class DlgPrefController : public DlgPreferencePage { Q_OBJECT @@ -39,24 +38,18 @@ class DlgPrefController : public DlgPreferencePage { void slotCancel(); // Called when preference dialog (not this dialog) is displayed. void slotUpdate(); - // Called when the user toggles the enabled checkbox. - void slotEnableDevice(bool enable); - // Called when the user selects a preset from the combobox. - void slotLoadPreset(int index); - // Mark that we need to apply the settings. - void slotDirty(); signals: - void controllerEnabled(DlgPrefController*, bool); - void openController(Controller* pController); - void closeController(Controller* pController); - void loadPreset(Controller* pController, QString controllerName); - void loadPreset(Controller* pController, ControllerPresetPointer pPreset); + void applyPreset(Controller* pController, ControllerPresetPointer pPreset, bool bEnabled); void mappingStarted(); void mappingEnded(); private slots: - void slotPresetLoaded(ControllerPresetPointer preset); + /// Called when the user selects another preset in the combobox + void slotPresetSelected(int index); + /// Used to selected the current preset in the combobox and display the + /// preset information. + void slotShowPreset(ControllerPresetPointer preset); // Input mappings void addInputMapping(); @@ -79,11 +72,35 @@ class DlgPrefController : public DlgPreferencePage { QString presetForumLink(const ControllerPresetPointer pPreset) const; QString presetWikiLink(const ControllerPresetPointer pPreset) const; QString presetScriptFileLinks(const ControllerPresetPointer pPreset) const; - void savePreset(QString path); + void applyPresetChanges(); + void savePreset(); void initTableView(QTableView* pTable); - // Reload the mappings in the dropdown dialog - void enumeratePresets(); + /// Set dirty state (i.e. changes have been made). + /// + /// When this preferences page is marked as "dirty", changes have occured + /// that can be applied or discarded. + /// + /// @param bDirty The new dialog's dirty state. + void setDirty(bool bDirty) { + m_bDirty = bDirty; + } + + /// Set dirty state (i.e. changes have been made). + /// + /// When this preferences page is marked as "dirty", changes have occured + /// that can be applied or discarded. + /// + /// @param bDirty The new dialog's dirty state. + bool isDirty() { + return m_bDirty; + } + + /// Reload the mappings in the dropdown dialog + void enumeratePresets(const QString& selectedPresetPath); + PresetInfo enumeratePresetsFromEnumerator( + QSharedPointer pPresetEnumerator, + QIcon icon = QIcon()); void enableDevice(); void disableDevice(); diff --git a/src/controllers/dlgprefcontrollers.cpp b/src/controllers/dlgprefcontrollers.cpp index babf12c0887..3af59017f85 100644 --- a/src/controllers/dlgprefcontrollers.cpp +++ b/src/controllers/dlgprefcontrollers.cpp @@ -55,11 +55,6 @@ void DlgPrefControllers::slotApply() { foreach (DlgPrefController* pControllerWindows, m_controllerWindows) { pControllerWindows->slotApply(); } - - // Save all controller presets. - // TODO(rryan): Get rid of this and make DlgPrefController do this for each - // preset. - m_pControllerManager->savePresets(); } bool DlgPrefControllers::handleTreeItemClick(QTreeWidgetItem* clickedItem) { @@ -116,8 +111,11 @@ void DlgPrefControllers::setupControllerWidgets() { m_controllerWindows.append(controllerDlg); m_pDlgPreferences->addPageWidget(controllerDlg); - connect(controllerDlg, SIGNAL(controllerEnabled(DlgPrefController*, bool)), - this, SLOT(slotHighlightDevice(DlgPrefController*, bool))); + connect(pController, + &Controller::openChanged, + [this, controllerDlg](bool bOpen) { + slotHighlightDevice(controllerDlg, bOpen); + }); QTreeWidgetItem * controllerWindowLink = new QTreeWidgetItem(QTreeWidgetItem::Type); controllerWindowLink->setIcon(0, QIcon(":/images/preferences/ic_preferences_controllers.png")); diff --git a/src/controllers/dlgprefcontrollers.h b/src/controllers/dlgprefcontrollers.h index 9fbef1bc47e..5e704f211ca 100644 --- a/src/controllers/dlgprefcontrollers.h +++ b/src/controllers/dlgprefcontrollers.h @@ -1,5 +1,4 @@ -#ifndef DLGPREFCONTROLLERS_H -#define DLGPREFCONTROLLERS_H +#pragma once #include @@ -11,6 +10,10 @@ class DlgPreferences; class DlgPrefController; class ControllerManager; +/// Controllers Overview in the preferences +/// +/// This dialog allows selecting controllers for configuration. + class DlgPrefControllers : public DlgPreferencePage, public Ui::DlgPrefControllersDlg { Q_OBJECT public: @@ -43,5 +46,3 @@ class DlgPrefControllers : public DlgPreferencePage, public Ui::DlgPrefControlle QList m_controllerWindows; QList m_controllerTreeItems; }; - -#endif /* DLGPREFCONTROLLERS_H */ diff --git a/src/controllers/hid/hidcontroller.cpp b/src/controllers/hid/hidcontroller.cpp index ec118012088..08bff7ef126 100644 --- a/src/controllers/hid/hidcontroller.cpp +++ b/src/controllers/hid/hidcontroller.cpp @@ -101,11 +101,6 @@ void HidController::visit(const HidControllerPreset* preset) { emit presetLoaded(getPreset()); } -bool HidController::savePreset(const QString fileName) const { - HidControllerPresetFileHandler handler; - return handler.save(m_preset, getName(), fileName); -} - bool HidController::matchPreset(const PresetInfo& preset) { const QList& products = preset.getProducts(); for (const auto& product : products) { diff --git a/src/controllers/hid/hidcontroller.h b/src/controllers/hid/hidcontroller.h index f60843067a8..543684ee766 100644 --- a/src/controllers/hid/hidcontroller.h +++ b/src/controllers/hid/hidcontroller.h @@ -31,8 +31,6 @@ class HidController final : public Controller { return ControllerPresetPointer(pClone); } - bool savePreset(const QString fileName) const override; - void visit(const MidiControllerPreset* preset) override; void visit(const HidControllerPreset* preset) override; diff --git a/src/controllers/hid/hidcontrollerpreset.cpp b/src/controllers/hid/hidcontrollerpreset.cpp new file mode 100644 index 00000000000..5bcbd327255 --- /dev/null +++ b/src/controllers/hid/hidcontrollerpreset.cpp @@ -0,0 +1,34 @@ +/// @file hidcontrollerpreset.cpp +/// @author Jan Holthuis holzhaus@mixxx.org +/// @date Mon 8 Apr 2020 +/// @brief HID/Bulk Controller Preset +/// +/// This class represents a HID or Bulk controller preset, containing the data +/// elements that make it up. + +#include "controllers/hid/hidcontrollerpreset.h" + +#include "controllers/controllerpresetvisitor.h" +#include "controllers/defs_controllers.h" +#include "controllers/hid/hidcontrollerpresetfilehandler.h" + +bool HidControllerPreset::savePreset(const QString& fileName) const { + HidControllerPresetFileHandler handler; + return handler.save(*this, fileName); +} + +void HidControllerPreset::accept(ControllerPresetVisitor* visitor) { + if (visitor) { + visitor->visit(this); + } +} + +void HidControllerPreset::accept(ConstControllerPresetVisitor* visitor) const { + if (visitor) { + visitor->visit(this); + } +} + +bool HidControllerPreset::isMappable() const { + return false; +} diff --git a/src/controllers/hid/hidcontrollerpreset.h b/src/controllers/hid/hidcontrollerpreset.h index 4a66ea0554b..77cfc7f34b3 100644 --- a/src/controllers/hid/hidcontrollerpreset.h +++ b/src/controllers/hid/hidcontrollerpreset.h @@ -1,29 +1,21 @@ -#ifndef HIDCONTROLLERPRESET_H -#define HIDCONTROLLERPRESET_H +#pragma once +/// @file hidcontrollerpreset.h +/// @brief HID/Bulk Controller Preset #include "controllers/controllerpreset.h" #include "controllers/controllerpresetvisitor.h" +#include "controllers/hid/hidcontrollerpresetfilehandler.h" +/// This class represents a HID or Bulk controller preset, containing the data +/// elements that make it up. class HidControllerPreset : public ControllerPreset { public: HidControllerPreset() {} virtual ~HidControllerPreset() {} - virtual void accept(ControllerPresetVisitor* visitor) { - if (visitor) { - visitor->visit(this); - } - } + bool savePreset(const QString& fileName) const override; - virtual void accept(ConstControllerPresetVisitor* visitor) const { - if (visitor) { - visitor->visit(this); - } - } - - virtual bool isMappable() const { - return false; - } + virtual void accept(ControllerPresetVisitor* visitor); + virtual void accept(ConstControllerPresetVisitor* visitor) const; + virtual bool isMappable() const; }; - -#endif /* HIDCONTROLLERPRESET_H */ diff --git a/src/controllers/hid/hidcontrollerpresetfilehandler.cpp b/src/controllers/hid/hidcontrollerpresetfilehandler.cpp index c300103a6fd..253090ce9fe 100644 --- a/src/controllers/hid/hidcontrollerpresetfilehandler.cpp +++ b/src/controllers/hid/hidcontrollerpresetfilehandler.cpp @@ -1,25 +1,26 @@ #include "controllers/hid/hidcontrollerpresetfilehandler.h" bool HidControllerPresetFileHandler::save(const HidControllerPreset& preset, - const QString deviceName, - const QString fileName) const { - QDomDocument doc = buildRootWithScripts(preset, deviceName); + const QString& fileName) const { + QDomDocument doc = buildRootWithScripts(preset); return writeDocument(doc, fileName); } -ControllerPresetPointer HidControllerPresetFileHandler::load(const QDomElement root, - const QString deviceName) { +ControllerPresetPointer HidControllerPresetFileHandler::load(const QDomElement& root, + const QString& filePath, + const QDir& systemPresetsPath) { if (root.isNull()) { return ControllerPresetPointer(); } - QDomElement controller = getControllerNode(root, deviceName); + QDomElement controller = getControllerNode(root); if (controller.isNull()) { return ControllerPresetPointer(); } HidControllerPreset* preset = new HidControllerPreset(); + preset->setFilePath(filePath); parsePresetInfo(root, preset); - addScriptFilesToPreset(controller, preset); + addScriptFilesToPreset(controller, preset, systemPresetsPath); return ControllerPresetPointer(preset); } diff --git a/src/controllers/hid/hidcontrollerpresetfilehandler.h b/src/controllers/hid/hidcontrollerpresetfilehandler.h index 7e27fc0eafe..7f99f56481d 100644 --- a/src/controllers/hid/hidcontrollerpresetfilehandler.h +++ b/src/controllers/hid/hidcontrollerpresetfilehandler.h @@ -9,12 +9,12 @@ class HidControllerPresetFileHandler : public ControllerPresetFileHandler { HidControllerPresetFileHandler() {}; virtual ~HidControllerPresetFileHandler() {}; - bool save(const HidControllerPreset& preset, - const QString deviceName, const QString fileName) const; + bool save(const HidControllerPreset& preset, const QString& fileName) const; private: - virtual ControllerPresetPointer load(const QDomElement root, - const QString deviceName); + virtual ControllerPresetPointer load(const QDomElement& root, + const QString& filePath, + const QDir& systemPresetsPath); }; #endif /* HIDCONTROLLERPRESETFILEHANDLER_H */ diff --git a/src/controllers/midi/midicontroller.cpp b/src/controllers/midi/midicontroller.cpp index 877897f00f1..7f6c8ea5d6b 100644 --- a/src/controllers/midi/midicontroller.cpp +++ b/src/controllers/midi/midicontroller.cpp @@ -54,14 +54,9 @@ bool MidiController::matchPreset(const PresetInfo& preset) { return false; } -bool MidiController::savePreset(const QString fileName) const { - MidiControllerPresetFileHandler handler; - return handler.save(m_preset, getName(), fileName); -} - -bool MidiController::applyPreset(QList scriptPaths, bool initializeScripts) { +bool MidiController::applyPreset(bool initializeScripts) { // Handles the engine - bool result = Controller::applyPreset(scriptPaths, initializeScripts); + bool result = Controller::applyPreset(initializeScripts); // Only execute this code if this is an output device if (isOutputDevice()) { @@ -75,11 +70,11 @@ bool MidiController::applyPreset(QList scriptPaths, bool initializeScri } void MidiController::createOutputHandlers() { - if (m_preset.outputMappings.isEmpty()) { + if (m_preset.getOutputMappings().isEmpty()) { return; } - QHashIterator outIt(m_preset.outputMappings); + QHashIterator outIt(m_preset.getOutputMappings()); QStringList failures; while (outIt.hasNext()) { outIt.next(); @@ -185,15 +180,19 @@ void MidiController::clearTemporaryInputMappings() { void MidiController::commitTemporaryInputMappings() { // We want to replace duplicates that exist in m_preset but allow duplicates // in m_temporaryInputMappings. To do this, we first remove every key in - // m_temporaryInputMappings from m_preset.inputMappings. + // m_temporaryInputMappings from m_preset's input mappings. for (auto it = m_temporaryInputMappings.constBegin(); it != m_temporaryInputMappings.constEnd(); ++it) { - m_preset.inputMappings.remove(it.key()); + m_preset.removeInputMapping(it.key()); } - // Now, we can just use unite since we manually removed the duplicates in - // the original set. - m_preset.inputMappings.unite(m_temporaryInputMappings); + // Now, we can just use add all mappings from m_temporaryInputMappings + // since we removed the duplicates in the original set. + for (auto it = m_temporaryInputMappings.constBegin(); + it != m_temporaryInputMappings.constEnd(); + ++it) { + m_preset.addInputMapping(it.key(), it.value()); + } m_temporaryInputMappings.clear(); } @@ -219,8 +218,8 @@ void MidiController::receive(unsigned char status, unsigned char control, } } - auto it = m_preset.inputMappings.constFind(mappingKey.key); - for (; it != m_preset.inputMappings.constEnd() && it.key() == mappingKey.key; ++it) { + auto it = m_preset.getInputMappings().constFind(mappingKey.key); + for (; it != m_preset.getInputMappings().constEnd() && it.key() == mappingKey.key; ++it) { processInputMapping(it.value(), status, control, value, timestamp); } } @@ -470,8 +469,8 @@ void MidiController::receive(QByteArray data, mixxx::Duration timestamp) { } } - auto it = m_preset.inputMappings.constFind(mappingKey.key); - for (; it != m_preset.inputMappings.constEnd() && it.key() == mappingKey.key; ++it) { + auto it = m_preset.getInputMappings().constFind(mappingKey.key); + for (; it != m_preset.getInputMappings().constEnd() && it.key() == mappingKey.key; ++it) { processInputMapping(it.value(), data, timestamp); } } diff --git a/src/controllers/midi/midicontroller.h b/src/controllers/midi/midicontroller.h index a81ff7d680c..0fe825f5661 100644 --- a/src/controllers/midi/midicontroller.h +++ b/src/controllers/midi/midicontroller.h @@ -34,8 +34,6 @@ class MidiController : public Controller { return ControllerPresetPointer(pClone); } - bool savePreset(const QString fileName) const override; - void visit(const MidiControllerPreset* preset) override; void visit(const HidControllerPreset* preset) override; @@ -59,9 +57,9 @@ class MidiController : public Controller { Q_INVOKABLE virtual void sendShortMsg(unsigned char status, unsigned char byte1, unsigned char byte2) = 0; - // Alias for send() - // The length parameter is here for backwards compatibility for when scripts - // were required to specify it. + /// Alias for send() + /// The length parameter is here for backwards compatibility for when scripts + /// were required to specify it. Q_INVOKABLE inline void sendSysexMsg(QList data, unsigned int length = 0) { Q_UNUSED(length); send(data); @@ -75,8 +73,13 @@ class MidiController : public Controller { int close() override; private slots: - // Initializes the engine and static output mappings. - bool applyPreset(QList scriptPaths, bool initializeScripts) override; + /// Apply the preset to the controller. + /// @brief Initializes both controller engine and static output mappings. + /// + /// @param initializeScripts Can be set to false to skip script + /// initialization for unit tests. + /// @return Returns whether it was successful. + bool applyPreset(bool initializeScripts = false) override; void learnTemporaryInputMappings(const MidiInputMappings& mappings); void clearTemporaryInputMappings(); @@ -97,8 +100,8 @@ class MidiController : public Controller { void updateAllOutputs(); void destroyOutputHandlers(); - // Returns a pointer to the currently loaded controller preset. For internal - // use only. + /// Returns a pointer to the currently loaded controller preset. For internal + /// use only. ControllerPreset* preset() override { return &m_preset; } diff --git a/src/controllers/midi/midicontrollerpreset.cpp b/src/controllers/midi/midicontrollerpreset.cpp new file mode 100644 index 00000000000..31d363349ac --- /dev/null +++ b/src/controllers/midi/midicontrollerpreset.cpp @@ -0,0 +1,77 @@ +/// @file midicontrollerpreset.cpp +/// @author Jan Holthuis holzhaus@mixxx.org +/// @date Wed 8 Apr 2020 +/// @brief MIDI Controller Preset +/// +/// This class represents a MIDI controller preset, containing the data elements +/// that make it up. + +#include "controllers/midi/midicontrollerpreset.h" + +#include "controllers/defs_controllers.h" +#include "controllers/midi/midicontrollerpresetfilehandler.h" + +bool MidiControllerPreset::savePreset(const QString& fileName) const { + MidiControllerPresetFileHandler handler; + return handler.save(*this, fileName); +} + +void MidiControllerPreset::accept(ControllerPresetVisitor* visitor) { + if (visitor) { + visitor->visit(this); + } +} + +void MidiControllerPreset::accept(ConstControllerPresetVisitor* visitor) const { + if (visitor) { + visitor->visit(this); + } +} + +bool MidiControllerPreset::isMappable() const { + return true; +} + +void MidiControllerPreset::addInputMapping(uint16_t key, MidiInputMapping mapping) { + m_inputMappings.insertMulti(key, mapping); + setDirty(true); +} + +void MidiControllerPreset::removeInputMapping(uint16_t key) { + m_inputMappings.remove(key); + setDirty(true); +} + +const QHash& MidiControllerPreset::getInputMappings() const { + return m_inputMappings; +} + +void MidiControllerPreset::setInputMappings(const QHash& mappings) { + if (m_inputMappings != mappings) { + m_inputMappings.clear(); + m_inputMappings.unite(mappings); + setDirty(true); + } +} + +void MidiControllerPreset::addOutputMapping(ConfigKey key, MidiOutputMapping mapping) { + m_outputMappings.insertMulti(key, mapping); + setDirty(true); +} + +void MidiControllerPreset::removeOutputMapping(ConfigKey key) { + m_outputMappings.remove(key); + setDirty(true); +} + +const QHash& MidiControllerPreset::getOutputMappings() const { + return m_outputMappings; +} + +void MidiControllerPreset::setOutputMappings(const QHash& mappings) { + if (m_outputMappings != mappings) { + m_outputMappings.clear(); + m_outputMappings.unite(mappings); + setDirty(true); + } +} diff --git a/src/controllers/midi/midicontrollerpreset.h b/src/controllers/midi/midicontrollerpreset.h index 12ef6f0a13d..530e1e34950 100644 --- a/src/controllers/midi/midicontrollerpreset.h +++ b/src/controllers/midi/midicontrollerpreset.h @@ -1,16 +1,8 @@ -/** - * @file midicontrollerpreset.h - * @author Sean Pappalardo spappalardo@mixxx.org - * @date Mon 9 Apr 2012 - * @brief MIDI Controller preset - * - * This class represents a MIDI controller preset, containing the data elements - * that make it up. - * - */ - -#ifndef MIDICONTROLLERPRESET_H -#define MIDICONTROLLERPRESET_H +#pragma once +/// @file midicontrollerpreset.h +/// @author Sean Pappalardo spappalardo@mixxx.org +/// @date Mon 9 Apr 2012 +/// @brief MIDI Controller preset #include @@ -18,30 +10,33 @@ #include "controllers/controllerpresetvisitor.h" #include "controllers/midi/midimessage.h" +/// This class represents a MIDI controller preset, containing the data elements +/// that make it up. class MidiControllerPreset : public ControllerPreset { public: - MidiControllerPreset() {} - virtual ~MidiControllerPreset() {} + MidiControllerPreset(){}; + virtual ~MidiControllerPreset(){}; - virtual void accept(ControllerPresetVisitor* visitor) { - if (visitor) { - visitor->visit(this); - } - } + bool savePreset(const QString& fileName) const override; - virtual void accept(ConstControllerPresetVisitor* visitor) const { - if (visitor) { - visitor->visit(this); - } - } + virtual void accept(ControllerPresetVisitor* visitor); + virtual void accept(ConstControllerPresetVisitor* visitor) const; + virtual bool isMappable() const; - virtual bool isMappable() const { - return true; - } + // Input mappings + void addInputMapping(uint16_t key, MidiInputMapping mapping); + void removeInputMapping(uint16_t key); + const QHash& getInputMappings() const; + void setInputMappings(const QHash& mappings); + // Output mappings + void addOutputMapping(ConfigKey key, MidiOutputMapping mapping); + void removeOutputMapping(ConfigKey key); + const QHash& getOutputMappings() const; + void setOutputMappings(const QHash& mappings); + + private: // MIDI input and output mappings. - QHash inputMappings; - QHash outputMappings; + QHash m_inputMappings; + QHash m_outputMappings; }; - -#endif diff --git a/src/controllers/midi/midicontrollerpresetfilehandler.cpp b/src/controllers/midi/midicontrollerpresetfilehandler.cpp index 32e01244768..fddafd929a7 100644 --- a/src/controllers/midi/midicontrollerpresetfilehandler.cpp +++ b/src/controllers/midi/midicontrollerpresetfilehandler.cpp @@ -14,22 +14,24 @@ #define DEFAULT_OUTPUT_ON 0x7F #define DEFAULT_OUTPUT_OFF 0x00 -ControllerPresetPointer MidiControllerPresetFileHandler::load(const QDomElement root, - const QString deviceName) { +ControllerPresetPointer MidiControllerPresetFileHandler::load(const QDomElement& root, + const QString& filePath, + const QDir& systemPresetsPath) { if (root.isNull()) { return ControllerPresetPointer(); } - QDomElement controller = getControllerNode(root, deviceName); + QDomElement controller = getControllerNode(root); if (controller.isNull()) { return ControllerPresetPointer(); } MidiControllerPreset* preset = new MidiControllerPreset(); + preset->setFilePath(filePath); // Superclass handles parsing tag and script files parsePresetInfo(root, preset); - addScriptFilesToPreset(controller, preset); + addScriptFilesToPreset(controller, preset, systemPresetsPath); QDomElement control = controller.firstChildElement("controls").firstChildElement("control"); @@ -101,7 +103,7 @@ ControllerPresetPointer MidiControllerPresetFileHandler::load(const QDomElement // Use insertMulti because we support multiple inputs mappings for the // same input MidiKey. - preset->inputMappings.insertMulti(mapping.key.key, mapping); + preset->addInputMapping(mapping.key.key, mapping); control = control.nextSiblingElement("control"); } @@ -181,7 +183,7 @@ ControllerPresetPointer MidiControllerPresetFileHandler::load(const QDomElement // Use insertMulti because we support multiple outputs from the same // control. - preset->outputMappings.insertMulti(mapping.controlKey, mapping); + preset->addOutputMapping(mapping.controlKey, mapping); output = output.nextSiblingElement("output"); } @@ -192,10 +194,9 @@ ControllerPresetPointer MidiControllerPresetFileHandler::load(const QDomElement } bool MidiControllerPresetFileHandler::save(const MidiControllerPreset& preset, - const QString deviceName, - const QString fileName) const { - qDebug() << "Saving preset for" << deviceName << "to" << fileName; - QDomDocument doc = buildRootWithScripts(preset, deviceName); + const QString& fileName) const { + qDebug() << "Saving preset" << preset.name() << "to" << fileName; + QDomDocument doc = buildRootWithScripts(preset); addControlsToDocument(preset, &doc); return writeDocument(doc, fileName); } @@ -209,11 +210,12 @@ void MidiControllerPresetFileHandler::addControlsToDocument(const MidiController QDomElement controls = doc->createElement("controls"); // We will iterate over all of the values that have the same keys, so we need // to remove duplicate keys or else we'll duplicate those values. - auto sortedInputKeys = preset.inputMappings.uniqueKeys(); + auto sortedInputKeys = preset.getInputMappings().uniqueKeys(); std::sort(sortedInputKeys.begin(), sortedInputKeys.end()); for (const auto& key : sortedInputKeys) { - for (auto it = preset.inputMappings.constFind(key); - it != preset.inputMappings.constEnd() && it.key() == key; ++it) { + for (auto it = preset.getInputMappings().constFind(key); + it != preset.getInputMappings().constEnd() && it.key() == key; + ++it) { QDomElement controlNode = inputMappingToXML(doc, it.value()); controls.appendChild(controlNode); } @@ -223,11 +225,12 @@ void MidiControllerPresetFileHandler::addControlsToDocument(const MidiController // Repeat the process for the output mappings. QDomElement outputs = doc->createElement("outputs"); - auto sortedOutputKeys = preset.outputMappings.uniqueKeys(); + auto sortedOutputKeys = preset.getOutputMappings().uniqueKeys(); std::sort(sortedOutputKeys.begin(), sortedOutputKeys.end()); for (const auto& key : sortedOutputKeys) { - for (auto it = preset.outputMappings.constFind(key); - it != preset.outputMappings.constEnd() && it.key() == key; ++it) { + for (auto it = preset.getOutputMappings().constFind(key); + it != preset.getOutputMappings().constEnd() && it.key() == key; + ++it) { QDomElement outputNode = outputMappingToXML(doc, it.value()); outputs.appendChild(outputNode); } diff --git a/src/controllers/midi/midicontrollerpresetfilehandler.h b/src/controllers/midi/midicontrollerpresetfilehandler.h index e43e850580f..ad7c43419e0 100644 --- a/src/controllers/midi/midicontrollerpresetfilehandler.h +++ b/src/controllers/midi/midicontrollerpresetfilehandler.h @@ -16,11 +16,12 @@ class MidiControllerPresetFileHandler : public ControllerPresetFileHandler { MidiControllerPresetFileHandler() {}; virtual ~MidiControllerPresetFileHandler() {}; - bool save(const MidiControllerPreset& preset, - const QString deviceName, const QString fileName) const; + bool save(const MidiControllerPreset& preset, const QString& fileName) const; private: - virtual ControllerPresetPointer load(const QDomElement root, const QString deviceName); + virtual ControllerPresetPointer load(const QDomElement& root, + const QString& filePath, + const QDir& systemPresetPath); void addControlsToDocument(const MidiControllerPreset& preset, QDomDocument* doc) const; diff --git a/src/controllers/midi/midimessage.h b/src/controllers/midi/midimessage.h index 5e04d07ef3e..8223dea32e8 100644 --- a/src/controllers/midi/midimessage.h +++ b/src/controllers/midi/midimessage.h @@ -160,11 +160,19 @@ struct MidiInputMapping { control(control) { } - // Don't use descriptions in operator== since we only use equality testing - // for unit tests. + MidiInputMapping(MidiKey key, + MidiOptions options, + const ConfigKey& control, + QString description) + : key(key), + options(options), + control(control), + description(description) { + } + bool operator==(const MidiInputMapping& other) const { return key == other.key && options == other.options && - control == other.control; + control == other.control && description == other.description; } MidiKey key; diff --git a/src/test/controller_preset_validation_test.cpp b/src/test/controller_preset_validation_test.cpp index a1a65f4015f..415ca1920d8 100644 --- a/src/test/controller_preset_validation_test.cpp +++ b/src/test/controller_preset_validation_test.cpp @@ -35,11 +35,6 @@ class FakeController : public Controller { } } - bool savePreset(const QString fileName) const override { - Q_UNUSED(fileName); - return true; - } - void visit(const MidiControllerPreset* preset) override { m_bMidiPreset = true; m_bHidPreset = false; @@ -122,14 +117,16 @@ FakeController::~FakeController() { class ControllerPresetValidationTest : public MixxxTest { protected: void SetUp() override { - m_presetPaths << QDir::currentPath() + "/res/controllers"; - m_pEnumerator.reset(new PresetInfoEnumerator(m_presetPaths)); + m_presetPath = QDir::current(); + m_presetPath.cd("res/controllers"); + m_pEnumerator.reset(new PresetInfoEnumerator(QList{m_presetPath.absolutePath()})); } bool testLoadPreset(const PresetInfo& preset) { ControllerPresetPointer pPreset = - ControllerPresetFileHandler::loadPreset(preset.getPath(), - m_presetPaths); + ControllerPresetFileHandler::loadPreset( + preset.getPath(), + m_presetPath); if (pPreset.isNull()) { return false; } @@ -139,13 +136,12 @@ class ControllerPresetValidationTest : public MixxxTest { controller.startEngine(); controller.setPreset(*pPreset); // Do not initialize the scripts. - bool result = controller.applyPreset(m_presetPaths, false); + bool result = controller.applyPreset(false); controller.stopEngine(); return result; } - - QStringList m_presetPaths; + QDir m_presetPath; QScopedPointer m_pEnumerator; }; diff --git a/src/test/learningutilstest.cpp b/src/test/learningutilstest.cpp index 664c88a8275..c6e2057503b 100644 --- a/src/test/learningutilstest.cpp +++ b/src/test/learningutilstest.cpp @@ -15,6 +15,20 @@ class LearningUtilsTest : public MixxxTest { m_messages.append(qMakePair(MidiKey(status, control), value)); } + /// Check if mapping in present in mapping list. + /// Similar to MidiInputMappings::contains(const MidiInputMapping&), but + /// does not compare the description. + bool containsMapping(const MidiInputMappings& haystack, const MidiInputMapping& needle) { + for (const MidiInputMapping& mapping : haystack) { + if (mapping.key == needle.key && + mapping.options == needle.options && + mapping.control == needle.control) { + return true; + } + } + return false; + } + QList > m_messages; }; @@ -29,8 +43,10 @@ TEST_F(LearningUtilsTest, NoteOnButton) { ASSERT_EQ(1, mappings.size()); EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_NOTE_ON | 0x01, 0x10), - MidiOptions(), control), - mappings.first()); + MidiOptions(), + control, + mappings.first().description), + mappings.first()); } TEST_F(LearningUtilsTest, NoteOnNoteOffButton) { @@ -44,11 +60,15 @@ TEST_F(LearningUtilsTest, NoteOnNoteOffButton) { ASSERT_EQ(2, mappings.size()); EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_NOTE_ON | 0x01, 0x10), - MidiOptions(), control), - mappings.at(0)); + MidiOptions(), + control, + mappings.at(0).description), + mappings.at(0)); EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_NOTE_OFF | 0x01, 0x10), - MidiOptions(), control), - mappings.at(1)); + MidiOptions(), + control, + mappings.at(1).description), + mappings.at(1)); } TEST_F(LearningUtilsTest, CC7BitKnob) { @@ -69,8 +89,10 @@ TEST_F(LearningUtilsTest, CC7BitKnob) { ASSERT_EQ(1, mappings.size()); EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_CC | 0x01, 0x10), - MidiOptions(), control), - mappings.at(0)); + MidiOptions(), + control, + mappings.first().description), + mappings.first()); } TEST_F(LearningUtilsTest, CC7BitKnob_CenterPointButton_NoteOn) { @@ -103,10 +125,13 @@ TEST_F(LearningUtilsTest, CC7BitKnob_CenterPointButton_NoteOn) { LearningUtils::guessMidiInputMappings(control, m_messages); ASSERT_EQ(2, mappings.size()); - EXPECT_TRUE(mappings.contains(MidiInputMapping(MidiKey(MIDI_CC | 0x01, 0x10), - MidiOptions(), control))); - EXPECT_TRUE(mappings.contains(MidiInputMapping(MidiKey(MIDI_NOTE_ON | 0x01, 0xE0), - MidiOptions(), resetControl))); + EXPECT_TRUE(containsMapping(mappings, + MidiInputMapping( + MidiKey(MIDI_CC | 0x01, 0x10), MidiOptions(), control))); + EXPECT_TRUE(containsMapping(mappings, + MidiInputMapping(MidiKey(MIDI_NOTE_ON | 0x01, 0xE0), + MidiOptions(), + resetControl))); m_messages.clear(); @@ -124,10 +149,13 @@ TEST_F(LearningUtilsTest, CC7BitKnob_CenterPointButton_NoteOn) { addMessage(MIDI_CC | 0x01, 0x10, 0x00); ASSERT_EQ(2, mappings.size()); - EXPECT_TRUE(mappings.contains(MidiInputMapping(MidiKey(MIDI_CC | 0x01, 0x10), - MidiOptions(), control))); - EXPECT_TRUE(mappings.contains(MidiInputMapping(MidiKey(MIDI_NOTE_ON | 0x01, 0xE0), - MidiOptions(), resetControl))); + EXPECT_TRUE(containsMapping(mappings, + MidiInputMapping( + MidiKey(MIDI_CC | 0x01, 0x10), MidiOptions(), control))); + EXPECT_TRUE(containsMapping(mappings, + MidiInputMapping(MidiKey(MIDI_NOTE_ON | 0x01, 0xE0), + MidiOptions(), + resetControl))); } TEST_F(LearningUtilsTest, CC14BitKnob_MSBFirst) { @@ -167,12 +195,14 @@ TEST_F(LearningUtilsTest, CC14BitKnob_MSBFirst) { ASSERT_EQ(2, mappings.size()); MidiOptions lsb_option; lsb_option.fourteen_bit_lsb = true; - EXPECT_TRUE(mappings.contains(MidiInputMapping(MidiKey(MIDI_CC | 0x01, 0x10), - lsb_option, control))); + EXPECT_TRUE(containsMapping(mappings, + MidiInputMapping( + MidiKey(MIDI_CC | 0x01, 0x10), lsb_option, control))); MidiOptions msb_option; msb_option.fourteen_bit_msb = true; - EXPECT_TRUE(mappings.contains(MidiInputMapping(MidiKey(MIDI_CC | 0x01, 0x11), - msb_option, control))); + EXPECT_TRUE(containsMapping(mappings, + MidiInputMapping( + MidiKey(MIDI_CC | 0x01, 0x11), msb_option, control))); } TEST_F(LearningUtilsTest, CC14BitKnob_LSBFirst) { @@ -212,12 +242,14 @@ TEST_F(LearningUtilsTest, CC14BitKnob_LSBFirst) { ASSERT_EQ(2, mappings.size()); MidiOptions lsb_option; lsb_option.fourteen_bit_lsb = true; - EXPECT_TRUE(mappings.contains(MidiInputMapping(MidiKey(MIDI_CC | 0x01, 0x10), - lsb_option, control))); + EXPECT_TRUE(containsMapping(mappings, + MidiInputMapping( + MidiKey(MIDI_CC | 0x01, 0x10), lsb_option, control))); MidiOptions msb_option; msb_option.fourteen_bit_msb = true; - EXPECT_TRUE(mappings.contains(MidiInputMapping(MidiKey(MIDI_CC | 0x01, 0x11), - msb_option, control))); + EXPECT_TRUE(containsMapping(mappings, + MidiInputMapping( + MidiKey(MIDI_CC | 0x01, 0x11), msb_option, control))); } TEST_F(LearningUtilsTest, CC7BitKnob_ConfusableForCC7BitTicker_Zeroes) { @@ -236,8 +268,11 @@ TEST_F(LearningUtilsTest, CC7BitKnob_ConfusableForCC7BitTicker_Zeroes) { MidiOptions options; options.selectknob = true; ASSERT_EQ(1, mappings.size()); - EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_CC | 0x01, 0x10), options, control), - mappings.at(0)); + EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_CC | 0x01, 0x10), + options, + control, + mappings.first().description), + mappings.first()); m_messages.clear(); @@ -252,8 +287,10 @@ TEST_F(LearningUtilsTest, CC7BitKnob_ConfusableForCC7BitTicker_Zeroes) { ASSERT_EQ(1, mappings.size()); EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_CC | 0x01, 0x10), - MidiOptions(), control), - mappings.at(0)); + MidiOptions(), + control, + mappings.first().description), + mappings.first()); } TEST_F(LearningUtilsTest, CC7BitKnob_ConfusableForCC7BitTicker_ZeroIncluded) { @@ -285,8 +322,10 @@ TEST_F(LearningUtilsTest, CC7BitKnob_ConfusableForCC7BitTicker_ZeroIncluded) { ASSERT_EQ(1, mappings.size()); EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_CC | 0x01, 0x10), - MidiOptions(), control), - mappings.at(0)); + MidiOptions(), + control, + mappings.first().description), + mappings.first()); } TEST_F(LearningUtilsTest, CC7BitKnob_ConfusableForCC7BitTicker) { @@ -312,11 +351,14 @@ TEST_F(LearningUtilsTest, CC7BitKnob_ConfusableForCC7BitTicker) { ASSERT_EQ(1, mappings.size()); EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_CC | 0x01, 0x10), - MidiOptions(), control), - mappings.at(0)); + MidiOptions(), + control, + mappings.first().description), + mappings.first()); } -TEST_F(LearningUtilsTest, CC7BitKnob_ConfusableForSpread64Ticker_StartAndStopOn41) { +TEST_F(LearningUtilsTest, + CC7BitKnob_ConfusableForSpread64Ticker_StartAndStopOn41) { // Moving a CC knob through its range multiple times is confusable for // Spread64 select knobs when a 0x41 or 0x3F is repeated. If we start and // stop on 0x41 (and don't pass through 0x40) then this can set off Spread64 @@ -342,8 +384,10 @@ TEST_F(LearningUtilsTest, CC7BitKnob_ConfusableForSpread64Ticker_StartAndStopOn4 ASSERT_EQ(1, mappings.size()); EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_CC | 0x01, 0x10), - MidiOptions(), control), - mappings.at(0)); + MidiOptions(), + control, + mappings.first().description), + mappings.first()); } TEST_F(LearningUtilsTest, CC7BitKnob_ConfusableForSpread64Ticker_0x40Included) { @@ -375,8 +419,10 @@ TEST_F(LearningUtilsTest, CC7BitKnob_ConfusableForSpread64Ticker_0x40Included) { ASSERT_EQ(1, mappings.size()); EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_CC | 0x01, 0x10), - MidiOptions(), control), - mappings.at(0)); + MidiOptions(), + control, + mappings.first().description), + mappings.first()); } TEST_F(LearningUtilsTest, CC7BitTicker) { @@ -401,8 +447,10 @@ TEST_F(LearningUtilsTest, CC7BitTicker) { MidiOptions options; options.selectknob = true; EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_CC | 0x01, 0x10), - options, control), - mappings.at(0)); + options, + control, + mappings.first().description), + mappings.first()); } TEST_F(LearningUtilsTest, Spread64Ticker) { @@ -425,8 +473,10 @@ TEST_F(LearningUtilsTest, Spread64Ticker) { MidiOptions options; options.spread64 = true; EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_CC | 0x01, 0x10), - options, control), - mappings.at(0)); + options, + control, + mappings.first().description), + mappings.first()); } TEST_F(LearningUtilsTest, CC7BitTicker_SingleDirection) { @@ -452,8 +502,10 @@ TEST_F(LearningUtilsTest, CC7BitTicker_SingleDirection) { LearningUtils::guessMidiInputMappings(control, m_messages); ASSERT_EQ(1, mappings.size()); EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_CC | 0x01, 0x10), - options, control), - mappings.at(0)); + options, + control, + mappings.first().description), + mappings.first()); m_messages.clear(); @@ -466,8 +518,10 @@ TEST_F(LearningUtilsTest, CC7BitTicker_SingleDirection) { mappings = LearningUtils::guessMidiInputMappings(control, m_messages); ASSERT_EQ(1, mappings.size()); EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_CC | 0x01, 0x10), - options, control), - mappings.at(0)); + options, + control, + mappings.first().description), + mappings.first()); } TEST_F(LearningUtilsTest, SingleMessageSwitchMode_NoteOn) { @@ -485,8 +539,10 @@ TEST_F(LearningUtilsTest, SingleMessageSwitchMode_NoteOn) { MidiOptions options; options.sw = true; EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_NOTE_ON | 0x01, 0x10), - options, control), - mappings.at(0)); + options, + control, + mappings.first().description), + mappings.first()); m_messages.clear(); @@ -497,8 +553,10 @@ TEST_F(LearningUtilsTest, SingleMessageSwitchMode_NoteOn) { ASSERT_EQ(1, mappings.size()); EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_NOTE_ON | 0x01, 0x10), - options, control), - mappings.at(0)); + options, + control, + mappings.first().description), + mappings.first()); } TEST_F(LearningUtilsTest, SingleMessageSwitchMode_CC) { @@ -516,8 +574,10 @@ TEST_F(LearningUtilsTest, SingleMessageSwitchMode_CC) { MidiOptions options; options.sw = true; EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_CC | 0x01, 0x10), - options, control), - mappings.at(0)); + options, + control, + mappings.first().description), + mappings.first()); m_messages.clear(); @@ -528,11 +588,12 @@ TEST_F(LearningUtilsTest, SingleMessageSwitchMode_CC) { ASSERT_EQ(1, mappings.size()); EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_CC | 0x01, 0x10), - options, control), - mappings.at(0)); + options, + control, + mappings.first().description), + mappings.first()); } - TEST_F(LearningUtilsTest, MultipleControlsUnrecognized_BindsFirst) { // Status 0x91, Control 0x10 addMessage(MIDI_NOTE_ON | 0x01, 0x10, 0x7F); @@ -547,8 +608,10 @@ TEST_F(LearningUtilsTest, MultipleControlsUnrecognized_BindsFirst) { LearningUtils::guessMidiInputMappings(control, m_messages); ASSERT_EQ(1, mappings.size()); EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_NOTE_ON | 0x01, 0x10), - MidiOptions(), control), - mappings.first()); + MidiOptions(), + control, + mappings.first().description), + mappings.first()); } TEST_F(LearningUtilsTest, MultipleChannelsUnrecognized_BindsFirst) { @@ -565,6 +628,8 @@ TEST_F(LearningUtilsTest, MultipleChannelsUnrecognized_BindsFirst) { LearningUtils::guessMidiInputMappings(control, m_messages); ASSERT_EQ(1, mappings.size()); EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_NOTE_ON | 0x01, 0x10), - MidiOptions(), control), - mappings.first()); + MidiOptions(), + control, + mappings.first().description), + mappings.first()); } diff --git a/src/test/midicontrollertest.cpp b/src/test/midicontrollertest.cpp index ac520908638..1e44a89e850 100644 --- a/src/test/midicontrollertest.cpp +++ b/src/test/midicontrollertest.cpp @@ -33,7 +33,7 @@ class MidiControllerTest : public MixxxTest { } void addMapping(MidiInputMapping mapping) { - m_preset.inputMappings.insertMulti(mapping.key.key, mapping); + m_preset.addInputMapping(mapping.key.key, mapping); } void loadPreset(const MidiControllerPreset& preset) {