Add BarModelEditor and improve layouts

Add the new class `BarModelEditor` which is intended to become a new way
to adjust values of float models.

Simplify the layout in `LadspaMatrixControlDialog` by removing some
nested layouts. Remove the "Parameters" column.

Adjust `LadspaMatrixControlView` to implement the following changes:
* Show the name of the control next to toggle buttons (`LedCheckBox`).
* Use the new `BarModelEditor` for integer and float types.
* SHow the name of the control next to time based parameters that use
`TempoSyncKnob`.

The names are shown so that the "Parameters" column can be removed.

Technical details
------------------
The class `LadspaMatrixControlDialog` now creates a widget that contains
the matrix layout with the controls. This widget is then added to a
scroll area. The layout is populated in the new method
`arrangeControls`.

Add some helper methods to `LadspaMatrixControlDialog` which retrieve
the `LadspaControls` instance and the number of channels.

Add the implementation of `BarModelEditor` to `src/gui/CMakeLists.txt`.

TODOs
------
Extract common code out of the `Knob` class so that it can be reused by
`BarModelEditor`.
This commit is contained in:
Michael Gregorius
2023-07-08 12:11:46 +02:00
parent 1295deb3a8
commit 610fb3442f
6 changed files with 327 additions and 66 deletions

View File

@@ -29,6 +29,8 @@
#include <QLabel>
#include <QScrollArea>
#include <QScrollBar>
#include <QSpacerItem>
#include "LadspaBase.h"
#include "LadspaControl.h"
@@ -43,110 +45,153 @@
namespace lmms::gui
{
static int const s_linkBaseColumn = 0;
static int const s_parameterNameBaseColumn = 2;
static int const s_channelBaseColumn = 4;
LadspaMatrixControlDialog::LadspaMatrixControlDialog(LadspaControls * ladspaControls) :
EffectControlDialog(ladspaControls),
m_effectGridLayout(nullptr),
m_scrollArea(nullptr),
m_stereoLink(nullptr)
{
QVBoxLayout * mainLayout = new QVBoxLayout(this);
m_effectGridLayout = new QGridLayout();
mainLayout->addLayout(m_effectGridLayout);
m_scrollArea = new QScrollArea(this);
m_scrollArea->setWidgetResizable(true);
m_scrollArea->setFrameShape(QFrame::NoFrame);
// Add a scroll area that grows
mainLayout->addWidget(m_scrollArea, 1);
// Populate the parameter matrix and put it into the scroll area
updateEffectView(ladspaControls);
if (ladspaControls->m_processors > 1)
// Add button to link all channels if there's more than one channel
if (getChannelCount() > 1)
{
mainLayout->addSpacing(3);
QHBoxLayout * center = new QHBoxLayout();
mainLayout->addLayout(center);
m_stereoLink = new LedCheckBox(tr("Link Channels"), this);
m_stereoLink->setModel(&ladspaControls->m_stereoLinkModel);
center->addWidget(m_stereoLink);
mainLayout->addWidget(m_stereoLink, 0, Qt::AlignCenter);
}
}
void LadspaMatrixControlDialog::updateEffectView(LadspaControls * ladspaControls)
bool LadspaMatrixControlDialog::needsLinkColumn() const
{
for (auto child : findChildren<QWidget *>())
LadspaControls * ladspaControls = getLadspaControls();
ch_cnt_t const channelCount = getChannelCount();
for (ch_cnt_t i = 0; i < channelCount; ++i)
{
delete child;
// Create a const reference so that the C++11 based for loop does not detach the Qt container
auto const & currentControls = ladspaControls->m_controls[i];
for (auto ladspaControl : currentControls)
{
if (ladspaControl->m_link)
{
return true;
}
}
}
m_effectControls = ladspaControls;
return false;
}
QWidget *widget = new QWidget(this);
QGridLayout *gridLayout = new QGridLayout(widget);
widget->setLayout(gridLayout);
void LadspaMatrixControlDialog::arrangeControls(QWidget * parent, QGridLayout* gridLayout)
{
LadspaControls * ladspaControls = getLadspaControls();
gridLayout->addWidget(new QLabel("<b>" + tr("Parameter") + "</b>", widget), 0, s_parameterNameBaseColumn, Qt::AlignRight);
int const headerRow = 0;
int const linkColumn = 0;
bool linkLabelAdded = false;
ch_cnt_t const numberOfChannels = ladspaControls->m_processors;
bool const linkColumnNeeded = needsLinkColumn();
if (linkColumnNeeded)
{
gridLayout->addWidget(new QLabel("<b>" + tr("Link") + "</b>", parent), headerRow, linkColumn, Qt::AlignHCenter);
gridLayout->setColumnMinimumWidth(1, 20);
// If there's a link column then it should not stretch
gridLayout->setColumnStretch(linkColumn, 0);
}
int const channelStartColumn = linkColumnNeeded ? 1 : 0;
// The header row should not grow vertically
gridLayout->setRowStretch(0, 0);
// Records the maximum row with parameters so that we can add a vertical spacer after that row
int maxRow = 0;
// Iterate the channels and add widgets for each control
ch_cnt_t const numberOfChannels = getChannelCount();
for (ch_cnt_t i = 0; i < numberOfChannels; ++i)
{
QString channelString(tr("Channel %1"));
int currentChannelColumn = s_channelBaseColumn + 2*i;
int currentChannelColumn = channelStartColumn + i;
gridLayout->setColumnStretch(currentChannelColumn, 1);
gridLayout->addWidget(new QLabel("<b>" + channelString.arg(QString::number(i + 1)) + "</b>", widget), 0, currentChannelColumn, Qt::AlignHCenter);
// First add the channel header with the channel number
gridLayout->addWidget(new QLabel("<b>" + tr("Channel %1").arg(QString::number(i + 1)) + "</b>", parent), headerRow, currentChannelColumn, Qt::AlignHCenter);
int currentRow = 1;
for (auto ladspaControl : ladspaControls->m_controls[i])
if (i == 0)
{
if (i == 0)
// Configure the current parameter row to not stretch.
// Only do this once, i.e. when working with the first channel.
gridLayout->setRowStretch(currentRow, 0);
}
// Create a const reference so that the C++11 based for loop does not detach the Qt container
auto const & currentControls = ladspaControls->m_controls[i];
for (auto ladspaControl : currentControls)
{
// Only use the first channel to determine if we need to add link controls
if (i == 0 && ladspaControl->m_link)
{
// TODO Assumes that all processors are equal! Change to more general approach, e.g. map from name to row
// Link
if (ladspaControl->m_link)
{
if (!linkLabelAdded)
{
gridLayout->addWidget(new QLabel("<b>" + tr("Link") + "</b>", widget), 0, s_linkBaseColumn, Qt::AlignHCenter);
linkLabelAdded = true;
}
LedCheckBox * linkCheckBox = new LedCheckBox("", widget);
linkCheckBox->setModel(&ladspaControl->m_linkEnabledModel);
linkCheckBox->setToolTip(tr("Link channels"));
gridLayout->addWidget(linkCheckBox, currentRow, s_linkBaseColumn, Qt::AlignHCenter);
}
// Parameter name
QString portName = ladspaControl->port()->name;
QLabel *portNameLabel = new QLabel(portName, widget);
gridLayout->addWidget(portNameLabel, currentRow, s_parameterNameBaseColumn, Qt::AlignRight);
LedCheckBox * linkCheckBox = new LedCheckBox("", parent);
linkCheckBox->setModel(&ladspaControl->m_linkEnabledModel);
linkCheckBox->setToolTip(tr("Link channels"));
gridLayout->addWidget(linkCheckBox, currentRow, linkColumn, Qt::AlignHCenter);
}
LadspaMatrixControlView *ladspaMatrixControlView = new LadspaMatrixControlView(widget, ladspaControl);
gridLayout->addWidget(ladspaMatrixControlView, currentRow, currentChannelColumn, Qt::AlignHCenter);
gridLayout->setColumnMinimumWidth(currentChannelColumn - 1, 20);
// TODO Use a factory to directly create the widgets? Currently they are wrapped in another layout in LadspaMatrixControlView...
LadspaMatrixControlView *ladspaMatrixControlView = new LadspaMatrixControlView(parent, ladspaControl);
gridLayout->addWidget(ladspaMatrixControlView, currentRow, currentChannelColumn);
// Record the maximum row so that we add a vertical spacer after that row
maxRow = std::max(maxRow, currentRow);
++currentRow;
}
}
QScrollArea *scrollArea = new QScrollArea(this);
scrollArea->setWidgetResizable(true);
scrollArea->setWidget(widget);
scrollArea->setFrameShape(QFrame::NoFrame);
// Add a spacer item after the maximum row
QSpacerItem * spacer = new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
gridLayout->addItem(spacer, maxRow + 1, 0);
}
m_effectGridLayout->addWidget(scrollArea, 0, 0);
m_effectGridLayout->setMargin(0);
QWidget * LadspaMatrixControlDialog::createMatrixWidget()
{
QWidget *widget = new QWidget(this);
QGridLayout *gridLayout = new QGridLayout(widget);
widget->setLayout(gridLayout);
if (numberOfChannels > 1 && m_stereoLink != nullptr)
arrangeControls(widget, gridLayout);
return widget;
}
void LadspaMatrixControlDialog::updateEffectView(LadspaControls * ladspaControls)
{
m_effectControls = ladspaControls;
// No need to delete the existing widget as it's deleted
// by the scroll view when we replace it.
QWidget * matrixWidget = createMatrixWidget();
m_scrollArea->setWidget(matrixWidget);
// Make sure that the horizontal scroll bar does not show
// From: https://forum.qt.io/topic/13374/solved-qscrollarea-vertical-scroll-only/4
m_scrollArea->setMinimumWidth(matrixWidget->minimumSizeHint().width() + m_scrollArea->verticalScrollBar()->width());
if (getChannelCount() > 1 && m_stereoLink != nullptr)
{
m_stereoLink->setModel(&ladspaControls->m_stereoLinkModel);
}
@@ -156,4 +201,14 @@ void LadspaMatrixControlDialog::updateEffectView(LadspaControls * ladspaControls
Qt::DirectConnection);
}
LadspaControls * LadspaMatrixControlDialog::getLadspaControls() const
{
return dynamic_cast<LadspaControls *>(m_effectControls);
}
ch_cnt_t LadspaMatrixControlDialog::getChannelCount() const
{
return getLadspaControls()->m_processors;
}
} // namespace lmms::gui

View File

@@ -28,8 +28,11 @@
#include "EffectControlDialog.h"
#include "lmms_basics.h"
class QGridLayout;
class QScrollArea;
namespace lmms
{
@@ -53,9 +56,32 @@ public:
private slots:
void updateEffectView(LadspaControls* ctl);
private:
/**
* @brief Checks if a link column is needed for the current effect controls.
* @return true if a link column is needed.
*/
bool needsLinkColumn() const;
/**
* @brief Arranges widgets for the current controls in a grid/matrix layout.
* @param parent The parent of all created widgets
* @param gridLayout The layout into which the controls are organized
*/
void arrangeControls(QWidget * parent, QGridLayout* gridLayout);
/**
* @brief Creates a widget that holds the widgets of the current controls in a matrix arrangement.
* @param ladspaControls
* @return
*/
QWidget * createMatrixWidget();
LadspaControls * getLadspaControls() const;
ch_cnt_t getChannelCount() const;
private:
QGridLayout* m_effectGridLayout;
QScrollArea* m_scrollArea;
LedCheckBox* m_stereoLink;
};