#include <QLineEdit>
#include <QToolButton>
#include <QFileDialog>
#include <QDir>
#include <QMessageBox>
#include <QStandardPaths>
#include <QHeaderView>
#include <QCheckBox>
#include <set>
#include "qmpsettingswindow.hpp"
#include "qmpdeviceprioritydialog.hpp"
#include "ui_qmpsettingswindow.h"
#include "qmpmainwindow.hpp"
qmpSettingsWindow::qmpSettingsWindow(qmpSettings *qmpsettings, QWidget *parent) :
QDialog(parent),
ui(new Ui::qmpSettingsWindow)
{
ui->setupUi(this);
customOptions.clear();
customOptPages.clear();
connect(this, &qmpSettingsWindow::dialogClosing, (qmpMainWindow *)parent, &qmpMainWindow::dialogClosed);
settings = qmpsettings;
cwt = new qmpCustomizeWindow(this);
cwa = new qmpCustomizeWindow(this);
dps = new qmpDevPropDialog(this);
devpriod = new qmpDevicePriorityDialog(this);
}
qmpSettingsWindow::~qmpSettingsWindow()
{
delete ui;
}
void qmpSettingsWindow::closeEvent(QCloseEvent *event)
{
setVisible(false);
loadOption();
emit dialogClosing();
event->accept();
}
void qmpSettingsWindow::hideEvent(QHideEvent *event)
{
emit dialogClosing();
event->accept();
}
void qmpSettingsWindow::on_buttonBox_accepted()
{
saveOption();
qmpMainWindow::getInstance()->setupWidget();
emit dialogClosing();
}
void qmpSettingsWindow::on_buttonBox_rejected()
{
loadOption();
emit dialogClosing();
}
void qmpSettingsWindow::updatePluginList(qmpPluginManager *pmgr)
{
std::vector<qmpPlugin> *plugins = pmgr->getPlugins();
QVariant *data = static_cast<QVariant *>(settings->getOptionCustom("DisabledPlugins"));
QList<QVariant> disabled_plugins_l = static_cast<QVariant *>(data)->toList();
delete data;
std::set<std::string> disabled_plugins_s;
for (auto &i : disabled_plugins_l)
disabled_plugins_s.insert(i.toString().toStdString());
for (unsigned i = 0; i < plugins->size(); ++i)
{
bool enabled = disabled_plugins_s.find(plugins->at(i).name) == disabled_plugins_s.end();
plugins->at(i).enabled = enabled;
}
}
void qmpSettingsWindow::postInit()
{
setupWidgets();
int sf = 0;
QVariant *data = static_cast<QVariant *>(settings->getOptionCustom("FluidSynth/SoundFonts"));
for (auto i : data->toList())
if (!i.toString().startsWith('#'))
{
sf = 1;
break;
}
delete data;
std::string selecteddev;
std::vector<std::string> devs = qmpMainWindow::getInstance()->getPlayer()->getMidiOutDevices();
std::set<std::string> devset;
for (auto dev : devs)
devset.insert(dev);
QVariant *devpriov = static_cast<QVariant *>(qmpMainWindow::getInstance()->getSettings()->getOptionCustom("Midi/DevicePriority"));
QList<QVariant> devprio = devpriov->toList();
delete devpriov;
for (auto &setdev : devprio)
if (devset.find(setdev.toString().toStdString()) != devset.end())
{
selecteddev = setdev.toString().toStdString();
break;
}
if (selecteddev == "Internal FluidSynth" && !sf)
{
if (QMessageBox::question(this,
tr("No soundfont loaded"),
tr("Internal fluidsynth is the only available MIDI output but it has no soundfont set. "
"Would you like to setup soundfonts now? You may have to reload the internal synth afterwards.")) == QMessageBox::Yes)
{
show();
ui->tabWidget->setCurrentWidget(qobject_cast<QWidget *>(pageForTab("SoundFonts")->parent()));
}
}
}
void qmpSettingsWindow::registerCustomizeWidgetOptions()
{
QPushButton *pbCustomizeToolbar = new QPushButton(tr("Customize..."));
QPushButton *pbCustomizeActions = new QPushButton(tr("Customize..."));
QVariant toolbar_def_val = QList<QVariant>({"Channel", "Playlist", "Effects", "Visualization"});
QVariant actions_def_val = QList<QVariant>({"FileInfo", "Render", "Panic", "ReloadSynth"});
settings->registerOptionCustom("Behavior", "Customize toolbar", "Behavior/Toolbar", pbCustomizeToolbar, &toolbar_def_val, std::bind(&qmpCustomizeWindow::save, cwt), std::bind(&qmpCustomizeWindow::load, cwt, std::placeholders::_1));
settings->registerOptionCustom("Behavior", "Customize actions", "Behavior/Actions", pbCustomizeActions, &actions_def_val, std::bind(&qmpCustomizeWindow::save, cwa), std::bind(&qmpCustomizeWindow::load, cwa, std::placeholders::_1));
connect(pbCustomizeToolbar, &QPushButton::clicked, [this] {loadOption("Behavior/Toolbar"); cwt->show();});
connect(pbCustomizeActions, &QPushButton::clicked, [this] {loadOption("Behavior/Actions"); cwa->show();});
connect(cwt, &QDialog::accepted, [this] {saveOption("Behavior/Toolbar"); qmpMainWindow::getInstance()->setupWidget();});
connect(cwa, &QDialog::accepted, [this] {saveOption("Behavior/Actions"); qmpMainWindow::getInstance()->setupWidget();});
connect(cwt, &QDialog::rejected, [this] {loadOption("Behavior/Toolbar");});
connect(cwa, &QDialog::rejected, [this] {loadOption("Behavior/Actions");});
qmpMainWindow::getInstance()->setupWidget();
}
void qmpSettingsWindow::registerSoundFontOption()
{
QWidget *sfpanel = new QWidget();
sfpanel->setLayout(new QVBoxLayout);
sfpanel->layout()->setMargin(0);
QTableWidget *twsf = new QTableWidget();
twsf->setColumnCount(2);
twsf->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeMode::ResizeToContents);
twsf->setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection);
twsf->setSelectionBehavior(QAbstractItemView::SelectionBehavior::SelectRows);
twsf->setHorizontalHeaderLabels({tr("E"), tr("Path")});
twsf->setHorizontalScrollMode(QAbstractItemView::ScrollMode::ScrollPerPixel);
twsf->setVerticalScrollMode(QAbstractItemView::ScrollMode::ScrollPerPixel);
sfpanel->layout()->addWidget(twsf);
QWidget *controls = new QWidget();
controls->setLayout(new QHBoxLayout);
controls->layout()->setMargin(0);
QPushButton *pbsfadd = new QPushButton(style()->standardIcon(QStyle::StandardPixmap::SP_DialogOpenButton), QString());
QPushButton *pbsfrem = new QPushButton(style()->standardIcon(QStyle::StandardPixmap::SP_DialogDiscardButton), QString());
QPushButton *pbsfmup = new QPushButton(style()->standardIcon(QStyle::StandardPixmap::SP_ArrowUp), QString());
QPushButton *pbsfmdn = new QPushButton(style()->standardIcon(QStyle::StandardPixmap::SP_ArrowDown), QString());
controls->layout()->addWidget(pbsfadd);
controls->layout()->addWidget(pbsfrem);
controls->layout()->addWidget(pbsfmup);
controls->layout()->addWidget(pbsfmdn);
sfpanel->layout()->addWidget(controls);
connect(pbsfadd, &QPushButton::clicked, [twsf, this]
{
QStringList sl = QFileDialog::getOpenFileNames(this, "Add File", "", "SoundFont files (*.sf2)");
for (int i = 0; i < sl.size(); ++i)
{
twsf->insertRow(twsf->rowCount());
QTableWidgetItem *sfn, *sfe;
twsf->setItem(twsf->rowCount() - 1, 1, sfn = new QTableWidgetItem(sl[i]));
twsf->setItem(twsf->rowCount() - 1, 0, sfe = new QTableWidgetItem());
sfe->setCheckState(Qt::CheckState::Unchecked);
sfn->setFlags(Qt::ItemFlag::ItemIsEnabled | Qt::ItemFlag::ItemIsSelectable);
sfe->setFlags(Qt::ItemFlag::ItemIsEnabled | Qt::ItemFlag::ItemIsSelectable | Qt::ItemFlag::ItemIsUserCheckable);
}
});
connect(pbsfrem, &QPushButton::clicked, [twsf]
{
QList<QTableWidgetItem *> sl = twsf->selectedItems();
for (int i = 0; i < sl.size(); ++i)
twsf->removeRow(twsf->row(sl[i]));
});
connect(pbsfmup, &QPushButton::clicked, [twsf]
{
int cid = twsf->currentRow();
if (!cid)
return;
QTableWidgetItem *ci = twsf->takeItem(cid, 1);
QTableWidgetItem *ce = twsf->takeItem(cid, 0);
twsf->removeRow(cid);
twsf->insertRow(cid - 1);
twsf->setItem(cid - 1, 0, ce);
twsf->setItem(cid - 1, 1, ci);
twsf->setCurrentCell(cid - 1, 1);
});
connect(pbsfmdn, &QPushButton::clicked, [twsf]
{
int cid = twsf->currentRow(); if (cid == twsf->rowCount() - 1)
return;
QTableWidgetItem *ci = twsf->takeItem(cid, 1);
QTableWidgetItem *ce = twsf->takeItem(cid, 0);
twsf->removeRow(cid);
twsf->insertRow(cid + 1);
twsf->setItem(cid + 1, 0, ce);
twsf->setItem(cid + 1, 1, ci);
twsf->setCurrentCell(cid + 1, 1);
});
QVariant sf_def_val = QList<QVariant>();
auto save_func = [twsf]()->void *
{
QList<QVariant> sflist;
for (int i = 0; i < twsf->rowCount(); ++i)
{
QString sfs = twsf->item(i, 1)->text();
if (twsf->item(i, 0)->checkState() == Qt::CheckState::Unchecked)
sfs = "#" + sfs;
sflist.push_back(sfs);
}
return new QVariant(sflist);
};
auto load_func = [twsf](void *data)
{
QList<QVariant> sflist = static_cast<QVariant *>(data)->toList();
twsf->clearContents();
twsf->setRowCount(0);
for (int i = 0; i < sflist.size(); ++i)
{
twsf->insertRow(i);
QTableWidgetItem *sfn, *sfe;
QString sf = sflist[i].toString();
bool enabled = !sf.startsWith('#');
if (!enabled)
sf = sf.mid(1);
twsf->setItem(i, 1, sfn = new QTableWidgetItem(sf));
twsf->setItem(i, 0, sfe = new QTableWidgetItem());
sfn->setFlags(Qt::ItemFlag::ItemIsEnabled | Qt::ItemFlag::ItemIsSelectable);
sfe->setFlags(Qt::ItemFlag::ItemIsEnabled | Qt::ItemFlag::ItemIsSelectable | Qt::ItemFlag::ItemIsUserCheckable);
sfe->setCheckState(enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked);
}
};
settings->registerOptionCustom("SoundFonts", "", "FluidSynth/SoundFonts", sfpanel, &sf_def_val, save_func, load_func);
}
void qmpSettingsWindow::registerPluginOption(qmpPluginManager *pmgr)
{
QTableWidget *twplugins = new QTableWidget();
twplugins->setColumnCount(4);
twplugins->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeMode::ResizeToContents);
twplugins->setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection);
twplugins->setSelectionBehavior(QAbstractItemView::SelectionBehavior::SelectRows);
twplugins->setHorizontalHeaderLabels({tr("E"), tr("Plugin Name"), tr("Version"), tr("Path")});
twplugins->setHorizontalScrollMode(QAbstractItemView::ScrollMode::ScrollPerPixel);
twplugins->setVerticalScrollMode(QAbstractItemView::ScrollMode::ScrollPerPixel);
QVariant ep_def_val = QList<QVariant>();
auto save_func = [twplugins, this]()->void *
{
QVariant *data = static_cast<QVariant *>(settings->getOptionCustom("DisabledPlugins"));
QList<QVariant> disabled_plugins_ol = static_cast<QVariant *>(data)->toList();
delete data;
std::set<std::string> disabled_plugins_s;
for (auto &i : disabled_plugins_ol)
disabled_plugins_s.insert(i.toString().toStdString());
for (int i = 0; i < twplugins->rowCount(); ++i)
{
QString pn = twplugins->item(i, 1)->text();
if (twplugins->item(i, 0)->checkState() == Qt::CheckState::Unchecked)
disabled_plugins_s.insert(pn.toStdString());
else
disabled_plugins_s.erase(pn.toStdString());
}
QList<QVariant> disabled_plugins;
for (auto &i : disabled_plugins_s)
disabled_plugins.push_back(QString(i.c_str()));
return new QVariant(disabled_plugins);
};
auto load_func = [twplugins, pmgr](void *data)
{
QList<QVariant> disabled_plugins_l = static_cast<QVariant *>(data)->toList();
std::set<std::string> disabled_plugins;
for (auto i : disabled_plugins_l)
disabled_plugins.insert(i.toString().toStdString());
twplugins->clearContents();
twplugins->setRowCount(0);
std::vector<qmpPlugin> *plugins = pmgr->getPlugins();
for (int i = 0; static_cast<size_t>(i) < plugins->size(); ++i)
{
twplugins->insertRow(i);
qmpPlugin &p = plugins->at(static_cast<size_t>(i));
QTableWidgetItem *icb;
twplugins->setItem(i, 0, icb = new QTableWidgetItem());
bool enabled = disabled_plugins.find(p.name) == disabled_plugins.end();
icb->setCheckState(enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked);
icb->setFlags(Qt::ItemFlag::ItemIsEnabled | Qt::ItemFlag::ItemIsSelectable | Qt::ItemFlag::ItemIsUserCheckable);
twplugins->setItem(i, 1, new QTableWidgetItem(p.name.c_str()));
twplugins->setItem(i, 2, new QTableWidgetItem(p.version.c_str()));
twplugins->setItem(i, 3, new QTableWidgetItem(p.path.c_str()));
for (int j = 1; j <= 3; ++j)
twplugins->item(i, j)->setFlags(Qt::ItemFlag::ItemIsEnabled | Qt::ItemFlag::ItemIsSelectable);
}
};
settings->registerOptionCustom("Plugins", "", "DisabledPlugins", twplugins, &ep_def_val, save_func, load_func);
}
void qmpSettingsWindow::registerExtraMidiOptions()
{
QPushButton *pbDevPrio = new QPushButton("...");
connect(pbDevPrio, &QPushButton::clicked, [this] {loadOption("Midi/DevicePriority"); devpriod->show();});
connect(devpriod, &QDialog::accepted, [this] {saveOption("Midi/DevicePriority");});
connect(devpriod, &QDialog::rejected, [this] {loadOption("Midi/DevicePriority");});
QVariant devprio_def_val = QList<QVariant>({"Internal FluidSynth"});
settings->registerOptionCustom("MIDI", "Select MIDI output devices", "Midi/DevicePriority", pbDevPrio, &devprio_def_val, std::bind(&qmpDevicePriorityDialog::save, devpriod), std::bind(&qmpDevicePriorityDialog::load, devpriod, std::placeholders::_1));
QPushButton *pbDevProp = new QPushButton("...");
connect(pbDevProp, &QPushButton::clicked, [this] {loadOption("Midi/DeviceInitializationFiles"); dps->show();});
connect(dps, &QDialog::accepted, [this] {saveOption("Midi/DeviceInitializationFiles");});
connect(dps, &QDialog::rejected, [this] {loadOption("Midi/DeviceInitializationFiles");});
QVariant devprop_def_val = QList<QVariant>({});
settings->registerOptionCustom("MIDI", "External MIDI device setup", "Midi/DeviceInitializationFiles", pbDevProp, &devprop_def_val, std::bind(&qmpDevPropDialog::save, dps), std::bind(&qmpDevPropDialog::load, dps, std::placeholders::_1));
}
void qmpSettingsWindow::saveOption(std::string key)
{
auto save_opt = [this](std::string & key)->QVariant
{
qmpOption &o = settings->options[key];
QVariant ret;
switch (o.type)
{
case qmpOption::ParameterType::parameter_int:
{
QSpinBox *sb = qobject_cast<QSpinBox *>(o.widget);
if (sb)
ret = sb->value();
}
break;
case qmpOption::ParameterType::parameter_uint:
{
QHexSpinBox *sb = qobject_cast<QHexSpinBox *>(o.widget);
if (sb)
{
int val = sb->value();
ret = reinterpret_cast<unsigned &>(val);
}
}
break;
case qmpOption::ParameterType::parameter_bool:
{
QCheckBox *cb = qobject_cast<QCheckBox *>(o.widget);
if (cb)
ret = cb->isChecked();
}
break;
case qmpOption::ParameterType::parameter_double:
{
QDoubleSpinBox *sb = qobject_cast<QDoubleSpinBox *>(o.widget);
if (sb)
ret = sb->value();
}
break;
case qmpOption::ParameterType::parameter_str:
{
QLineEdit *le = qobject_cast<QLineEdit *>(o.widget);
if (le)
ret = le->text();
}
break;
case qmpOption::ParameterType::parameter_enum:
{
QComboBox *cb = qobject_cast<QComboBox *>(o.widget);
if (cb)
ret = cb->currentText();
}
break;
case qmpOption::ParameterType::parameter_url:
{
QFileEdit *fe = qobject_cast<QFileEdit *>(o.widget);
if (fe)
ret = fe->text();
}
break;
default:
if (o.save_func)
{
QVariant *var = static_cast<QVariant *>(o.save_func());
ret = QVariant(*var);
delete var;
}
break;
}
return ret;
};
if (key.length())
{
QVariant r = save_opt(key);
if (r.isValid())
settings->settings->setValue(QString(key.c_str()), r);
}
else for (std::string &key : settings->optionlist)
{
QVariant r = save_opt(key);
if (r.isValid())
settings->settings->setValue(QString(key.c_str()), r);
}
settings->settings->sync();
}
void qmpSettingsWindow::loadOption(std::string key)
{
auto load_opt = [this](std::string & key)
{
qmpOption &o = settings->options[key];
switch (o.type)
{
case qmpOption::ParameterType::parameter_int:
{
QSpinBox *sb = qobject_cast<QSpinBox *>(o.widget);
if (sb)
sb->setValue(settings->getOptionInt(key));
}
break;
case qmpOption::ParameterType::parameter_uint:
{
QHexSpinBox *sb = qobject_cast<QHexSpinBox *>(o.widget);
if (sb)
sb->setValue(settings->getOptionUint(key));
}
break;
case qmpOption::ParameterType::parameter_bool:
{
QCheckBox *cb = qobject_cast<QCheckBox *>(o.widget);
if (cb)
cb->setChecked(settings->getOptionBool(key));
}
break;
case qmpOption::ParameterType::parameter_double:
{
QDoubleSpinBox *sb = qobject_cast<QDoubleSpinBox *>(o.widget);
if (sb)
sb->setValue(settings->getOptionDouble(key));
}
break;
case qmpOption::ParameterType::parameter_str:
{
QLineEdit *le = qobject_cast<QLineEdit *>(o.widget);
if (le)
le->setText(QString(settings->getOptionString(key).c_str()));
}
break;
case qmpOption::ParameterType::parameter_enum:
{
QComboBox *cb = qobject_cast<QComboBox *>(o.widget);
if (cb)
cb->setCurrentIndex(settings->getOptionEnumInt(key));
}
break;
case qmpOption::ParameterType::parameter_url:
{
QFileEdit *fe = qobject_cast<QFileEdit *>(o.widget);
if (fe)
fe->setText(QString(settings->getOptionString(key).c_str()));
}
break;
default:
if (o.load_func)
{
void *var = settings->getOptionCustom(key);
o.load_func(var);
delete static_cast<QVariant *>(var);
}
break;
}
};
if (key.length())
load_opt(key);
else for (std::string &key : settings->optionlist)
load_opt(key);
}
void qmpSettingsWindow::setupWidgets()
{
for (std::string &key : settings->optionlist)
{
if (!settings->options[key].desc.length() && settings->options[key].type != qmpOption::ParameterType::parameter_custom)
continue;
QWidget *optw = nullptr;
qmpOption &o = settings->options[key];
switch (o.type)
{
case qmpOption::ParameterType::parameter_int:
{
QSpinBox *sb = new QSpinBox;
sb->setMinimum(o.minv.toInt());
sb->setMaximum(o.maxv.toInt());
sb->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
optw = sb;
}
break;
case qmpOption::ParameterType::parameter_uint:
{
QHexSpinBox *sb = new QHexSpinBox;
sb->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
optw = sb;
}
break;
case qmpOption::ParameterType::parameter_bool:
{
QCheckBox *cb = new QCheckBox(QString(o.desc.c_str()));
cb->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
optw = cb;
}
break;
case qmpOption::ParameterType::parameter_double:
{
QDoubleSpinBox *sb = new QDoubleSpinBox;
sb->setMinimum(o.minv.toDouble());
sb->setMaximum(o.maxv.toDouble());
sb->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
optw = sb;
}
break;
case qmpOption::ParameterType::parameter_str:
{
QLineEdit *te = new QLineEdit();
te->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
optw = te;
}
break;
case qmpOption::ParameterType::parameter_enum:
{
QComboBox *cb = new QComboBox();
for (std::string &item : o.enumlist)
cb->addItem(QString(item.c_str()));
cb->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
optw = cb;
}
break;
case qmpOption::ParameterType::parameter_url:
{
QFileEdit *fe = new QFileEdit();
fe->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
optw = fe;
}
break;
default:
optw = o.widget;
break;
}
o.widget = optw;
QGridLayout *page = pageForTab(o.tab);
if (o.type == qmpOption::ParameterType::parameter_bool ||
(o.type == qmpOption::parameter_custom && !o.desc.length()))
{
int row = page->rowCount();
page->addWidget(o.widget, row, 0, 1, 2);
}
else
{
QLabel *lb = new QLabel(o.desc.c_str(), page->parentWidget());
lb->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
int row = page->rowCount();
page->addWidget(lb, row, 0);
page->addWidget(o.widget, row, 1);
}
}
loadOption();
}
QGridLayout *qmpSettingsWindow::pageForTab(std::string tab)
{
if (customOptPages.find(tab) != customOptPages.end())
return customOptPages[tab];
QWidget *w = new QWidget;
QGridLayout *page = new QGridLayout(w);
w->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
ui->tabWidget->addTab(w, QString(tab.c_str()));
customOptPages[tab] = page;
return page;
}
QFileEdit::QFileEdit(QWidget *par): QWidget(par)
{
QHBoxLayout *layout = new QHBoxLayout(this);
layout->setMargin(0);
le = new QLineEdit(this);
layout->addWidget(le);
tb = new QToolButton(this);
tb->setText("...");
layout->addWidget(tb);
connect(tb, &QToolButton::clicked, this, &QFileEdit::chooseFile);
}
QString QFileEdit::text()
{
return le->text();
}
void QFileEdit::setText(const QString &s)
{
le->setText(s);
}
void QFileEdit::chooseFile()
{
QString s = QFileDialog::getOpenFileName(nullptr, tr("Select a file"), QFileInfo(text()).dir().absolutePath());
if (s.length())
setText(s);
}