Improved: Updated Qt to version 5.7 Fixed: The GUI for the Channel command was showing the wrong channels as selected when channel numbers were used instead of names.
1084 lines
33 KiB
C++
1084 lines
33 KiB
C++
/*
|
|
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 <sstream>
|
|
#include <QDrag>
|
|
#include <QElapsedTimer>
|
|
#include <QLabel>
|
|
#include <QMimeData>
|
|
#include <QPushButton>
|
|
#include <QStandardItemModel>
|
|
#include <QStringBuilder>
|
|
#include <QScrollArea>
|
|
#include <QFileInfo>
|
|
#include <QFileDialog>
|
|
#include <QMessageBox>
|
|
#include <QProcess>
|
|
#include <QSettings>
|
|
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <windows.h>
|
|
#include <shellapi.h>
|
|
|
|
#include "helpers/StringHelper.h"
|
|
#include "helpers/LogHelper.h"
|
|
#include "helpers/ChannelHelper.h"
|
|
#include "Editor/helpers/GUIChannelHelper.h"
|
|
#include "Editor/helpers/DPIHelper.h"
|
|
#include "version.h"
|
|
#include "FilterTable.h"
|
|
#include "MainWindow.h"
|
|
#include "ui_MainWindow.h"
|
|
|
|
using namespace std;
|
|
|
|
MainWindow::MainWindow(QDir configDir, QWidget* parent)
|
|
: QMainWindow(parent), ui(new Ui::MainWindow), configDir(configDir)
|
|
{
|
|
outputDevices = QList<DeviceAPOInfo>::fromVector(QVector<DeviceAPOInfo>::fromStdVector(DeviceAPOInfo::loadAllInfos(false)));
|
|
inputDevices = QList<DeviceAPOInfo>::fromVector(QVector<DeviceAPOInfo>::fromStdVector(DeviceAPOInfo::loadAllInfos(true)));
|
|
|
|
defaultOutputDevice = NULL;
|
|
for (DeviceAPOInfo& apoInfo : outputDevices)
|
|
{
|
|
if (apoInfo.isDefaultDevice)
|
|
{
|
|
defaultOutputDevice = &apoInfo;
|
|
break;
|
|
}
|
|
}
|
|
|
|
ui->setupUi(this);
|
|
resize(DPIHelper::scale(QSize(1024, 768)));
|
|
|
|
LogHelper::set(stderr, true, false, false);
|
|
|
|
QString version = QString("%0.%1").arg(MAJOR).arg(MINOR);
|
|
if (REVISION != 0)
|
|
version += QString(".%0").arg(REVISION);
|
|
setWindowTitle(tr("Equalizer APO %0 Configuration Editor").arg(version));
|
|
|
|
QWidget* spacer = new QWidget;
|
|
spacer->setFixedWidth(10);
|
|
ui->mainToolBar->addWidget(spacer);
|
|
|
|
instantModeCheckBox = new QCheckBox(tr("Instant mode"));
|
|
instantModeCheckBox->setChecked(true);
|
|
instantModeCheckBox->setToolTip(tr("Changes are saved immediately"));
|
|
connect(instantModeCheckBox, SIGNAL(toggled(bool)), this, SLOT(instantModeEnabled(bool)));
|
|
ui->mainToolBar->addWidget(instantModeCheckBox);
|
|
|
|
spacer = new QWidget;
|
|
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
|
ui->mainToolBar->addWidget(spacer);
|
|
|
|
ui->mainToolBar->addWidget(new QLabel(tr("Device: ")));
|
|
|
|
deviceComboBox = new QComboBox;
|
|
connect(deviceComboBox, SIGNAL(activated(int)), this, SLOT(deviceSelected(int)));
|
|
ui->mainToolBar->addWidget(deviceComboBox);
|
|
|
|
spacer = new QWidget;
|
|
spacer->setFixedWidth(10);
|
|
ui->mainToolBar->addWidget(spacer);
|
|
|
|
ui->mainToolBar->addWidget(new QLabel(tr("Channel configuration: ")));
|
|
|
|
channelConfigurationComboBox = new QComboBox;
|
|
channelConfigurationComboBox->setSizeAdjustPolicy(QComboBox::AdjustToContents);
|
|
ui->mainToolBar->addWidget(channelConfigurationComboBox);
|
|
|
|
QStandardItemModel* model = qobject_cast<QStandardItemModel*>(deviceComboBox->model());
|
|
if (defaultOutputDevice != NULL)
|
|
deviceComboBox->addItem(tr("Default") + " (" + QString::fromStdWString(defaultOutputDevice->connectionName) + " - " + QString::fromStdWString(defaultOutputDevice->deviceName) + ")", NULL);
|
|
|
|
deviceComboBox->addItem(tr("Playback devices:"));
|
|
QStandardItem* item = model->item(model->rowCount() - 1);
|
|
QFont font = item->font();
|
|
font.setBold(true);
|
|
item->setFont(font);
|
|
item->setSelectable(false);
|
|
|
|
for (DeviceAPOInfo& apoInfo : outputDevices)
|
|
if (apoInfo.isInstalled)
|
|
deviceComboBox->addItem(QString::fromStdWString(apoInfo.connectionName) + " - " + QString::fromStdWString(apoInfo.deviceName), QVariant::fromValue(&apoInfo));
|
|
|
|
deviceComboBox->addItem(tr("Capture devices:"));
|
|
item = model->item(model->rowCount() - 1);
|
|
item->setFont(font);
|
|
item->setSelectable(false);
|
|
|
|
for (DeviceAPOInfo& apoInfo : inputDevices)
|
|
if (apoInfo.isInstalled)
|
|
deviceComboBox->addItem(QString::fromStdWString(apoInfo.connectionName) + " - " + QString::fromStdWString(apoInfo.deviceName), QVariant::fromValue(&apoInfo));
|
|
|
|
connect(channelConfigurationComboBox, SIGNAL(activated(int)), this, SLOT(channelConfigurationSelected(int)));
|
|
|
|
analysisPlotScene = new AnalysisPlotScene(ui->graphicsView);
|
|
ui->graphicsView->setScene(analysisPlotScene);
|
|
|
|
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()
|
|
{
|
|
delete ui;
|
|
|
|
delete analysisThread;
|
|
}
|
|
|
|
void MainWindow::doChecks()
|
|
{
|
|
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)
|
|
{
|
|
runConfigurator();
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!defaultOutputDevice->isInstalled)
|
|
{
|
|
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)
|
|
{
|
|
runConfigurator();
|
|
return;
|
|
}
|
|
}
|
|
|
|
DeviceAPOInfo* disabledApoInfo = NULL;
|
|
for (DeviceAPOInfo& apoInfo : outputDevices)
|
|
{
|
|
if (apoInfo.isInstalled && apoInfo.isEnhancementsDisabled)
|
|
{
|
|
disabledApoInfo = &apoInfo;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (disabledApoInfo == NULL)
|
|
{
|
|
for (DeviceAPOInfo& apoInfo : inputDevices)
|
|
{
|
|
if (apoInfo.isInstalled && apoInfo.isEnhancementsDisabled)
|
|
{
|
|
disabledApoInfo = &apoInfo;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (disabledApoInfo != NULL)
|
|
{
|
|
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)
|
|
{
|
|
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<QScrollArea*>(ui->tabWidget->widget(i));
|
|
FilterTable* filterTable = qobject_cast<FilterTable*>(scrollArea->widget());
|
|
|
|
if (filterTable->getConfigPath() == path)
|
|
{
|
|
ui->tabWidget->setCurrentIndex(i);
|
|
return;
|
|
}
|
|
}
|
|
|
|
QElapsedTimer timer;
|
|
timer.start();
|
|
|
|
HANDLE hFile = INVALID_HANDLE_VALUE;
|
|
while (hFile == INVALID_HANDLE_VALUE)
|
|
{
|
|
hFile = CreateFile(path.toStdWString().c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
|
if (hFile == INVALID_HANDLE_VALUE)
|
|
{
|
|
DWORD error = GetLastError();
|
|
if (error != ERROR_SHARING_VIOLATION)
|
|
{
|
|
QMessageBox::critical(this, tr("Error"), tr("Error while reading configuration file: %0").arg(QString::fromStdWString(StringHelper::getSystemErrorString(error))));
|
|
return;
|
|
}
|
|
|
|
// file is being written, so wait
|
|
Sleep(1);
|
|
}
|
|
}
|
|
|
|
stringstream inputStream;
|
|
|
|
char buf[8192];
|
|
unsigned long bytesRead = -1;
|
|
while (ReadFile(hFile, buf, sizeof(buf), &bytesRead, NULL) && bytesRead != 0)
|
|
{
|
|
inputStream.write(buf, bytesRead);
|
|
}
|
|
|
|
CloseHandle(hFile);
|
|
|
|
inputStream.seekg(0);
|
|
|
|
QList<QString> lines;
|
|
while (inputStream.good())
|
|
{
|
|
string encodedLine;
|
|
getline(inputStream, encodedLine);
|
|
if (encodedLine.size() > 0 && encodedLine[encodedLine.size() - 1] == '\r')
|
|
encodedLine.resize(encodedLine.size() - 1);
|
|
|
|
wstring line = StringHelper::toWString(encodedLine, CP_UTF8);
|
|
if (line.find(L'\uFFFD') != -1)
|
|
line = StringHelper::toWString(encodedLine, CP_ACP);
|
|
|
|
lines.append(QString::fromStdWString(line));
|
|
}
|
|
|
|
QFileInfo fileInfo(path);
|
|
FilterTable* filterTable = addTab(fileInfo.fileName(), QDir::toNativeSeparators(fileInfo.absoluteFilePath()), path, lines);
|
|
|
|
connect(filterTable, SIGNAL(linesChanged()), this, SLOT(linesChanged()));
|
|
|
|
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)
|
|
{
|
|
QElapsedTimer timer;
|
|
timer.start();
|
|
|
|
QList<QString> lines = filterTable->getLines();
|
|
|
|
bool first = true;
|
|
QByteArray byteArray;
|
|
for (QString line : lines)
|
|
{
|
|
if (first)
|
|
first = false;
|
|
else
|
|
byteArray.append("\r\n");
|
|
byteArray.append(line.toUtf8());
|
|
}
|
|
|
|
HANDLE hFile = INVALID_HANDLE_VALUE;
|
|
while (hFile == INVALID_HANDLE_VALUE)
|
|
{
|
|
hFile = CreateFile(path.toStdWString().c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
|
if (hFile == INVALID_HANDLE_VALUE)
|
|
{
|
|
DWORD error = GetLastError();
|
|
if (error != ERROR_SHARING_VIOLATION)
|
|
{
|
|
QMessageBox::critical(this, tr("Error"), tr("Error while writing configuration file: %0").arg(QString::fromStdWString(StringHelper::getSystemErrorString(error))));
|
|
return;
|
|
}
|
|
|
|
// file is being written, so wait
|
|
Sleep(1);
|
|
}
|
|
}
|
|
|
|
unsigned long bytesWritten;
|
|
WriteFile(hFile, byteArray.constData(), byteArray.length(), &bytesWritten, NULL);
|
|
if (bytesWritten != byteArray.length())
|
|
{
|
|
// should never happen
|
|
QMessageBox::critical(this, tr("Error"), tr("Only %0/%1 bytes have been written!").arg(bytesWritten).arg(byteArray.length()));
|
|
}
|
|
|
|
CloseHandle(hFile);
|
|
|
|
qDebug("Saving took %.1f ms", timer.nsecsElapsed() / 1e6);
|
|
|
|
startAnalysis();
|
|
}
|
|
|
|
bool MainWindow::isEmpty()
|
|
{
|
|
return ui->tabWidget->count() == 0;
|
|
}
|
|
|
|
bool MainWindow::shouldRestart()
|
|
{
|
|
return restart;
|
|
}
|
|
|
|
void MainWindow::closeEvent(QCloseEvent* event)
|
|
{
|
|
bool canceled = false;
|
|
for (int i = 0; i < ui->tabWidget->count(); i++)
|
|
{
|
|
if (!askForClose(i))
|
|
{
|
|
canceled = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (canceled)
|
|
{
|
|
event->ignore();
|
|
restart = false;
|
|
noSavePreferences = false;
|
|
noSaveFilePreferences = false;
|
|
}
|
|
else
|
|
{
|
|
savePreferences();
|
|
}
|
|
}
|
|
|
|
void MainWindow::deviceSelected(int index)
|
|
{
|
|
DeviceAPOInfo* apoInfo = deviceComboBox->itemData(index).value<DeviceAPOInfo*>();
|
|
if (apoInfo == NULL)
|
|
apoInfo = defaultOutputDevice;
|
|
|
|
channelConfigurationComboBox->clear();
|
|
|
|
const QList<GUIChannelHelper::ChannelConfigurationInfo>& infos = GUIChannelHelper::getInstance()->getChannelConfigurationInfos();
|
|
|
|
if (apoInfo != NULL)
|
|
{
|
|
const GUIChannelHelper::ChannelConfigurationInfo* selectedInfo = NULL;
|
|
for (const GUIChannelHelper::ChannelConfigurationInfo& info : infos)
|
|
{
|
|
if (info.channelMask == apoInfo->channelMask)
|
|
{
|
|
selectedInfo = &info;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (selectedInfo != NULL)
|
|
channelConfigurationComboBox->addItem(tr("From device") + " (" + selectedInfo->name + ")", 0);
|
|
else
|
|
channelConfigurationComboBox->addItem(tr("From device") + " (" + apoInfo->channelCount + " channels)", 0);
|
|
}
|
|
|
|
for (const GUIChannelHelper::ChannelConfigurationInfo& info : infos)
|
|
channelConfigurationComboBox->addItem(info.name, info.channelMask);
|
|
|
|
channelConfigurationSelected(channelConfigurationComboBox->currentIndex());
|
|
}
|
|
|
|
void MainWindow::channelConfigurationSelected(int index)
|
|
{
|
|
DeviceAPOInfo* selectedDevice;
|
|
int channelMask;
|
|
getDeviceAndChannelMask(&selectedDevice, &channelMask);
|
|
|
|
for (int i = 0; i < ui->tabWidget->count(); i++)
|
|
{
|
|
QScrollArea* scrollArea = qobject_cast<QScrollArea*>(ui->tabWidget->widget(i));
|
|
FilterTable* filterTable = qobject_cast<FilterTable*>(scrollArea->widget());
|
|
filterTable->updateDeviceAndChannelMask(selectedDevice, channelMask);
|
|
}
|
|
|
|
ui->analysisChannelComboBox->clear();
|
|
|
|
if (selectedDevice != NULL)
|
|
{
|
|
unsigned channelCount = selectedDevice->channelCount;
|
|
if (channelMask != 0 && channelMask != selectedDevice->channelMask)
|
|
{
|
|
channelCount = 0;
|
|
for (int i = 0; i < 31; i++)
|
|
{
|
|
int channelPos = 1 << i;
|
|
if (channelMask & channelPos)
|
|
channelCount++;
|
|
}
|
|
}
|
|
|
|
vector<wstring> channelNames = ChannelHelper::getChannelNames(channelCount, channelMask);
|
|
for (wstring channelName : channelNames)
|
|
{
|
|
ui->analysisChannelComboBox->addItem(QString::fromStdWString(channelName));
|
|
}
|
|
}
|
|
}
|
|
|
|
void MainWindow::linesChanged()
|
|
{
|
|
FilterTable* filterTable = qobject_cast<FilterTable*>(sender());
|
|
|
|
if (instantModeCheckBox->isChecked())
|
|
{
|
|
QString configPath = filterTable->getConfigPath();
|
|
if (configPath.length() > 0)
|
|
{
|
|
save(filterTable, configPath);
|
|
return;
|
|
}
|
|
}
|
|
|
|
int tabIndex = -1;
|
|
for (int i = 0; i < ui->tabWidget->count(); i++)
|
|
{
|
|
QScrollArea* scrollArea = qobject_cast<QScrollArea*>(ui->tabWidget->widget(i));
|
|
if (scrollArea->widget() == filterTable)
|
|
{
|
|
tabIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
QString tabText = ui->tabWidget->tabText(tabIndex);
|
|
if (!tabText.endsWith('*'))
|
|
{
|
|
tabText += '*';
|
|
ui->tabWidget->setTabText(tabIndex, tabText);
|
|
}
|
|
}
|
|
|
|
bool MainWindow::on_tabWidget_tabCloseRequested(int index)
|
|
{
|
|
if (askForClose(index))
|
|
ui->tabWidget->removeTab(index);
|
|
return true;
|
|
}
|
|
|
|
void MainWindow::on_actionOpen_triggered()
|
|
{
|
|
QScrollArea* scrollArea = qobject_cast<QScrollArea*>(ui->tabWidget->currentWidget());
|
|
QString path;
|
|
if (scrollArea != NULL)
|
|
{
|
|
FilterTable* filterTable = qobject_cast<FilterTable*>(scrollArea->widget());
|
|
if (filterTable->getConfigPath().length() > 0)
|
|
{
|
|
QFileInfo fileInfo(filterTable->getConfigPath());
|
|
path = fileInfo.absolutePath();
|
|
}
|
|
}
|
|
if (path.length() == 0)
|
|
path = configDir.absolutePath();
|
|
|
|
QFileDialog dialog(this, tr("Open file"), path, "*.txt");
|
|
dialog.setFileMode(QFileDialog::ExistingFiles);
|
|
dialog.setNameFilter(tr("E-APO configurations (*.txt)"));
|
|
|
|
if (dialog.exec() == QDialog::Accepted)
|
|
{
|
|
for (QString file : dialog.selectedFiles())
|
|
load(file);
|
|
}
|
|
}
|
|
|
|
void MainWindow::on_actionSave_triggered()
|
|
{
|
|
QScrollArea* scrollArea = qobject_cast<QScrollArea*>(ui->tabWidget->currentWidget());
|
|
if (scrollArea == NULL)
|
|
return;
|
|
|
|
FilterTable* filterTable = qobject_cast<FilterTable*>(scrollArea->widget());
|
|
|
|
if (filterTable->getConfigPath().length() == 0)
|
|
{
|
|
ui->actionSaveAs->trigger();
|
|
}
|
|
else
|
|
{
|
|
save(filterTable, filterTable->getConfigPath());
|
|
|
|
QString tabText = ui->tabWidget->tabText(ui->tabWidget->currentIndex());
|
|
if (tabText.endsWith('*'))
|
|
ui->tabWidget->setTabText(ui->tabWidget->currentIndex(), tabText.left(tabText.length() - 1));
|
|
}
|
|
}
|
|
|
|
void MainWindow::on_actionSaveAs_triggered()
|
|
{
|
|
QScrollArea* scrollArea = qobject_cast<QScrollArea*>(ui->tabWidget->currentWidget());
|
|
if (scrollArea == NULL)
|
|
return;
|
|
|
|
FilterTable* filterTable = qobject_cast<FilterTable*>(scrollArea->widget());
|
|
QString path;
|
|
QString filename;
|
|
if (filterTable->getConfigPath().length() == 0)
|
|
{
|
|
path = configDir.absolutePath();
|
|
}
|
|
else
|
|
{
|
|
QFileInfo fileInfo(filterTable->getConfigPath());
|
|
path = fileInfo.absolutePath();
|
|
filename = fileInfo.fileName();
|
|
}
|
|
QFileDialog dialog(this, tr("Save file as"), path, "*.txt");
|
|
dialog.setFileMode(QFileDialog::AnyFile);
|
|
dialog.setAcceptMode(QFileDialog::AcceptSave);
|
|
dialog.setNameFilter(tr("E-APO configurations (*.txt)"));
|
|
dialog.setDefaultSuffix(".txt");
|
|
if (filename.length() > 0)
|
|
dialog.selectFile(filename);
|
|
|
|
if (dialog.exec() == QDialog::Accepted)
|
|
{
|
|
QString savePath = dialog.selectedFiles().first();
|
|
save(filterTable, savePath);
|
|
filterTable->setConfigPath(QDir::toNativeSeparators(savePath));
|
|
|
|
QFileInfo fileInfo(savePath);
|
|
ui->tabWidget->setTabText(ui->tabWidget->currentIndex(), fileInfo.fileName());
|
|
ui->tabWidget->setTabToolTip(ui->tabWidget->currentIndex(), QDir::toNativeSeparators(fileInfo.absoluteFilePath()));
|
|
}
|
|
}
|
|
|
|
void MainWindow::on_actionNew_triggered()
|
|
{
|
|
FilterTable* filterTable = addTab(tr("Unsaved"), "", "", QList<QString>());
|
|
|
|
connect(filterTable, SIGNAL(linesChanged()), this, SLOT(linesChanged()));
|
|
ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1);
|
|
}
|
|
|
|
void MainWindow::recentFileSelected()
|
|
{
|
|
QAction* action = qobject_cast<QAction*>(sender());
|
|
load(action->text());
|
|
}
|
|
|
|
void MainWindow::on_actionCut_triggered()
|
|
{
|
|
QScrollArea* scrollArea = qobject_cast<QScrollArea*>(ui->tabWidget->currentWidget());
|
|
if (scrollArea == NULL)
|
|
return;
|
|
|
|
FilterTable* filterTable = qobject_cast<FilterTable*>(scrollArea->widget());
|
|
filterTable->cut();
|
|
}
|
|
|
|
void MainWindow::on_actionCopy_triggered()
|
|
{
|
|
QScrollArea* scrollArea = qobject_cast<QScrollArea*>(ui->tabWidget->currentWidget());
|
|
if (scrollArea == NULL)
|
|
return;
|
|
|
|
FilterTable* filterTable = qobject_cast<FilterTable*>(scrollArea->widget());
|
|
filterTable->copy();
|
|
}
|
|
|
|
void MainWindow::on_actionPaste_triggered()
|
|
{
|
|
QScrollArea* scrollArea = qobject_cast<QScrollArea*>(ui->tabWidget->currentWidget());
|
|
if (scrollArea == NULL)
|
|
return;
|
|
|
|
FilterTable* filterTable = qobject_cast<FilterTable*>(scrollArea->widget());
|
|
filterTable->paste();
|
|
}
|
|
|
|
void MainWindow::on_actionDelete_triggered()
|
|
{
|
|
QScrollArea* scrollArea = qobject_cast<QScrollArea*>(ui->tabWidget->currentWidget());
|
|
if (scrollArea == NULL)
|
|
return;
|
|
|
|
FilterTable* filterTable = qobject_cast<FilterTable*>(scrollArea->widget());
|
|
filterTable->deleteSelectedLines();
|
|
}
|
|
|
|
void MainWindow::instantModeEnabled(bool enabled)
|
|
{
|
|
if (enabled)
|
|
{
|
|
for (int i = 0; i < ui->tabWidget->count(); i++)
|
|
{
|
|
QScrollArea* scrollArea = qobject_cast<QScrollArea*>(ui->tabWidget->widget(i));
|
|
FilterTable* filterTable = qobject_cast<FilterTable*>(scrollArea->widget());
|
|
|
|
if (filterTable->getConfigPath().length() > 0)
|
|
{
|
|
save(filterTable, filterTable->getConfigPath());
|
|
|
|
QString tabText = ui->tabWidget->tabText(i);
|
|
if (tabText.endsWith('*'))
|
|
ui->tabWidget->setTabText(i, tabText.left(tabText.length() - 1));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void MainWindow::on_tabWidget_currentChanged(int index)
|
|
{
|
|
startAnalysis();
|
|
}
|
|
|
|
void MainWindow::on_startFromComboBox_activated(int index)
|
|
{
|
|
startAnalysis();
|
|
}
|
|
|
|
void MainWindow::on_analysisChannelComboBox_activated(int index)
|
|
{
|
|
startAnalysis();
|
|
}
|
|
|
|
void MainWindow::on_resolutionSpinBox_valueChanged(int value)
|
|
{
|
|
startAnalysis();
|
|
}
|
|
|
|
void MainWindow::updateAnalysisPanel()
|
|
{
|
|
analysisThread->beginGetResult();
|
|
int sampleRate = analysisThread->getFreqDataSampleRate();
|
|
int latency = analysisThread->getLatency();
|
|
analysisPlotScene->setFreqData(analysisThread->getFreqData(), analysisThread->getFreqDataLength(), sampleRate);
|
|
|
|
double peakGain = analysisThread->getPeakGain();
|
|
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 s.)").arg(latency * 1000.0 / sampleRate, 0, 'f', 1).arg(latency));
|
|
|
|
ui->initTimeValueLabel->setText(tr("%0 ms").arg(analysisThread->getInitializationTime(), 0, 'f', 1));
|
|
|
|
double cpuUsage = analysisThread->getProcessingTime() * 100.0 / (analysisThread->getProcessedFrames() * 1000.0 / sampleRate);
|
|
ui->cpuUsageValueLabel->setText(tr("%0 % (one core)").arg(cpuUsage, 0, 'f', 1));
|
|
ui->cpuUsageValueLabel->setForegroundRole(cpuUsage >= 20 ? (cpuUsage >= 50 ? QPalette::Dark : QPalette::Midlight) : QPalette::WindowText);
|
|
|
|
analysisThread->endGetResult();
|
|
}
|
|
|
|
void MainWindow::on_mainToolBar_visibilityChanged(bool visible)
|
|
{
|
|
ui->actionToolbar->setChecked(visible);
|
|
}
|
|
|
|
void MainWindow::on_analysisDockWidget_visibilityChanged(bool visible)
|
|
{
|
|
ui->actionAnalysisPanel->setChecked(visible);
|
|
}
|
|
|
|
void MainWindow::on_actionToolbar_triggered(bool checked)
|
|
{
|
|
ui->mainToolBar->setVisible(checked);
|
|
}
|
|
|
|
void MainWindow::on_actionAnalysisPanel_triggered(bool checked)
|
|
{
|
|
ui->analysisDockWidget->setVisible(checked);
|
|
}
|
|
|
|
void MainWindow::languageSelected(bool selected)
|
|
{
|
|
QAction* action = qobject_cast<QAction*>(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, QString configPath, QList<QString> lines)
|
|
{
|
|
QScrollArea* scrollArea = new QScrollArea(ui->tabWidget);
|
|
scrollArea->setWidgetResizable(true);
|
|
FilterTable* filterTable = new FilterTable(this);
|
|
scrollArea->setWidget(filterTable);
|
|
filterTable->setAcceptDrops(true);
|
|
filterTable->setFocusPolicy(Qt::WheelFocus);
|
|
|
|
DeviceAPOInfo* selectedDevice;
|
|
int channelMask;
|
|
getDeviceAndChannelMask(&selectedDevice, &channelMask);
|
|
filterTable->updateDeviceAndChannelMask(selectedDevice, channelMask);
|
|
filterTable->initialize(scrollArea, outputDevices, inputDevices);
|
|
filterTable->setLines(configPath, lines);
|
|
|
|
int tabIndex = ui->tabWidget->addTab(scrollArea, title);
|
|
ui->tabWidget->setTabToolTip(tabIndex, tooltip);
|
|
|
|
return filterTable;
|
|
}
|
|
|
|
void MainWindow::getDeviceAndChannelMask(DeviceAPOInfo** selectedDevice, int* channelMask)
|
|
{
|
|
*selectedDevice = deviceComboBox->currentData().value<DeviceAPOInfo*>();
|
|
if (*selectedDevice == NULL)
|
|
*selectedDevice = defaultOutputDevice;
|
|
|
|
*channelMask = channelConfigurationComboBox->currentData().toInt();
|
|
if (*channelMask == 0 && selectedDevice != NULL)
|
|
{
|
|
*channelMask = (*selectedDevice)->channelMask;
|
|
|
|
if (*channelMask == 0)
|
|
*channelMask = ChannelHelper::getDefaultChannelMask((*selectedDevice)->channelCount);
|
|
}
|
|
}
|
|
|
|
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<QScrollArea*>(ui->tabWidget->widget(tabIndex));
|
|
if (scrollArea != NULL)
|
|
{
|
|
FilterTable* filterTable = qobject_cast<FilterTable*>(scrollArea->widget());
|
|
filterTable->savePreferences();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void MainWindow::startAnalysis()
|
|
{
|
|
DeviceAPOInfo* selectedDevice;
|
|
|
|
int channelMask;
|
|
getDeviceAndChannelMask(&selectedDevice, &channelMask);
|
|
|
|
if (selectedDevice != NULL)
|
|
{
|
|
QString configPath;
|
|
|
|
if (ui->startFromComboBox->currentIndex() == 1)
|
|
{
|
|
QScrollArea* scrollArea = qobject_cast<QScrollArea*>(ui->tabWidget->currentWidget());
|
|
if (scrollArea != NULL)
|
|
{
|
|
FilterTable* filterTable = qobject_cast<FilterTable*>(scrollArea->widget());
|
|
|
|
if (filterTable->getConfigPath().length() > 0)
|
|
configPath = filterTable->getConfigPath();
|
|
}
|
|
}
|
|
|
|
if (configPath.isEmpty())
|
|
configPath = configDir.absoluteFilePath("config.txt");
|
|
configPath = QDir::toNativeSeparators(configPath);
|
|
|
|
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());
|
|
QString selectedDeviceGuid = settings.value("selectedDeviceGuid").toString();
|
|
if (!selectedDeviceGuid.isEmpty())
|
|
{
|
|
for (int i = 0; i < deviceComboBox->count(); i++)
|
|
{
|
|
DeviceAPOInfo* apoInfo = deviceComboBox->itemData(i).value<DeviceAPOInfo*>();
|
|
if (apoInfo != NULL)
|
|
{
|
|
if (QString::fromStdWString(apoInfo->deviceGuid).compare(selectedDeviceGuid, Qt::CaseInsensitive) == 0)
|
|
{
|
|
deviceComboBox->setCurrentIndex(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
deviceSelected(deviceComboBox->currentIndex());
|
|
|
|
int selectedChannelMask = settings.value("selectedChannelMask").toInt();
|
|
if (selectedChannelMask != 0)
|
|
{
|
|
int index = channelConfigurationComboBox->findData(selectedChannelMask);
|
|
if (index != -1)
|
|
channelConfigurationComboBox->setCurrentIndex(index);
|
|
}
|
|
channelConfigurationSelected(channelConfigurationComboBox->currentIndex());
|
|
|
|
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 = DPIHelper::scaleZoom(settings.value("analysis/zoomX", 1.0).toDouble());
|
|
double zoomY = DPIHelper::scaleZoom(settings.value("analysis/zoomY", 1.0).toDouble());
|
|
analysisPlotScene->setZoom(zoomX, zoomY);
|
|
bool ok;
|
|
int scrollX = DPIHelper::scale(settings.value("analysis/scrollX").toDouble(&ok));
|
|
if (!ok)
|
|
scrollX = round(analysisPlotScene->hzToX(20));
|
|
int scrollY = DPIHelper::scale(settings.value("analysis/scrollY").toDouble(&ok));
|
|
if (!ok)
|
|
scrollY = round(analysisPlotScene->dbToY(22));
|
|
|
|
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());
|
|
DeviceAPOInfo* selectedDevice = deviceComboBox->currentData().value<DeviceAPOInfo*>();
|
|
settings.setValue("selectedDeviceGuid", selectedDevice != NULL ? QString::fromStdWString(selectedDevice->deviceGuid) : "");
|
|
int channelMask = channelConfigurationComboBox->currentData().toInt();
|
|
settings.setValue("selectedChannelMask", channelMask);
|
|
|
|
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", DPIHelper::invScaleZoom(analysisPlotScene->getZoomX()));
|
|
settings.setValue("analysis/zoomY", DPIHelper::invScaleZoom(analysisPlotScene->getZoomY()));
|
|
QScrollBar* hScrollBar = ui->graphicsView->horizontalScrollBar();
|
|
double value = DPIHelper::invScale(hScrollBar->value());
|
|
settings.setValue("analysis/scrollX", value);
|
|
QScrollBar* vScrollBar = ui->graphicsView->verticalScrollBar();
|
|
value = DPIHelper::invScale(vScrollBar->value());
|
|
settings.setValue("analysis/scrollY", value);
|
|
|
|
QStringList fileList;
|
|
for (int i = 0; i < ui->tabWidget->count(); i++)
|
|
{
|
|
QScrollArea* scrollArea = qobject_cast<QScrollArea*>(ui->tabWidget->widget(i));
|
|
if (scrollArea == NULL)
|
|
continue;
|
|
FilterTable* filterTable = qobject_cast<FilterTable*>(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<QAction*> 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<QAction*> 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);
|
|
}
|
|
}
|
|
}
|