aboutsummaryrefslogblamecommitdiff
path: root/qmidiplayer-desktop/qmpchannelswindow.cpp
blob: 8957254288bbf22407b67be6c1cba1d4b24d17dd (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
                 
                     
              


                      
                 



                                 
                                                                                
 





















                                                                                                                            
 
                                                                 
 
                                    
 
                                                              
 
                                     
 
                                                                   
 

                         
 
                                                                        
 





















































                                                                                                                                                
 
                                                                                         
 












































                                                                                                  


                                              

                                                                                    
 
                                                               
 





                                                                                                   


                                                  













                                                                                        

 


                                                                                                                       
 








                                                                                                         
 
                                                                                                        
 







                                                                                                       
 
                                                                                                                                
 









                                                                                                                          
 
                                                                                         
 













                                                                                                   
 
                                                                                                                   
 

                                                                            
 
                                                                                                                                    
 

                                     

 


                                                      
 













































                                                                                                        

                                                                                                     




                                                                                      

 

                                                    



                                                                              

                                                                                                     
                    

 
                                                      
 










                                                                                                                           

 

                                             






















                                                                                                                                      

 
                                       
 





                                                        

 
                                             
 
                                  

 
                                             
 
                                  
 
 
                                                         
 

                              
 
 
                                                      


            
                           


              
                            


               
#include <cstdio>
#include <functional>
#include <set>
#include <QCheckBox>
#include <QPushButton>
#include <QComboBox>
#include <QTimer>
#include "qmpchannelswindow.hpp"
#include "ui_qmpchannelswindow.h"
#include "qmpmainwindow.hpp"

qmpChannelsModel::qmpChannelsModel(QObject *parent): QAbstractTableModel(parent)
{
    evh = qmpMainWindow::getInstance()->getPlayer()->registerEventHandler(
            [this](const void *_e, void *)
    {
        if (!updatequeued)
        {
            updatequeued = true;
            const SEvent *e = (const SEvent *)(_e);
            if ((e->p1 & 0xF0) == 0xC0)
                emit dataChanged(index(e->p1 & 0xF0, 4), index(e->p1 & 0xF0, 4), {Qt::ItemDataRole::DisplayRole});
            QMetaObject::invokeMethod(this, &qmpChannelsModel::updateChannelActivity, Qt::ConnectionType::QueuedConnection);
        }
    }
    , nullptr, false);
    QTimer *t = new QTimer(this);
    t->setInterval(500);
    t->setSingleShot(false);
    connect(t, &QTimer::timeout, [this]()
    {
        emit this->dataChanged(this->index(0, 4), this->index(15, 4), {Qt::ItemDataRole::DisplayRole});
    });
    memset(mute, 0, sizeof(mute));
    memset(solo, 0, sizeof(solo));
}
int qmpChannelsModel::columnCount(const QModelIndex &parent)const
{
    return parent.isValid() ? 0 : 6;
}
int qmpChannelsModel::rowCount(const QModelIndex &parent)const
{
    return parent.isValid() ? 0 : 16;
}
QModelIndex qmpChannelsModel::parent(const QModelIndex &child)const
{
    Q_UNUSED(child)
    return QModelIndex();
}
QVariant qmpChannelsModel::data(const QModelIndex &index, int role)const
{
    switch (index.column())
    {
        case 0:
            if (role == Qt::ItemDataRole::DecorationRole)
            {
                using namespace std::chrono_literals;
                bool lit = (std::chrono::system_clock::now() - qmpMainWindow::getInstance()->getPlayer()->getLastEventTS()[index.row()]) < 50ms;
                return lit ? QIcon(":/img/ledon.svg") : QIcon(":/img/ledoff.svg");
            }
            break;
        case 1:
            if (role == Qt::ItemDataRole::CheckStateRole)
                return mute[index.row()] ? Qt::CheckState::Checked : Qt::CheckState::Unchecked;
            break;
        case 2:
            if (role == Qt::ItemDataRole::CheckStateRole)
                return solo[index.row()] ? Qt::CheckState::Checked : Qt::CheckState::Unchecked;
            break;
        case 3:
            if (role == Qt::ItemDataRole::DisplayRole)
            {
                std::vector<std::string> devs = qmpMainWindow::getInstance()->getPlayer()->getMidiOutDevices();
                return QString::fromStdString(devs[qmpMainWindow::getInstance()->getPlayer()->getChannelOutput(index.row())]);
            }
            break;
        case 4:
        {
            if (role == Qt::ItemDataRole::DisplayRole)
            {
                int ch = index.row();
                uint16_t b;
                uint8_t p;
                std::string nm;
                char data[256];
                CMidiPlayer *plyr = qmpMainWindow::getInstance()->getPlayer();
                bool r = plyr->getChannelOutputDevice(ch)->getChannelPreset(ch, &b, &p, nm);
                sprintf(data, "%03d:%03d %s", b, p, nm.c_str());
                if (!r)
                {
                    nm = plyr->getChannelOutputDevice(ch)->getPresetName(plyr->getCC(ch, 0) << 7 | plyr->getCC(ch, 32), plyr->getCC(ch, 128));
                    sprintf(data, "%03d:%03d:%03d %s", plyr->getCC(ch, 0), plyr->getCC(ch, 32), plyr->getCC(ch, 128), nm.c_str());
                }
                return QString(data);
            }
        }
        break;
        case 5:
            if (role == Qt::ItemDataRole::DisplayRole)
                return "...";
            if (role == Qt::ItemDataRole::TextAlignmentRole)
                return Qt::AlignmentFlag::AlignCenter;
            break;
    }
    return QVariant();
}
bool qmpChannelsModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (index.column() == 3)
    {
        if (role != Qt::ItemDataRole::DisplayRole)
            return false;
        std::vector<std::string> dsv = CMidiPlayer::getInstance()->getMidiOutDevices();
        int idx = std::find(dsv.begin(), dsv.end(), value.toString().toStdString()) - dsv.begin();
        if (idx == CMidiPlayer::getInstance()->getChannelOutput(index.row()))
            return false;
        CMidiPlayer::getInstance()->setChannelOutput(index.row(), idx);
        emit dataChanged(index, index, {Qt::DisplayRole});
        return true;
    }
    return false;
}
QVariant qmpChannelsModel::headerData(int section, Qt::Orientation orientation, int role)const
{
    if (role != Qt::ItemDataRole::DisplayRole)
        return QVariant();
    if (orientation == Qt::Orientation::Vertical)
        return section + 1;
    switch (section)
    {
        case 0:
            return QString("A");
        case 1:
            return QString("M");
        case 2:
            return QString("S");
        case 3:
            return QString("Device");
        case 4:
            return QString("Preset");
        case 5:
            return QString("...");
    }
    return QString();
}
Qt::ItemFlags qmpChannelsModel::flags(const QModelIndex &idx)const
{
    Qt::ItemFlags ret = Qt::ItemFlag::ItemIsEnabled | Qt::ItemFlag::ItemIsSelectable;
    if (idx.column() == 1 || idx.column() == 2)
        ret |= Qt::ItemFlag::ItemIsUserCheckable;
    if (idx.column() == 3)
        ret |= Qt::ItemFlag::ItemIsEditable;
    return ret;
}
void qmpChannelsModel::updateChannelActivity()
{
    emit dataChanged(index(0, 0), index(15, 0), {Qt::ItemDataRole::DecorationRole});
    updatequeued = false;
}
void qmpChannelsModel::channelMSClicked(const QModelIndex &idx)
{
    bool *x[3] = {nullptr, mute, solo};
    if (x[idx.column()][idx.row()] ^= 1)
        x[3 - idx.column()][idx.row()] = 0;
    qmpMainWindow::getInstance()->getPlayer()->setMute(idx.row(), mute[idx.row()]);
    qmpMainWindow::getInstance()->getPlayer()->setSolo(idx.row(), solo[idx.row()]);
    emit dataChanged(index(idx.row(), 1), index(idx.row(), 2), {Qt::ItemDataRole::CheckStateRole});
}
void qmpChannelsModel::channelMSClearAll(int type)
{
    if (type == 1)
    {
        memset(mute, 0, sizeof(mute));
        for (int i = 0; i < 16; ++i)
            qmpMainWindow::getInstance()->getPlayer()->setMute(i, 0);
        emit dataChanged(index(0, 1), index(15, 1), {Qt::ItemDataRole::CheckStateRole});
    }
    if (type == 2)
    {
        memset(solo, 0, sizeof(solo));
        for (int i = 0; i < 16; ++i)
            qmpMainWindow::getInstance()->getPlayer()->setSolo(i, 0);
        emit dataChanged(index(0, 2), index(15, 2), {Qt::ItemDataRole::CheckStateRole});
    }
}

qmpDeviceItemDelegate::qmpDeviceItemDelegate(bool ignoreInternal, QWidget *parent):
    QStyledItemDelegate(parent), par(parent), nofs(ignoreInternal) {}
void qmpDeviceItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index)const
{
    QStyleOptionViewItem opt;
    initStyleOption(&opt, index);
    QStyleOptionComboBox socb;
    socb.currentText = opt.text;
    socb.editable = false;
    socb.rect = option.rect;
    socb.state = opt.state;
    par->style()->drawComplexControl(QStyle::ComplexControl::CC_ComboBox, &socb, painter, option.widget);
    par->style()->drawControl(QStyle::CE_ComboBoxLabel, &socb, painter, option.widget);
}
QSize qmpDeviceItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index)const
{
    QStyleOptionViewItem opt;
    initStyleOption(&opt, index);
    QStyleOptionComboBox socb;
    socb.currentText = opt.text;
    socb.editable = false;
    socb.rect = option.rect;
    QSize sz = par->fontMetrics().size(Qt::TextFlag::TextSingleLine, socb.currentText);
    return par->style()->sizeFromContents(QStyle::ContentsType::CT_ComboBox, &socb, sz, option.widget);
}
QWidget *qmpDeviceItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index)const
{
    Q_UNUSED(option)
    Q_UNUSED(index)
    QComboBox *cb = new QComboBox(parent);
    cb->setEditable(false);
    connect(cb, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [index, cb](int)
    {
        const_cast<QAbstractItemModel *>(index.model())->setData(index, cb->currentText(), Qt::ItemDataRole::DisplayRole);
        cb->hidePopup();
    });
    return cb;
}
void qmpDeviceItemDelegate::setEditorData(QWidget *widget, const QModelIndex &index)const
{
    /*
     * We want to quit editing as soon as the popup of the combobox is closed.
     * Unfortunately QTableView does not do that. And I don't feel like sub-classing
     * it. So here are some dirty tricks to make it work that way.
     */
    QComboBox *cb = qobject_cast<QComboBox *>(widget);
    QSignalBlocker sblk(cb);
    cb->clear();
    std::vector<std::string> devs = qmpMainWindow::getInstance()->getPlayer()->getMidiOutDevices();
    for (auto s : devs)
        if (!nofs || (nofs && s != "Internal FluidSynth"))
            cb->addItem(QString::fromStdString(s));
    cb->setCurrentText(index.data().toString());
    cb->showPopup();
}
void qmpDeviceItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index)const
{
    QComboBox *cb = qobject_cast<QComboBox *>(editor);
    model->setData(index, cb->currentText(), Qt::ItemDataRole::DisplayRole);
}
void qmpDeviceItemDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index)const
{
    Q_UNUSED(index)
    editor->setGeometry(option.rect);
}

qmpChannelsWindow::qmpChannelsWindow(QWidget *parent):
    QWidget(parent, Qt::Dialog),
    ui(new Ui::qmpChannelsWindow)
{
    ui->setupUi(this);
    mainwindow = qmpMainWindow::getInstance();
    ui->tvChannels->setHorizontalHeader(new QHeaderView(Qt::Orientation::Horizontal));
    ui->tvChannels->setModel(chmodel = new qmpChannelsModel(ui->tvChannels));
    ui->tvChannels->setItemDelegateForColumn(3, new qmpDeviceItemDelegate(false, ui->tvChannels));
    ui->tvChannels->setAlternatingRowColors(true);
    ui->tvChannels->setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection);
    ui->tvChannels->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeMode::ResizeToContents);
    ui->tvChannels->horizontalHeader()->setSectionResizeMode(4, QHeaderView::ResizeMode::Stretch);
    connect(ui->tvChannels, &QTableView::clicked, [this](const QModelIndex &idx)
    {
        if (idx.column() == 1 || idx.column() == 2)
            this->chmodel->channelMSClicked(idx);
        if (idx.column() == 3)
            this->ui->tvChannels->edit(idx);
        if (idx.column() == 5)
            this->showChannelEditorWindow(idx.row());
    });
    connect(ui->tvChannels, &QTableView::activated, [this](const QModelIndex &idx)
    {
        if (idx.column() == 4)
        {
            pselectw->show();
            pselectw->setupWindow(idx.row());
        }
    });
    pselectw = new qmpPresetSelector(this);
    ceditw = new qmpChannelEditor(this);
    cha = new QIcon(":/img/ledon.svg");
    chi = new QIcon(":/img/ledoff.svg");
    eh = qmpMainWindow::getInstance()->getPlayer()->registerEventHandler(
            [this](const void *ee, void *)
    {
        const SEvent *e = (const SEvent *)ee;
        if ((e->type & 0xF0) == 0x90 && e->p2 > 0 && (e->flags & 0x01))
            emit this->noteOn();
    }
    , nullptr, false);
    qmpMainWindow::getInstance()->registerFunctionality(
        chnlf = new qmpChannelFunc(this),
        std::string("Channel"),
        tr("Channel").toStdString(),
        getThemedIconc(":/img/channel.svg"),
        0,
        true
    );
    if (!mainwindow->getSettings()->getOptionRaw("DialogStatus/ChnlW", QRect()).toRect().isNull())
        setGeometry(mainwindow->getSettings()->getOptionRaw("DialogStatus/ChnlW", QRect()).toRect());
    if (mainwindow->getSettings()->getOptionRaw("DialogStatus/ChnlWShown", 0).toInt())
    {
        show();
        qmpMainWindow::getInstance()->setFuncState("Channel", true);
    }
}

void qmpChannelsWindow::showEvent(QShowEvent *event)
{
    if (mainwindow->getSettings()->getOptionBool("Behavior/DialogStatus"))
    {
        mainwindow->getSettings()->setOptionRaw("DialogStatus/ChnlWShown", 1);
    }
    if (!mainwindow->getSettings()->getOptionRaw("DialogStatus/ChnlW", QRect()).toRect().isNull())
        setGeometry(mainwindow->getSettings()->getOptionRaw("DialogStatus/ChnlW", QRect()).toRect());
    event->accept();
}

void qmpChannelsWindow::closeEvent(QCloseEvent *event)
{
    if (mainwindow->getSettings()->getOptionBool("Behavior/DialogStatus"))
    {
        mainwindow->getSettings()->setOptionRaw("DialogStatus/ChnlW", geometry());
    }
    setVisible(false);
    if (!qmpMainWindow::getInstance()->isFinalizing() && mainwindow->getSettings()->getOptionBool("Behavior/DialogStatus"))
    {
        mainwindow->getSettings()->setOptionRaw("DialogStatus/ChnlWShown", 0);
    }
    mainwindow->setFuncState("Channel", false);
    event->accept();
}

void qmpChannelsWindow::selectDefaultDevice()
{
    std::string selecteddev;
    std::vector<std::string> devs = mainwindow->getPlayer()->getMidiOutDevices();
    size_t devc = devs.size();
    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;
        }
    for (int ch = 0; ch < 16; ++ch)
    {
        for (size_t j = 0; j < devc; ++j)
        {
            if (selecteddev == devs[j])
                mainwindow->getPlayer()->setChannelOutput(ch, j);
        }
    }
}

qmpChannelsWindow::~qmpChannelsWindow()
{
    mainwindow->unregisterFunctionality("Channel");
    mainwindow->getPlayer()->unregisterEventHandler(eh);
    delete chnlf;
    delete chi;
    delete cha;
    delete ui;
}

void qmpChannelsWindow::on_pbUnmute_clicked()
{
    chmodel->channelMSClearAll(1);
}

void qmpChannelsWindow::on_pbUnsolo_clicked()
{
    chmodel->channelMSClearAll(2);
}

void qmpChannelsWindow::showChannelEditorWindow(int chid)
{
    ceditw->show();
    ceditw->setupWindow(chid);
}

qmpChannelFunc::qmpChannelFunc(qmpChannelsWindow *par)
{
    p = par;
}
void qmpChannelFunc::show()
{
    p->show();
}
void qmpChannelFunc::close()
{
    p->close();
}