From 360254fd81cda14cbcec3fff3dff5456005ebfc8 Mon Sep 17 00:00:00 2001 From: Lost Robot <34612565+LostRobotMusic@users.noreply.github.com> Date: Mon, 19 Feb 2024 09:03:25 -0800 Subject: [PATCH] Rewrite EffectSelectDialog to add effect groups and delete Qt Designer UI file (#7024) --- include/EffectSelectDialog.h | 80 +++++-- src/gui/CMakeLists.txt | 1 - src/gui/modals/EffectSelectDialog.cpp | 290 +++++++++++++++----------- src/gui/modals/EffectSelectDialog.ui | 109 ---------- 4 files changed, 227 insertions(+), 253 deletions(-) delete mode 100644 src/gui/modals/EffectSelectDialog.ui diff --git a/include/EffectSelectDialog.h b/include/EffectSelectDialog.h index 493b07774..db8e60f0d 100644 --- a/include/EffectSelectDialog.h +++ b/include/EffectSelectDialog.h @@ -2,6 +2,7 @@ * EffectSelectDialog.h - dialog to choose effect plugin * * Copyright (c) 2006-2009 Tobias Doerffel + * Copyright (c) 2023 Lost Robot * * This file is part of LMMS - https://lmms.io * @@ -25,49 +26,86 @@ #ifndef LMMS_GUI_EFFECT_SELECT_DIALOG_H #define LMMS_GUI_EFFECT_SELECT_DIALOG_H -#include -#include -#include - #include "Effect.h" - -namespace Ui { class EffectSelectDialog; } +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace lmms::gui { +class DualColumnFilterProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT +public: + DualColumnFilterProxyModel(QObject* parent = nullptr) : QSortFilterProxyModel(parent) + { + } + + void setEffectTypeFilter(const QString& filter) + { + m_effectTypeFilter = filter; + invalidateFilter(); + } + +protected: + bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override + { + QModelIndex nameIndex = sourceModel()->index(source_row, 0, source_parent); + QModelIndex typeIndex = sourceModel()->index(source_row, 1, source_parent); + + QString name = sourceModel()->data(nameIndex, Qt::DisplayRole).toString(); + QString type = sourceModel()->data(typeIndex, Qt::DisplayRole).toString(); + + QRegExp nameRegExp(filterRegExp()); + nameRegExp.setCaseSensitivity(Qt::CaseInsensitive); + + bool nameFilterPassed = nameRegExp.indexIn(name) != -1; + bool typeFilterPassed = type.contains(m_effectTypeFilter, Qt::CaseInsensitive); + + return nameFilterPassed && typeFilterPassed; + } + +private: + QString m_effectTypeFilter; +}; + class EffectSelectDialog : public QDialog { Q_OBJECT public: - EffectSelectDialog( QWidget * _parent ); - ~EffectSelectDialog() override; - - Effect * instantiateSelectedPlugin( EffectChain * _parent ); + EffectSelectDialog(QWidget* parent); + Effect* instantiateSelectedPlugin(EffectChain* parent); protected slots: void acceptSelection(); - void rowChanged( const QModelIndex &, const QModelIndex & ); - void sortAgain(); + void rowChanged(const QModelIndex&, const QModelIndex&); void updateSelection(); - + + bool eventFilter(QObject* obj, QEvent* event) override; private: - Ui::EffectSelectDialog * ui; - EffectKeyList m_effectKeys; EffectKey m_currentSelection; QStandardItemModel m_sourceModel; - QSortFilterProxyModel m_model; - QWidget * m_descriptionWidget; - -} ; - + DualColumnFilterProxyModel m_model; + QWidget* m_descriptionWidget; + QTableView* m_pluginList; + QScrollArea* m_scrollArea; + QLineEdit* m_filterEdit; +}; } // namespace lmms::gui -#endif // LMMS_GUI_EFFECT_SELECT_DIALOG_H +#endif diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 001c92c79..e2342b465 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -132,7 +132,6 @@ SET(LMMS_SRCS set(LMMS_UIS ${LMMS_UIS} gui/modals/about_dialog.ui - gui/modals/EffectSelectDialog.ui gui/modals/export_project.ui PARENT_SCOPE diff --git a/src/gui/modals/EffectSelectDialog.cpp b/src/gui/modals/EffectSelectDialog.cpp index 993052fab..4e6427e81 100644 --- a/src/gui/modals/EffectSelectDialog.cpp +++ b/src/gui/modals/EffectSelectDialog.cpp @@ -2,6 +2,7 @@ * EffectSelectDialog.cpp - dialog to choose effect plugin * * Copyright (c) 2006-2009 Tobias Doerffel + * Copyright (c) 2023 Lost Robot * * This file is part of LMMS - https://lmms.io * @@ -23,63 +24,62 @@ */ #include "EffectSelectDialog.h" - -#include "ui_EffectSelectDialog.h" - #include "DummyEffect.h" #include "EffectChain.h" #include "embed.h" #include "PluginFactory.h" +#include +#include +#include #include +#include +#include +#include namespace lmms::gui { - -EffectSelectDialog::EffectSelectDialog( QWidget * _parent ) : - QDialog( _parent ), - ui( new Ui::EffectSelectDialog ), +EffectSelectDialog::EffectSelectDialog(QWidget* parent) : + QDialog(parent), + m_effectKeys(), + m_currentSelection(), m_sourceModel(), m_model(), - m_descriptionWidget( nullptr ) + m_descriptionWidget(nullptr), + m_pluginList(new QTableView(this)), + m_scrollArea(new QScrollArea(this)) { - ui->setupUi( this ); - - setWindowIcon( embed::getIconPixmap( "setup_audio" ) ); - - // query effects + setWindowTitle(tr("Add effect")); + resize(640, 480); + + setWindowIcon(embed::getIconPixmap("setup_audio")); + // Query effects EffectKeyList subPluginEffectKeys; - - for (const Plugin::Descriptor* desc: getPluginFactory()->descriptors(Plugin::Type::Effect)) + for (const auto desc : getPluginFactory()->descriptors(Plugin::Type::Effect)) { - if( desc->subPluginFeatures ) + if (desc->subPluginFeatures) { - desc->subPluginFeatures->listSubPluginKeys( - desc, - subPluginEffectKeys ); + desc->subPluginFeatures->listSubPluginKeys(desc, subPluginEffectKeys); } else { - m_effectKeys << EffectKey( desc, desc->name ); - + m_effectKeys << EffectKey(desc, desc->name); } } - m_effectKeys += subPluginEffectKeys; - // and fill our source model - m_sourceModel.setHorizontalHeaderItem( 0, new QStandardItem( tr( "Name" ) ) ); - m_sourceModel.setHorizontalHeaderItem( 1, new QStandardItem( tr( "Type" ) ) ); + // Fill the source model + m_sourceModel.setHorizontalHeaderItem(0, new QStandardItem(tr("Name"))); + m_sourceModel.setHorizontalHeaderItem(1, new QStandardItem(tr("Type"))); int row = 0; - for( EffectKeyList::ConstIterator it = m_effectKeys.begin(); - it != m_effectKeys.end(); ++it ) + for (EffectKeyList::ConstIterator it = m_effectKeys.begin(); it != m_effectKeys.end(); ++it) { QString name; QString type; - if( it->desc->subPluginFeatures ) + if (it->desc->subPluginFeatures) { name = it->displayName(); type = it->desc->displayName; @@ -89,114 +89,148 @@ EffectSelectDialog::EffectSelectDialog( QWidget * _parent ) : name = it->desc->displayName; type = "LMMS"; } - m_sourceModel.setItem( row, 0, new QStandardItem( name ) ); - m_sourceModel.setItem( row, 1, new QStandardItem( type ) ); + m_sourceModel.setItem(row, 0, new QStandardItem(name)); + m_sourceModel.setItem(row, 1, new QStandardItem(type)); ++row; } - // setup filtering - m_model.setSourceModel( &m_sourceModel ); - m_model.setFilterCaseSensitivity( Qt::CaseInsensitive ); + // Setup filtering + m_model.setSourceModel(&m_sourceModel); + m_model.setFilterCaseSensitivity(Qt::CaseInsensitive); - ui->filterEdit->setPlaceholderText(tr("Search")); - ui->filterEdit->setClearButtonEnabled(true); - ui->filterEdit->addAction(embed::getIconPixmap("zoom"), QLineEdit::LeadingPosition); + QHBoxLayout* mainLayout = new QHBoxLayout(this); - connect(ui->filterEdit, &QLineEdit::textChanged, &m_model, &QSortFilterProxyModel::setFilterFixedString); - connect(ui->filterEdit, &QLineEdit::textChanged, this, &EffectSelectDialog::updateSelection); - connect(ui->filterEdit, &QLineEdit::textChanged, this, &EffectSelectDialog::sortAgain); + QVBoxLayout* leftSectionLayout = new QVBoxLayout(); - ui->pluginList->setModel( &m_model ); + QStringList buttonLabels = { tr("All"), "LMMS", "LADSPA", "LV2", "VST" }; + QStringList buttonSearchString = { "", "LMMS", "LADSPA", "LV2", "VST" }; + + for (int i = 0; i < buttonLabels.size(); ++i) + { + const QString& label = buttonLabels[i]; + const QString& searchString = buttonSearchString[i]; + + QPushButton* button = new QPushButton(label, this); + button->setFixedSize(50, 50); + button->setFocusPolicy(Qt::NoFocus); + leftSectionLayout->addWidget(button); + + connect(button, &QPushButton::clicked, this, [this, searchString] { + m_model.setEffectTypeFilter(searchString); + updateSelection(); + }); + } + + leftSectionLayout->addStretch();// Add stretch to the button layout to push buttons to the top + mainLayout->addLayout(leftSectionLayout); + + m_filterEdit = new QLineEdit(this); + connect(m_filterEdit, &QLineEdit::textChanged, this, [this](const QString &text) { + m_model.setFilterRegExp(QRegExp(text, Qt::CaseInsensitive)); + }); + connect(m_filterEdit, &QLineEdit::textChanged, this, &EffectSelectDialog::updateSelection); + m_filterEdit->setFocus(); + m_filterEdit->setFocusPolicy(Qt::StrongFocus); + m_filterEdit->setPlaceholderText(tr("Search")); + m_filterEdit->setClearButtonEnabled(true); + m_filterEdit->addAction(embed::getIconPixmap("zoom"), QLineEdit::LeadingPosition); + + m_pluginList->setModel(&m_model); + m_pluginList->setEditTriggers(QAbstractItemView::NoEditTriggers); + m_pluginList->setSelectionBehavior(QAbstractItemView::SelectRows); + m_pluginList->setSelectionMode(QAbstractItemView::SingleSelection); + m_pluginList->setSortingEnabled(true); + m_pluginList->sortByColumn(0, Qt::AscendingOrder); // Initial sort by column 0 (Name) + m_pluginList->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); + m_pluginList->verticalHeader()->hide(); + m_pluginList->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + m_pluginList->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + m_pluginList->setFocusPolicy(Qt::NoFocus); + + // Scroll Area + m_scrollArea->setWidgetResizable(true); + QWidget* scrollAreaWidgetContents = new QWidget(m_scrollArea); + scrollAreaWidgetContents->setObjectName("scrollAreaWidgetContents"); + m_scrollArea->setWidget(scrollAreaWidgetContents); + m_scrollArea->setMaximumHeight(180); + m_scrollArea->setFocusPolicy(Qt::NoFocus); + + // Button Box + QDialogButtonBox* buttonBox = new QDialogButtonBox(Qt::Horizontal, this); + buttonBox->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); + buttonBox->setFocusPolicy(Qt::NoFocus); + connect(buttonBox, &QDialogButtonBox::accepted, this, &EffectSelectDialog::acceptSelection); + connect(buttonBox, &QDialogButtonBox::rejected, this, &EffectSelectDialog::reject); + + QVBoxLayout* rightSectionLayout = new QVBoxLayout(); + rightSectionLayout->addWidget(m_filterEdit); + rightSectionLayout->addWidget(m_pluginList); + rightSectionLayout->addWidget(m_scrollArea); + rightSectionLayout->addWidget(buttonBox); + mainLayout->addLayout(rightSectionLayout); + + setLayout(mainLayout); - // setup selection model auto selectionModel = new QItemSelectionModel(&m_model); - ui->pluginList->setSelectionModel( selectionModel ); - connect( selectionModel, SIGNAL( currentRowChanged( const QModelIndex&, - const QModelIndex & ) ), - SLOT( rowChanged( const QModelIndex &, const QModelIndex& ) ) ); - connect( ui->pluginList, SIGNAL( doubleClicked( const QModelIndex& ) ), - SLOT(acceptSelection())); + m_pluginList->setSelectionModel(selectionModel); + connect(selectionModel, &QItemSelectionModel::currentRowChanged, + this, &EffectSelectDialog::rowChanged); - // try to accept current selection when pressing "OK" - connect( ui->buttonBox, SIGNAL(accepted()), - this, SLOT(acceptSelection())); - - ui->filterEdit->setClearButtonEnabled( true ); - ui->pluginList->verticalHeader()->setSectionResizeMode( - QHeaderView::ResizeToContents ); - ui->pluginList->verticalHeader()->hide(); - ui->pluginList->horizontalHeader()->setSectionResizeMode( 0, - QHeaderView::Stretch ); - ui->pluginList->horizontalHeader()->setSectionResizeMode( 1, - QHeaderView::ResizeToContents ); - ui->pluginList->sortByColumn( 0, Qt::AscendingOrder ); + connect(m_pluginList, &QTableView::doubleClicked, + this, &EffectSelectDialog::acceptSelection); + + setModal(true); + installEventFilter(this); updateSelection(); show(); } - - -EffectSelectDialog::~EffectSelectDialog() -{ - delete ui; -} - - - - -Effect * EffectSelectDialog::instantiateSelectedPlugin( EffectChain * _parent ) +Effect* EffectSelectDialog::instantiateSelectedPlugin(EffectChain* parent) { Effect* result = nullptr; - if(!m_currentSelection.name.isEmpty() && m_currentSelection.desc) + if (!m_currentSelection.name.isEmpty() && m_currentSelection.desc) { - result = Effect::instantiate(m_currentSelection.desc->name, - _parent, &m_currentSelection); + result = Effect::instantiate(m_currentSelection.desc->name, parent, &m_currentSelection); } - if(!result) + if (!result) { - result = new DummyEffect(_parent, QDomElement()); + result = new DummyEffect(parent, QDomElement()); } return result; } - - - void EffectSelectDialog::acceptSelection() { - if( m_currentSelection.isValid() ) + if (m_currentSelection.isValid()) { accept(); } } - - - -void EffectSelectDialog::rowChanged( const QModelIndex & _idx, - const QModelIndex & ) +void EffectSelectDialog::rowChanged(const QModelIndex& idx, const QModelIndex&) { delete m_descriptionWidget; m_descriptionWidget = nullptr; - if( m_model.mapToSource( _idx ).row() < 0 ) + if (m_model.mapToSource(idx).row() < 0) { - // invalidate current selection + // Invalidate current selection m_currentSelection = Plugin::Descriptor::SubPluginFeatures::Key(); } else { - m_currentSelection = m_effectKeys[m_model.mapToSource( _idx ).row()]; + m_currentSelection = m_effectKeys[m_model.mapToSource(idx).row()]; } - if( m_currentSelection.desc ) + if (m_currentSelection.desc) { m_descriptionWidget = new QWidget; auto hbox = new QHBoxLayout(m_descriptionWidget); + hbox->setAlignment(Qt::AlignTop); - Plugin::Descriptor const & descriptor = *( m_currentSelection.desc ); + Plugin::Descriptor const& descriptor = *(m_currentSelection.desc); const PixmapLoader* pixLoa = m_currentSelection.logo(); if (pixLoa) @@ -204,76 +238,88 @@ void EffectSelectDialog::rowChanged( const QModelIndex & _idx, auto logoLabel = new QLabel(m_descriptionWidget); logoLabel->setPixmap(pixLoa->pixmap()); logoLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + logoLabel->setMaximumSize(64, 64); - hbox->addWidget( logoLabel ); - hbox->setAlignment( logoLabel, Qt::AlignTop); + hbox->addWidget(logoLabel); } auto textualInfoWidget = new QWidget(m_descriptionWidget); - hbox->addWidget(textualInfoWidget); auto textWidgetLayout = new QVBoxLayout(textualInfoWidget); textWidgetLayout->setContentsMargins(4, 4, 4, 4); - textWidgetLayout->setSpacing( 0 ); + textWidgetLayout->setSpacing(8); - if ( m_currentSelection.desc->subPluginFeatures ) + if (m_currentSelection.desc->subPluginFeatures) { auto subWidget = new QWidget(textualInfoWidget); auto subLayout = new QVBoxLayout(subWidget); - subLayout->setContentsMargins(4, 4, 4, 4); - subLayout->setSpacing( 0 ); + subLayout->setContentsMargins(0, 0, 0, 0); + subLayout->setSpacing(8); + m_currentSelection.desc->subPluginFeatures-> - fillDescriptionWidget( subWidget, &m_currentSelection ); - for( QWidget * w : subWidget->findChildren() ) + fillDescriptionWidget(subWidget, &m_currentSelection); + for (QWidget* w : subWidget->findChildren()) { - if( w->parent() == subWidget ) + if (w->parent() == subWidget) { - subLayout->addWidget( w ); + subLayout->addWidget(w); + subLayout->setAlignment(w, QFlags(Qt::AlignTop | Qt::AlignLeft)); } } - textWidgetLayout->addWidget(subWidget); } else { auto label = new QLabel(m_descriptionWidget); QString labelText = "

" + tr("Name") + ": " + QString::fromUtf8(descriptor.displayName) + "

"; - labelText += "

" + tr("Description") + ": " + qApp->translate( "PluginBrowser", descriptor.description ) + "

"; + labelText += "

" + tr("Description") + ": " + qApp->translate("PluginBrowser", descriptor.description) + "

"; labelText += "

" + tr("Author") + ": " + QString::fromUtf8(descriptor.author) + "

"; label->setText(labelText); + label->setWordWrap(true); + textWidgetLayout->addWidget(label); } - ui->scrollArea->setWidget( m_descriptionWidget ); + m_scrollArea->setWidget(m_descriptionWidget); m_descriptionWidget->show(); } } - - - -void EffectSelectDialog::sortAgain() -{ - ui->pluginList->setSortingEnabled( ui->pluginList->isSortingEnabled() ); -} - - - - void EffectSelectDialog::updateSelection() { - // no valid selection anymore due to changed filter? - if( ui->pluginList->selectionModel()->selection().size() <= 0 ) + // No valid selection anymore due to changed filter? + if (m_pluginList->selectionModel()->selection().size() <= 0) { - // then select our first item - ui->pluginList->selectionModel()->select( m_model.index( 0, 0 ), - QItemSelectionModel::ClearAndSelect - | QItemSelectionModel::Rows ); - rowChanged( m_model.index( 0, 0 ), QModelIndex() ); + // Then select our first item + m_pluginList->selectionModel()-> + select(m_model.index(0, 0), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); + rowChanged(m_model.index(0, 0), QModelIndex()); } } +bool EffectSelectDialog::eventFilter(QObject *obj, QEvent *event) +{ + if (obj == this && event->type() == QEvent::KeyPress) + { + QKeyEvent *keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down) + { + QItemSelectionModel* selectionModel = m_pluginList->selectionModel(); + int currentRow = selectionModel->currentIndex().row(); + int newRow = (keyEvent->key() == Qt::Key_Up) ? currentRow - 1 : currentRow + 1; + int rowCount = m_pluginList->model()->rowCount(); + newRow = qBound(0, newRow, rowCount - 1); + + selectionModel->setCurrentIndex(m_pluginList->model()->index(newRow, 0), + QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); + m_pluginList->scrollTo(m_pluginList->model()->index(newRow, 0)); + return true; + } + } + + return QDialog::eventFilter(obj, event); +} } // namespace lmms::gui diff --git a/src/gui/modals/EffectSelectDialog.ui b/src/gui/modals/EffectSelectDialog.ui deleted file mode 100644 index b0433e66c..000000000 --- a/src/gui/modals/EffectSelectDialog.ui +++ /dev/null @@ -1,109 +0,0 @@ - - - EffectSelectDialog - - - - 0 - 0 - 585 - 550 - - - - Add effect - - - true - - - - 10 - - - - - - - - - 500 - 250 - - - - QAbstractItemView::NoEditTriggers - - - Qt::ScrollBarAlwaysOff - - - QAbstractItemView::SelectRows - - - QAbstractItemView::SingleSelection - - - false - - - true - - - - - - - QFrame::NoFrame - - - - - 0 - 0 - 497 - 109 - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - buttonBox - rejected() - EffectSelectDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - - - - RowTableView - QTableView -
RowTableView.h
-
-
-