diff --git a/Configurator/Configurator.cpp b/Configurator/Configurator.cpp index 0306e98..5f82868 100644 --- a/Configurator/Configurator.cpp +++ b/Configurator/Configurator.cpp @@ -61,7 +61,7 @@ void Configurator::onInitDialog(HWND hDlg) expandTroubleShooting(false); - wchar_t stringBuf[255]; + wchar_t stringBuf[512]; TCITEM tci; tci.pszText = stringBuf; @@ -206,10 +206,13 @@ void Configurator::onInitDialog(HWND hDlg) ListView_SetColumnWidth(deviceList, 2, LVSCW_AUTOSIZE); } - if (!DeviceAPOInfo::checkProtectedAudioDG(true)) + bool fixedAudioDG = !DeviceAPOInfo::checkProtectedAudioDG(true); + bool fixedRegistration = !DeviceAPOInfo::checkAPORegistration(true); + if (fixedAudioDG || fixedRegistration) { LoadStringW(hInstance, IDS_REGISTRY_VALUE_FIXED, stringBuf, sizeof(stringBuf) / sizeof(wchar_t)); MessageBoxW(hDlg, stringBuf, L"Info", MB_ICONINFORMATION | MB_OK); + askForReboot = true; } } @@ -377,7 +380,8 @@ bool Configurator::onButtonClicked(unsigned sourceId) LoadStringW(hInstance, IDS_AFTERINSTALL, stringBuf, sizeof(stringBuf)/sizeof(wchar_t)); MessageBoxW(hDlg, stringBuf, L"Info", MB_ICONINFORMATION | MB_OK); } - else if(sourceId == IDOK && RegistryHelper::isWindowsVersionAtLeast(6, 3)) // Windows 8.1 + else if(sourceId == IDOK && RegistryHelper::isWindowsVersionAtLeast(6, 3) // Windows 8.1 + || askForReboot) { wchar_t captionBuf[255]; LoadStringW(hInstance, IDS_SHOULD_REBOOT_CAPTION, captionBuf, sizeof(captionBuf)/sizeof(wchar_t)); diff --git a/Configurator/Configurator.h b/Configurator/Configurator.h index de85802..26504ef 100644 --- a/Configurator/Configurator.h +++ b/Configurator/Configurator.h @@ -63,4 +63,5 @@ private: void updateButtons(int listIndex); std::vector apoInfos[2]; + bool askForReboot = false; }; diff --git a/Configurator/Configurator.rc b/Configurator/Configurator.rc index 4c164a0..a87121a 100644 Binary files a/Configurator/Configurator.rc and b/Configurator/Configurator.rc differ diff --git a/Configurator/Configurator.vcxproj b/Configurator/Configurator.vcxproj index b8f1a87..621d777 100644 --- a/Configurator/Configurator.vcxproj +++ b/Configurator/Configurator.vcxproj @@ -138,7 +138,7 @@ Windows true - audioeng.lib;version.lib;comctl32.lib;%(AdditionalDependencies) + audioeng.lib;version.lib;Shlwapi.lib;comctl32.lib;%(AdditionalDependencies) @@ -151,7 +151,7 @@ Windows true - audioeng.lib;version.lib;wmcodecdspuuid.lib;AudioBaseProcessingObject.lib;audiomediatypecrt.lib;Shlwapi.lib;comctl32.lib;%(AdditionalDependencies) + audioeng.lib;version.lib;Shlwapi.lib;comctl32.lib;%(AdditionalDependencies) @@ -168,7 +168,7 @@ true true true - audioeng.lib;version.lib;comctl32.lib;%(AdditionalDependencies) + audioeng.lib;version.lib;Shlwapi.lib;comctl32.lib;%(AdditionalDependencies) @@ -185,7 +185,7 @@ true true true - audioeng.lib;version.lib;comctl32.lib;%(AdditionalDependencies) + audioeng.lib;version.lib;Shlwapi.lib;comctl32.lib;%(AdditionalDependencies) diff --git a/DeviceAPOInfo.cpp b/DeviceAPOInfo.cpp index 52f5970..c9f369d 100644 --- a/DeviceAPOInfo.cpp +++ b/DeviceAPOInfo.cpp @@ -20,6 +20,7 @@ #include "stdafx.h" #include #include +#include #include "DeviceAPOInfo.h" @@ -30,6 +31,8 @@ using namespace std; #define protectedDGKeyPath L"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Audio" #define protectedDGValueName L"DisableProtectedAudioDG" +#define apoRegistrationKeyPath L"HKEY_CLASSES_ROOT\\AudioEngine\\AudioProcessingObjects" +#define clsidKeyPath L"HKEY_CLASSES_ROOT\\CLSID" #define commonKeyPath L"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\MMDevices\\Audio" #define renderKeyPath commonKeyPath L"\\Render" #define captureKeyPath commonKeyPath L"\\Capture" @@ -134,6 +137,32 @@ bool DeviceAPOInfo::checkProtectedAudioDG(bool fix) return result; } +bool DeviceAPOInfo::checkAPORegistration(bool fix) +{ + bool result = true; + + if (!RegistryHelper::keyExists(apoRegistrationKeyPath L"\\" + RegistryHelper::getGuidString(EQUALIZERAPO_PRE_MIX_GUID)) + || !RegistryHelper::keyExists(apoRegistrationKeyPath L"\\" + RegistryHelper::getGuidString(EQUALIZERAPO_POST_MIX_GUID)) + || !RegistryHelper::keyExists(clsidKeyPath L"\\" + RegistryHelper::getGuidString(EQUALIZERAPO_PRE_MIX_GUID)) + || !RegistryHelper::keyExists(clsidKeyPath L"\\" + RegistryHelper::getGuidString(EQUALIZERAPO_POST_MIX_GUID))) + { + result = false; + + if (fix) + { + wchar_t path[MAX_PATH]; + if(GetModuleFileNameW(NULL, path, MAX_PATH) != 0) + { + PathRemoveFileSpecW(path); + wstring params = wstring(L"/s \"") + path + L"\\EqualizerAPO.dll\""; + ShellExecuteW(NULL, L"open", L"regsvr32.exe", params.c_str(), NULL, SW_SHOWNORMAL); + } + } + } + + return result; +} + bool DeviceAPOInfo::load(const wstring& deviceGuid, wstring defaultDeviceGuid) { wstring keyPath; diff --git a/DeviceAPOInfo.h b/DeviceAPOInfo.h index 6845a7d..ca20870 100644 --- a/DeviceAPOInfo.h +++ b/DeviceAPOInfo.h @@ -62,6 +62,7 @@ public: static std::vector loadAllInfos(bool input); static std::wstring getDefaultDevice(bool input, int role = 1); static bool checkProtectedAudioDG(bool fix); + static bool checkAPORegistration(bool fix); bool load(const std::wstring& deviceGuid, std::wstring defaultDeviceGuid = L""); bool canBeUpgraded(); bool hasChanges(); diff --git a/Editor/Editor.pro b/Editor/Editor.pro index 4639f62..e4ae66f 100644 --- a/Editor/Editor.pro +++ b/Editor/Editor.pro @@ -111,7 +111,8 @@ SOURCES += main.cpp\ ../parser/RegistryFunctions.cpp \ ../parser/StringOperators.cpp \ AnalysisThread.cpp \ - widgets/ExponentialSpinBox.cpp + widgets/ExponentialSpinBox.cpp \ + FilterTableMimeData.cpp HEADERS += \ ../helpers/LogHelper.h \ @@ -211,7 +212,8 @@ HEADERS += \ ../parser/RegistryFunctions.h \ ../parser/StringOperators.h \ AnalysisThread.h \ - widgets/ExponentialSpinBox.h + widgets/ExponentialSpinBox.h \ + FilterTableMimeData.h FORMS += \ guis/PreampFilterGUI.ui \ diff --git a/Editor/FilterTable.cpp b/Editor/FilterTable.cpp index 1ab3622..38fcf49 100644 --- a/Editor/FilterTable.cpp +++ b/Editor/FilterTable.cpp @@ -30,9 +30,12 @@ #include #include #include +#include +#include #include "MainWindow.h" #include "FilterTableRow.h" +#include "FilterTableMimeData.h" #include "guis/ExpressionFilterGUIFactory.h" #include "guis/CommentFilterGUIFactory.h" #include "guis/DeviceFilterGUIFactory.h" @@ -48,6 +51,7 @@ #include "helpers/StringHelper.h" #include "helpers/LogHelper.h" #include "helpers/ChannelHelper.h" +#include "helpers/RegistryHelper.h" #include "FilterTable.h" using namespace std; @@ -102,7 +106,15 @@ void FilterTable::updateGuis() QElapsedTimer timer; timer.start(); - guis.clear(); + for(Item* item : items) + { + if(item->gui != NULL) + { + item->prefs.clear(); + item->gui->storePreferences(item->prefs); + } + } + delete layout(); for(QObject* object : children()) @@ -133,7 +145,7 @@ void FilterTable::updateGuis() { QString line = item->text; IFilterGUI* gui = NULL; - int pos = line.indexOf(L':'); + int pos = line.indexOf(':'); if(pos != -1) { QString key = line.mid(0, pos); @@ -164,9 +176,12 @@ void FilterTable::updateGuis() FilterTableRow* rowWidget = new FilterTableRow(this, row + 1, item, gui); gridLayout->addWidget(rowWidget, row, 0); + item->gui = gui; + if(gui != NULL) { - guis.append(gui); + gui->loadPreferences(item->prefs); + connect(gui, SIGNAL(updateModel()), this, SLOT(updateModel())); connect(gui, SIGNAL(updateChannels()), this, SLOT(updateChannels())); } @@ -208,9 +223,10 @@ void FilterTable::propagateChannels() { vector channelNames = ChannelHelper::getChannelNames(selectedDevice->channelCount, selectedChannelMask); - for(IFilterGUI* gui : guis) + for(Item* item : items) { - gui->configureChannels(channelNames); + if(item->gui != NULL) + item->gui->configureChannels(channelNames); } } @@ -231,7 +247,50 @@ void FilterTable::setLines(const QString& configPath, const QList& line items.clear(); for(QString line : lines) + { items.append(new Item(line)); + } + + QSettings settings(QString::fromWCharArray(EDITOR_PER_FILE_REGPATH), QSettings::NativeFormat); + settings.beginGroup(QString(configPath).replace('\\', '|')); + QVariant prefsValue = settings.value("rowPrefs"); + QStringList prefLines; + if(prefsValue.isValid()) + prefLines = prefsValue.toStringList(); + for(QString prefLine : prefLines) + { + int index = prefLine.indexOf(':'); + int lineNumber; + if(index != -1) + lineNumber = prefLine.left(index).toInt(); + + QString prefCommand; + QString prefString; + if(lineNumber > 0) + { + int index2 = prefLine.indexOf(':', index + 1); + if(index2 != -1) + { + prefCommand = prefLine.mid(index + 1, index2 - index - 1); + prefString = prefLine.mid(index2 + 1); + + if(lineNumber <= items.size()) + { + Item* item = items[lineNumber - 1]; + + QString command; + int index = item->text.indexOf(':'); + if(index != -1) + command = item->text.left(index).trimmed(); + + if(command == prefCommand) + item->prefs = QJsonDocument::fromJson(prefString.toUtf8()).toVariant().toMap(); + } + } + } + } + setScrollOffsets(settings.value("scrollX", 0).toInt(), settings.value("scrollY", 0).toInt()); + settings.endGroup(); if(!items.isEmpty()) { @@ -319,6 +378,7 @@ void FilterTable::cut() void FilterTable::copy() { QString text; + QList prefsList; bool first = true; for(Item* item : items) { @@ -329,13 +389,15 @@ void FilterTable::copy() else text += "\n"; text += item->text; + prefsList.append(item->prefs); } } if(selected.size() > 0) { - QMimeData* mimeData = new QMimeData; + FilterTableMimeData* mimeData = new FilterTableMimeData; mimeData->setText(text); + mimeData->setPrefsList(prefsList); QClipboard* clipboard = QApplication::clipboard(); clipboard->setMimeData(mimeData); } @@ -359,12 +421,20 @@ void FilterTable::paste() QString text = mimeData->text(); QStringList textLines = text.split("\n"); + QList prefsList; + const FilterTableMimeData* filterTableMimeData = qobject_cast(mimeData); + if(filterTableMimeData != NULL) + prefsList = filterTableMimeData->getPrefsList(); + selected.clear(); focused = NULL; selectionStart = NULL; - for(QString line : textLines) + for(int i = 0; i < textLines.size(); i++) { + QString line = textLines[i]; Item* item = new Item(line); + if(!prefsList.isEmpty()) + item->prefs = prefsList[i]; selected.insert(item); items.insert(dropRow++, item); if(focused == NULL) @@ -468,6 +538,50 @@ void FilterTable::setMinimumHeightHint(int height) updateGeometry(); } +void FilterTable::savePreferences() +{ + if(!configPath.isEmpty()) + { + QStringList prefLines; + + for(int i = 0; i < items.size(); i++) + { + Item* item = items[i]; + + if(item->gui != NULL) + { + item->prefs.clear(); + item->gui->storePreferences(item->prefs); + } + + if(!item->prefs.isEmpty()) + { + QString command; + int index = item->text.indexOf(':'); + if(index != -1) + command = item->text.left(index).trimmed(); + + QByteArray byteArray = QJsonDocument::fromVariant(item->prefs).toJson(QJsonDocument::Compact); + QString string = QString("%0:%1:%2").arg(i + 1).arg(command).arg(QString::fromUtf8(byteArray)); + prefLines.append(string); + } + } + + QSettings settings(QString::fromWCharArray(EDITOR_PER_FILE_REGPATH), QSettings::NativeFormat); + settings.beginGroup(QString(configPath).replace('\\', '|')); + settings.setValue("rowPrefs", prefLines); + settings.setValue("scrollX", scrollArea->horizontalScrollBar()->value()); + settings.setValue("scrollY", scrollArea->verticalScrollBar()->value()); + settings.endGroup(); + } +} + +void FilterTable::setScrollOffsets(int x, int y) +{ + presetScrollX = x; + presetScrollY = y; +} + void FilterTable::mousePressEvent(QMouseEvent* event) { if(event->buttons() & Qt::LeftButton ) @@ -525,6 +639,7 @@ void FilterTable::mouseMoveEvent(QMouseEvent* event) if((event->pos() - dragStartPos).manhattanLength() >= QApplication::startDragDistance()) { QString text; + QList prefsList; bool first = true; int i = 0; bool dragPosInside = false; @@ -537,6 +652,9 @@ void FilterTable::mouseMoveEvent(QMouseEvent* event) else text += "\n"; text += item->text; + if(item->gui != NULL) + item->gui->storePreferences(item->prefs); + prefsList.append(item->prefs); if(!dragPosInside) { @@ -551,8 +669,9 @@ void FilterTable::mouseMoveEvent(QMouseEvent* event) if(selected.size() > 0 && dragPosInside) { - QMimeData* mimeData = new QMimeData; + FilterTableMimeData* mimeData = new FilterTableMimeData; mimeData->setText(text); + mimeData->setPrefsList(prefsList); QDrag* drag = new QDrag(this); drag->setMimeData(mimeData); @@ -628,15 +747,21 @@ void FilterTable::dragLeaveEvent(QDragLeaveEvent* event) void FilterTable::dropEvent(QDropEvent* event) { - if(event->mimeData()->hasText()) + const QMimeData* mimeData = event->mimeData(); + if(mimeData->hasText()) { if(event->keyboardModifiers() & Qt::ControlModifier) event->setDropAction(Qt::CopyAction); else event->setDropAction(Qt::MoveAction); - QString text = event->mimeData()->text(); + QString text = mimeData->text(); QStringList textLines = text.split("\n"); + QList prefsList; + const FilterTableMimeData* filterTableMimeData = qobject_cast(mimeData); + if(filterTableMimeData != NULL) + prefsList = filterTableMimeData->getPrefsList(); + int dropRow = rowForPos(event->pos(), true); if(dropRow == -1) dropRow = items.size(); @@ -644,9 +769,12 @@ void FilterTable::dropEvent(QDropEvent* event) selected.clear(); focused = NULL; selectionStart = NULL; - for(QString line : textLines) + for(int i = 0; i < textLines.size(); i++) { + QString line = textLines[i]; Item* item = new Item(line); + if(!prefsList.isEmpty()) + item->prefs = prefsList[i]; selected.insert(item); items.insert(dropRow++, item); if(focused == NULL) @@ -787,6 +915,21 @@ bool FilterTable::eventFilter(QObject* obj, QEvent* event) return false; } +void FilterTable::showEvent(QShowEvent*) +{ + if(presetScrollX != -1) + { + scrollArea->horizontalScrollBar()->setValue(presetScrollX); + presetScrollX = -1; + } + + if(presetScrollY != -1) + { + scrollArea->verticalScrollBar()->setValue(presetScrollY); + presetScrollY = -1; + } +} + void FilterTable::ensureRowVisible(int row) { QScrollBar* vScrollBar = scrollArea->verticalScrollBar(); diff --git a/Editor/FilterTable.h b/Editor/FilterTable.h index 0f0950c..cf15e46 100644 --- a/Editor/FilterTable.h +++ b/Editor/FilterTable.h @@ -26,6 +26,7 @@ #include #include #include +#include #include "Editor/helpers/DisableWheelFilter.h" #include "DeviceAPOInfo.h" @@ -49,6 +50,8 @@ public: } QString text; + QVariantMap prefs; + IFilterGUI* gui = NULL; }; explicit FilterTable(MainWindow* mainWindow, QWidget *parent = 0); @@ -85,6 +88,9 @@ public: QSize minimumSizeHint() const override; void setMinimumHeightHint(int height); + void savePreferences(); + void setScrollOffsets(int x, int y); + signals: void linesChanged(); @@ -103,6 +109,7 @@ protected: void keyPressEvent(QKeyEvent* event) override; void wheelEvent(QWheelEvent* event) override; bool eventFilter(QObject* obj, QEvent* event) override; + void showEvent(QShowEvent*) override; private: void ensureRowVisible(int row); @@ -121,7 +128,6 @@ private: Item* focused = NULL; Item* selectionStart = NULL; QList factories; - QList guis; bool scrollingNow = false; QPoint scrollStartPoint; QList outputDevices; @@ -130,6 +136,8 @@ private: int selectedChannelMask; QString configPath; int minimumHeightHint = 0; + int presetScrollX = -1; + int presetScrollY = -1; }; template inline uint qHash(QList list) diff --git a/Editor/FilterTableMimeData.cpp b/Editor/FilterTableMimeData.cpp new file mode 100644 index 0000000..9c8b30c --- /dev/null +++ b/Editor/FilterTableMimeData.cpp @@ -0,0 +1,35 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2015 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "FilterTableMimeData.h" + +FilterTableMimeData::FilterTableMimeData() +{ + +} + +QList FilterTableMimeData::getPrefsList() const +{ + return prefsList; +} + +void FilterTableMimeData::setPrefsList(const QList& value) +{ + prefsList = value; +} diff --git a/Editor/FilterTableMimeData.h b/Editor/FilterTableMimeData.h new file mode 100644 index 0000000..1dabd88 --- /dev/null +++ b/Editor/FilterTableMimeData.h @@ -0,0 +1,36 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2015 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include + +class FilterTableMimeData : public QMimeData +{ + Q_OBJECT + +public: + FilterTableMimeData(); + + QList getPrefsList() const; + void setPrefsList(const QList& value); + +private: + QList prefsList; +}; diff --git a/Editor/IFilterGUI.h b/Editor/IFilterGUI.h index 743cbfe..7f105a8 100644 --- a/Editor/IFilterGUI.h +++ b/Editor/IFilterGUI.h @@ -22,6 +22,7 @@ #include #include #include +#include class IFilterGUI : public QWidget { @@ -35,6 +36,9 @@ public: virtual void store(QString& command, QString& parameters) = 0; + virtual void loadPreferences(const QVariantMap& prefs) {} + virtual void storePreferences(QVariantMap& prefs) {} + signals: void updateModel(); void updateChannels(); diff --git a/Editor/MainWindow.cpp b/Editor/MainWindow.cpp index 04053b9..a3812ed 100644 --- a/Editor/MainWindow.cpp +++ b/Editor/MainWindow.cpp @@ -30,9 +30,11 @@ #include #include #include +#include #define WIN32_LEAN_AND_MEAN #include +#include #include "helpers/StringHelper.h" #include "helpers/LogHelper.h" @@ -132,6 +134,33 @@ MainWindow::MainWindow(QDir configDir, QWidget *parent) : analysisThread = new AnalysisThread; analysisThread->start(); connect(analysisThread, SIGNAL(analysisFinished()), this, SLOT(updateAnalysisPanel())); + + QLocale autoLocale = QLocale::system(); + if(autoLocale.language() != QLocale::German) + autoLocale = QLocale("en"); + QLocale::Language languages[] = {QLocale::AnyLanguage, QLocale::English, QLocale::German}; + for(int i = 0; i < sizeof(languages) / sizeof(QLocale::Language); i++) + { + QLocale::Language language = languages[i]; + QString languageName; + if(language == QLocale::AnyLanguage) + languageName = autoLocale.nativeLanguageName(); + else + languageName = QLocale(language).nativeLanguageName(); + if(languageName == "American English") + languageName = "English"; + QString text; + if(language == QLocale::AnyLanguage) + text = tr("Automatic (%0)").arg(languageName); + else + text = languageName; + QAction* action = ui->menuLanguage->addAction(text); + action->setData(language); + action->setCheckable(true); + connect(action, SIGNAL(triggered(bool)), this, SLOT(languageSelected(bool))); + } + + loadPreferences(); } MainWindow::~MainWindow() @@ -143,11 +172,11 @@ MainWindow::~MainWindow() void MainWindow::doChecks() { - if(!DeviceAPOInfo::checkProtectedAudioDG(false)) + if(!DeviceAPOInfo::checkProtectedAudioDG(false) || !DeviceAPOInfo::checkAPORegistration(false)) { if(QMessageBox::warning(this, tr("Registry problem"), tr("A registry value that is required for the operation of Equalizer APO is not set correctly.\nDo you want to run the Configurator application to fix the problem?"), QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) { - QProcess::startDetached(QCoreApplication::applicationDirPath() + "/Configurator.exe", QStringList()); + runConfigurator(); return; } } @@ -156,7 +185,7 @@ void MainWindow::doChecks() { if(QMessageBox::warning(this, tr("APO not installed to device"), tr("Equalizer APO has not been installed to the selected device.\nDo you want to run the Configurator application to fix the problem?"), QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) { - QProcess::startDetached(QCoreApplication::applicationDirPath() + "/Configurator.exe", QStringList()); + runConfigurator(); return; } } @@ -187,14 +216,25 @@ void MainWindow::doChecks() { if(QMessageBox::warning(this, tr("Audio enhancements disabled"), tr("Audio enhancements are not enabled for the device\n%0 %1.\nDo you want to run the Configurator application to fix the problem?").arg(QString::fromStdWString(disabledApoInfo->connectionName)).arg(QString::fromStdWString(disabledApoInfo->deviceName)), QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) { - QProcess::startDetached(QCoreApplication::applicationDirPath() + "/Configurator.exe", QStringList()); + runConfigurator(); return; } } } +void MainWindow::runConfigurator() +{ + // cannot use QProcess::startDetached because of UAC + wstring file = (QDir::toNativeSeparators(QCoreApplication::applicationDirPath() + "/Configurator.exe")).toStdWString(); + int result = (int)ShellExecuteW(NULL, L"open", file.c_str(), NULL, NULL, SW_SHOWNORMAL); + if(result == SE_ERR_ACCESSDENIED) + ShellExecuteW(NULL, L"runas", file.c_str(), NULL, NULL, SW_SHOWNORMAL); +} + void MainWindow::load(QString path) { + path = QDir::toNativeSeparators(path); + for(int i = 0; i < ui->tabWidget->count(); i++) { QScrollArea* scrollArea = qobject_cast(ui->tabWidget->widget(i)); @@ -266,6 +306,12 @@ void MainWindow::load(QString path) qDebug("Loading took %.1f ms", timer.nsecsElapsed() / 1e6); ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); + + recentFiles.removeAll(path); + recentFiles.prepend(path); + if(recentFiles.size() > 10) + recentFiles.removeLast(); + updateRecentFiles(); } void MainWindow::save(FilterTable* filterTable, QString path) @@ -319,18 +365,39 @@ void MainWindow::save(FilterTable* filterTable, QString path) startAnalysis(); } +bool MainWindow::isEmpty() +{ + return ui->tabWidget->count() == 0; +} + +bool MainWindow::shouldRestart() +{ + return restart; +} + void MainWindow::closeEvent(QCloseEvent* event) { - while(ui->tabWidget->count() > 0) + bool canceled = false; + for(int i = 0; i < ui->tabWidget->count(); i++) { - ui->tabWidget->setCurrentIndex(0); - - if(!on_tabWidget_tabCloseRequested(0)) + if(!askForClose(i)) { - event->ignore(); + canceled = true; break; } } + + if(canceled) + { + event->ignore(); + restart = false; + noSavePreferences = false; + noSaveFilePreferences = false; + } + else + { + savePreferences(); + } } void MainWindow::deviceSelected(int index) @@ -438,37 +505,8 @@ void MainWindow::linesChanged() bool MainWindow::on_tabWidget_tabCloseRequested(int index) { - if(ui->tabWidget->tabText(index).endsWith('*')) - { - ui->tabWidget->setCurrentIndex(index); - QString configPath = ui->tabWidget->tabToolTip(index); - QMessageBox messageBox; - messageBox.setWindowTitle(tr("Unsaved changes")); - messageBox.setText(tr("The configuration file %0 has unsaved changes.").arg(configPath)); - messageBox.setInformativeText(tr("Do you want to save the changes before closing the file?")); - messageBox.setIcon(QMessageBox::Question); - messageBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel); - messageBox.setDefaultButton(QMessageBox::Save); - messageBox.setEscapeButton(QMessageBox::Cancel); - messageBox.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowCloseButtonHint); - int result = messageBox.exec(); - - switch(result) - { - case QMessageBox::Save: - ui->actionSave->trigger(); - if(ui->tabWidget->tabText(index).endsWith('*')) - // saving was canceled - return false; - break; - case QMessageBox::Discard: - break; - case QMessageBox::Cancel: - return false; - } - } - - ui->tabWidget->removeTab(index); + if(askForClose(index)) + ui->tabWidget->removeTab(index); return true; } @@ -552,7 +590,7 @@ void MainWindow::on_actionSaveAs_triggered() { QString savePath = dialog.selectedFiles().first(); save(filterTable, savePath); - filterTable->setConfigPath(savePath); + filterTable->setConfigPath(QDir::toNativeSeparators(savePath)); QFileInfo fileInfo(savePath); ui->tabWidget->setTabText(ui->tabWidget->currentIndex(), fileInfo.fileName()); @@ -569,6 +607,12 @@ void MainWindow::on_actionNew_triggered() ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); } +void MainWindow::recentFileSelected() +{ + QAction* action = qobject_cast(sender()); + load(action->text()); +} + void MainWindow::on_actionCut_triggered() { QScrollArea* scrollArea = qobject_cast(ui->tabWidget->currentWidget()); @@ -661,7 +705,7 @@ void MainWindow::updateAnalysisPanel() ui->peakGainValueLabel->setText(tr("%0 dB").arg(peakGain, 0, 'f', 1)); ui->peakGainValueLabel->setForegroundRole(peakGain > 0 ? QPalette::Dark : QPalette::WindowText); - ui->latencyValueLabel->setText(tr("%0 ms (%1 samples)").arg(latency * 1000.0 / sampleRate).arg(latency)); + ui->latencyValueLabel->setText(tr("%0 ms (%1 s.)").arg(latency * 1000.0 / sampleRate, 0, 'f', 1).arg(latency)); ui->initTimeValueLabel->setText(tr("%0 ms").arg(analysisThread->getInitializationTime(), 0, 'f', 1)); @@ -692,6 +736,78 @@ void MainWindow::on_actionAnalysisPanel_triggered(bool checked) ui->analysisDockWidget->setVisible(checked); } +void MainWindow::languageSelected(bool selected) +{ + QAction* action = qobject_cast(sender()); + + if(!selected) + { + action->setChecked(true); + return; + } + + QLocale::Language language = (QLocale::Language)action->data().toInt(); + + if(QMessageBox::question(this, tr("Restart required"), tr("Configuration Editor will be restarted to apply the changed settings. Proceed?")) == QMessageBox::Yes) + { + QSettings settings(QString::fromWCharArray(EDITOR_REGPATH), QSettings::NativeFormat); + if(language == QLocale::AnyLanguage) + { + settings.remove("language"); + } + else + { + QString name = QLocale(language).name(); + int index = name.indexOf('_'); + if(index != -1) + name = name.left(index); + settings.setValue("language", name); + } + + restart = true; + close(); + } + else + { + action->setChecked(false); + } +} + +void MainWindow::on_actionResetAllGlobalPreferences_triggered() +{ + if(QMessageBox::question(this, tr("Restart required"), tr("Configuration Editor will be restarted to apply the changed settings. Proceed?")) == QMessageBox::Yes) + { + QSettings settings(QString::fromWCharArray(EDITOR_REGPATH), QSettings::NativeFormat); + for(QString key : settings.childGroups()) + { + if(key != "file-specific") + settings.remove(key); + } + for(QString key : settings.childKeys()) + settings.remove(key); + + restart = true; + noSavePreferences = true; + close(); + } +} + +void MainWindow::on_actionResetAllFileSpecificPreferences_triggered() +{ + if(QMessageBox::question(this, tr("Restart required"), tr("Configuration Editor will be restarted to apply the changed settings. Proceed?")) == QMessageBox::Yes) + { + QSettings settings(QString::fromWCharArray(EDITOR_PER_FILE_REGPATH), QSettings::NativeFormat); + for(QString key : settings.childGroups()) + settings.remove(key); + for(QString key : settings.childKeys()) + settings.remove(key); + + restart = true; + noSaveFilePreferences = true; + close(); + } +} + FilterTable* MainWindow::addTab(QString title, QString tooltip) { QScrollArea* scrollArea = new QScrollArea(ui->tabWidget); @@ -729,6 +845,53 @@ void MainWindow::getDeviceAndChannelMask(DeviceAPOInfo** selectedDevice, int* ch } } +bool MainWindow::askForClose(int tabIndex) +{ + bool discarded = false; + if(ui->tabWidget->tabText(tabIndex).endsWith('*')) + { + ui->tabWidget->setCurrentIndex(tabIndex); + QString configPath = ui->tabWidget->tabToolTip(tabIndex); + QMessageBox messageBox; + messageBox.setWindowTitle(tr("Unsaved changes")); + messageBox.setText(tr("The configuration file %0 has unsaved changes.").arg(configPath)); + messageBox.setInformativeText(tr("Do you want to save the changes before closing the file?")); + messageBox.setIcon(QMessageBox::Question); + messageBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel); + messageBox.setDefaultButton(QMessageBox::Save); + messageBox.setEscapeButton(QMessageBox::Cancel); + messageBox.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowCloseButtonHint); + int result = messageBox.exec(); + + switch(result) + { + case QMessageBox::Save: + ui->actionSave->trigger(); + if(ui->tabWidget->tabText(tabIndex).endsWith('*')) + // saving was canceled + return false; + break; + case QMessageBox::Discard: + discarded = true; + break; + case QMessageBox::Cancel: + return false; + } + } + + if(!discarded && !noSaveFilePreferences) + { + QScrollArea* scrollArea = qobject_cast(ui->tabWidget->widget(tabIndex)); + if(scrollArea != NULL) + { + FilterTable* filterTable = qobject_cast(scrollArea->widget()); + filterTable->savePreferences(); + } + } + + return true; +} + void MainWindow::startAnalysis() { DeviceAPOInfo* selectedDevice; @@ -759,3 +922,127 @@ void MainWindow::startAnalysis() analysisThread->setParameters(selectedDevice, channelMask, ui->analysisChannelComboBox->currentIndex(), configPath, ui->resolutionSpinBox->value()); } } + +void MainWindow::loadPreferences() +{ + QSettings settings(QString::fromWCharArray(EDITOR_REGPATH), QSettings::NativeFormat); + QVariant geometryValue = settings.value("geometry"); + if(geometryValue.isValid()) + restoreGeometry(geometryValue.toByteArray()); + QVariant stateValue = settings.value("windowState"); + if(stateValue.isValid()) + restoreState(stateValue.toByteArray()); + instantModeCheckBox->setChecked(settings.value("instantMode", true).toBool()); + + ui->startFromComboBox->setCurrentIndex(settings.value("analysis/startFrom").toInt()); + ui->analysisChannelComboBox->setCurrentText(settings.value("analysis/channel").toString()); + ui->resolutionSpinBox->setValue(settings.value("analysis/resolution", 65536).toInt()); + double zoomX = settings.value("analysis/zoomX", 1.0).toDouble(); + double zoomY = settings.value("analysis/zoomY", 1.0).toDouble(); + if(zoomX != 1.0 || zoomY != 1.0) + analysisPlotScene->setZoom(zoomX, zoomY); + int scrollX = settings.value("analysis/scrollX", round(analysisPlotScene->hzToX(20))).toInt(); + int scrollY = settings.value("analysis/scrollY", round(analysisPlotScene->dbToY(22))).toInt(); + ui->graphicsView->setScrollOffsets(scrollX, scrollY); + + QVariant openFilesValue = settings.value("openFiles"); + int tabIndex = settings.value("tabIndex").toInt(); + if(openFilesValue.isValid()) + { + QStringList fileList = openFilesValue.toStringList(); + for(int i = 0; i < fileList.size(); i++) + { + load(fileList[i]); + if(i == tabIndex) + tabIndex = ui->tabWidget->currentIndex(); + } + } + ui->tabWidget->setCurrentIndex(tabIndex); + recentFiles = settings.value("recentFiles").toStringList(); + updateRecentFiles(); + + QVariant languageValue = settings.value("language"); + QLocale::Language language; + if(languageValue.isValid()) + language = QLocale(languageValue.toString()).language(); + else + language = QLocale::AnyLanguage; + + for(QAction* action : ui->menuLanguage->actions()) + action->setChecked(action->data().toInt() == language); +} + +void MainWindow::savePreferences() +{ + if(noSavePreferences) + return; + + QSettings settings(QString::fromWCharArray(EDITOR_REGPATH), QSettings::NativeFormat); + settings.setValue("geometry", saveGeometry()); + settings.setValue("windowState", saveState()); + settings.setValue("instantMode", instantModeCheckBox->isChecked()); + + settings.setValue("analysis/startFrom", ui->startFromComboBox->currentIndex()); + settings.setValue("analysis/channel", ui->analysisChannelComboBox->currentText()); + settings.setValue("analysis/resolution", ui->resolutionSpinBox->value()); + settings.setValue("analysis/zoomX", analysisPlotScene->getZoomX()); + settings.setValue("analysis/zoomY", analysisPlotScene->getZoomY()); + QScrollBar* hScrollBar = ui->graphicsView->horizontalScrollBar(); + int value = hScrollBar->value(); + settings.setValue("analysis/scrollX", value); + QScrollBar* vScrollBar = ui->graphicsView->verticalScrollBar(); + value = vScrollBar->value(); + settings.setValue("analysis/scrollY", value); + + QStringList fileList; + for(int i = 0; i < ui->tabWidget->count(); i++) + { + QScrollArea* scrollArea = qobject_cast(ui->tabWidget->widget(i)); + if(scrollArea == NULL) + continue; + FilterTable* filterTable = qobject_cast(scrollArea->widget()); + if(filterTable->getConfigPath().length() > 0) + { + fileList.append(filterTable->getConfigPath()); + } + } + settings.setValue("openFiles", fileList); + settings.setValue("tabIndex", ui->tabWidget->currentIndex()); + settings.setValue("recentFiles", recentFiles); + + settings.sync(); +} + +void MainWindow::updateRecentFiles() +{ + QList actions = ui->menuFile->actions(); + int separatorsFound = 0; + for(int i = actions.size() - 1; i >= 0; i--) + { + QAction* action = actions[i]; + if(action->isSeparator()) + { + separatorsFound++; + + if(separatorsFound == 1) + { + QList newActions; + for(QString recentFile : recentFiles) + { + QAction* newAction = new QAction(recentFile, ui->menuFile); + connect(newAction, SIGNAL(triggered(bool)), this, SLOT(recentFileSelected())); + newActions.append(newAction); + } + ui->menuFile->insertActions(action, newActions); + } + else + { + break; + } + } + else if(separatorsFound >= 1) + { + ui->menuFile->removeAction(action); + } + } +} diff --git a/Editor/MainWindow.h b/Editor/MainWindow.h index cc1c663..eaf4dee 100644 --- a/Editor/MainWindow.h +++ b/Editor/MainWindow.h @@ -30,6 +30,10 @@ #include "DeviceAPOInfo.h" #include "Editor/AnalysisPlotScene.h" #include "Editor/AnalysisThread.h" +#include "helpers/RegistryHelper.h" + +#define EDITOR_REGPATH USER_REGPATH L"\\Configuration Editor" +#define EDITOR_PER_FILE_REGPATH EDITOR_REGPATH L"\\file-specific" namespace Ui { class MainWindow; @@ -43,8 +47,11 @@ public: explicit MainWindow(QDir configDir, QWidget *parent = 0); ~MainWindow(); void doChecks(); + void runConfigurator(); void load(QString path); void save(FilterTable* filterTable, QString path); + bool isEmpty(); + bool shouldRestart(); protected: void closeEvent(QCloseEvent* event) override; @@ -59,6 +66,7 @@ private slots: void on_actionSave_triggered(); void on_actionSaveAs_triggered(); void on_actionNew_triggered(); + void recentFileSelected(); void on_actionCut_triggered(); void on_actionDelete_triggered(); @@ -77,10 +85,18 @@ private slots: void on_actionToolbar_triggered(bool checked); void on_actionAnalysisPanel_triggered(bool checked); + void languageSelected(bool selected); + void on_actionResetAllGlobalPreferences_triggered(); + void on_actionResetAllFileSpecificPreferences_triggered(); + private: FilterTable* addTab(QString title, QString tooltip); void getDeviceAndChannelMask(DeviceAPOInfo** selectedDevice, int* channelMask); + bool askForClose(int tabIndex); void startAnalysis(); + void loadPreferences(); + void savePreferences(); + void updateRecentFiles(); Ui::MainWindow *ui; @@ -93,6 +109,10 @@ private: DeviceAPOInfo* defaultOutputDevice; AnalysisPlotScene* analysisPlotScene; AnalysisThread* analysisThread; + bool restart = false; + bool noSavePreferences = false; + bool noSaveFilePreferences = false; + QStringList recentFiles; }; Q_DECLARE_METATYPE(DeviceAPOInfo*) diff --git a/Editor/MainWindow.ui b/Editor/MainWindow.ui index 7caa3c1..8926c33 100644 --- a/Editor/MainWindow.ui +++ b/Editor/MainWindow.ui @@ -60,6 +60,7 @@ + @@ -78,9 +79,25 @@ + + + Settings + + + + Language + + + + + + + + + @@ -141,7 +158,7 @@ - FFT resolution: + Resolution: @@ -185,7 +202,7 @@ - config.txt + config.txt @@ -665,6 +682,16 @@ Analysis panel + + + Reset all global preferences + + + + + Reset all file-specific preferences + + diff --git a/Editor/guis/CommentFilterGUI.cpp b/Editor/guis/CommentFilterGUI.cpp index f515900..40b01bf 100644 --- a/Editor/guis/CommentFilterGUI.cpp +++ b/Editor/guis/CommentFilterGUI.cpp @@ -69,6 +69,16 @@ void CommentFilterGUI::store(QString& command, QString& parameters) command = "# " + command; } +void CommentFilterGUI::loadPreferences(const QVariantMap& prefs) +{ + child->loadPreferences(prefs); +} + +void CommentFilterGUI::storePreferences(QVariantMap& prefs) +{ + child->storePreferences(prefs); +} + void CommentFilterGUI::on_actionPowerOn_toggled(bool checked) { child->setEnabled(checked); diff --git a/Editor/guis/CommentFilterGUI.h b/Editor/guis/CommentFilterGUI.h index 8e547d5..4886850 100644 --- a/Editor/guis/CommentFilterGUI.h +++ b/Editor/guis/CommentFilterGUI.h @@ -36,6 +36,9 @@ public: void configureChannels(std::vector& channelNames) override; void store(QString& command, QString& parameters) override; + void loadPreferences(const QVariantMap& prefs) override; + void storePreferences(QVariantMap& prefs) override; + private slots: void on_actionPowerOn_toggled(bool checked); diff --git a/Editor/guis/CopyFilterGUI.cpp b/Editor/guis/CopyFilterGUI.cpp index 7f7b56b..6f0c33c 100644 --- a/Editor/guis/CopyFilterGUI.cpp +++ b/Editor/guis/CopyFilterGUI.cpp @@ -23,6 +23,8 @@ #include "CopyFilterGUI.h" #include "ui_CopyFilterGUI.h" +static const int DEFAULT_HEIGHT = 88; + using namespace std; CopyFilterGUI::CopyFilterGUI(CopyFilter* filter, FilterTable* filterTable) : @@ -42,7 +44,7 @@ CopyFilterGUI::CopyFilterGUI(CopyFilter* filter, FilterTable* filterTable) : return QSize(0, ui->scrollArea->height()); }, [this](QSize size) { - ui->scrollArea->setMinimumHeight(size.height()); + ui->scrollArea->setFixedHeight(size.height()); }, ui->scrollArea); cornerWidget->setCursor(Qt::SizeVerCursor); cornerWidget->setAutoFillBackground(true); @@ -162,3 +164,17 @@ void CopyFilterGUI::store(QString& command, QString& parameters) scene->load(inputChannelNames, assignments); } } + +void CopyFilterGUI::loadPreferences(const QVariantMap& prefs) +{ + ui->scrollArea->setFixedHeight(prefs.value("height", DEFAULT_HEIGHT).toInt()); + ui->tabWidget->setCurrentIndex(prefs.value("tabIndex", 0).toInt()); +} + +void CopyFilterGUI::storePreferences(QVariantMap& prefs) +{ + if(ui->scrollArea->height() != DEFAULT_HEIGHT) + prefs.insert("height", ui->scrollArea->height()); + if(ui->tabWidget->currentIndex() != 0) + prefs.insert("tabIndex", ui->tabWidget->currentIndex()); +} diff --git a/Editor/guis/CopyFilterGUI.h b/Editor/guis/CopyFilterGUI.h index 16d3ea3..718c19a 100644 --- a/Editor/guis/CopyFilterGUI.h +++ b/Editor/guis/CopyFilterGUI.h @@ -40,6 +40,9 @@ public: void store(QString& command, QString& parameters) override; + void loadPreferences(const QVariantMap& prefs) override; + void storePreferences(QVariantMap& prefs) override; + private: Ui::CopyFilterGUI *ui; CopyFilterGUIScene* scene; diff --git a/Editor/guis/CopyFilterGUIScene.cpp b/Editor/guis/CopyFilterGUIScene.cpp index 6fd55c7..9e71121 100644 --- a/Editor/guis/CopyFilterGUIScene.cpp +++ b/Editor/guis/CopyFilterGUIScene.cpp @@ -48,7 +48,7 @@ void CopyFilterGUIScene::load(const vector& channelNames, vector& channelNames, vectorgraphicsView->setFixedHeight(size.height()); }, ui->graphicsView); ui->graphicsView->setCornerWidget(cornerWidget); - ui->graphicsView->setFixedHeight(150); ui->toolBar->addAction(ui->actionImport); ui->toolBar->addAction(ui->actionExport); @@ -105,6 +107,39 @@ void GraphicEQFilterGUI::store(QString & command, QString & parameters) } } +void GraphicEQFilterGUI::loadPreferences(const QVariantMap& prefs) +{ + ui->tableWidget->setFixedWidth(prefs.value("tableWidth", DEFAULT_TABLE_WIDTH).toInt()); + ui->graphicsView->setFixedHeight(prefs.value("viewHeight", DEFAULT_VIEW_HEIGHT).toInt()); + double zoomX = prefs.value("zoomX", 1.0).toDouble(); + double zoomY = prefs.value("zoomY", 1.0).toDouble(); + if(zoomX != 1.0 || zoomY != 1.0) + scene->setZoom(zoomX, zoomY); + int scrollX = prefs.value("scrollX", round(scene->hzToX(20))).toInt(); + int scrollY = prefs.value("scrollY", round(scene->dbToY(22))).toInt(); + ui->graphicsView->setScrollOffsets(scrollX, scrollY); +} + +void GraphicEQFilterGUI::storePreferences(QVariantMap& prefs) +{ + if(ui->tableWidget->width() != DEFAULT_TABLE_WIDTH) + prefs.insert("tableWidth", ui->tableWidget->width()); + if(ui->graphicsView->height() != DEFAULT_VIEW_HEIGHT) + prefs.insert("viewHeight", ui->graphicsView->height()); + if(scene->getZoomX() != 1.0) + prefs.insert("zoomX", scene->getZoomX()); + if(scene->getZoomY() != 1.0) + prefs.insert("zoomY", scene->getZoomY()); + QScrollBar* hScrollBar = ui->graphicsView->horizontalScrollBar(); + int value = hScrollBar->value(); + if(value != round(scene->hzToX(20))) + prefs.insert("scrollX", value); + QScrollBar* vScrollBar = ui->graphicsView->verticalScrollBar(); + value = vScrollBar->value(); + if(value != round(scene->dbToY(22))) + prefs.insert("scrollY", value); +} + void GraphicEQFilterGUI::insertRow(int index, double hz, double db) { ui->tableWidget->blockSignals(true); diff --git a/Editor/guis/GraphicEQFilterGUI.h b/Editor/guis/GraphicEQFilterGUI.h index f026490..ede3379 100644 --- a/Editor/guis/GraphicEQFilterGUI.h +++ b/Editor/guis/GraphicEQFilterGUI.h @@ -41,6 +41,9 @@ public: void store(QString& command, QString& parameters) override; + void loadPreferences(const QVariantMap& prefs) override; + void storePreferences(QVariantMap& prefs) override; + private slots: void insertRow(int index, double hz, double db); void removeRow(int index); diff --git a/Editor/main.cpp b/Editor/main.cpp index 7f182f2..4be4133 100644 --- a/Editor/main.cpp +++ b/Editor/main.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include "MainWindow.h" #include "helpers/RegistryHelper.h" @@ -34,17 +35,26 @@ int main(int argc, char *argv[]) //_CrtSetBreakAlloc(3318); #endif - { - QCoreApplication::addLibraryPath("qt"); + QCoreApplication::addLibraryPath("qt"); + bool restart; + do + { QApplication application(argc, argv); + QSettings settings(QString::fromWCharArray(EDITOR_REGPATH), QSettings::NativeFormat); + QVariant languageValue = settings.value("language"); + if(languageValue.isValid()) + QLocale::setDefault(languageValue.toString()); + else + QLocale::setDefault(QLocale::system()); + QTranslator qtTranslator; - qtTranslator.load(QLocale::system(), ":/translations/qtbase", "_"); + qtTranslator.load(QLocale(), ":/translations/qtbase", "_"); application.installTranslator(&qtTranslator); QTranslator editorTranslator; - editorTranslator.load(QLocale::system(), ":/translations/Editor", "_"); + editorTranslator.load(QLocale(), ":/translations/Editor", "_"); application.installTranslator(&editorTranslator); QString configPath = QDir::currentPath(); @@ -52,13 +62,22 @@ int main(int argc, char *argv[]) configPath = QString::fromStdWString(RegistryHelper::readValue(APP_REGPATH, L"ConfigPath")); QDir configDir(configPath); + if(!RegistryHelper::keyExists(USER_REGPATH)) + RegistryHelper::createKey(USER_REGPATH); + + if(!RegistryHelper::keyExists(EDITOR_REGPATH)) + RegistryHelper::createKey(EDITOR_REGPATH); + + if(!RegistryHelper::keyExists(EDITOR_PER_FILE_REGPATH)) + RegistryHelper::createKey(EDITOR_PER_FILE_REGPATH); + MainWindow w(configDir); w.show(); QCommandLineParser parser; parser.process(application); QStringList args = parser.positionalArguments(); - if(args.isEmpty()) + if(args.isEmpty() && w.isEmpty()) args = QStringList("config.txt"); for(QString arg : args) @@ -67,7 +86,10 @@ int main(int argc, char *argv[]) w.doChecks(); result = application.exec(); + + restart = w.shouldRestart(); } + while(restart); return result; } diff --git a/Editor/translations/Editor_de.qm b/Editor/translations/Editor_de.qm index ca0cc02..150511d 100644 Binary files a/Editor/translations/Editor_de.qm and b/Editor/translations/Editor_de.qm differ diff --git a/Editor/translations/Editor_de.ts b/Editor/translations/Editor_de.ts index b4d99b5..034039e 100644 --- a/Editor/translations/Editor_de.ts +++ b/Editor/translations/Editor_de.ts @@ -381,12 +381,12 @@ Wählen Sie eine andere Datei or ändern Sie die Konfiguration des Geräts.Graph - + Assignments Zuweisungen - + Selected channels: Gewählte Kanäle: @@ -402,12 +402,12 @@ Wählen Sie eine andere Datei or ändern Sie die Konfiguration des Geräts.Steuerung - + Copy (Copy between channels) Copy (Kopieren zwischen Kanälen) - + Basic filters Einfache Filter @@ -647,7 +647,7 @@ Wählen Sie eine andere Datei or ändern Sie die Konfiguration des Geräts. FilterTable - + Add filter Filter hinzufügen @@ -659,17 +659,17 @@ Wählen Sie eine andere Datei or ändern Sie die Konfiguration des Geräts.99 - + Add Hinzufügen - + Remove Entfernen - + Edit text Text bearbeiten @@ -715,79 +715,83 @@ Wählen Sie eine andere Datei or ändern Sie die Konfiguration des Geräts. GraphicEQFilterGUI - + Graphic EQ: Grafischer EQ: - + Freq. Freq. - + Gain Verst. - + 15-band 15-Band - + 31-band 31-Band - + variable variabel - + Import Importieren - + Export Exportieren - - Mirror response - Frequenzgang spiegeln + + Invert response + Frequenzgang invertieren - + Mirror response + Frequenzgang spiegeln + + + Normalize response Frequenzgang normalisieren - + Reset response Frequenzgang zurücksetzen - + Import frequency response Frequenzgang importieren - - + + Frequency response (*.csv) Frequenzgang (*.csv) - - + + All files (*.*) Alle Dateien (*.*) - + Export frequency response Frequenzgang exportieren @@ -795,24 +799,24 @@ Wählen Sie eine andere Datei or ändern Sie die Konfiguration des Geräts. GraphicEQFilterGUIFactory - + 15-band graphic equalizer Grafischer Equalizer mit 15 Bändern - - - + + + Graphic equalizers Grafische Equalizer - + 31-band graphic equalizer Grafischer Equalizer mit 31 Bändern - + Graphic equalizer with variable bands Grafischer Equalizer mit variablen Bändern @@ -874,120 +878,207 @@ Wählen Sie eine andere Datei or ändern Sie die Konfiguration des Geräts.Datei - + Edit Bearbeiten - + + View + Ansicht + + + + + Settings + Einstellungen + + + + Language + Sprache + + + + + Toolbar + Werkzeugleiste + + + + + Analysis panel + Analysepanel + + + FFT resolution: + FFT-Auflösung: + + + + Resolution: + Auflösung: + + + + Channel: + Kanal: + + + + Start from: + Beginne bei: + + + + Current file + Aktuelle Datei + + + + Estimated properties + Geschätzte Eigenschaften + + + + CPU usage: + CPU-Last: + + + + Init. time: + Init.-Zeit: + + + + Latency: + Latenz: + + + + Peak gain: + Maximum: + + + New Neu - + Ctrl+N Ctrl+N - - + + Open Öffnen - + Ctrl+O Ctrl+O - - + + Save Speichern - + Ctrl+S Ctrl+S - - + + Save as... Speichern unter... - + Exit Beenden - + Alt+F4 Alt+F4 - + Cut Ausschneiden - + Ctrl+X Ctrl+X - + Copy Kopieren - + Ctrl+C Ctrl+C - + Paste Einfügen - + Ctrl+V Ctrl+V - + Delete Löschen - + + Reset all global preferences + Alle globalen Einstellungen zurücksetzen + + + + Reset all file-specific preferences + Alle dateispezifischen Einstellungen zurücksetzen + + + Equalizer APO %0 Configuration Editor Equalizer APO %0 Konfigurationseditor - + Instant mode Sofort-Modus - + Changes are saved immediately Änderungen werden sofort gespeichert - + Device: Gerät: - + Channel configuration: Kanalkonfiguration: - + Default Standard @@ -1000,17 +1091,17 @@ Wählen Sie eine andere Datei or ändern Sie die Konfiguration des Geräts.Eingabegeräte: - + Playback devices: Wiedergabegeräte: - + Capture devices: Aufnahmegeräte: - + Registry problem Registry-Problem @@ -1019,31 +1110,31 @@ Wählen Sie eine andere Datei or ändern Sie die Konfiguration des Geräts.Ein Registrywert, der für den Betrieb von Equalizer APO erforderlich ist, ist nicht korrekt gesetzt. Möchten Sie die Configurator-Anwendung starten, um das Problem zu beheben? - + A registry value that is required for the operation of Equalizer APO is not set correctly. Do you want to run the Configurator application to fix the problem? Ein Registrywert, der für den Betrieb von Equalizer APO erforderlich ist, ist nicht korrekt gesetzt. Möchten Sie die Configurator-Anwendung starten, um das Problem zu beheben? - + APO not installed to device APO nicht für das Gerät installiert - + Equalizer APO has not been installed to the selected device. Do you want to run the Configurator application to fix the problem? Equalizer APO wurde nicht für das gewählte Gerät installiert. Möchten Sie die Configurator-Anwendung starten, um das Problem zu beheben? - + Audio enhancements disabled Signalverbesserungen deaktiviert - + Audio enhancements are not enabled for the device %0 %1. Do you want to run the Configurator application to fix the problem? @@ -1052,66 +1143,113 @@ Do you want to run the Configurator application to fix the problem? Möchten Sie die Configurator-Anwendung starten, um das Problem zu beheben? - - - + + + Error Fehler - + Error while reading configuration file: %0 Fehler beim Lesen der Konfigurationsdatei: %0 - + Error while writing configuration file: %0 Fehler beim Schreiben der Konfigurationsdatei: %0 - + Only %0/%1 bytes have been written! Nur %0/%1 Bytes wurden geschrieben! - - + + From device Von Gerät - + + %0 dB + %0 dB + + + %0 ms (%1 samp.) + %0 ms (%1 Samp.) + + + + %0 ms (%1 s.) + %0 ms (%1 S.) + + + %0 ms (%1 samples) + %0 ms (%1 Samples) + + + + %0 ms + %0 ms + + + + %0 % (one core) + %0 % (ein Kern) + + + + + + Restart required + Neustart erforderlich + + + + + + Configuration Editor will be restarted to apply the changed settings. Proceed? + Konfigurationseditor wird neugestartet, um die geänderten Einstellungen zu übernehmen. Fortsetzen? + + + Unsaved changes Ungespeicherte Änderungen - + The configuration file %0 has unsaved changes. Die Konfigurationsdatei %0 hat ungespeicherte Änderungen. - + Do you want to save the changes before closing the file? Möchten Sie die Änderungen speichern, bevor die Datei geschlossen wird? - + Open file Datei öffnen - - + + Automatic (%0) + Automatisch (%0) + + + + E-APO configurations (*.txt) E-APO-Konfigurationen (*.txt) - + Save file as Datei speichern unter - + Unsaved Ungespeichert diff --git a/Editor/widgets/FrequencyPlotView.cpp b/Editor/widgets/FrequencyPlotView.cpp index b284daf..44faacf 100644 --- a/Editor/widgets/FrequencyPlotView.cpp +++ b/Editor/widgets/FrequencyPlotView.cpp @@ -168,6 +168,12 @@ void FrequencyPlotView::zoom(int deltaX, int deltaY, int mouseX, int mouseY) vRuler->update(); } +void FrequencyPlotView::setScrollOffsets(int x, int y) +{ + presetScrollX = x; + presetScrollY = y; +} + void FrequencyPlotView::wheelEvent(QWheelEvent* event) { int delta = event->angleDelta().y(); @@ -224,5 +230,15 @@ void FrequencyPlotView::showEvent(QShowEvent* event) { QGraphicsView::showEvent(event); - horizontalScrollBar()->setValue(scene()->hzToX(20)); + if(presetScrollX != -1) + { + horizontalScrollBar()->setValue(presetScrollX); + presetScrollX = -1; + } + + if(presetScrollY != -1) + { + verticalScrollBar()->setValue(presetScrollY); + presetScrollY = -1; + } } diff --git a/Editor/widgets/FrequencyPlotView.h b/Editor/widgets/FrequencyPlotView.h index d766c43..98fac58 100644 --- a/Editor/widgets/FrequencyPlotView.h +++ b/Editor/widgets/FrequencyPlotView.h @@ -43,6 +43,7 @@ public: using QGraphicsView::viewportMargins; void zoom(int deltaX, int deltaY, int mouseX, int mouseY); + void setScrollOffsets(int x, int y); protected: void wheelEvent(QWheelEvent* event) override; @@ -57,4 +58,6 @@ private: FrequencyPlotHRuler* hRuler; FrequencyPlotVRuler* vRuler; QPoint lastMousePos; + int presetScrollX = -1; + int presetScrollY = -1; }; diff --git a/EqualizerAPO.cpp b/EqualizerAPO.cpp index 2b4a857..e3e623d 100644 --- a/EqualizerAPO.cpp +++ b/EqualizerAPO.cpp @@ -404,10 +404,14 @@ void EqualizerAPO::APOProcess(UINT32 u32NumInputConnections, switch( ppInputConnections[0]->u32BufferFlags ) { case BUFFER_VALID: + case BUFFER_SILENT: { float* inputFrames = reinterpret_cast(ppInputConnections[0]->pBuffer); float* outputFrames = reinterpret_cast(ppOutputConnections[0]->pBuffer); + if(ppInputConnections[0]->u32BufferFlags == BUFFER_SILENT) + memset(inputFrames, 0, ppInputConnections[0]->u32ValidFrameCount * engine.getInputChannelCount() * sizeof(float)); + if(childRT) { childRT->APOProcess(u32NumInputConnections, ppInputConnections, u32NumOutputConnections, ppOutputConnections); @@ -418,14 +422,7 @@ void EqualizerAPO::APOProcess(UINT32 u32NumInputConnections, engine.process(outputFrames, inputFrames, ppInputConnections[0]->u32ValidFrameCount); ppOutputConnections[0]->u32ValidFrameCount = ppInputConnections[0]->u32ValidFrameCount; - ppOutputConnections[0]->u32BufferFlags = ppInputConnections[0]->u32BufferFlags; - - break; - } - case BUFFER_SILENT: - { - ppOutputConnections[0]->u32ValidFrameCount = ppInputConnections[0]->u32ValidFrameCount; - ppOutputConnections[0]->u32BufferFlags = ppInputConnections[0]->u32BufferFlags; + ppOutputConnections[0]->u32BufferFlags = BUFFER_VALID; break; } diff --git a/FilterEngine.cpp b/FilterEngine.cpp index eed4e02..f9c6094 100644 --- a/FilterEngine.cpp +++ b/FilterEngine.cpp @@ -365,33 +365,6 @@ void FilterEngine::watchRegistryKey(const std::wstring& key) #pragma AVRT_CODE_BEGIN void FilterEngine::process(float *output, float *input, unsigned frameCount) { - bool inputSilent = true; - - for (unsigned i = 0; i < frameCount * realChannelCount; i++) - { - if(input[i] != 0) - { - inputSilent = false; - break; - } - } - - if(inputSilent) - { - if(lastInputWasSilent) - { - //Avoid processing cost if silence would be output anyway - if(input != output) - memset(output, 0, frameCount * outputChannelCount * sizeof(float)); - - return; - } - else - lastInputWasSilent = true; - } - else - lastInputWasSilent = false; - if(currentConfig->isEmpty() && nextConfig == NULL) { // avoid (de-)interleaving cost if no processing will happen anyway diff --git a/Setup/Setup.nsi b/Setup/Setup.nsi index ddbdd2e..2acdb0c 100644 --- a/Setup/Setup.nsi +++ b/Setup/Setup.nsi @@ -197,6 +197,7 @@ Section /o un.$(SecRemoveName) Delete "$INSTDIR\*.reg" RMDir /REBOOTOK /r "$INSTDIR\config" + DeleteRegKey HKCU ${REGPATH} SectionEnd diff --git a/helpers/RegistryHelper.cpp b/helpers/RegistryHelper.cpp index 0d66e50..11f680b 100644 --- a/helpers/RegistryHelper.cpp +++ b/helpers/RegistryHelper.cpp @@ -66,7 +66,7 @@ wstring RegistryHelper::readValue(wstring key, wstring valuename) //Remove zero-termination if(buf[bufSize / sizeof(wchar_t) - 1] == L'\0') bufSize -= sizeof(wchar_t); - result = wstring((wchar_t*)buf, (wstring::size_type)bufSize/sizeof(wchar_t)); + result = wstring((wchar_t*)buf, (wstring::size_type)bufSize / sizeof(wchar_t)); delete buf; return result; @@ -111,6 +111,62 @@ unsigned long RegistryHelper::readDWORDValue(wstring key, wstring valuename) return result; } +vector RegistryHelper::readMultiValue(wstring key, wstring valuename) +{ + vector result; + + HKEY keyHandle = openKey(key, KEY_QUERY_VALUE | KEY_WOW64_64KEY); + + LSTATUS status; + DWORD type; + DWORD bufSize; + status = RegQueryValueExW(keyHandle, valuename.c_str(), NULL, &type, NULL, &bufSize); + if(status != ERROR_SUCCESS) + { + RegCloseKey(keyHandle); + throw RegistryException(L"Error while reading registry value " + key + L"\\" + valuename + L": " + StringHelper::getSystemErrorString(status)); + } + + if(type != REG_MULTI_SZ) + { + RegCloseKey(keyHandle); + throw RegistryException(L"Registry value " + key + L"\\" + valuename + L" has wrong type"); + } + + wchar_t* buf = new wchar_t[bufSize / sizeof(wchar_t) + 1]; + status = RegQueryValueExW(keyHandle, valuename.c_str(), NULL, NULL, (LPBYTE)buf, &bufSize); + + RegCloseKey(keyHandle); + + if(status != ERROR_SUCCESS) + { + delete buf; + throw RegistryException(L"Error while reading registry value " + key + L"\\" + valuename + L": " + StringHelper::getSystemErrorString(status)); + } + + size_t length = bufSize / sizeof(wchar_t); + //Remove zero-termination + while(length > 0 && buf[length - 1] == L'\0') + length--; + + size_t start = 0; + for(size_t i = 0; i < length; i++) + { + if(buf[i] == L'\0') + { + result.push_back(wstring(buf + start, i - start)); + start = i + 1; + } + } + + if(length > start) + result.push_back(wstring(buf + start, length - start)); + + delete buf; + + return result; +} + vector RegistryHelper::readBinaryValue(wstring key, wstring valuename) { HKEY keyHandle = openKey(key, KEY_QUERY_VALUE | KEY_WOW64_64KEY); @@ -148,7 +204,7 @@ void RegistryHelper::writeValue(wstring key, wstring valuename, wstring value) { HKEY keyHandle = openKey(key, KEY_SET_VALUE | KEY_WOW64_64KEY); - LSTATUS status = RegSetValueExW(keyHandle, valuename.c_str(), 0, REG_SZ, (const BYTE*)value.c_str(), (DWORD)((value.size()+1)*sizeof(wchar_t))); + LSTATUS status = RegSetValueExW(keyHandle, valuename.c_str(), 0, REG_SZ, (const BYTE*)value.c_str(), (DWORD)((value.size() + 1) * sizeof(wchar_t))); RegCloseKey(keyHandle); @@ -173,11 +229,39 @@ void RegistryHelper::writeMultiValue(wstring key, wstring valuename, wstring val HKEY keyHandle = openKey(key, KEY_SET_VALUE | KEY_WOW64_64KEY); wchar_t* data = new wchar_t[value.size() + 2]; - value._Copy_s(data, (value.size()+2)*sizeof(wchar_t), value.size()); + value._Copy_s(data, (value.size() + 2)*sizeof(wchar_t), value.size()); data[value.size()] = L'\0'; - data[value.size()+1] = L'\0'; + data[value.size() + 1] = L'\0'; - LSTATUS status = RegSetValueExW(keyHandle, valuename.c_str(), 0, REG_MULTI_SZ, (const BYTE*)data, (DWORD)((value.size()+2)*sizeof(wchar_t))); + LSTATUS status = RegSetValueExW(keyHandle, valuename.c_str(), 0, REG_MULTI_SZ, (const BYTE*)data, (DWORD)((value.size() + 2) * sizeof(wchar_t))); + + delete data; + + RegCloseKey(keyHandle); + + if(status != ERROR_SUCCESS) + throw RegistryException(L"Error while writing to registry value " + key + L"\\" + valuename + L": " + StringHelper::getSystemErrorString(status)); +} + +void RegistryHelper::writeMultiValue(wstring key, wstring valuename, vector values) +{ + HKEY keyHandle = openKey(key, KEY_SET_VALUE | KEY_WOW64_64KEY); + + size_t size = 1; + for(wstring value : values) + size += value.size() + 1; + + wchar_t* data = new wchar_t[size]; + size_t offset = 0; + for(wstring value : values) + { + value._Copy_s(data + offset, size * sizeof(wchar_t), value.size()); + offset += value.size(); + data[offset++] = L'\0'; + } + data[offset] = L'\0'; + + LSTATUS status = RegSetValueExW(keyHandle, valuename.c_str(), 0, REG_MULTI_SZ, (const BYTE*)data, (DWORD)(size * sizeof(wchar_t))); delete data; @@ -242,13 +326,13 @@ void RegistryHelper::makeWritable(wstring key) PSID sid = NULL; SID_IDENTIFIER_AUTHORITY authority = SECURITY_NT_AUTHORITY; if(!AllocateAndInitializeSid(&authority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, - 0, 0, 0, 0, 0, 0, &sid)) + 0, 0, 0, 0, 0, 0, &sid)) throw RegistryException(L"Error in AllocateAndInitializeSid while ensuring writability"); EXPLICIT_ACCESS ea; ea.grfAccessPermissions = KEY_ALL_ACCESS; ea.grfAccessMode = SET_ACCESS; - ea.grfInheritance= SUB_CONTAINERS_AND_OBJECTS_INHERIT; + ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; ea.Trustee.TrusteeType = TRUSTEE_IS_GROUP; ea.Trustee.ptstrName = (LPWSTR)sid; @@ -307,7 +391,7 @@ void RegistryHelper::takeOwnership(wstring key) PSID sid = NULL; SID_IDENTIFIER_AUTHORITY authority = SECURITY_NT_AUTHORITY; if(!AllocateAndInitializeSid(&authority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, - 0, 0, 0, 0, 0, 0, &sid)) + 0, 0, 0, 0, 0, 0, &sid)) throw RegistryException(L"Error in AllocateAndInitializeSid while taking ownership"); if(!SetSecurityDescriptorOwner(sd, sid, FALSE)) @@ -361,7 +445,7 @@ vector RegistryHelper::enumSubKeys(wstring key) wchar_t keyName[256]; DWORD keyLength = sizeof(keyName) / sizeof(wchar_t); - int i=0; + int i = 0; LSTATUS status; while((status = RegEnumKeyExW(keyHandle, i++, keyName, &keyLength, NULL, NULL, NULL, NULL)) == ERROR_SUCCESS) @@ -472,7 +556,7 @@ wstring RegistryHelper::splitKey(const wstring& key, HKEY* rootKey) throw RegistryException(L"Key " + key + L" has invalid format"); wstring rootPart = key.substr(0, pos); - wstring pathPart = key.substr(pos+1); + wstring pathPart = key.substr(pos + 1); wstring p = StringHelper::toUpperCase(rootPart); if(p == L"HKEY_CLASSES_ROOT") diff --git a/helpers/RegistryHelper.h b/helpers/RegistryHelper.h index d8b1ff8..80043af 100644 --- a/helpers/RegistryHelper.h +++ b/helpers/RegistryHelper.h @@ -31,16 +31,19 @@ const GUID EQUALIZERAPO_PRE_MIX_GUID = { 0xeacd2258, 0xfcac, 0x4ff4, { 0xb3, 0x6 const GUID EQUALIZERAPO_POST_MIX_GUID = { 0xec1cc9ce, 0xfaed, 0x4822, { 0x82, 0x8a, 0x82, 0xa8, 0x1a, 0x6f, 0x01, 0x8f } }; #define APP_REGPATH L"HKEY_LOCAL_MACHINE\\SOFTWARE\\EqualizerAPO" +#define USER_REGPATH L"HKEY_CURRENT_USER\\SOFTWARE\\EqualizerAPO" class RegistryHelper { public: static std::wstring readValue(std::wstring key, std::wstring valuename); static unsigned long readDWORDValue(std::wstring key, std::wstring valuename); + static std::vector readMultiValue(std::wstring key, std::wstring valuename); static std::vector readBinaryValue(std::wstring key, std::wstring valuename); static void writeValue(std::wstring key, std::wstring valuename, std::wstring value); static void writeDWORDValue(std::wstring key, std::wstring valuename, unsigned long value); static void writeMultiValue(std::wstring key, std::wstring valuename, std::wstring value); + static void writeMultiValue(std::wstring key, std::wstring valuename, std::vector values); static void deleteValue(std::wstring key, std::wstring valuename); static void createKey(std::wstring key); static void deleteKey(std::wstring key);