#include "StudioSetupWidget.hpp" #include #include #include #include #include #include #include #include #include #include using namespace std::literals; struct EffectIntrospection { struct Field { enum class Type { Invalid, UInt32, UInt32x8, Float, }; Type m_tp{}; std::string_view m_name; float m_min{}; float m_max{}; float m_default{}; }; amuse::EffectType m_tp; std::string_view m_name; std::string_view m_description; std::array m_fields; }; namespace { constexpr EffectIntrospection ReverbStdIntrospective = { amuse::EffectType::ReverbStd, "Reverb Std"sv, "Standard Reverb"sv, { {{EffectIntrospection::Field::Type::Float, "Coloration"sv, 0.f, 1.f, 0.f}, {EffectIntrospection::Field::Type::Float, "Mix"sv, 0.f, 1.f, 0.f}, {EffectIntrospection::Field::Type::Float, "Time"sv, 0.01f, 10.f, 0.01f}, {EffectIntrospection::Field::Type::Float, "Damping"sv, 0.f, 1.f, 0.f}, {EffectIntrospection::Field::Type::Float, "Pre Delay"sv, 0.f, 0.1f, 0.f}}, }, }; using ReverbStdGetFunc = float (amuse::EffectReverbStd::*)() const; using ReverbStdSetFunc = void (amuse::EffectReverbStd::*)(float); constexpr std::array ReverbStdGetters{ &amuse::EffectReverbStd::getColoration, &amuse::EffectReverbStd::getMix, &amuse::EffectReverbStd::getTime, &amuse::EffectReverbStd::getDamping, &amuse::EffectReverbStd::getPreDelay, }; constexpr std::array ReverbStdSetters{ &amuse::EffectReverbStd::setColoration, &amuse::EffectReverbStd::setMix, &amuse::EffectReverbStd::setTime, &amuse::EffectReverbStd::setDamping, &amuse::EffectReverbStd::setPreDelay, }; constexpr EffectIntrospection ReverbHiIntrospective = { amuse::EffectType::ReverbHi, "Reverb Hi"sv, "High Reverb"sv, { {{EffectIntrospection::Field::Type::Float, "Coloration"sv, 0.f, 1.f, 0.f}, {EffectIntrospection::Field::Type::Float, "Mix"sv, 0.f, 1.f, 0.f}, {EffectIntrospection::Field::Type::Float, "Time"sv, 0.01f, 10.f, 0.01f}, {EffectIntrospection::Field::Type::Float, "Damping"sv, 0.f, 1.f, 0.f}, {EffectIntrospection::Field::Type::Float, "Pre Delay"sv, 0.f, 0.1f, 0.f}, {EffectIntrospection::Field::Type::Float, "Crosstalk"sv, 0.f, 1.f, 0.f}}, }, }; using ReverbHiGetFunc = float (amuse::EffectReverbHi::*)() const; using ReverbHiSetFunc = void (amuse::EffectReverbHi::*)(float); constexpr std::array ReverbHiGetters{ &amuse::EffectReverbHi::getColoration, &amuse::EffectReverbHi::getMix, &amuse::EffectReverbHi::getTime, &amuse::EffectReverbHi::getDamping, &amuse::EffectReverbHi::getPreDelay, &amuse::EffectReverbHi::getCrosstalk, }; constexpr std::array ReverbHiSetters{ &amuse::EffectReverbHi::setColoration, &amuse::EffectReverbHi::setMix, &amuse::EffectReverbHi::setTime, &amuse::EffectReverbHi::setDamping, &amuse::EffectReverbHi::setPreDelay, &amuse::EffectReverbHi::setCrosstalk, }; constexpr EffectIntrospection DelayIntrospective = { amuse::EffectType::Delay, "Delay"sv, "Delay"sv, {{ {EffectIntrospection::Field::Type::UInt32x8, "Delay"sv, 10, 5000, 10}, {EffectIntrospection::Field::Type::UInt32x8, "Feedback"sv, 0, 100, 0}, {EffectIntrospection::Field::Type::UInt32x8, "Output"sv, 0, 100, 0}, }}, }; using DelayGetFunc = uint32_t (amuse::EffectDelay::*)(int) const; using DelaySetFunc = void (amuse::EffectDelay::*)(int, uint32_t); constexpr std::array DelayGetters{ &amuse::EffectDelay::getChanDelay, &amuse::EffectDelay::getChanFeedback, &amuse::EffectDelay::getChanOutput, }; constexpr std::array DelaySetters{ &amuse::EffectDelay::setChanDelay, &amuse::EffectDelay::setChanFeedback, &amuse::EffectDelay::setChanOutput, }; constexpr EffectIntrospection ChorusIntrospective = { amuse::EffectType::Chorus, "Chorus"sv, "Chorus"sv, {{ {EffectIntrospection::Field::Type::UInt32, "Base Delay"sv, 5, 15, 5}, {EffectIntrospection::Field::Type::UInt32, "Variation"sv, 0, 5, 0}, {EffectIntrospection::Field::Type::UInt32, "Period"sv, 500, 10000, 500}, }}, }; using ChorusGetFunc = uint32_t (amuse::EffectChorus::*)() const; using ChorusSetFunc = void (amuse::EffectChorus::*)(uint32_t); constexpr std::array ChorusGetters{ &amuse::EffectChorus::getBaseDelay, &amuse::EffectChorus::getVariation, &amuse::EffectChorus::getPeriod, }; constexpr std::array ChorusSetters{ &amuse::EffectChorus::setBaseDelay, &amuse::EffectChorus::setVariation, &amuse::EffectChorus::setPeriod, }; constexpr const EffectIntrospection* GetEffectIntrospection(amuse::EffectType type) { switch (type) { case amuse::EffectType::ReverbStd: return &ReverbStdIntrospective; case amuse::EffectType::ReverbHi: return &ReverbHiIntrospective; case amuse::EffectType::Delay: return &DelayIntrospective; case amuse::EffectType::Chorus: return &ChorusIntrospective; default: return nullptr; } } template T GetEffectParm(const amuse::EffectBaseTypeless* effect, int idx, int chanIdx) { switch (effect->Isa()) { case amuse::EffectType::ReverbStd: return (static_cast*>(effect)->*ReverbStdGetters[idx])(); case amuse::EffectType::ReverbHi: return (static_cast*>(effect)->*ReverbHiGetters[idx])(); case amuse::EffectType::Delay: return (static_cast*>(effect)->*DelayGetters[idx])(chanIdx); case amuse::EffectType::Chorus: return (static_cast*>(effect)->*ChorusGetters[idx])(); default: return 0.f; } } template void SetEffectParm(amuse::EffectBaseTypeless* effect, int idx, int chanIdx, T val) { switch (effect->Isa()) { case amuse::EffectType::ReverbStd: (static_cast*>(effect)->*ReverbStdSetters[idx])(val); break; case amuse::EffectType::ReverbHi: (static_cast*>(effect)->*ReverbHiSetters[idx])(val); break; case amuse::EffectType::Delay: (static_cast*>(effect)->*DelaySetters[idx])(chanIdx, val); break; case amuse::EffectType::Chorus: (static_cast*>(effect)->*ChorusSetters[idx])(val); break; default: break; } } constexpr int NumChanNames = 8; constexpr std::array ChanNames{ QT_TRANSLATE_NOOP("Uint32X8Popup", "Front Left"), QT_TRANSLATE_NOOP("Uint32X8Popup", "Front Right"), QT_TRANSLATE_NOOP("Uint32X8Popup", "Rear Left"), QT_TRANSLATE_NOOP("Uint32X8Popup", "Rear Right"), QT_TRANSLATE_NOOP("Uint32X8Popup", "Front Center"), QT_TRANSLATE_NOOP("Uint32X8Popup", "LFE"), QT_TRANSLATE_NOOP("Uint32X8Popup", "Side Left"), QT_TRANSLATE_NOOP("Uint32X8Popup", "Side Right"), }; constexpr std::array EffectStrings{ QT_TRANSLATE_NOOP("EffectCatalogue", "Reverb Standard"), QT_TRANSLATE_NOOP("EffectCatalogue", "Reverb High"), QT_TRANSLATE_NOOP("EffectCatalogue", "Delay"), QT_TRANSLATE_NOOP("EffectCatalogue", "Chorus"), }; constexpr std::array EffectDocStrings{ QT_TRANSLATE_NOOP("EffectCatalogue", "Reverb Standard"), QT_TRANSLATE_NOOP("EffectCatalogue", "Reverb High"), QT_TRANSLATE_NOOP("EffectCatalogue", "Delay"), QT_TRANSLATE_NOOP("EffectCatalogue", "Chorus"), }; } // Anonymous namespace Uint32X8Popup::Uint32X8Popup(int min, int max, QWidget* parent) : QFrame(parent, Qt::Popup) { setAttribute(Qt::WA_WindowPropagation); setAttribute(Qt::WA_X11NetWmWindowTypeCombo); Uint32X8Button* combo = static_cast(parent); QStyleOptionComboBox opt = combo->comboStyleOption(); setFrameStyle(combo->style()->styleHint(QStyle::SH_ComboBox_PopupFrameStyle, &opt, combo)); QGridLayout* layout = new QGridLayout; for (int i = 0; i < NumChanNames; ++i) { layout->addWidget(new QLabel(tr(ChanNames[i])), i, 0); FieldSlider* slider = new FieldSlider(this); m_sliders[i] = slider; slider->setToolTip(QStringLiteral("[%1,%2]").arg(min).arg(max)); slider->setProperty("chanIdx", i); slider->setRange(min, max); connect(slider, qOverload(&FieldSlider::valueChanged), this, &Uint32X8Popup::doValueChanged); layout->addWidget(slider, i, 1); } setLayout(layout); } Uint32X8Popup::~Uint32X8Popup() = default; void Uint32X8Popup::setValue(int chanIdx, int val) { m_sliders[chanIdx]->setValue(val); } void Uint32X8Popup::doValueChanged(int val) { FieldSlider* slider = static_cast(sender()); int chanIdx = slider->property("chanIdx").toInt(); emit valueChanged(chanIdx, val); } Uint32X8Button::Uint32X8Button(int min, int max, QWidget* parent) : QPushButton(parent), m_popup(new Uint32X8Popup(min, max, this)) { connect(this, &Uint32X8Button::pressed, this, &Uint32X8Button::onPressed); } Uint32X8Button::~Uint32X8Button() = default; void Uint32X8Button::paintEvent(QPaintEvent*) { QStylePainter painter(this); painter.setPen(palette().color(QPalette::Text)); // draw the combobox frame, focusrect and selected etc. QStyleOptionComboBox opt = comboStyleOption(); painter.drawComplexControl(QStyle::CC_ComboBox, opt); // draw the icon and text painter.drawControl(QStyle::CE_ComboBoxLabel, opt); } QStyleOptionComboBox Uint32X8Button::comboStyleOption() const { QStyleOptionComboBox opt; opt.initFrom(this); opt.editable = false; opt.frame = true; opt.currentText = tr("Channels"); return opt; } void Uint32X8Button::onPressed() { QPoint pt = parentWidget()->mapToGlobal(pos()); m_popup->move(pt.x(), pt.y()); m_popup->show(); } EffectWidget::EffectWidget(QWidget* parent, amuse::EffectBaseTypeless* effect, amuse::EffectType type) : QWidget(parent) , m_titleLabel(this) , m_deleteButton(this) , m_effect(effect) , m_introspection(GetEffectIntrospection(type)) { QFont titleFont = m_titleLabel.font(); titleFont.setWeight(QFont::Bold); m_titleLabel.setFont(titleFont); m_titleLabel.setForegroundRole(QPalette::Window); m_titleLabel.setContentsMargins(46, 0, 0, 0); m_titleLabel.setFixedHeight(20); m_numberText.setTextOption(QTextOption(Qt::AlignRight)); m_numberText.setTextWidth(25); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); m_numberFont.setWeight(QFont::Bold); m_numberFont.setStyleHint(QFont::Monospace); m_numberFont.setPointSize(m_numberFont.pointSize() * 2); setContentsMargins(QMargins()); setFixedHeight(100); QVBoxLayout* mainLayout = new QVBoxLayout; mainLayout->setContentsMargins(QMargins()); mainLayout->setSpacing(0); QHBoxLayout* headLayout = new QHBoxLayout; headLayout->setContentsMargins(QMargins()); headLayout->setSpacing(0); headLayout->addWidget(&m_titleLabel); m_deleteButton.setVisible(true); connect(&m_deleteButton, &ListingDeleteButton::clicked, this, &EffectWidget::deleteClicked); headLayout->addWidget(&m_deleteButton); mainLayout->addLayout(headLayout); mainLayout->addSpacing(8); QGridLayout* layout = new QGridLayout; layout->setSpacing(6); layout->setContentsMargins(64, 0, 12, 12); if (m_introspection) { m_titleLabel.setText(tr(m_introspection->m_name.data())); m_titleLabel.setToolTip(tr(m_introspection->m_description.data())); for (int f = 0; f < int(m_introspection->m_fields.size()); ++f) { const EffectIntrospection::Field& field = m_introspection->m_fields[f]; if (!field.m_name.empty()) { QString fieldName = tr(field.m_name.data()); layout->addWidget(new QLabel(fieldName, this), 0, f); switch (field.m_tp) { case EffectIntrospection::Field::Type::UInt32: { FieldSlider* sb = new FieldSlider(this); sb->setProperty("fieldIndex", f); sb->setRange(int(field.m_min), int(field.m_max)); sb->setToolTip(QStringLiteral("[%1,%2]").arg(int(field.m_min)).arg(int(field.m_max))); sb->setValue(GetEffectParm(m_effect, f, 0)); connect(sb, qOverload(&FieldSlider::valueChanged), this, qOverload(&EffectWidget::numChanged)); layout->addWidget(sb, 1, f); break; } case EffectIntrospection::Field::Type::UInt32x8: { Uint32X8Button* sb = new Uint32X8Button(int(field.m_min), int(field.m_max), this); sb->popup()->setProperty("fieldIndex", f); for (int i = 0; i < Uint32X8Popup::NumSliders; ++i) { sb->popup()->setValue(i, GetEffectParm(m_effect, f, i)); } connect(sb->popup(), &Uint32X8Popup::valueChanged, this, &EffectWidget::chanNumChanged); layout->addWidget(sb, 1, f); break; } case EffectIntrospection::Field::Type::Float: { FieldDoubleSlider* sb = new FieldDoubleSlider(this); sb->setProperty("fieldIndex", f); sb->setRange(field.m_min, field.m_max); sb->setToolTip(QStringLiteral("[%1,%2]").arg(field.m_min).arg(field.m_max)); sb->setValue(GetEffectParm(m_effect, f, 0)); connect(sb, qOverload(&FieldDoubleSlider::valueChanged), this, qOverload(&EffectWidget::numChanged)); layout->addWidget(sb, 1, f); break; } default: break; } } } } mainLayout->addLayout(layout); layout->setRowMinimumHeight(0, 22); layout->setRowMinimumHeight(1, 22); setLayout(mainLayout); } EffectWidget::~EffectWidget() = default; void EffectWidget::paintEvent(QPaintEvent* event) { /* Rounded frame */ QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); const std::array points{{ {1, 20}, {1, 99}, {width() - 1, 99}, {width() - 1, 1}, {20, 1}, {1, 20}, }}; painter.setBrush(palette().brush(QPalette::Window)); painter.drawPolygon(points.data(), int(points.size())); painter.setPen(QPen(QColor(127, 127, 127), 2.0)); painter.drawPolyline(points.data(), int(points.size())); const std::array headPoints{{ {1, 20}, {1, 55}, {35, 20}, {width() - 1, 20}, {width() - 1, 1}, {20, 1}, {1, 20}, }}; painter.setBrush(QColor(127, 127, 127)); painter.drawPolygon(headPoints.data(), int(headPoints.size())); painter.drawRect(17, 51, 32, 32); QTransform rotate; rotate.rotate(-45.0).translate(-15, 8); painter.setTransform(rotate); painter.setFont(m_numberFont); painter.setPen(palette().color(QPalette::Window)); painter.drawStaticText(0, 0, m_numberText); } EffectWidget::EffectWidget(QWidget* parent, amuse::EffectBaseTypeless* cmd) : EffectWidget(parent, cmd, cmd->Isa()) {} EffectWidget::EffectWidget(QWidget* parent, amuse::EffectType type) : EffectWidget(parent, nullptr, type) {} void EffectWidget::numChanged(int value) { SetEffectParm(m_effect, sender()->property("fieldIndex").toInt(), 0, value); } void EffectWidget::numChanged(double value) { SetEffectParm(m_effect, sender()->property("fieldIndex").toInt(), 0, value); } void EffectWidget::chanNumChanged(int chanIdx, int value) { SetEffectParm(m_effect, sender()->property("fieldIndex").toInt(), chanIdx, value); } void EffectWidget::deleteClicked() { if (m_index != -1) if (EffectListing* listing = getParent()) listing->deleteEffect(m_index); } EffectListing* EffectWidget::getParent() const { return qobject_cast(parentWidget()->parentWidget()); } void EffectWidget::setIndex(int index) { m_index = index; m_numberText.setText(QString::number(index)); update(); } void EffectWidgetContainer::animateOpen() { int newHeight = 200 + parentWidget()->layout()->spacing(); m_animation = new QPropertyAnimation(this, "minimumHeight"); m_animation->setDuration(abs(minimumHeight() - newHeight) * 4); m_animation->setStartValue(minimumHeight()); m_animation->setEndValue(newHeight); m_animation->setEasingCurve(QEasingCurve::InOutExpo); connect(m_animation, &QPropertyAnimation::valueChanged, parentWidget(), qOverload<>(&QWidget::update)); connect(m_animation, &QPropertyAnimation::destroyed, this, &EffectWidgetContainer::animationDestroyed); m_animation->start(QAbstractAnimation::DeleteWhenStopped); } void EffectWidgetContainer::animateClosed() { m_animation = new QPropertyAnimation(this, "minimumHeight"); m_animation->setDuration(abs(minimumHeight() - 100) * 4); m_animation->setStartValue(minimumHeight()); m_animation->setEndValue(100); m_animation->setEasingCurve(QEasingCurve::InOutExpo); connect(m_animation, &QPropertyAnimation::valueChanged, parentWidget(), qOverload<>(&QWidget::update)); connect(m_animation, &QPropertyAnimation::destroyed, this, &EffectWidgetContainer::animationDestroyed); m_animation->start(QAbstractAnimation::DeleteWhenStopped); } void EffectWidgetContainer::snapOpen() { if (m_animation) m_animation->stop(); setMinimumHeight(200 + parentWidget()->layout()->spacing()); } void EffectWidgetContainer::snapClosed() { if (m_animation) m_animation->stop(); setMinimumHeight(100); } void EffectWidgetContainer::animationDestroyed() { m_animation = nullptr; } template EffectWidgetContainer::EffectWidgetContainer(QWidget* parent, _Args&&... args) : QWidget(parent), m_effectWidget(new EffectWidget(this, std::forward<_Args>(args)...)) { setMinimumHeight(100); setContentsMargins(QMargins()); QVBoxLayout* outerLayout = new QVBoxLayout; outerLayout->setAlignment(Qt::AlignBottom); outerLayout->setContentsMargins(QMargins()); outerLayout->setSpacing(0); outerLayout->addWidget(m_effectWidget); setLayout(outerLayout); } EffectWidgetContainer::~EffectWidgetContainer() = default; void EffectListing::startAutoscroll(QWidget* source, QMouseEvent* event, int delta) { if (m_autoscrollTimer == -1) m_autoscrollTimer = startTimer(50); m_autoscrollDelta = delta; m_autoscrollSource = source; m_autoscrollEvent = *event; } void EffectListing::stopAutoscroll() { if (m_autoscrollTimer != -1) { killTimer(m_autoscrollTimer); m_autoscrollTimer = -1; } m_autoscrollDelta = 0; m_autoscrollSource = nullptr; } void EffectListing::timerEvent(QTimerEvent* event) { if (QScrollArea* scrollArea = qobject_cast(parentWidget()->parentWidget())) { QScrollBar* bar = scrollArea->verticalScrollBar(); int oldValue = bar->value(); bar->setValue(oldValue + m_autoscrollDelta); int valueDelta = bar->value() - oldValue; if (valueDelta != 0) { if (m_autoscrollSource) QApplication::sendEvent(m_autoscrollSource, &m_autoscrollEvent); update(); } } } bool EffectListing::beginDrag(EffectWidget* widget) { int origIdx = m_layout->indexOf(widget->parentWidget()); if (origIdx < 0 || origIdx >= m_layout->count() - 1) return false; if (origIdx < m_layout->count() - 1) { // Animate next item open m_dragOpenIdx = origIdx; if (EffectWidgetContainer* nextItem = qobject_cast(m_layout->itemAt(origIdx + 1)->widget())) nextItem->snapOpen(); } else { m_dragOpenIdx = -1; } m_origIdx = origIdx; m_dragItem = m_layout->takeAt(origIdx); m_dragItem->widget()->raise(); return true; } void EffectListing::endDrag() { int insertIdx; if (m_dragOpenIdx != -1) { if (EffectWidgetContainer* prevItem = qobject_cast(m_layout->itemAt(m_dragOpenIdx)->widget())) prevItem->snapClosed(); insertIdx = m_dragOpenIdx; m_dragOpenIdx = -1; } else { insertIdx = m_layout->count() - 1; } if (m_prevDragOpen) { m_prevDragOpen->snapClosed(); m_prevDragOpen = nullptr; } if (m_origIdx != insertIdx) std::swap(m_submix->getEffectStack()[m_origIdx], m_submix->getEffectStack()[insertIdx]); m_layout->insertItem(insertIdx, m_dragItem); m_dragItem = nullptr; stopAutoscroll(); reindex(); } void EffectListing::cancelDrag() { if (m_dragOpenIdx != -1) { if (EffectWidgetContainer* prevItem = qobject_cast(m_layout->itemAt(m_dragOpenIdx)->widget())) prevItem->snapClosed(); m_dragOpenIdx = -1; } m_layout->insertItem(m_origIdx, m_dragItem); if (m_prevDragOpen) { m_prevDragOpen->snapClosed(); m_prevDragOpen = nullptr; } m_dragItem = nullptr; stopAutoscroll(); } void EffectListing::_moveDrag(int hoverIdx, const QPoint& pt, QWidget* source, QMouseEvent* event) { QRect scrollVpRect = parentWidget()->parentWidget()->rect(); QPoint scrollVpPoint = mapTo(parentWidget()->parentWidget(), pt); if (scrollVpRect.bottom() - scrollVpPoint.y() < 50) startAutoscroll(source, event, 10); // Scroll Down else if (scrollVpRect.top() - scrollVpPoint.y() > -50) startAutoscroll(source, event, -10); else stopAutoscroll(); hoverIdx = std::max(0, std::min(hoverIdx, m_layout->count() - 1)); if (hoverIdx != m_dragOpenIdx) { if (m_dragOpenIdx != -1) if (EffectWidgetContainer* prevItem = qobject_cast(m_layout->itemAt(m_dragOpenIdx)->widget())) { m_prevDragOpen = prevItem; prevItem->animateClosed(); } if (EffectWidgetContainer* nextItem = qobject_cast(m_layout->itemAt(hoverIdx)->widget())) nextItem->animateOpen(); m_dragOpenIdx = hoverIdx; } update(); } void EffectListing::moveDrag(EffectWidget* widget, const QPoint& pt, QWidget* source, QMouseEvent* event) { EffectWidgetContainer* container = static_cast(widget->parentWidget()); int pitch = 100 + m_layout->spacing(); _moveDrag((container->pos().y() - m_layout->contentsMargins().top() + pitch / 2) / pitch, pt, source, event); } int EffectListing::moveInsertDrag(const QPoint& pt, QWidget* source, QMouseEvent* event) { int pitch = 100 + m_layout->spacing(); _moveDrag(pt.y() / pitch, pt, source, event); return m_dragOpenIdx; } void EffectListing::insertDragout() { if (m_dragOpenIdx != -1) { if (EffectWidgetContainer* prevItem = qobject_cast(m_layout->itemAt(m_dragOpenIdx)->widget())) { m_prevDragOpen = prevItem; prevItem->animateClosed(); } m_dragOpenIdx = -1; } stopAutoscroll(); } void EffectListing::insert(amuse::EffectType type, const QString& text) { int insertIdx; if (m_dragOpenIdx != -1) { if (EffectWidgetContainer* prevItem = qobject_cast(m_layout->itemAt(m_dragOpenIdx)->widget())) prevItem->snapClosed(); insertIdx = m_dragOpenIdx; m_dragOpenIdx = -1; } else { insertIdx = m_layout->count() - 1; } if (m_prevDragOpen) { m_prevDragOpen->snapClosed(); m_prevDragOpen = nullptr; } std::unique_ptr newEffect; switch (type) { case amuse::EffectType::ReverbStd: newEffect = m_submix->_makeEffect(amuse::EffectReverbStdInfo{}); break; case amuse::EffectType::ReverbHi: newEffect = m_submix->_makeEffect(amuse::EffectReverbHiInfo{}); break; case amuse::EffectType::Delay: newEffect = m_submix->_makeEffect(amuse::EffectDelayInfo{}); break; case amuse::EffectType::Chorus: newEffect = m_submix->_makeEffect(amuse::EffectChorusInfo{}); break; default: break; } auto it = m_submix->getEffectStack().insert(m_submix->getEffectStack().begin() + insertIdx, std::move(newEffect)); m_layout->insertWidget(insertIdx, new EffectWidgetContainer(this, it->get())); stopAutoscroll(); reindex(); } void EffectListing::deleteEffect(int index) { QLayoutItem* item = m_layout->takeAt(index); m_submix->getEffectStack().erase(m_submix->getEffectStack().begin() + index); item->widget()->deleteLater(); delete item; reindex(); } void EffectListing::reindex() { for (int i = 0; i < m_layout->count() - 1; ++i) if (EffectWidgetContainer* item = qobject_cast(m_layout->itemAt(i)->widget())) item->m_effectWidget->setIndex(i); } void EffectListing::clear() { while (m_layout->count() > 2) { QLayoutItem* item = m_layout->takeAt(0); item->widget()->deleteLater(); delete item; } } bool EffectListing::loadData(amuse::Submix* submix) { m_submix = submix; clear(); int i = 0; for (auto& effect : submix->getEffectStack()) m_layout->insertWidget(i++, new EffectWidgetContainer(this, effect.get())); reindex(); update(); return true; } void EffectListing::unloadData() { m_submix = nullptr; clear(); reindex(); update(); } EffectListing::EffectListing(QWidget* parent) : QWidget(parent), m_layout(new QVBoxLayout) { m_layout->addStretch(); setLayout(m_layout); reindex(); } EffectListing::~EffectListing() = default; EffectCatalogueItem::EffectCatalogueItem(amuse::EffectType type, const QString& name, const QString& doc, QWidget* parent) : QWidget(parent), m_type(type), m_iconLab(this), m_label(name, this) { QHBoxLayout* layout = new QHBoxLayout; QString iconPath = QStringLiteral(":/commands/%1.svg").arg(name); if (QFile(iconPath).exists()) m_iconLab.setPixmap(QIcon(iconPath).pixmap(32, 32)); else m_iconLab.setPixmap(QIcon(QStringLiteral(":/icons/IconOpen.svg")).pixmap(32, 32)); layout->addWidget(&m_iconLab); layout->addWidget(&m_label); layout->addStretch(); layout->setContentsMargins(QMargins()); setLayout(layout); setToolTip(doc); } EffectCatalogueItem::EffectCatalogueItem(const EffectCatalogueItem& other, QWidget* parent) : QWidget(parent), m_type(other.getType()) { QHBoxLayout* layout = new QHBoxLayout; QHBoxLayout* oldLayout = static_cast(other.layout()); m_iconLab.setPixmap(*static_cast(oldLayout->itemAt(0)->widget())->pixmap()); layout->addWidget(&m_iconLab); m_label.setText(static_cast(oldLayout->itemAt(1)->widget())->text()); layout->addWidget(&m_label); layout->addStretch(); layout->setContentsMargins(QMargins()); setLayout(layout); } EffectCatalogueItem::~EffectCatalogueItem() = default; EffectCatalogue::EffectCatalogue(QWidget* parent) : QTreeWidget(parent) { setSelectionMode(QAbstractItemView::NoSelection); setColumnCount(1); setHeaderHidden(true); for (int i = 1; i < int(amuse::EffectType::EffectTypeMAX); ++i) { QTreeWidgetItem* item = new QTreeWidgetItem(this); setItemWidget( item, 0, new EffectCatalogueItem(amuse::EffectType(i), tr(EffectStrings[i - 1]), tr(EffectDocStrings[i - 1]), this)); } } void EffectCatalogue::mousePressEvent(QMouseEvent* event) { QTreeWidget::mousePressEvent(event); event->ignore(); } void EffectCatalogue::mouseReleaseEvent(QMouseEvent* event) { QTreeWidget::mouseReleaseEvent(event); event->ignore(); } void EffectCatalogue::mouseMoveEvent(QMouseEvent* event) { StudioSetupWidget* editor = qobject_cast(parentWidget()->parentWidget()->parentWidget()); if (!editor || !editor->m_draggedItem) QTreeWidget::mouseMoveEvent(event); event->ignore(); } EffectListing* StudioSetupWidget::getCurrentListing() const { return static_cast(static_cast(m_tabs->currentWidget())->widget()); } void StudioSetupWidget::beginCommandDrag(EffectWidget* widget, const QPoint& eventPt, const QPoint& pt) { if (widget->getParent()->beginDrag(widget)) { m_draggedPt = pt; m_draggedCmd = widget; } } void StudioSetupWidget::beginCatalogueDrag(EffectCatalogueItem* item, const QPoint& eventPt, const QPoint& pt) { m_draggedPt = pt; m_draggedItem = new EffectCatalogueItem(*item, this); m_draggedItem->setGeometry(item->geometry()); m_draggedItem->move(eventPt - m_draggedPt); m_draggedItem->raise(); m_draggedItem->show(); } void StudioSetupWidget::mousePressEvent(QMouseEvent* event) { if (m_catalogue->geometry().contains(event->pos())) { QPoint fromParent1 = m_catalogue->mapFrom(this, event->pos()); QWidget* ch = m_catalogue->childAt(fromParent1); if (ch) { EffectCatalogueItem* child = nullptr; while (ch && !(child = qobject_cast(ch))) ch = ch->parentWidget(); if (child) { QPoint fromParent2 = child->mapFrom(m_catalogue, fromParent1); beginCatalogueDrag(child, event->pos(), fromParent2); } } } else { EffectListing* listing = getCurrentListing(); if (listing->parentWidget()->parentWidget()->parentWidget()->geometry().contains(event->pos())) { QPoint fromParent1 = listing->mapFrom(this, event->pos()); QWidget* ch = listing->childAt(fromParent1); if (ch) { EffectWidget* child = nullptr; while (ch && !(child = qobject_cast(ch))) ch = ch->parentWidget(); if (child) { QPoint fromParent2 = child->mapFrom(listing, fromParent1); beginCommandDrag(child, event->pos(), fromParent2); } } } } } void StudioSetupWidget::mouseReleaseEvent(QMouseEvent* event) { EffectListing* listing = getCurrentListing(); if (m_draggedItem) { amuse::EffectType type = m_draggedItem->getType(); QString text = m_draggedItem->getText(); m_draggedItem->deleteLater(); m_draggedItem = nullptr; if (listing->parentWidget()->parentWidget()->parentWidget()->geometry().contains(event->pos())) { if (m_dragInsertIdx != -1) listing->insert(type, text); else listing->insertDragout(); } else { listing->insertDragout(); } m_dragInsertIdx = -1; } else if (m_draggedCmd) { listing->endDrag(); m_draggedCmd = nullptr; } } void StudioSetupWidget::mouseMoveEvent(QMouseEvent* event) { EffectListing* listing = getCurrentListing(); if (m_draggedItem) { m_draggedItem->move(event->pos() - m_draggedPt); if (listing->parentWidget()->parentWidget()->parentWidget()->geometry().contains(event->pos())) { m_dragInsertIdx = listing->moveInsertDrag(listing->mapFrom(this, event->pos()), this, event); } else if (m_dragInsertIdx != -1) { listing->insertDragout(); m_dragInsertIdx = -1; } m_catalogue->update(); update(); } else if (m_draggedCmd) { QPoint listingPt = listing->mapFrom(this, event->pos()); EffectWidgetContainer* container = static_cast(m_draggedCmd->parentWidget()); container->move(container->x(), listingPt.y() - m_draggedPt.y()); if (listing->parentWidget()->parentWidget()->parentWidget()->geometry().contains(event->pos())) listing->moveDrag(m_draggedCmd, listingPt, this, event); listing->update(); update(); } } void StudioSetupWidget::keyPressEvent(QKeyEvent* event) { if (event->key() == Qt::Key_Escape) { EffectListing* listing = getCurrentListing(); if (m_draggedItem) { m_draggedItem->deleteLater(); m_draggedItem = nullptr; listing->insertDragout(); } else if (m_draggedCmd) { listing->cancelDrag(); m_draggedCmd = nullptr; } } } void StudioSetupWidget::hideEvent(QHideEvent* event) { emit hidden(); } void StudioSetupWidget::showEvent(QShowEvent* event) { emit shown(); } void StudioSetupWidget::updateWindowPosition() { QWidget* parent = parentWidget(); move(parent->width() / 2 - width() / 2 + parent->x(), parent->height() / 2 - height() / 2 + parent->y()); } void StudioSetupWidget::catalogueDoubleClicked(QTreeWidgetItem* item, int column) { if (EffectCatalogueItem* cItem = qobject_cast(m_catalogue->itemWidget(item, column))) { amuse::EffectType type = cItem->getType(); if (type != amuse::EffectType::Invalid) { EffectListing* listing = getCurrentListing(); listing->insert(type, cItem->getText()); } } } bool StudioSetupWidget::loadData(amuse::Studio* studio) { m_listing[0]->loadData(&studio->getAuxA()); m_listing[1]->loadData(&studio->getAuxB()); return true; } void StudioSetupWidget::unloadData() { m_listing[0]->unloadData(); m_listing[1]->unloadData(); } StudioSetupWidget::StudioSetupWidget(QWidget* parent) : QWidget(parent, Qt::Tool), m_splitter(new QSplitter), m_tabs(new QTabWidget), m_catalogue(new EffectCatalogue) { setWindowTitle(tr("Studio Setup")); setGeometry(0, 0, 900, 450); std::array scrollAreas{}; for (size_t i = 0; i < m_listing.size(); ++i) { m_listing[i] = new EffectListing; QScrollArea* listingScroll = new QScrollArea; scrollAreas[i] = listingScroll; listingScroll->setWidget(m_listing[i]); listingScroll->setWidgetResizable(true); QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); sizePolicy.setHorizontalStretch(1); sizePolicy.setVerticalStretch(0); listingScroll->setSizePolicy(sizePolicy); listingScroll->setMinimumWidth(350); m_splitter->addWidget(listingScroll); } m_tabs->addTab(scrollAreas[0], tr("Aux A")); m_tabs->addTab(scrollAreas[1], tr("Aux B")); m_splitter->addWidget(m_tabs); { QSizePolicy sizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); sizePolicy.setHorizontalStretch(0); sizePolicy.setVerticalStretch(0); m_catalogue->setSizePolicy(sizePolicy); m_catalogue->setMinimumWidth(150); m_catalogue->setGeometry(0, 0, 215, 0); m_catalogue->setMaximumWidth(300); m_splitter->addWidget(m_catalogue); } connect(m_catalogue, &EffectCatalogue::itemDoubleClicked, this, &StudioSetupWidget::catalogueDoubleClicked); m_splitter->setCollapsible(0, false); QGridLayout* layout = new QGridLayout; layout->setContentsMargins(QMargins()); layout->addWidget(m_splitter); setLayout(layout); } StudioSetupWidget::~StudioSetupWidget() = default;