aboutsummaryrefslogblamecommitdiff
path: root/qmidiplayer-desktop/qmpsettingswindow.cpp
blob: 03e4cd189c23ef98bbd129e9ae55a89af9b6f403 (plain) (tree)
1
2
3
4
5
6
7
8
9
                    
                      
                      
               
                      
                         


                      
                                
                                      


                                 


                                                                                 
 








                                                                                                            



                                       
              



                                                      



                         
 

                                                    

                         

 

                                               


                                                



                                               

                         
 
 

                                                                











                                                                                                
 
 
                                  
 


































                                                                                                                                          

 

                                                        












                                                                                                                                                                                                                                           



                                                 























                                                                                                                             
 












































                                                                                                                            
 

































                                                                                                                            



                                                                    



































                                                                                                   
 

                                   
 

















                                                                                                                            

 
                                                  
 





                                                                                                                                                                                                                                                              
 





                                                                                                                                                                                                                                                 

 
                                                   
 

















































































                                                                              

 
                                                   
 



































































                                                                                 

 
                                      
 




















































































                                                                                                                               

 
                                                           
 







                                                                     
 
 
                                                
 















                                                                     
 

                            


                                                                                                                   
 
#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);
}