Add search bar for the soundfont player (#8193)
This commit is contained in:
@@ -29,7 +29,12 @@
|
||||
#include <QHeaderView>
|
||||
//#include <QFileInfo>
|
||||
#include <QLabel>
|
||||
#include <QKeyEvent>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QStandardItemModel>
|
||||
#include <QStandardItem>
|
||||
|
||||
#include "embed.h"
|
||||
#include "fluidsynthshims.h"
|
||||
|
||||
namespace lmms::gui
|
||||
@@ -63,45 +68,83 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Constructor.
|
||||
PatchesDialog::PatchesDialog( QWidget *pParent, Qt::WindowFlags wflags )
|
||||
: QDialog( pParent, wflags )
|
||||
: QDialog(pParent, wflags)
|
||||
, m_pSynth{nullptr}
|
||||
, m_iChan{0}
|
||||
, m_iBank{0}
|
||||
, m_iProg{0}
|
||||
, m_selProg{0}
|
||||
{
|
||||
// Setup UI struct...
|
||||
setupUi( this );
|
||||
|
||||
m_pSynth = nullptr;
|
||||
m_iChan = 0;
|
||||
m_iBank = 0;
|
||||
m_iProg = 0;
|
||||
// Configure bank list view
|
||||
auto bankHeader = m_bankListView->header();
|
||||
bankHeader->setSectionResizeMode(0, QHeaderView::Stretch);
|
||||
bankHeader->setStretchLastSection(true);
|
||||
bankHeader->resizeSection(0, 30);
|
||||
|
||||
// Soundfonts list view...
|
||||
QHeaderView *pHeader = m_progListView->header();
|
||||
// pHeader->setResizeMode(QHeaderView::Custom);
|
||||
pHeader->setDefaultAlignment(Qt::AlignLeft);
|
||||
// pHeader->setDefaultSectionSize(200);
|
||||
pHeader->setSectionsMovable(false);
|
||||
pHeader->setStretchLastSection(true);
|
||||
m_splitter->setStretchFactor(0, 2);
|
||||
m_splitter->setStretchFactor(1, 6);
|
||||
|
||||
m_progListView->resizeColumnToContents(0); // Prog.
|
||||
//pHeader->resizeSection(1, 200); // Name.
|
||||
// Configure program list models
|
||||
m_progListSourceModel.setHorizontalHeaderLabels({tr("Patch"), tr("Name")});
|
||||
m_progListProxyModel.setSourceModel(&m_progListSourceModel);
|
||||
m_progListProxyModel.setFilterCaseSensitivity(Qt::CaseInsensitive);
|
||||
m_progListProxyModel.setFilterKeyColumn(1); // "Name" column
|
||||
m_progListProxyModel.setDynamicSortFilter(true);
|
||||
|
||||
// Configure program list view
|
||||
m_progListView->setModel(&m_progListProxyModel);
|
||||
m_progListView->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
m_progListView->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
m_progListView->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
m_progListView->setSortingEnabled(true);
|
||||
m_progListView->sortByColumn(0, Qt::AscendingOrder); // Initial sort by column 0 (Name)
|
||||
|
||||
constexpr int RowHeight = 18;
|
||||
auto progVHeader = m_progListView->verticalHeader();
|
||||
progVHeader->setSectionResizeMode(QHeaderView::Fixed);
|
||||
progVHeader->setMinimumSectionSize(RowHeight);
|
||||
progVHeader->setMaximumSectionSize(RowHeight);
|
||||
progVHeader->setDefaultSectionSize(RowHeight);
|
||||
progVHeader->hide();
|
||||
|
||||
auto progHeader = m_progListView->horizontalHeader();
|
||||
progHeader->setDefaultAlignment(Qt::AlignLeft);
|
||||
progHeader->setSectionResizeMode(0, QHeaderView::ResizeToContents);
|
||||
progHeader->setSectionResizeMode(1, QHeaderView::Stretch);
|
||||
progHeader->setSectionsMovable(false);
|
||||
progHeader->setStretchLastSection(true);
|
||||
|
||||
// Initial sort order...
|
||||
m_bankListView->sortItems(0, Qt::AscendingOrder);
|
||||
m_progListView->sortItems(0, Qt::AscendingOrder);
|
||||
|
||||
m_filterEdit->setPlaceholderText(tr("Search"));
|
||||
m_filterEdit->setClearButtonEnabled(true);
|
||||
m_filterEdit->addAction(embed::getIconPixmap("zoom"), QLineEdit::LeadingPosition);
|
||||
|
||||
// Configure focus (only allow for search bar and dialog buttons)
|
||||
m_filterEdit->setFocus();
|
||||
m_filterEdit->setFocusPolicy(Qt::StrongFocus);
|
||||
m_bankListView->setFocusPolicy(Qt::NoFocus);
|
||||
m_progListView->setFocusPolicy(Qt::NoFocus);
|
||||
|
||||
// UI connections...
|
||||
QObject::connect(m_filterEdit, &QLineEdit::textChanged, this, [this](const QString& text) {
|
||||
m_progListProxyModel.setFilterRegularExpression(
|
||||
QRegularExpression(text, QRegularExpression::CaseInsensitiveOption));
|
||||
diffSelectProgRow(0); // fix the selection if it has been invalidated
|
||||
});
|
||||
QObject::connect(m_bankListView,
|
||||
SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)),
|
||||
SLOT(bankChanged()));
|
||||
QObject::connect(m_progListView,
|
||||
SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)),
|
||||
SLOT(progChanged(QTreeWidgetItem*,QTreeWidgetItem*)));
|
||||
QObject::connect(m_progListView,
|
||||
SIGNAL(itemActivated(QTreeWidgetItem*,int)),
|
||||
SLOT(accept()));
|
||||
&QTableView::doubleClicked, this, &PatchesDialog::accept);
|
||||
QObject::connect(m_progListView->selectionModel(), &QItemSelectionModel::currentRowChanged,
|
||||
this, &PatchesDialog::progChanged);
|
||||
QObject::connect(m_okButton,
|
||||
SIGNAL(clicked()),
|
||||
SLOT(accept()));
|
||||
@@ -118,7 +161,6 @@ void PatchesDialog::setup ( fluid_synth_t * pSynth, int iChan,
|
||||
LcdSpinBoxModel * _progModel,
|
||||
QLabel * _patchLabel )
|
||||
{
|
||||
|
||||
// We'll going to changes the whole thing...
|
||||
m_dirty = 0;
|
||||
m_bankModel = _bankModel;
|
||||
@@ -139,7 +181,6 @@ void PatchesDialog::setup ( fluid_synth_t * pSynth, int iChan,
|
||||
m_pSynth = pSynth;
|
||||
m_iChan = iChan;
|
||||
|
||||
|
||||
QTreeWidgetItem *pBankItem = nullptr;
|
||||
// For all soundfonts (in reversed stack order) fill the available banks...
|
||||
int cSoundFonts = ::fluid_synth_sfcount(m_pSynth);
|
||||
@@ -187,11 +228,23 @@ void PatchesDialog::setup ( fluid_synth_t * pSynth, int iChan,
|
||||
bankChanged();
|
||||
|
||||
// Set the selected program.
|
||||
if (pPreset)
|
||||
m_iProg = fluid_preset_get_num(pPreset);
|
||||
QTreeWidgetItem *pProgItem = findProgItem(m_iProg);
|
||||
m_progListView->setCurrentItem(pProgItem);
|
||||
m_progListView->scrollToItem(pProgItem);
|
||||
if (pPreset) { m_iProg = fluid_preset_get_num(pPreset); }
|
||||
|
||||
if (auto progItem = findProgItem(m_iProg); progItem != nullptr)
|
||||
{
|
||||
auto sourceIdx = progItem->index();
|
||||
auto proxyIdx = m_progListProxyModel.mapFromSource(sourceIdx);
|
||||
|
||||
if (proxyIdx.isValid())
|
||||
{
|
||||
constexpr auto setMask = QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows;
|
||||
int row = proxyIdx.row();
|
||||
auto idx = m_progListView->model()->index(row, 0);
|
||||
|
||||
m_progListView->selectionModel()->setCurrentIndex(idx, setMask);
|
||||
m_progListView->scrollTo(idx);
|
||||
}
|
||||
}
|
||||
|
||||
// Done with setup...
|
||||
//m_iDirtySetup--;
|
||||
@@ -211,7 +264,6 @@ bool PatchesDialog::validateForm()
|
||||
bool bValid = true;
|
||||
|
||||
bValid = bValid && (m_bankListView->currentItem() != nullptr);
|
||||
bValid = bValid && (m_progListView->currentItem() != nullptr);
|
||||
|
||||
return bValid;
|
||||
}
|
||||
@@ -235,23 +287,14 @@ void PatchesDialog::setBankProg ( int iBank, int iProg )
|
||||
void PatchesDialog::accept()
|
||||
{
|
||||
if (validateForm()) {
|
||||
// Unload from current selected dialog items.
|
||||
int iBank = (m_bankListView->currentItem())->text(0).toInt();
|
||||
int iProg = (m_progListView->currentItem())->text(0).toInt();
|
||||
// And set it right away...
|
||||
setBankProg(iBank, iProg);
|
||||
|
||||
if (m_dirty > 0) {
|
||||
m_bankModel->setValue( iBank );
|
||||
m_progModel->setValue( iProg );
|
||||
m_patchLabel->setText( m_progListView->
|
||||
currentItem()->text( 1 ) );
|
||||
}
|
||||
bool updateUi = m_dirty > 0;
|
||||
updatePatch(updateUi);
|
||||
|
||||
// Do remember preview state...
|
||||
// if (m_pOptions)
|
||||
// m_pOptions->bPresetPreview = m_ui.PreviewCheckBox->isChecked();
|
||||
// We got it.
|
||||
|
||||
QDialog::accept();
|
||||
}
|
||||
}
|
||||
@@ -282,23 +325,15 @@ QTreeWidgetItem *PatchesDialog::findBankItem ( int iBank )
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
// Find the program item of given program number id.
|
||||
QTreeWidgetItem *PatchesDialog::findProgItem ( int iProg )
|
||||
QStandardItem* PatchesDialog::findProgItem(int iProg)
|
||||
{
|
||||
QList<QTreeWidgetItem *> progs
|
||||
= m_progListView->findItems(
|
||||
QString::number(iProg), Qt::MatchExactly, 0);
|
||||
QList<QStandardItem*> progs = m_progListSourceModel.findItems(QString::number(iProg), Qt::MatchExactly, 0);
|
||||
|
||||
QListIterator<QTreeWidgetItem *> iter(progs);
|
||||
if (iter.hasNext())
|
||||
return iter.next();
|
||||
else
|
||||
return nullptr;
|
||||
auto it = QListIterator<QStandardItem*>(progs);
|
||||
return it.hasNext() ? it.next() : nullptr;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Bank change slot.
|
||||
void PatchesDialog::bankChanged ()
|
||||
{
|
||||
@@ -311,13 +346,14 @@ void PatchesDialog::bankChanged ()
|
||||
|
||||
int iBankSelected = pBankItem->text(0).toInt();
|
||||
|
||||
// Clear up the program listview.
|
||||
// Clear up the program list to refill
|
||||
m_progListView->setSortingEnabled(false);
|
||||
m_progListView->clear();
|
||||
QTreeWidgetItem *pProgItem = nullptr;
|
||||
m_progListSourceModel.setRowCount(0);
|
||||
|
||||
// For all soundfonts (in reversed stack order) fill the available programs...
|
||||
bool stop = false; // replaces the `pProgItem` check that used to exist here
|
||||
int cSoundFonts = ::fluid_synth_sfcount(m_pSynth);
|
||||
for (int i = 0; i < cSoundFonts && !pProgItem; i++) {
|
||||
for (int i = 0; i < cSoundFonts && !stop; i++) {
|
||||
fluid_sfont_t *pSoundFont = ::fluid_synth_get_sfont(m_pSynth, i);
|
||||
if (pSoundFont) {
|
||||
#ifdef CONFIG_FLUID_BANK_OFFSET
|
||||
@@ -337,14 +373,18 @@ void PatchesDialog::bankChanged ()
|
||||
#endif
|
||||
int iProg = fluid_preset_get_num(pCurPreset);
|
||||
if (iBank == iBankSelected && !findProgItem(iProg)) {
|
||||
pProgItem = new PatchItem(m_progListView, pProgItem);
|
||||
if (pProgItem) {
|
||||
pProgItem->setText(0, QString::number(iProg));
|
||||
pProgItem->setText(1, fluid_preset_get_name(pCurPreset));
|
||||
//pProgItem->setText(2, QString::number(fluid_sfont_get_id(pSoundFont)));
|
||||
//pProgItem->setText(3, QFileInfo(
|
||||
// fluid_sfont_get_name(pSoundFont).baseName());
|
||||
}
|
||||
// Numeric value on the batch number column - allows for numerical sorting
|
||||
auto patchNumItem = new QStandardItem();
|
||||
patchNumItem->setData(iProg, Qt::DisplayRole);
|
||||
|
||||
auto patchNameItem = new QStandardItem(fluid_preset_get_name(pCurPreset));
|
||||
|
||||
stop = true;
|
||||
|
||||
m_progListSourceModel.appendRow({patchNumItem, patchNameItem});
|
||||
// Old columns:
|
||||
// Col. 2: QString::number(fluid_sfont_get_id(pSoundFont))
|
||||
// Col. 3: QFileInfo(fluid_sfont_get_name(pSoundFont).baseName())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -355,20 +395,37 @@ void PatchesDialog::bankChanged ()
|
||||
stabilizeForm();
|
||||
}
|
||||
|
||||
|
||||
// Program change slot.
|
||||
void PatchesDialog::progChanged (QTreeWidgetItem * _curr, QTreeWidgetItem * _prev)
|
||||
void PatchesDialog::updatePatch(bool updateUi)
|
||||
{
|
||||
if (m_pSynth == nullptr || _curr == nullptr)
|
||||
return;
|
||||
int iBank = m_bankListView->currentItem()->text(0).toInt();
|
||||
setBankProg(iBank, m_selProg);
|
||||
|
||||
if (updateUi)
|
||||
{
|
||||
m_bankModel->setValue(iBank);
|
||||
m_progModel->setValue(m_selProg);
|
||||
m_patchLabel->setText(m_selProgName);
|
||||
}
|
||||
}
|
||||
|
||||
void PatchesDialog::progChanged(const QModelIndex& cur, const QModelIndex& prev)
|
||||
{
|
||||
if (m_pSynth == nullptr) { return; }
|
||||
|
||||
auto curRow = m_progListProxyModel.mapToSource(cur).row();
|
||||
if (curRow < 0) { return; }
|
||||
|
||||
auto progIdx = m_progListSourceModel.index(curRow, 0);
|
||||
m_selProg = m_progListSourceModel.data(progIdx).toInt();
|
||||
|
||||
auto nameIdx = m_progListSourceModel.index(curRow, 1);
|
||||
m_selProgName = m_progListSourceModel.data(nameIdx).toString();
|
||||
|
||||
// Which preview state...
|
||||
if( validateForm() ) {
|
||||
// Set current selection.
|
||||
int iBank = (m_bankListView->currentItem())->text(0).toInt();
|
||||
int iProg = _curr->text(0).toInt();
|
||||
// And set it right away...
|
||||
setBankProg(iBank, iProg);
|
||||
if (validateForm())
|
||||
{
|
||||
updatePatch(false);
|
||||
|
||||
// Now we're dirty nuff.
|
||||
m_dirty++;
|
||||
}
|
||||
@@ -377,5 +434,41 @@ void PatchesDialog::progChanged (QTreeWidgetItem * _curr, QTreeWidgetItem * _pre
|
||||
stabilizeForm();
|
||||
}
|
||||
|
||||
void PatchesDialog::keyPressEvent(QKeyEvent* event)
|
||||
{
|
||||
const auto key = event->key();
|
||||
|
||||
if (key == Qt::Key_Up || key == Qt::Key_Down)
|
||||
{
|
||||
event->accept();
|
||||
int rowDiff = (key == Qt::Key_Up) ? -1 : +1;
|
||||
diffSelectProgRow(rowDiff);
|
||||
}
|
||||
else if (key == Qt::Key_Return || key == Qt::Key_Enter)
|
||||
{
|
||||
event->accept();
|
||||
accept();
|
||||
}
|
||||
else if (key == Qt::Key_Escape)
|
||||
{
|
||||
event->accept();
|
||||
reject();
|
||||
}
|
||||
}
|
||||
|
||||
void PatchesDialog::diffSelectProgRow(int offset)
|
||||
{
|
||||
QItemSelectionModel* selectionModel = m_progListView->selectionModel();
|
||||
|
||||
int curRow = selectionModel->currentIndex().row();
|
||||
int newRow = curRow + offset;
|
||||
int rowCount = m_progListView->model()->rowCount();
|
||||
newRow = qBound(0, newRow, rowCount - 1);
|
||||
|
||||
constexpr auto selMask = QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows;
|
||||
const auto idx = m_progListView->model()->index(newRow, 0);
|
||||
selectionModel->setCurrentIndex(idx, selMask);
|
||||
m_progListView->scrollTo(idx);
|
||||
}
|
||||
|
||||
} // namespace lmms::gui
|
||||
|
||||
@@ -27,6 +27,9 @@
|
||||
#define _PATCHES_DIALOG_H
|
||||
|
||||
#include <fluidsynth/types.h>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QStandardItemModel>
|
||||
#include <QEvent>
|
||||
|
||||
#include "ui_PatchesDialog.h"
|
||||
#include "LcdSpinBox.h"
|
||||
@@ -45,25 +48,18 @@ class PatchesDialog : public QDialog, private Ui::PatchesDialog
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
// Constructor.
|
||||
PatchesDialog(QWidget *pParent = 0, Qt::WindowFlags wflags = QFlag(0));
|
||||
|
||||
// Destructor.
|
||||
PatchesDialog(QWidget* pParent = 0, Qt::WindowFlags wflags = QFlag(0));
|
||||
~PatchesDialog() override = default;
|
||||
|
||||
|
||||
void setup(fluid_synth_t *pSynth, int iChan, const QString & _chanName,
|
||||
LcdSpinBoxModel * _bankModel, LcdSpinBoxModel * _progModel, QLabel *_patchLabel );
|
||||
void setup(fluid_synth_t* pSynth, int iChan, const QString& _chanName, LcdSpinBoxModel* _bankModel,
|
||||
LcdSpinBoxModel* _progModel, QLabel* _patchLabel);
|
||||
|
||||
public slots:
|
||||
|
||||
void stabilizeForm();
|
||||
void bankChanged();
|
||||
void progChanged( QTreeWidgetItem * _curr, QTreeWidgetItem * _prev );
|
||||
void progChanged(const QModelIndex& cur, const QModelIndex& prev);
|
||||
|
||||
protected slots:
|
||||
|
||||
void accept() override;
|
||||
void reject() override;
|
||||
|
||||
@@ -71,27 +67,44 @@ protected:
|
||||
|
||||
void setBankProg(int iBank, int iProg);
|
||||
|
||||
QTreeWidgetItem *findBankItem(int iBank);
|
||||
QTreeWidgetItem *findProgItem(int iProg);
|
||||
QTreeWidgetItem* findBankItem(int iBank);
|
||||
|
||||
//! Finds the program item of given program number id in the source model.
|
||||
QStandardItem* findProgItem(int iProg);
|
||||
|
||||
bool validateForm();
|
||||
|
||||
/**
|
||||
Updates the current patch, and updates the UI controls if `updateUi` is true.
|
||||
*/
|
||||
void updatePatch(bool updateUi);
|
||||
|
||||
/**
|
||||
Selects a row in the program selector based off a signed offset from the currently selected row. Also clamps the
|
||||
selection.
|
||||
*/
|
||||
void diffSelectProgRow(int offset);
|
||||
|
||||
private:
|
||||
|
||||
// Instance variables.
|
||||
fluid_synth_t *m_pSynth;
|
||||
void keyPressEvent(QKeyEvent* event) override;
|
||||
|
||||
fluid_synth_t* m_pSynth;
|
||||
int m_iChan;
|
||||
int m_iBank;
|
||||
int m_iProg;
|
||||
|
||||
//int m_iDirtySetup;
|
||||
//int m_iDirtyCount;
|
||||
int m_dirty;
|
||||
// int m_iDirtySetup;
|
||||
// int m_iDirtyCount;
|
||||
|
||||
LcdSpinBoxModel * m_bankModel;
|
||||
LcdSpinBoxModel * m_progModel;
|
||||
QLabel *m_patchLabel;
|
||||
int m_selProg;
|
||||
QString m_selProgName;
|
||||
|
||||
LcdSpinBoxModel* m_bankModel;
|
||||
LcdSpinBoxModel* m_progModel;
|
||||
QLabel* m_patchLabel;
|
||||
QStandardItemModel m_progListSourceModel; //!< Programs on the selected bank
|
||||
QSortFilterProxyModel m_progListProxyModel; //!< Model to allow searching
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -39,6 +39,10 @@
|
||||
<string>Qsynth: Channel Preset</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" >
|
||||
<item>
|
||||
<widget class="QLineEdit" name="m_filterEdit" >
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSplitter" name="m_splitter" >
|
||||
<property name="sizePolicy" >
|
||||
@@ -93,41 +97,10 @@
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
<widget class="QTreeWidget" name="m_progListView" >
|
||||
<widget class="QTableView" name="m_progListView" >
|
||||
<property name="toolTip" >
|
||||
<string>Program selector</string>
|
||||
</property>
|
||||
<property name="alternatingRowColors" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="indentation" >
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="rootIsDecorated" >
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="uniformRowHeights" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="itemsExpandable" >
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sortingEnabled" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="allColumnsShowFocus" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<column>
|
||||
<property name="text" >
|
||||
<string>Patch</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text" >
|
||||
<string>Name</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
Reference in New Issue
Block a user