#include <cstdio>
#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),
updatequeued(false)
{
evh = qmpMainWindow::getInstance()->getPlayer()->registerEventHandler(
[this](const void *_e, void *)
{
const SEvent *e = (const SEvent *)(_e);
if ((e->type & 0xF0) == 0xC0)
emit dataChanged(index(e->type & 0x0F, 4), index(e->type & 0x0F, 4), {Qt::ItemDataRole::DisplayRole});
if (!updatequeued)
{
updatequeued = true;
QMetaObject::invokeMethod(this, &qmpChannelsModel::updateChannelActivity, Qt::ConnectionType::QueuedConnection);
}
}
, nullptr, false);
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();
}