#include "SoundMacroEditor.hpp" #include "MainWindow.hpp" #include #include #include #include #include #include #include #include #include TargetButton::TargetButton(QWidget* parent) : QPushButton(parent) { QIcon targetIcon(QStringLiteral(":/icons/IconSoundMacroTarget.svg")); targetIcon.addFile(QStringLiteral(":/icons/IconSoundMacroTargetDisabled.svg"), QSize(), QIcon::Disabled); setIcon(targetIcon); setToolTip(tr("Set step with target click")); setFixedSize(29, 29); } SoundMacroEditor* FieldSoundMacroStep::getEditor() const { return qobject_cast( parentWidget()->parentWidget()->parentWidget()->parentWidget()->parentWidget()->parentWidget()->parentWidget()); } SoundMacroListing* FieldSoundMacroStep::getListing() const { return qobject_cast(parentWidget()->parentWidget()->parentWidget()); } void FieldSoundMacroStep::targetPressed() { ProjectModel::SoundMacroNode* node = nullptr; if (m_macroField) node = static_cast(m_macroField->currentNode()); if (!m_macroField || node == getListing()->currentNode()) if (SoundMacroEditor* editor = getEditor()) editor->beginStepTarget(this); } void FieldSoundMacroStep::updateMacroField() { if (!m_macroField) { int numCmds = int(static_cast(getListing()->currentNode())->m_obj->m_cmds.size()); m_spinBox.setMaximum(numCmds - 1); m_spinBox.setDisabled(false); m_targetButton.setDisabled(false); return; } ProjectModel::SoundMacroNode* node = static_cast(m_macroField->currentNode()); if (node == nullptr) { m_spinBox.setValue(0); m_spinBox.setDisabled(true); m_targetButton.setDisabled(true); } else { int numCmds = int(node->m_obj->m_cmds.size()); m_spinBox.setMaximum(numCmds - 1); m_spinBox.setDisabled(false); m_targetButton.setEnabled(node == getListing()->currentNode()); } } void FieldSoundMacroStep::setIndex(int index) { m_targetButton.setDown(false); m_spinBox.setValue(index); if (SoundMacroEditor* editor = getEditor()) editor->endStepTarget(); } void FieldSoundMacroStep::cancel() { m_targetButton.setDown(false); if (SoundMacroEditor* editor = getEditor()) editor->endStepTarget(); } FieldSoundMacroStep::~FieldSoundMacroStep() { if (SoundMacroEditor* editor = getEditor()) if (editor->m_targetField == this) editor->endStepTarget(); } FieldSoundMacroStep::FieldSoundMacroStep(FieldProjectNode* macroField, QWidget* parent) : QWidget(parent), m_macroField(macroField), m_spinBox(this), m_targetButton(this) { QHBoxLayout* layout = new QHBoxLayout; layout->setContentsMargins(QMargins()); layout->setSpacing(0); layout->addWidget(&m_spinBox); layout->addWidget(&m_targetButton); m_spinBox.setMinimum(0); m_spinBox.setDisabled(true); m_targetButton.setDisabled(true); connect(&m_spinBox, qOverload(&QSpinBox::valueChanged), this, &FieldSoundMacroStep::valueChanged); connect(&m_targetButton, &QPushButton::pressed, this, &FieldSoundMacroStep::targetPressed); if (macroField) { connect(macroField, &FieldProjectNode::currentIndexChanged, this, &FieldSoundMacroStep::updateMacroField); } setLayout(layout); } CommandWidget::CommandWidget(QWidget* parent, amuse::SoundMacro::ICmd* cmd, amuse::SoundMacro::CmdOp op, SoundMacroListing* listing) : QWidget(parent) , m_titleLabel(this) , m_deleteButton(this) , m_cmd(cmd) , m_introspection(amuse::SoundMacro::GetCmdIntrospection(op)) { 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); if (op != amuse::SoundMacro::CmdOp::End) { m_deleteButton.setVisible(true); connect(&m_deleteButton, &ListingDeleteButton::clicked, this, &CommandWidget::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())); FieldProjectNode* nf = nullptr; for (int f = 0; f < 7; ++f) { const amuse::SoundMacro::CmdIntrospection::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 amuse::SoundMacro::CmdIntrospection::Field::Type::Bool: { QCheckBox* cb = new QCheckBox(this); cb->setProperty("fieldIndex", f); cb->setProperty("fieldName", fieldName); cb->setCheckState(amuse::AccessField(m_cmd, field) ? Qt::Checked : Qt::Unchecked); connect(cb, qOverload(&QCheckBox::stateChanged), this, &CommandWidget::boolChanged); layout->addWidget(cb, 1, f); break; } case amuse::SoundMacro::CmdIntrospection::Field::Type::Int8: case amuse::SoundMacro::CmdIntrospection::Field::Type::UInt8: case amuse::SoundMacro::CmdIntrospection::Field::Type::Int16: case amuse::SoundMacro::CmdIntrospection::Field::Type::UInt16: case amuse::SoundMacro::CmdIntrospection::Field::Type::Int32: case amuse::SoundMacro::CmdIntrospection::Field::Type::UInt32: { FieldSpinBox* sb = new FieldSpinBox(this); sb->setFixedHeight(30); sb->setProperty("fieldIndex", f); sb->setProperty("fieldName", fieldName); sb->setMinimum(int(field.m_min)); sb->setMaximum(int(field.m_max)); sb->setToolTip(QStringLiteral("[%1,%2]").arg(int(field.m_min)).arg(int(field.m_max))); switch (field.m_tp) { case amuse::SoundMacro::CmdIntrospection::Field::Type::Int8: sb->setValue(amuse::AccessField(m_cmd, field)); break; case amuse::SoundMacro::CmdIntrospection::Field::Type::UInt8: sb->setValue(amuse::AccessField(m_cmd, field)); break; case amuse::SoundMacro::CmdIntrospection::Field::Type::Int16: sb->setValue(amuse::AccessField(m_cmd, field)); break; case amuse::SoundMacro::CmdIntrospection::Field::Type::UInt16: sb->setValue(amuse::AccessField(m_cmd, field)); break; case amuse::SoundMacro::CmdIntrospection::Field::Type::Int32: sb->setValue(amuse::AccessField(m_cmd, field)); break; case amuse::SoundMacro::CmdIntrospection::Field::Type::UInt32: sb->setValue(amuse::AccessField(m_cmd, field)); break; default: break; } connect(sb, qOverload(&FieldSpinBox::valueChanged), this, &CommandWidget::numChanged); layout->addWidget(sb, 1, f); break; } case amuse::SoundMacro::CmdIntrospection::Field::Type::SoundMacroId: case amuse::SoundMacro::CmdIntrospection::Field::Type::TableId: case amuse::SoundMacro::CmdIntrospection::Field::Type::SampleId: { ProjectModel::INode::Type collectionType = ProjectModel::INode::Type::Null; if (field.m_tp == amuse::SoundMacro::CmdIntrospection::Field::Type::SoundMacroId) { collectionType = ProjectModel::INode::Type::SoundMacro; } else if (field.m_tp == amuse::SoundMacro::CmdIntrospection::Field::Type::TableId) { if (!field.m_name.compare("ADSR")) collectionType = ProjectModel::INode::Type::ADSR; else collectionType = ProjectModel::INode::Type::Curve; } else if (field.m_tp == amuse::SoundMacro::CmdIntrospection::Field::Type::SampleId) { collectionType = ProjectModel::INode::Type::Sample; } auto* collection = g_MainWindow->projectModel()->getGroupNode(listing->currentNode())->getCollectionOfType(collectionType); nf = new FieldProjectNode(collection, this); nf->setProperty("fieldIndex", f); nf->setProperty("fieldName", fieldName); int index = collection->indexOfId(amuse::AccessField>(m_cmd, field).id); nf->setCurrentIndex(index < 0 ? 0 : index + 1); connect(nf, &FieldProjectNode::currentIndexChanged, this, &CommandWidget::nodeChanged); layout->addWidget(nf, 1, f); break; } case amuse::SoundMacro::CmdIntrospection::Field::Type::SoundMacroStep: { FieldSoundMacroStep* sb = new FieldSoundMacroStep(nf, this); sb->setFixedHeight(30); sb->setProperty("fieldIndex", f); sb->setProperty("fieldName", fieldName); sb->m_spinBox.setValue(amuse::AccessField>(m_cmd, field).step); connect(sb, &FieldSoundMacroStep::valueChanged, this, &CommandWidget::numChanged); layout->addWidget(sb, 1, f); m_stepField = sb; break; } case amuse::SoundMacro::CmdIntrospection::Field::Type::Choice: { FieldComboBox* cb = new FieldComboBox(this); cb->setFixedHeight(30); cb->setProperty("fieldIndex", f); cb->setProperty("fieldName", fieldName); for (int j = 0; j < 4; ++j) { if (field.m_choices[j].empty()) break; cb->addItem(tr(field.m_choices[j].data())); } cb->setCurrentIndex(int(amuse::AccessField(m_cmd, field))); connect(cb, qOverload(&FieldComboBox::currentIndexChanged), this, &CommandWidget::numChanged); layout->addWidget(cb, 1, f); break; } default: break; } } } } mainLayout->addLayout(layout); layout->setRowMinimumHeight(0, 22); layout->setRowMinimumHeight(1, 37); setLayout(mainLayout); } CommandWidget::CommandWidget(QWidget* parent, amuse::SoundMacro::ICmd* cmd, SoundMacroListing* listing) : CommandWidget(parent, cmd, cmd->Isa(), listing) {} CommandWidget::CommandWidget(QWidget* parent, amuse::SoundMacro::CmdOp op, SoundMacroListing* listing) : CommandWidget(parent, nullptr, op, listing) {} class ValChangedUndoCommand : public EditorUndoCommand { amuse::SoundMacro::ICmd* m_cmd; const amuse::SoundMacro::CmdIntrospection::Field& m_field; int m_redoVal, m_undoVal; bool m_undid = false; public: ValChangedUndoCommand(amuse::SoundMacro::ICmd* cmd, const QString& fieldName, const amuse::SoundMacro::CmdIntrospection::Field& field, int redoVal, amuse::ObjToken node) : EditorUndoCommand(node.get(), CommandWidget::tr("Change %1").arg(fieldName)) , m_cmd(cmd) , m_field(field) , m_redoVal(redoVal) {} void undo() override { m_undid = true; switch (m_field.m_tp) { case amuse::SoundMacro::CmdIntrospection::Field::Type::Bool: amuse::AccessField(m_cmd, m_field) = bool(m_undoVal); break; case amuse::SoundMacro::CmdIntrospection::Field::Type::Int8: case amuse::SoundMacro::CmdIntrospection::Field::Type::Choice: amuse::AccessField(m_cmd, m_field) = int8_t(m_undoVal); break; case amuse::SoundMacro::CmdIntrospection::Field::Type::UInt8: amuse::AccessField(m_cmd, m_field) = uint8_t(m_undoVal); break; case amuse::SoundMacro::CmdIntrospection::Field::Type::Int16: amuse::AccessField(m_cmd, m_field) = int16_t(m_undoVal); break; case amuse::SoundMacro::CmdIntrospection::Field::Type::UInt16: amuse::AccessField(m_cmd, m_field) = uint16_t(m_undoVal); break; case amuse::SoundMacro::CmdIntrospection::Field::Type::Int32: amuse::AccessField(m_cmd, m_field) = int32_t(m_undoVal); break; case amuse::SoundMacro::CmdIntrospection::Field::Type::UInt32: amuse::AccessField(m_cmd, m_field) = uint32_t(m_undoVal); break; case amuse::SoundMacro::CmdIntrospection::Field::Type::SoundMacroId: case amuse::SoundMacro::CmdIntrospection::Field::Type::SoundMacroStep: case amuse::SoundMacro::CmdIntrospection::Field::Type::TableId: case amuse::SoundMacro::CmdIntrospection::Field::Type::SampleId: amuse::AccessField>(m_cmd, m_field).id = uint16_t(m_undoVal); break; default: break; } EditorUndoCommand::undo(); } void redo() override { switch (m_field.m_tp) { case amuse::SoundMacro::CmdIntrospection::Field::Type::Bool: m_undoVal = amuse::AccessField(m_cmd, m_field); amuse::AccessField(m_cmd, m_field) = bool(m_redoVal); break; case amuse::SoundMacro::CmdIntrospection::Field::Type::Int8: case amuse::SoundMacro::CmdIntrospection::Field::Type::Choice: m_undoVal = amuse::AccessField(m_cmd, m_field); amuse::AccessField(m_cmd, m_field) = int8_t(m_redoVal); break; case amuse::SoundMacro::CmdIntrospection::Field::Type::UInt8: m_undoVal = amuse::AccessField(m_cmd, m_field); amuse::AccessField(m_cmd, m_field) = uint8_t(m_redoVal); break; case amuse::SoundMacro::CmdIntrospection::Field::Type::Int16: m_undoVal = amuse::AccessField(m_cmd, m_field); amuse::AccessField(m_cmd, m_field) = int16_t(m_redoVal); break; case amuse::SoundMacro::CmdIntrospection::Field::Type::UInt16: m_undoVal = amuse::AccessField(m_cmd, m_field); amuse::AccessField(m_cmd, m_field) = uint16_t(m_redoVal); break; case amuse::SoundMacro::CmdIntrospection::Field::Type::Int32: m_undoVal = amuse::AccessField(m_cmd, m_field); amuse::AccessField(m_cmd, m_field) = int32_t(m_redoVal); break; case amuse::SoundMacro::CmdIntrospection::Field::Type::UInt32: m_undoVal = amuse::AccessField(m_cmd, m_field); amuse::AccessField(m_cmd, m_field) = uint32_t(m_redoVal); break; case amuse::SoundMacro::CmdIntrospection::Field::Type::SoundMacroId: case amuse::SoundMacro::CmdIntrospection::Field::Type::SoundMacroStep: case amuse::SoundMacro::CmdIntrospection::Field::Type::TableId: case amuse::SoundMacro::CmdIntrospection::Field::Type::SampleId: m_undoVal = amuse::AccessField>(m_cmd, m_field).id.id; amuse::AccessField>(m_cmd, m_field).id = uint16_t(m_redoVal); break; default: break; } if (m_undid) EditorUndoCommand::redo(); } bool mergeWith(const QUndoCommand* other) override { if (other->id() == id() && m_cmd == static_cast(other)->m_cmd && &m_field == &static_cast(other)->m_field) { m_redoVal = static_cast(other)->m_redoVal; return true; } return false; } int id() const override { return int(Id::SMChangeVal); } }; void CommandWidget::boolChanged(int state) { if (m_introspection) { const amuse::SoundMacro::CmdIntrospection::Field& field = m_introspection->m_fields[sender()->property("fieldIndex").toInt()]; g_MainWindow->pushUndoCommand(new ValChangedUndoCommand(m_cmd, sender()->property("fieldName").toString(), field, state == Qt::Checked, getParent()->m_node)); } } void CommandWidget::numChanged(int value) { if (value < 0) return; if (m_introspection) { const amuse::SoundMacro::CmdIntrospection::Field& field = m_introspection->m_fields[sender()->property("fieldIndex").toInt()]; g_MainWindow->pushUndoCommand(new ValChangedUndoCommand(m_cmd, sender()->property("fieldName").toString(), field, value, getParent()->m_node)); } } void CommandWidget::nodeChanged(int value) { if (value < 0) return; if (m_introspection) { FieldProjectNode* fieldW = static_cast(sender()); int v = value == 0 ? 65535 : fieldW->collection()->idOfIndex(value - 1).id; const amuse::SoundMacro::CmdIntrospection::Field& field = m_introspection->m_fields[fieldW->property("fieldIndex").toInt()]; g_MainWindow->pushUndoCommand( new ValChangedUndoCommand(m_cmd, fieldW->property("fieldName").toString(), field, v, getParent()->m_node)); } } void CommandWidget::deleteClicked() { if (m_index != -1) if (SoundMacroListing* listing = qobject_cast(parentWidget()->parentWidget())) listing->deleteCommand(m_index); } void CommandWidget::setIndex(int index) { m_index = index; m_numberText.setText(QString::number(index)); if (m_stepField) m_stepField->updateMacroField(); update(); } SoundMacroListing* CommandWidget::getParent() const { return qobject_cast(parentWidget()->parentWidget()); } void CommandWidget::paintEvent(QPaintEvent* event) { /* Rounded frame */ QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); QPoint points[] = { {1, 20}, {1, 99}, {width() - 1, 99}, {width() - 1, 1}, {20, 1}, {1, 20}, }; painter.setBrush(palette().brush(QPalette::Window)); painter.drawPolygon(points, 6); painter.setPen(QPen(QColor(127, 127, 127), 2.0)); painter.drawPolyline(points, 6); QPoint 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, 7); 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); } void CommandWidgetContainer::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, &CommandWidgetContainer::animationDestroyed); m_animation->start(QAbstractAnimation::DeleteWhenStopped); } void CommandWidgetContainer::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, &CommandWidgetContainer::animationDestroyed); m_animation->start(QAbstractAnimation::DeleteWhenStopped); } void CommandWidgetContainer::snapOpen() { if (m_animation) m_animation->stop(); setMinimumHeight(200 + parentWidget()->layout()->spacing()); } void CommandWidgetContainer::snapClosed() { if (m_animation) m_animation->stop(); setMinimumHeight(100); } void CommandWidgetContainer::animationDestroyed() { m_animation = nullptr; } template CommandWidgetContainer::CommandWidgetContainer(QWidget* parent, _Args&&... args) : QWidget(parent), m_commandWidget(new CommandWidget(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_commandWidget); setLayout(outerLayout); } void SoundMacroListing::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 SoundMacroListing::stopAutoscroll() { if (m_autoscrollTimer != -1) { killTimer(m_autoscrollTimer); m_autoscrollTimer = -1; } m_autoscrollDelta = 0; m_autoscrollSource = nullptr; } void SoundMacroListing::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 SoundMacroListing::beginDrag(CommandWidget* widget) { int origIdx = m_layout->indexOf(widget->parentWidget()); /* Don't allow dragging last command (END command) */ if (origIdx < 0 || origIdx >= m_layout->count() - 2) return false; if (origIdx < m_layout->count() - 2) { // Animate next item open m_dragOpenIdx = origIdx; if (CommandWidgetContainer* 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; } class ReorderCommandsUndoCommand : public EditorUndoCommand { int m_a, m_b; bool m_undid = false; public: ReorderCommandsUndoCommand(int a, int b, const QString& text, amuse::ObjToken node) : EditorUndoCommand(node.get(), SoundMacroListing::tr("Reorder %1").arg(text)), m_a(a), m_b(b) {} void undo() override { m_undid = true; m_node.cast()->m_obj->swapPositions(m_a, m_b); EditorUndoCommand::undo(); } void redo() override { m_node.cast()->m_obj->swapPositions(m_a, m_b); if (m_undid) EditorUndoCommand::redo(); } }; void SoundMacroListing::endDrag() { int insertIdx; if (m_dragOpenIdx != -1) { if (CommandWidgetContainer* prevItem = qobject_cast(m_layout->itemAt(m_dragOpenIdx)->widget())) prevItem->snapClosed(); insertIdx = m_dragOpenIdx; m_dragOpenIdx = -1; } else { insertIdx = m_layout->count() - 2; } if (m_prevDragOpen) { m_prevDragOpen->snapClosed(); m_prevDragOpen = nullptr; } if (m_origIdx != insertIdx) { CommandWidget* cmd = static_cast(m_dragItem->widget())->m_commandWidget; g_MainWindow->pushUndoCommand(new ReorderCommandsUndoCommand(m_origIdx, insertIdx, cmd->getText(), m_node)); } m_layout->insertItem(insertIdx, m_dragItem); m_dragItem = nullptr; stopAutoscroll(); reindex(); } void SoundMacroListing::cancelDrag() { if (m_dragOpenIdx != -1) { if (CommandWidgetContainer* 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 SoundMacroListing::_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(); /* Don't allow insertion after last command (END command) */ hoverIdx = std::max(0, std::min(hoverIdx, m_layout->count() - 2)); if (hoverIdx != m_dragOpenIdx) { if (m_dragOpenIdx != -1) if (CommandWidgetContainer* prevItem = qobject_cast(m_layout->itemAt(m_dragOpenIdx)->widget())) { m_prevDragOpen = prevItem; prevItem->animateClosed(); } if (CommandWidgetContainer* nextItem = qobject_cast(m_layout->itemAt(hoverIdx)->widget())) nextItem->animateOpen(); m_dragOpenIdx = hoverIdx; } update(); } void SoundMacroListing::moveDrag(CommandWidget* widget, const QPoint& pt, QWidget* source, QMouseEvent* event) { CommandWidgetContainer* 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 SoundMacroListing::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 SoundMacroListing::insertDragout() { if (m_dragOpenIdx != -1) { if (CommandWidgetContainer* prevItem = qobject_cast(m_layout->itemAt(m_dragOpenIdx)->widget())) { m_prevDragOpen = prevItem; prevItem->animateClosed(); } m_dragOpenIdx = -1; } stopAutoscroll(); } class InsertCommandUndoCommand : public EditorUndoCommand { int m_insertIdx; std::unique_ptr m_cmd; public: InsertCommandUndoCommand(int insertIdx, const QString& text, amuse::ObjToken node) : EditorUndoCommand(node.get(), SoundMacroListing::tr("Insert %1").arg(text)), m_insertIdx(insertIdx) {} void undo() override { m_cmd = m_node.cast()->m_obj->deleteCmd(m_insertIdx); EditorUndoCommand::undo(); } void redo() override { if (!m_cmd) return; m_node.cast()->m_obj->insertCmd(m_insertIdx, std::move(m_cmd)); m_cmd.reset(); EditorUndoCommand::redo(); } }; void SoundMacroListing::insert(amuse::SoundMacro::CmdOp op, const QString& text) { int insertIdx; if (m_dragOpenIdx != -1) { if (CommandWidgetContainer* prevItem = qobject_cast(m_layout->itemAt(m_dragOpenIdx)->widget())) prevItem->snapClosed(); insertIdx = m_dragOpenIdx; m_dragOpenIdx = -1; } else { insertIdx = m_layout->count() - 2; } if (m_prevDragOpen) { m_prevDragOpen->snapClosed(); m_prevDragOpen = nullptr; } g_MainWindow->pushUndoCommand(new InsertCommandUndoCommand(insertIdx, text, m_node)); m_layout->insertWidget(insertIdx, new CommandWidgetContainer(this, m_node->m_obj->insertNewCmd(insertIdx, op), this)); stopAutoscroll(); reindex(); } class DeleteCommandUndoCommand : public EditorUndoCommand { int m_deleteIdx; std::unique_ptr m_cmd; bool m_undid = false; public: DeleteCommandUndoCommand(int deleteIdx, const QString& text, amuse::ObjToken node) : EditorUndoCommand(node.get(), SoundMacroListing::tr("Delete %1").arg(text)), m_deleteIdx(deleteIdx) {} void undo() override { m_undid = true; m_node.cast()->m_obj->insertCmd(m_deleteIdx, std::move(m_cmd)); m_cmd.reset(); EditorUndoCommand::undo(); } void redo() override { m_cmd = m_node.cast()->m_obj->deleteCmd(m_deleteIdx); if (m_undid) EditorUndoCommand::redo(); } }; void SoundMacroListing::deleteCommand(int index) { QLayoutItem* item = m_layout->takeAt(index); CommandWidget* cmd = static_cast(item->widget())->m_commandWidget; g_MainWindow->pushUndoCommand(new DeleteCommandUndoCommand(index, cmd->getText(), m_node)); item->widget()->deleteLater(); delete item; reindex(); } void SoundMacroListing::reindex() { for (int i = 0; i < m_layout->count() - 1; ++i) if (CommandWidgetContainer* item = qobject_cast(m_layout->itemAt(i)->widget())) item->m_commandWidget->setIndex(i); } void SoundMacroListing::clear() { while (m_layout->count() > 2) { QLayoutItem* item = m_layout->takeAt(0); item->widget()->deleteLater(); delete item; } } bool SoundMacroListing::loadData(ProjectModel::SoundMacroNode* node) { m_node = node; clear(); int i = 0; for (auto& cmd : node->m_obj->m_cmds) { if (cmd->Isa() == amuse::SoundMacro::CmdOp::End) break; m_layout->insertWidget(i++, new CommandWidgetContainer(this, cmd.get(), this)); } reindex(); update(); return true; } void SoundMacroListing::unloadData() { m_node.reset(); clear(); reindex(); update(); } ProjectModel::INode* SoundMacroListing::currentNode() const { return m_node.get(); } SoundMacroListing::SoundMacroListing(QWidget* parent) : QWidget(parent), m_layout(new QVBoxLayout) { m_layout->addWidget(new CommandWidgetContainer(this, amuse::SoundMacro::CmdOp::End, this)); m_layout->addStretch(); setLayout(m_layout); reindex(); } CatalogueItem::CatalogueItem(amuse::SoundMacro::CmdOp op, const QString& name, const QString& doc, QWidget* parent) : QWidget(parent), m_op(op), m_label(name) { 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); } CatalogueItem::CatalogueItem(const CatalogueItem& other, QWidget* parent) : QWidget(parent), m_op(other.getCmdOp()) { 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); } static const char* CategoryStrings[] = { QT_TRANSLATE_NOOP("SoundMacroCatalogue", "Control"), QT_TRANSLATE_NOOP("SoundMacroCatalogue", "Pitch"), QT_TRANSLATE_NOOP("SoundMacroCatalogue", "Sample"), QT_TRANSLATE_NOOP("SoundMacroCatalogue", "Setup"), QT_TRANSLATE_NOOP("SoundMacroCatalogue", "Special"), QT_TRANSLATE_NOOP("SoundMacroCatalogue", "Structure"), QT_TRANSLATE_NOOP("SoundMacroCatalogue", "Volume")}; static const char* CategoryDocStrings[] = { QT_TRANSLATE_NOOP("SoundMacroCatalogue", "Commands to control the voice"), QT_TRANSLATE_NOOP("SoundMacroCatalogue", "Commands to control the voice's pitch"), QT_TRANSLATE_NOOP("SoundMacroCatalogue", "Commands to control the voice's sample playback"), QT_TRANSLATE_NOOP("SoundMacroCatalogue", "Commands to setup the voice's mixing process"), QT_TRANSLATE_NOOP("SoundMacroCatalogue", "Miscellaneous commands"), QT_TRANSLATE_NOOP("SoundMacroCatalogue", "Commands to control macro branching"), QT_TRANSLATE_NOOP("SoundMacroCatalogue", "Commands to control the voice's volume")}; SoundMacroCatalogue::SoundMacroCatalogue(QWidget* parent) : QTreeWidget(parent) { setSelectionMode(QAbstractItemView::NoSelection); setColumnCount(1); setHeaderHidden(true); QTreeWidgetItem* rootItems[int(amuse::SoundMacro::CmdType::CmdTypeMAX)]; for (int i = 0; i < int(amuse::SoundMacro::CmdType::CmdTypeMAX); ++i) { rootItems[i] = new QTreeWidgetItem(this); setItemWidget( rootItems[i], 0, new CatalogueItem(amuse::SoundMacro::CmdOp::Invalid, tr(CategoryStrings[i]), tr(CategoryDocStrings[i]), this)); } for (int i = 1; i < int(amuse::SoundMacro::CmdOp::CmdOpMAX); ++i) { const amuse::SoundMacro::CmdIntrospection* cmd = amuse::SoundMacro::GetCmdIntrospection(amuse::SoundMacro::CmdOp(i)); if (cmd) { QTreeWidgetItem* item = new QTreeWidgetItem(rootItems[int(cmd->m_tp)]); setItemWidget( item, 0, new CatalogueItem(amuse::SoundMacro::CmdOp(i), tr(cmd->m_name.data()), tr(cmd->m_description.data()), this)); } } } void SoundMacroCatalogue::mousePressEvent(QMouseEvent* event) { QTreeWidget::mousePressEvent(event); event->ignore(); } void SoundMacroCatalogue::mouseReleaseEvent(QMouseEvent* event) { QTreeWidget::mouseReleaseEvent(event); event->ignore(); } void SoundMacroCatalogue::mouseMoveEvent(QMouseEvent* event) { SoundMacroEditor* editor = qobject_cast(parentWidget()->parentWidget()); if (!editor || !editor->m_draggedItem) QTreeWidget::mouseMoveEvent(event); event->ignore(); } void SoundMacroEditor::beginCommandDrag(CommandWidget* widget, const QPoint& eventPt, const QPoint& pt) { if (m_listing->beginDrag(widget)) { m_draggedPt = pt; m_draggedCmd = widget; } } void SoundMacroEditor::beginCatalogueDrag(CatalogueItem* item, const QPoint& eventPt, const QPoint& pt) { m_draggedPt = pt; m_draggedItem = new CatalogueItem(*item, this); m_draggedItem->setGeometry(item->geometry()); m_draggedItem->move(eventPt - m_draggedPt); m_draggedItem->raise(); m_draggedItem->show(); } void SoundMacroEditor::beginStepTarget(FieldSoundMacroStep* stepField) { m_targetField = stepField; m_catalogue->setDisabled(true); m_listing->setDisabled(true); setFocus(); } void SoundMacroEditor::endStepTarget() { m_targetField = nullptr; m_catalogue->setDisabled(false); m_listing->setDisabled(false); } void SoundMacroEditor::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) { CatalogueItem* child = nullptr; while (ch && !(child = qobject_cast(ch))) ch = ch->parentWidget(); if (child && child->getCmdOp() != amuse::SoundMacro::CmdOp::Invalid) { QPoint fromParent2 = child->mapFrom(m_catalogue, fromParent1); beginCatalogueDrag(child, event->pos(), fromParent2); } } } else if (m_listing->parentWidget()->parentWidget()->geometry().contains(event->pos())) { QPoint fromParent1 = m_listing->mapFrom(this, event->pos()); QWidget* ch = m_listing->childAt(fromParent1); if (ch) { CommandWidget* child = nullptr; while (ch && !(child = qobject_cast(ch))) ch = ch->parentWidget(); if (child) { if (m_targetField) { m_targetField->setIndex(m_listing->layout()->indexOf(child->parentWidget())); return; } QPoint fromParent2 = child->mapFrom(m_listing, fromParent1); beginCommandDrag(child, event->pos(), fromParent2); } } } if (m_targetField) m_targetField->cancel(); } void SoundMacroEditor::mouseReleaseEvent(QMouseEvent* event) { if (m_draggedItem) { amuse::SoundMacro::CmdOp op = m_draggedItem->getCmdOp(); QString text = m_draggedItem->getText(); m_draggedItem->deleteLater(); m_draggedItem = nullptr; if (m_listing->parentWidget()->parentWidget()->geometry().contains(event->pos())) { if (m_dragInsertIdx != -1) m_listing->insert(op, text); else m_listing->insertDragout(); } else { m_listing->insertDragout(); } m_dragInsertIdx = -1; } else if (m_draggedCmd) { m_listing->endDrag(); m_draggedCmd = nullptr; } } void SoundMacroEditor::mouseMoveEvent(QMouseEvent* event) { if (m_draggedItem) { m_draggedItem->move(event->pos() - m_draggedPt); if (m_listing->parentWidget()->parentWidget()->geometry().contains(event->pos())) { m_dragInsertIdx = m_listing->moveInsertDrag(m_listing->mapFrom(this, event->pos()), this, event); } else if (m_dragInsertIdx != -1) { m_listing->insertDragout(); m_dragInsertIdx = -1; } m_catalogue->update(); update(); } else if (m_draggedCmd) { QPoint listingPt = m_listing->mapFrom(this, event->pos()); CommandWidgetContainer* container = static_cast(m_draggedCmd->parentWidget()); container->move(container->x(), listingPt.y() - m_draggedPt.y()); if (m_listing->parentWidget()->parentWidget()->geometry().contains(event->pos())) m_listing->moveDrag(m_draggedCmd, listingPt, this, event); m_listing->update(); update(); } } void SoundMacroEditor::keyPressEvent(QKeyEvent* event) { if (event->key() == Qt::Key_Escape) { if (m_draggedItem) { m_draggedItem->deleteLater(); m_draggedItem = nullptr; m_listing->insertDragout(); } else if (m_draggedCmd) { m_listing->cancelDrag(); m_draggedCmd = nullptr; } else if (m_targetField) { m_targetField->cancel(); } } } void SoundMacroEditor::catalogueDoubleClicked(QTreeWidgetItem* item, int column) { if (CatalogueItem* cItem = qobject_cast(m_catalogue->itemWidget(item, column))) { amuse::SoundMacro::CmdOp op = cItem->getCmdOp(); if (op != amuse::SoundMacro::CmdOp::Invalid) m_listing->insert(op, cItem->getText()); } } bool SoundMacroEditor::loadData(ProjectModel::SoundMacroNode* node) { endStepTarget(); return m_listing->loadData(node); } void SoundMacroEditor::unloadData() { endStepTarget(); m_listing->unloadData(); } ProjectModel::INode* SoundMacroEditor::currentNode() const { return m_listing->currentNode(); } SoundMacroEditor::SoundMacroEditor(QWidget* parent) : EditorWidget(parent) , m_splitter(new QSplitter) , m_listing(new SoundMacroListing) , m_catalogue(new SoundMacroCatalogue) { { QScrollArea* listingScroll = new QScrollArea; listingScroll->setWidget(m_listing); 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); } { 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, &SoundMacroCatalogue::itemDoubleClicked, this, &SoundMacroEditor::catalogueDoubleClicked); m_splitter->setCollapsible(0, false); QGridLayout* layout = new QGridLayout; layout->setContentsMargins(QMargins()); layout->addWidget(m_splitter); setLayout(layout); }