Linked model groups (#4964)
Add labeled controls for different types with a common base class Implement a container for multiple equal groups of linked models and suiting views. Such groups are suited for representing mono effects where each Model occurs twice. A group provides Models for one mono processor and is visually represented with a group box. This concept is common for LADSPA and Lv2, and useful for any mono effect.
This commit is contained in:
@@ -31,6 +31,7 @@ set(LMMS_SRCS
|
||||
core/LadspaControl.cpp
|
||||
core/LadspaManager.cpp
|
||||
core/LfoController.cpp
|
||||
core/LinkedModelGroups.cpp
|
||||
core/LocklessAllocator.cpp
|
||||
core/MemoryHelper.cpp
|
||||
core/MemoryManager.cpp
|
||||
|
||||
185
src/core/LinkedModelGroups.cpp
Normal file
185
src/core/LinkedModelGroups.cpp
Normal file
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* LinkedModelGroups.cpp - base classes for groups of linked models
|
||||
*
|
||||
* Copyright (c) 2019-2019 Johannes Lorenz <j.git$$$lorenz-ho.me, $$$=@>
|
||||
*
|
||||
* This file is part of LMMS - https://lmms.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program (see COPYING); if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110-1301 USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "LinkedModelGroups.h"
|
||||
|
||||
#include <QDomDocument>
|
||||
#include <QDomElement>
|
||||
|
||||
#include "AutomatableModel.h"
|
||||
#include "stdshims.h"
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
LinkedModelGroup
|
||||
*/
|
||||
|
||||
|
||||
void LinkedModelGroup::linkControls(LinkedModelGroup *other)
|
||||
{
|
||||
foreach_model([&other](const std::string& id, ModelInfo& inf)
|
||||
{
|
||||
auto itr2 = other->m_models.find(id);
|
||||
Q_ASSERT(itr2 != other->m_models.end());
|
||||
AutomatableModel::linkModels(inf.m_model, itr2->second.m_model);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void LinkedModelGroup::saveValues(QDomDocument &doc, QDomElement &that)
|
||||
{
|
||||
foreach_model([&doc, &that](const std::string& , ModelInfo& inf)
|
||||
{
|
||||
inf.m_model->saveSettings(doc, that, /*m_models[idx].m_name*/ inf.m_name); /* TODO: m_name useful */
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void LinkedModelGroup::loadValues(const QDomElement &that)
|
||||
{
|
||||
foreach_model([&that](const std::string& , ModelInfo& inf)
|
||||
{
|
||||
// try to load, if it fails, this will load a sane initial value
|
||||
inf.m_model->loadSettings(that, /*m_models()[idx].m_name*/ inf.m_name); /* TODO: m_name useful */
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void LinkedModelGroup::addModel(AutomatableModel *model, const QString &name)
|
||||
{
|
||||
model->setObjectName(name);
|
||||
m_models.emplace(std::string(name.toUtf8().data()), ModelInfo(name, model));
|
||||
connect(model, &AutomatableModel::destroyed,
|
||||
this, [this, model](jo_id_t){
|
||||
if(containsModel(model->objectName()))
|
||||
{
|
||||
emit modelRemoved(model);
|
||||
eraseModel(model->objectName());
|
||||
}
|
||||
},
|
||||
Qt::DirectConnection);
|
||||
|
||||
// View needs to create another child view, e.g. a new knob:
|
||||
emit modelAdded(model);
|
||||
emit dataChanged();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void LinkedModelGroup::removeControl(AutomatableModel* mdl)
|
||||
{
|
||||
if(containsModel(mdl->objectName()))
|
||||
{
|
||||
emit modelRemoved(mdl);
|
||||
eraseModel(mdl->objectName());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
bool LinkedModelGroup::eraseModel(const QString& name)
|
||||
{
|
||||
return m_models.erase(name.toStdString()) > 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void LinkedModelGroup::clearModels()
|
||||
{
|
||||
m_models.clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
bool LinkedModelGroup::containsModel(const QString &name) const
|
||||
{
|
||||
return m_models.find(name.toStdString()) != m_models.end();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
LinkedModelGroups
|
||||
*/
|
||||
|
||||
|
||||
|
||||
LinkedModelGroups::~LinkedModelGroups() {}
|
||||
|
||||
|
||||
|
||||
|
||||
void LinkedModelGroups::linkAllModels()
|
||||
{
|
||||
LinkedModelGroup* first = getGroup(0);
|
||||
LinkedModelGroup* cur;
|
||||
|
||||
for (std::size_t i = 1; (cur = getGroup(i)); ++i)
|
||||
{
|
||||
first->linkControls(cur);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void LinkedModelGroups::saveSettings(QDomDocument& doc, QDomElement& that)
|
||||
{
|
||||
LinkedModelGroup* grp0 = getGroup(0);
|
||||
if (grp0)
|
||||
{
|
||||
QDomElement models = doc.createElement("models");
|
||||
that.appendChild(models);
|
||||
grp0->saveValues(doc, models);
|
||||
}
|
||||
else { /* don't even add a "models" node */ }
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void LinkedModelGroups::loadSettings(const QDomElement& that)
|
||||
{
|
||||
QDomElement models = that.firstChildElement("models");
|
||||
LinkedModelGroup* grp0;
|
||||
if (!models.isNull() && (grp0 = getGroup(0)))
|
||||
{
|
||||
// only load the first group, the others are linked to the first
|
||||
grp0->loadValues(models);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ SET(LMMS_SRCS
|
||||
gui/widgets/ComboBox.cpp
|
||||
gui/widgets/ControllerRackView.cpp
|
||||
gui/widgets/ControllerView.cpp
|
||||
gui/widgets/Controls.cpp
|
||||
gui/widgets/CPULoadWidget.cpp
|
||||
gui/widgets/EffectRackView.cpp
|
||||
gui/widgets/EffectView.cpp
|
||||
@@ -71,6 +72,8 @@ SET(LMMS_SRCS
|
||||
gui/widgets/LcdSpinBox.cpp
|
||||
gui/widgets/LcdWidget.cpp
|
||||
gui/widgets/LedCheckbox.cpp
|
||||
gui/widgets/ControlLayout.cpp
|
||||
gui/widgets/LinkedModelGroupViews.cpp
|
||||
gui/widgets/MeterDialog.cpp
|
||||
gui/widgets/MidiPortMenu.cpp
|
||||
gui/widgets/NStateButton.cpp
|
||||
|
||||
308
src/gui/widgets/ControlLayout.cpp
Normal file
308
src/gui/widgets/ControlLayout.cpp
Normal file
@@ -0,0 +1,308 @@
|
||||
/*
|
||||
* ControlLayout.cpp - implementation for ControlLayout.h
|
||||
*
|
||||
* Copyright (c) 2019-2019 Johannes Lorenz <j.git$$$lorenz-ho.me, $$$=@>
|
||||
*
|
||||
* This file is part of LMMS - https://lmms.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program (see COPYING); if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110-1301 USA.
|
||||
*
|
||||
*/
|
||||
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** BSD License Usage
|
||||
** Alternatively, you may use this file under the terms of the BSD license
|
||||
** as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in
|
||||
** the documentation and/or other materials provided with the
|
||||
** distribution.
|
||||
** * Neither the name of The Qt Company Ltd nor the names of its
|
||||
** contributors may be used to endorse or promote products derived
|
||||
** from this software without specific prior written permission.
|
||||
**
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
|
||||
#include "ControlLayout.h"
|
||||
|
||||
#include <QWidget>
|
||||
#include <QLayoutItem>
|
||||
#include <QLineEdit>
|
||||
#include <QRect>
|
||||
#include <QString>
|
||||
|
||||
constexpr const int ControlLayout::m_minWidth;
|
||||
|
||||
ControlLayout::ControlLayout(QWidget *parent, int margin, int hSpacing, int vSpacing)
|
||||
: QLayout(parent), m_hSpace(hSpacing), m_vSpace(vSpacing),
|
||||
m_searchBar(new QLineEdit(parent))
|
||||
{
|
||||
setContentsMargins(margin, margin, margin, margin);
|
||||
m_searchBar->setPlaceholderText("filter");
|
||||
m_searchBar->setObjectName(s_searchBarName);
|
||||
connect(m_searchBar, SIGNAL(textChanged(const QString&)),
|
||||
this, SLOT(onTextChanged(const QString& )));
|
||||
addWidget(m_searchBar);
|
||||
m_searchBar->setHidden(true); // nothing to filter yet
|
||||
}
|
||||
|
||||
ControlLayout::~ControlLayout()
|
||||
{
|
||||
QLayoutItem *item;
|
||||
while ((item = takeAt(0))) { delete item; }
|
||||
}
|
||||
|
||||
void ControlLayout::onTextChanged(const QString&)
|
||||
{
|
||||
invalidate();
|
||||
update();
|
||||
}
|
||||
|
||||
void ControlLayout::addItem(QLayoutItem *item)
|
||||
{
|
||||
QWidget* widget = item->widget();
|
||||
const QString str = widget ? widget->objectName() : QString("unnamed");
|
||||
m_itemMap.insert(str, item);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
int ControlLayout::horizontalSpacing() const
|
||||
{
|
||||
if (m_hSpace >= 0) { return m_hSpace; }
|
||||
else
|
||||
{
|
||||
return smartSpacing(QStyle::PM_LayoutHorizontalSpacing);
|
||||
}
|
||||
}
|
||||
|
||||
int ControlLayout::verticalSpacing() const
|
||||
{
|
||||
if (m_vSpace >= 0) { return m_vSpace; }
|
||||
else
|
||||
{
|
||||
return smartSpacing(QStyle::PM_LayoutVerticalSpacing);
|
||||
}
|
||||
}
|
||||
|
||||
int ControlLayout::count() const
|
||||
{
|
||||
return m_itemMap.size() - 1;
|
||||
}
|
||||
|
||||
QMap<QString, QLayoutItem*>::const_iterator
|
||||
ControlLayout::pairAt(int index) const
|
||||
{
|
||||
if (index < 0) { return m_itemMap.cend(); }
|
||||
|
||||
auto skip = [&](QLayoutItem* item) -> bool
|
||||
{
|
||||
return item->widget()->objectName() == s_searchBarName;
|
||||
};
|
||||
|
||||
QMap<QString, QLayoutItem*>::const_iterator itr = m_itemMap.cbegin();
|
||||
for (; itr != m_itemMap.cend() && (index > 0 || skip(itr.value())); ++itr)
|
||||
{
|
||||
if(!skip(itr.value())) { index--; }
|
||||
}
|
||||
return itr;
|
||||
}
|
||||
|
||||
// linear time :-(
|
||||
QLayoutItem *ControlLayout::itemAt(int index) const
|
||||
{
|
||||
auto itr = pairAt(index);
|
||||
return (itr == m_itemMap.end()) ? nullptr : itr.value();
|
||||
}
|
||||
|
||||
QLayoutItem *ControlLayout::itemByString(const QString &key) const
|
||||
{
|
||||
auto itr = m_itemMap.find(key);
|
||||
return (itr == m_itemMap.end()) ? nullptr : *itr;
|
||||
}
|
||||
|
||||
// linear time :-(
|
||||
QLayoutItem *ControlLayout::takeAt(int index)
|
||||
{
|
||||
auto itr = pairAt(index);
|
||||
return (itr == m_itemMap.end()) ? nullptr : m_itemMap.take(itr.key());
|
||||
}
|
||||
|
||||
Qt::Orientations ControlLayout::expandingDirections() const
|
||||
{
|
||||
return Qt::Orientations();
|
||||
}
|
||||
|
||||
bool ControlLayout::hasHeightForWidth() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
int ControlLayout::heightForWidth(int width) const
|
||||
{
|
||||
int height = doLayout(QRect(0, 0, width, 0), true);
|
||||
return height;
|
||||
}
|
||||
|
||||
void ControlLayout::setGeometry(const QRect &rect)
|
||||
{
|
||||
QLayout::setGeometry(rect);
|
||||
doLayout(rect, false);
|
||||
}
|
||||
|
||||
QSize ControlLayout::sizeHint() const
|
||||
{
|
||||
return minimumSize();
|
||||
}
|
||||
|
||||
QSize ControlLayout::minimumSize() const
|
||||
{
|
||||
// original formula from Qt's FlowLayout example:
|
||||
// get maximum height and width for all children.
|
||||
// as Qt will later call heightForWidth, only the width here really matters
|
||||
QSize size;
|
||||
for (const QLayoutItem *item : qAsConst(m_itemMap))
|
||||
{
|
||||
size = size.expandedTo(item->minimumSize());
|
||||
}
|
||||
const QMargins margins = contentsMargins();
|
||||
size += QSize(margins.left() + margins.right(), margins.top() + margins.bottom());
|
||||
|
||||
// the original formula would leed to ~1 widget per row
|
||||
// bash it at least to 400 so we have ~4 knobs per row
|
||||
size.setWidth(qMax(size.width(), m_minWidth));
|
||||
return size;
|
||||
}
|
||||
|
||||
int ControlLayout::doLayout(const QRect &rect, bool testOnly) const
|
||||
{
|
||||
int left, top, right, bottom;
|
||||
getContentsMargins(&left, &top, &right, &bottom);
|
||||
QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom);
|
||||
int x = effectiveRect.x();
|
||||
int y = effectiveRect.y();
|
||||
int lineHeight = 0;
|
||||
|
||||
const QString filterText = m_searchBar->text();
|
||||
bool first = true;
|
||||
|
||||
QMapIterator<QString, QLayoutItem*> itr(m_itemMap);
|
||||
while (itr.hasNext())
|
||||
{
|
||||
itr.next();
|
||||
QLayoutItem* item = itr.value();
|
||||
QWidget *wid = item->widget();
|
||||
if (wid)
|
||||
{
|
||||
if ( first || // do not filter search bar
|
||||
filterText.isEmpty() || // no filter - pass all
|
||||
itr.key().contains(filterText, Qt::CaseInsensitive))
|
||||
{
|
||||
if (first)
|
||||
{
|
||||
// for the search bar, only show it if there are at least
|
||||
// two control widgets (i.e. at least 3 widgets)
|
||||
if (m_itemMap.size() > 2) { wid->show(); }
|
||||
else { wid->hide(); }
|
||||
}
|
||||
else { wid->show(); }
|
||||
|
||||
int spaceX = horizontalSpacing();
|
||||
if (spaceX == -1)
|
||||
{
|
||||
spaceX = wid->style()->layoutSpacing(
|
||||
QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal);
|
||||
}
|
||||
int spaceY = verticalSpacing();
|
||||
if (spaceY == -1)
|
||||
{
|
||||
spaceY = wid->style()->layoutSpacing(
|
||||
QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical);
|
||||
}
|
||||
int nextX = x + item->sizeHint().width() + spaceX;
|
||||
if (nextX - spaceX > effectiveRect.right() && lineHeight > 0)
|
||||
{
|
||||
x = effectiveRect.x();
|
||||
y = y + lineHeight + spaceY;
|
||||
nextX = x + item->sizeHint().width() + spaceX;
|
||||
lineHeight = 0;
|
||||
}
|
||||
|
||||
if (!testOnly)
|
||||
{
|
||||
item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));
|
||||
}
|
||||
|
||||
x = nextX;
|
||||
lineHeight = qMax(lineHeight, item->sizeHint().height());
|
||||
first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
wid->hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
return y + lineHeight - rect.y() + bottom;
|
||||
}
|
||||
|
||||
int ControlLayout::smartSpacing(QStyle::PixelMetric pm) const
|
||||
{
|
||||
QObject *parent = this->parent();
|
||||
if (!parent) { return -1; }
|
||||
else if (parent->isWidgetType())
|
||||
{
|
||||
QWidget *pw = static_cast<QWidget *>(parent);
|
||||
return pw->style()->pixelMetric(pm, nullptr, pw);
|
||||
}
|
||||
else { return static_cast<QLayout *>(parent)->spacing(); }
|
||||
}
|
||||
|
||||
|
||||
140
src/gui/widgets/Controls.cpp
Normal file
140
src/gui/widgets/Controls.cpp
Normal file
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Controls.cpp - labeled control widgets
|
||||
*
|
||||
* Copyright (c) 2019-2019 Johannes Lorenz <j.git$$$lorenz-ho.me, $$$=@>
|
||||
*
|
||||
* This file is part of LMMS - https://lmms.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program (see COPYING); if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110-1301 USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "Controls.h"
|
||||
|
||||
#include <QLabel>
|
||||
#include <QString>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "ComboBox.h"
|
||||
#include "LcdSpinBox.h"
|
||||
#include "LedCheckbox.h"
|
||||
#include "Knob.h"
|
||||
|
||||
|
||||
|
||||
|
||||
Control::~Control() {}
|
||||
|
||||
|
||||
|
||||
|
||||
void KnobControl::setText(const QString &text) { m_knob->setLabel(text); }
|
||||
|
||||
QWidget *KnobControl::topWidget() { return m_knob; }
|
||||
|
||||
void KnobControl::setModel(AutomatableModel *model)
|
||||
{
|
||||
m_knob->setModel(model->dynamicCast<FloatModel>(true));
|
||||
}
|
||||
|
||||
FloatModel *KnobControl::model() { return m_knob->model(); }
|
||||
|
||||
AutomatableModelView* KnobControl::modelView() { return m_knob; }
|
||||
|
||||
KnobControl::KnobControl(QWidget *parent) :
|
||||
m_knob(new Knob(parent)) {}
|
||||
|
||||
KnobControl::~KnobControl() {}
|
||||
|
||||
|
||||
|
||||
|
||||
void ComboControl::setText(const QString &text) { m_label->setText(text); }
|
||||
|
||||
void ComboControl::setModel(AutomatableModel *model)
|
||||
{
|
||||
m_combo->setModel(model->dynamicCast<ComboBoxModel>(true));
|
||||
}
|
||||
|
||||
ComboBoxModel *ComboControl::model() { return m_combo->model(); }
|
||||
|
||||
AutomatableModelView* ComboControl::modelView() { return m_combo; }
|
||||
|
||||
ComboControl::ComboControl(QWidget *parent) :
|
||||
m_widget(new QWidget(parent)),
|
||||
m_combo(new ComboBox(nullptr)),
|
||||
m_label(new QLabel(m_widget))
|
||||
{
|
||||
m_combo->setFixedSize(64, 22);
|
||||
QVBoxLayout* vbox = new QVBoxLayout(m_widget);
|
||||
vbox->addWidget(m_combo);
|
||||
vbox->addWidget(m_label);
|
||||
m_combo->repaint();
|
||||
}
|
||||
|
||||
ComboControl::~ComboControl() {}
|
||||
|
||||
|
||||
|
||||
|
||||
void CheckControl::setText(const QString &text) { m_label->setText(text); }
|
||||
|
||||
QWidget *CheckControl::topWidget() { return m_widget; }
|
||||
|
||||
void CheckControl::setModel(AutomatableModel *model)
|
||||
{
|
||||
m_checkBox->setModel(model->dynamicCast<BoolModel>(true));
|
||||
}
|
||||
|
||||
BoolModel *CheckControl::model() { return m_checkBox->model(); }
|
||||
|
||||
AutomatableModelView* CheckControl::modelView() { return m_checkBox; }
|
||||
|
||||
CheckControl::CheckControl(QWidget *parent) :
|
||||
m_widget(new QWidget(parent)),
|
||||
m_checkBox(new LedCheckBox(nullptr, QString(), LedCheckBox::Green)),
|
||||
m_label(new QLabel(m_widget))
|
||||
{
|
||||
QVBoxLayout* vbox = new QVBoxLayout(m_widget);
|
||||
vbox->addWidget(m_checkBox);
|
||||
vbox->addWidget(m_label);
|
||||
}
|
||||
|
||||
CheckControl::~CheckControl() {}
|
||||
|
||||
|
||||
|
||||
|
||||
void LcdControl::setText(const QString &text) { m_lcd->setLabel(text); }
|
||||
|
||||
QWidget *LcdControl::topWidget() { return m_lcd; }
|
||||
|
||||
void LcdControl::setModel(AutomatableModel *model)
|
||||
{
|
||||
m_lcd->setModel(model->dynamicCast<IntModel>(true));
|
||||
}
|
||||
|
||||
IntModel *LcdControl::model() { return m_lcd->model(); }
|
||||
|
||||
AutomatableModelView* LcdControl::modelView() { return m_lcd; }
|
||||
|
||||
LcdControl::LcdControl(int numDigits, QWidget *parent) :
|
||||
m_lcd(new LcdSpinBox(numDigits, parent))
|
||||
{
|
||||
}
|
||||
|
||||
LcdControl::~LcdControl() {}
|
||||
|
||||
160
src/gui/widgets/LinkedModelGroupViews.cpp
Normal file
160
src/gui/widgets/LinkedModelGroupViews.cpp
Normal file
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* LinkedModelGroupViews.h - view for groups of linkable models
|
||||
*
|
||||
* Copyright (c) 2019-2019 Johannes Lorenz <j.git$$$lorenz-ho.me, $$$=@>
|
||||
*
|
||||
* This file is part of LMMS - https://lmms.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program (see COPYING); if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110-1301 USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "LinkedModelGroupViews.h"
|
||||
|
||||
#include <QPushButton>
|
||||
#include "Controls.h"
|
||||
#include "ControlLayout.h"
|
||||
#include "LinkedModelGroups.h"
|
||||
|
||||
|
||||
/*
|
||||
LinkedModelGroupViewBase
|
||||
*/
|
||||
|
||||
|
||||
LinkedModelGroupView::LinkedModelGroupView(QWidget* parent,
|
||||
LinkedModelGroup *model, std::size_t colNum) :
|
||||
QWidget(parent),
|
||||
m_model(model),
|
||||
m_colNum(colNum),
|
||||
m_layout(new ControlLayout(this))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
LinkedModelGroupView::~LinkedModelGroupView() {}
|
||||
|
||||
|
||||
|
||||
|
||||
void LinkedModelGroupView::modelChanged(LinkedModelGroup *group)
|
||||
{
|
||||
// reconnect models
|
||||
group->foreach_model([this](const std::string& str,
|
||||
const LinkedModelGroup::ModelInfo& minf)
|
||||
{
|
||||
auto itr = m_widgets.find(str);
|
||||
// in case there are new or deleted widgets, the subclass has already
|
||||
// modified m_widgets, so this will go into the else case
|
||||
if (itr == m_widgets.end())
|
||||
{
|
||||
// no widget? this can happen when the whole view is being destroyed
|
||||
// (for some strange reasons)
|
||||
}
|
||||
else
|
||||
{
|
||||
itr->second->setModel(minf.m_model);
|
||||
}
|
||||
});
|
||||
|
||||
m_model = group;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void LinkedModelGroupView::addControl(Control* ctrl, const std::string& id,
|
||||
const std::string &display, bool removable)
|
||||
{
|
||||
int wdgNum = static_cast<int>(m_widgets.size());
|
||||
if (ctrl)
|
||||
{
|
||||
QWidget* box = new QWidget(this);
|
||||
QHBoxLayout* boxLayout = new QHBoxLayout(box);
|
||||
boxLayout->addWidget(ctrl->topWidget());
|
||||
|
||||
if (removable)
|
||||
{
|
||||
QPushButton* removeBtn = new QPushButton;
|
||||
removeBtn->setIcon( embed::getIconPixmap( "discard" ) );
|
||||
QObject::connect(removeBtn, &QPushButton::clicked,
|
||||
this, [this,ctrl](bool){
|
||||
AutomatableModel* controlModel = ctrl->model();
|
||||
// remove control out of model group
|
||||
// (will also remove it from the UI)
|
||||
m_model->removeControl(controlModel);
|
||||
// delete model (includes disconnecting all connections)
|
||||
delete controlModel;
|
||||
},
|
||||
Qt::DirectConnection);
|
||||
boxLayout->addWidget(removeBtn);
|
||||
}
|
||||
|
||||
// required, so the Layout knows how to sort/filter widgets by string
|
||||
box->setObjectName(QString::fromStdString(display));
|
||||
m_layout->addWidget(box);
|
||||
|
||||
// take ownership of control and add it
|
||||
m_widgets.emplace(id, std::unique_ptr<Control>(ctrl));
|
||||
++wdgNum;
|
||||
}
|
||||
|
||||
if (isHidden()) { setHidden(false); }
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void LinkedModelGroupView::removeControl(const QString& key)
|
||||
{
|
||||
auto itr = m_widgets.find(key.toStdString());
|
||||
if (itr != m_widgets.end())
|
||||
{
|
||||
QLayoutItem* item = m_layout->itemByString(key);
|
||||
Q_ASSERT(!!item);
|
||||
QWidget* wdg = item->widget();
|
||||
Q_ASSERT(!!wdg);
|
||||
|
||||
// remove item from layout
|
||||
m_layout->removeItem(item);
|
||||
// the widget still exists and is visible - remove it now
|
||||
delete wdg;
|
||||
// erase widget pointer from dictionary
|
||||
m_widgets.erase(itr);
|
||||
// repaint immediately, so we don't have dangling model views
|
||||
m_layout->update();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
LinkedModelGroupsViewBase
|
||||
*/
|
||||
|
||||
|
||||
void LinkedModelGroupsView::modelChanged(LinkedModelGroups *groups)
|
||||
{
|
||||
LinkedModelGroupView* groupView = getGroupView();
|
||||
LinkedModelGroup* group0 = groups->getGroup(0);
|
||||
if (group0 && groupView)
|
||||
{
|
||||
groupView->modelChanged(group0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user