aboutsummaryrefslogblamecommitdiff
path: root/qmidiplayer-desktop/qmpchannelswindow.cpp
blob: 41f545e9901ab4104d6a6cfbcb36cd29c3a2f6f8 (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);
	ui->tvChannels->setHorizontalHeader(new QHeaderView(Qt::Orientation::Horizontal));
	ui->tvChannels->setModel(chmodel=new qmpChannelsModel);
	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);
	std::vector<std::string> devs=qmpMainWindow::getInstance()->getPlayer()->getMidiOutDevices();
	size_t devc=devs.size();
	std::set<std::string> devset;
	for(auto dev:devs)devset.insert(dev);
	std::string selecteddev;
	for(auto setdev:qmpSettingsWindow::getSettingsIntf()->value("Midi/DevicePriority",QList<QVariant>{"Internal FluidSynth"}).toList())
		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])
				qmpMainWindow::getInstance()->getPlayer()->setChannelOutput(ch,j);
		}
	}
	qmpMainWindow::getInstance()->registerFunctionality(
		chnlf=new qmpChannelFunc(this),
		std::string("Channel"),
		tr("Channel").toStdString(),
		getThemedIconc(":/img/channel.svg"),
		0,
		true
	);
	if(qmpSettingsWindow::getSettingsIntf()->value("DialogStatus/ChnlW",QRect(-999,-999,999,999)).toRect()!=QRect(-999,-999,999,999))
		setGeometry(qmpSettingsWindow::getSettingsIntf()->value("DialogStatus/ChnlW",QRect(-999,-999,999,999)).toRect());
	if(qmpSettingsWindow::getSettingsIntf()->value("DialogStatus/ChnlWShown",0).toInt())
	{show();qmpMainWindow::getInstance()->setFuncState("Channel",true);}
}

void qmpChannelsWindow::showEvent(QShowEvent *event)
{
	if(qmpSettingsWindow::getSettingsIntf()->value("Behavior/DialogStatus","").toInt())
	{
		qmpSettingsWindow::getSettingsIntf()->setValue("DialogStatus/ChnlWShown",1);
	}
	if(qmpSettingsWindow::getSettingsIntf()->value("DialogStatus/ChnlW",QRect(-999,-999,999,999)).toRect()!=QRect(-999,-999,999,999))
		setGeometry(qmpSettingsWindow::getSettingsIntf()->value("DialogStatus/ChnlW",QRect(-999,-999,999,999)).toRect());
	event->accept();
}

void qmpChannelsWindow::closeEvent(QCloseEvent *event)
{
	if(qmpSettingsWindow::getSettingsIntf()->value("Behavior/DialogStatus","").toInt())
	{
		qmpSettingsWindow::getSettingsIntf()->setValue("DialogStatus/ChnlW",geometry());
	}
	setVisible(false);
	if(!qmpMainWindow::getInstance()->isFinalizing()&&qmpSettingsWindow::getSettingsIntf()->value("Behavior/DialogStatus","").toInt())
	{
		qmpSettingsWindow::getSettingsIntf()->setValue("DialogStatus/ChnlWShown",0);
	}
	qmpMainWindow::getInstance()->setFuncState("Channel",false);
	event->accept();
}

qmpChannelsWindow::~qmpChannelsWindow()
{
	qmpMainWindow::getInstance()->unregisterFunctionality("Channel");
	qmpMainWindow::getInstance()->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();}