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:
Johannes Lorenz
2020-02-21 19:26:29 +01:00
committed by GitHub
parent 3410db4d99
commit eebdc0f4be
11 changed files with 1351 additions and 0 deletions

133
include/ControlLayout.h Normal file
View File

@@ -0,0 +1,133 @@
/*
* ControlLayout.h - layout for controls
*
* 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$
**
****************************************************************************/
#ifndef CONTROLLAYOUT_H
#define CONTROLLAYOUT_H
#include <QLayout>
#include <QMultiMap>
#include <QStyle>
class QLayoutItem;
class QRect;
class QString;
/**
Layout for controls (models)
Originally token from Qt's FlowLayout example. Modified.
Features a search bar, as well as looking up widgets with string keys
Keys have to be provided in the widgets' objectNames
*/
class ControlLayout : public QLayout
{
Q_OBJECT
public:
explicit ControlLayout(QWidget *parent,
int margin = -1, int hSpacing = -1, int vSpacing = -1);
~ControlLayout() override;
void addItem(QLayoutItem *item) override;
int horizontalSpacing() const;
int verticalSpacing() const;
Qt::Orientations expandingDirections() const override;
bool hasHeightForWidth() const override;
int heightForWidth(int) const override;
int count() const override;
QLayoutItem *itemAt(int index) const override;
QLayoutItem *itemByString(const QString& key) const;
QSize minimumSize() const override;
void setGeometry(const QRect &rect) override;
QSize sizeHint() const override;
QLayoutItem *takeAt(int index) override;
private slots:
void onTextChanged(const QString&);
private:
int doLayout(const QRect &rect, bool testOnly) const;
int smartSpacing(QStyle::PixelMetric pm) const;
QMap<QString, QLayoutItem *>::const_iterator pairAt(int index) const;
QMultiMap<QString, QLayoutItem *> m_itemMap;
int m_hSpace;
int m_vSpace;
// relevant dimension is width, as later, heightForWidth() will be called
// 400 looks good and is ~4 knobs in a row
constexpr const static int m_minWidth = 400;
class QLineEdit* m_searchBar;
//! name of search bar, must be ASCII sorted before any alpha numerics
static constexpr const char* s_searchBarName = "!!searchBar!!";
};
#endif // CONTROLLAYOUT_H

134
include/Controls.h Normal file
View File

@@ -0,0 +1,134 @@
/*
* Controls.h - 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.
*
*/
#ifndef CONTROLS_H
#define CONTROLS_H
#include "Model.h"
// headers only required for covariance
#include "AutomatableModel.h"
#include "ComboBoxModel.h"
class QString;
class QWidget;
class AutomatableModel;
/**
These classes provide
- a control with a text label
- a type safe way to set a model
(justification: setting the wrong typed model to a widget will cause
hard-to-find runtime errors)
*/
class Control
{
public:
virtual QWidget* topWidget() = 0;
virtual void setText(const QString& text) = 0;
virtual void setModel(AutomatableModel* model) = 0;
virtual AutomatableModel* model() = 0;
virtual class AutomatableModelView* modelView() = 0;
virtual ~Control();
};
class KnobControl : public Control
{
class Knob* m_knob;
public:
void setText(const QString& text) override;
QWidget* topWidget() override;
void setModel(AutomatableModel* model) override;
FloatModel* model() override;
class AutomatableModelView* modelView() override;
KnobControl(QWidget* parent = nullptr);
~KnobControl() override;
};
class ComboControl : public Control
{
QWidget* m_widget;
class ComboBox* m_combo;
class QLabel* m_label;
public:
void setText(const QString& text) override;
QWidget* topWidget() override { return m_widget; }
void setModel(AutomatableModel* model) override;
ComboBoxModel* model() override;
class AutomatableModelView* modelView() override;
ComboControl(QWidget* parent = nullptr);
~ComboControl() override;
};
class LcdControl : public Control
{
class LcdSpinBox* m_lcd;
public:
void setText(const QString& text) override;
QWidget* topWidget() override;
void setModel(AutomatableModel* model) override;
IntModel* model() override;
class AutomatableModelView* modelView() override;
LcdControl(int numDigits, QWidget* parent = nullptr);
~LcdControl() override;
};
class CheckControl : public Control
{
QWidget* m_widget;
class LedCheckBox* m_checkBox;
QLabel* m_label;
public:
void setText(const QString& text) override;
QWidget* topWidget() override;
void setModel(AutomatableModel* model) override;
BoolModel *model() override;
class AutomatableModelView* modelView() override;
CheckControl(QWidget* parent = nullptr);
~CheckControl() override;
};
#endif // CONTROLS_H

View File

@@ -0,0 +1,105 @@
/*
* 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.
*
*/
#ifndef LINKEDMODELGROUPVIEWS_H
#define LINKEDMODELGROUPVIEWS_H
#include <cstddef>
#include <memory>
#include <vector>
#include <QWidget>
/**
@file LinkedModelGroupViews.h
See Lv2ViewBase.h for example usage
*/
/**
View for a representative processor
Features:
* Remove button for removable models
* Simple handling of adding, removing and model changing
@note Neither this class, nor any inheriting classes, shall inherit
ModelView. The "view" in the name is just for consistency
with LinkedModelGroupsView.
*/
class LinkedModelGroupView : public QWidget
{
public:
/**
@param colNum numbers of columns for the controls
(link LEDs not counted)
*/
LinkedModelGroupView(QWidget *parent, class LinkedModelGroup* model,
std::size_t colNum);
~LinkedModelGroupView();
//! Reconnect models if model changed
void modelChanged(class LinkedModelGroup *linkedModelGroup);
protected:
//! Add a control to this widget
//! @warning This widget will own this control, do not free it
void addControl(class Control *ctrl, const std::string &id,
const std::string& display, bool removable);
void removeControl(const QString &key);
private:
class LinkedModelGroup* m_model;
//! column number in surrounding grid in LinkedModelGroupsView
std::size_t m_colNum;
class ControlLayout* m_layout;
std::map<std::string, std::unique_ptr<class Control>> m_widgets;
};
/**
Container class for one LinkedModelGroupView
@note It's intended this class does not inherit from ModelView.
Inheriting classes need to do that, see e.g. Lv2Instrument.h
*/
class LinkedModelGroupsView
{
protected:
~LinkedModelGroupsView() = default;
//! Reconnect models if model changed; to be called by child virtuals
void modelChanged(class LinkedModelGroups* ctrlBase);
private:
//! The base class must return the adressed group view,
//! which has the same value as "this"
virtual LinkedModelGroupView* getGroupView() = 0;
};
#endif // LINKEDMODELGROUPVIEWS_H

175
include/LinkedModelGroups.h Normal file
View File

@@ -0,0 +1,175 @@
/*
* LinkedModelGroups.h - 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.
*
*/
#ifndef LINKEDMODELGROUPS_H
#define LINKEDMODELGROUPS_H
#include <cstddef>
#include <memory>
#include <vector>
#include "Model.h"
/**
@file LinkedModelGroups.h
See Lv2ControlBase.h and Lv2Proc.h for example usage
*/
/**
Base class for a group of linked models
See the LinkedModelGroup class for explenations
Features:
* Models are stored by their QObject::objectName
* Models are linked automatically
*/
class LinkedModelGroup : public Model
{
Q_OBJECT
public:
/*
Initialization
*/
//! @param parent model of the LinkedModelGroups class
LinkedModelGroup(Model* parent) : Model(parent) {}
/*
Linking (initially only)
*/
void linkControls(LinkedModelGroup *other);
/*
Models
*/
struct ModelInfo
{
QString m_name;
class AutomatableModel* m_model;
ModelInfo() { /* hopefully no one will use this */ } // TODO: remove?
ModelInfo(const QString& name, AutomatableModel* model)
: m_name(name), m_model(model) {}
};
// TODO: refactor those 2
template<class Functor>
void foreach_model(const Functor& ftor)
{
for (auto itr = m_models.begin(); itr != m_models.end(); ++itr)
{
ftor(itr->first, itr->second);
}
}
template<class Functor>
void foreach_model(const Functor& ftor) const
{
for (auto itr = m_models.cbegin(); itr != m_models.cend(); ++itr)
{
ftor(itr->first, itr->second);
}
}
std::size_t modelNum() const { return m_models.size(); }
bool containsModel(const QString& name) const;
void removeControl(AutomatableModel *);
/*
Load/Save
*/
void saveValues(class QDomDocument& doc, class QDomElement& that);
void loadValues(const class QDomElement& that);
signals:
// NOTE: when separating core from UI, this will need to be removed
// (who would kno if the client is Qt, i.e. it may not have slots at all)
// In this case you'd e.g. send the UI something like
// "/added <model meta info>"
void modelAdded(AutomatableModel* added);
void modelRemoved(AutomatableModel* removed);
public:
AutomatableModel* getModel(const std::string& s)
{
auto itr = m_models.find(s);
return (itr == m_models.end()) ? nullptr : itr->second.m_model;
}
//! Register a further model
void addModel(class AutomatableModel* model, const QString& name);
//! Unregister a model, return true if a model was erased
bool eraseModel(const QString& name);
//! Remove all models
void clearModels();
private:
//! models for the controls
std::map<std::string, ModelInfo> m_models;
};
/**
Container for a group of linked models
Each group contains the same models and model types. The models are
numbered, and equal numbered models are associated and always linked.
A typical application are two mono plugins making a stereo plugin.
@note Though this class can contain multiple model groups, a corresponding
view ("LinkedModelGroupViews") will only display one group, as they all have
the same values
@note Though called "container", this class does not contain, but only
know the single groups. The inheriting classes are responsible for storage.
*/
class LinkedModelGroups
{
public:
virtual ~LinkedModelGroups();
void linkAllModels();
/*
Load/Save
*/
void saveSettings(class QDomDocument& doc, class QDomElement& that);
void loadSettings(const class QDomElement& that);
/*
General
*/
//! Derived classes must return the group with index @p idx,
//! or nullptr if @p is out of range
virtual LinkedModelGroup* getGroup(std::size_t idx) = 0;
//! @see getGroup
virtual const LinkedModelGroup* getGroup(std::size_t idx) const = 0;
};
#endif // LINKEDMODELGROUPS_H

View File

@@ -21,6 +21,13 @@ std::unique_ptr<T> make_unique(Args&&... args)
{
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
//! Overload for the case a deleter should be specified
template<typename T, typename Deleter, typename... Args>
std::unique_ptr<T, Deleter> make_unique(Args&&... args)
{
return std::unique_ptr<T, Deleter>(new T(std::forward<Args>(args)...));
}
#endif
#endif // include guard

View File

@@ -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

View 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);
}
}

View File

@@ -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

View 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(); }
}

View 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() {}

View 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);
}
}