mirror of https://github.com/AxioDL/amuse.git
Clipboard support and various bug fixes
This commit is contained in:
parent
cefa0ac18c
commit
27cdee0c14
|
@ -91,8 +91,22 @@ void FieldDoubleSlider::doValueChanged(int value)
|
||||||
}
|
}
|
||||||
|
|
||||||
FieldProjectNode::FieldProjectNode(ProjectModel::CollectionNode* collection, QWidget* parent)
|
FieldProjectNode::FieldProjectNode(ProjectModel::CollectionNode* collection, QWidget* parent)
|
||||||
: FieldComboBox(parent)
|
: QWidget(parent), m_comboBox(this), m_button(this)
|
||||||
{
|
{
|
||||||
|
m_button.setDisabled(true);
|
||||||
|
m_button.setFixedSize(30, 30);
|
||||||
|
m_comboBox.setFixedHeight(30);
|
||||||
|
connect(&m_comboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(_currentIndexChanged(int)));
|
||||||
|
connect(&m_button, SIGNAL(clicked(bool)), this, SLOT(openCurrent()));
|
||||||
|
QIcon icon(QStringLiteral(":/icons/IconForward.svg"));
|
||||||
|
icon.addFile(QStringLiteral(":/icons/IconForwardDisabled.svg"), {}, QIcon::Disabled);
|
||||||
|
m_button.setIcon(icon);
|
||||||
|
QHBoxLayout* layout = new QHBoxLayout;
|
||||||
|
layout->setContentsMargins({});
|
||||||
|
layout->setSpacing(0);
|
||||||
|
layout->addWidget(&m_comboBox);
|
||||||
|
layout->addWidget(&m_button);
|
||||||
|
setLayout(layout);
|
||||||
setCollection(collection);
|
setCollection(collection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,18 +116,65 @@ void FieldProjectNode::setCollection(ProjectModel::CollectionNode* collection)
|
||||||
|
|
||||||
if (!collection)
|
if (!collection)
|
||||||
{
|
{
|
||||||
setModel(new QStandardItemModel(0, 1, this));
|
m_comboBox.setModel(new QStandardItemModel(0, 1, this));
|
||||||
|
m_button.setDisabled(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ProjectModel* model = g_MainWindow->projectModel();
|
ProjectModel* model = g_MainWindow->projectModel();
|
||||||
setModel(model->getNullProxy());
|
m_comboBox.setModel(model->getNullProxy());
|
||||||
setRootModelIndex(model->getNullProxy()->mapFromSource(model->index(collection)));
|
m_comboBox.setRootModelIndex(model->getNullProxy()->mapFromSource(model->index(collection)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void FieldProjectNode::_currentIndexChanged(int index)
|
||||||
|
{
|
||||||
|
m_button.setEnabled(index != 0);
|
||||||
|
emit currentIndexChanged(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectModel::BasePoolObjectNode* FieldProjectNode::currentNode() const
|
||||||
|
{
|
||||||
|
int index = m_comboBox.currentIndex();
|
||||||
|
if (index == 0)
|
||||||
|
return nullptr;
|
||||||
|
else
|
||||||
|
return m_collection->nodeOfIndex(index - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FieldProjectNode::event(QEvent* ev)
|
||||||
|
{
|
||||||
|
if (ev->type() == QEvent::User)
|
||||||
|
{
|
||||||
|
showPopup();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return QWidget::event(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FieldProjectNode::openCurrent()
|
||||||
|
{
|
||||||
|
if (ProjectModel::BasePoolObjectNode* node = currentNode())
|
||||||
|
if (!g_MainWindow->isUiDisabled())
|
||||||
|
g_MainWindow->openEditor(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
FieldPageObjectNode::FieldPageObjectNode(ProjectModel::GroupNode* group, QWidget* parent)
|
FieldPageObjectNode::FieldPageObjectNode(ProjectModel::GroupNode* group, QWidget* parent)
|
||||||
: FieldComboBox(parent)
|
: QWidget(parent), m_comboBox(this), m_button(this)
|
||||||
{
|
{
|
||||||
|
m_button.setDisabled(true);
|
||||||
|
m_button.setFixedSize(30, 30);
|
||||||
|
m_comboBox.setFixedHeight(30);
|
||||||
|
connect(&m_comboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(_currentIndexChanged(int)));
|
||||||
|
connect(&m_button, SIGNAL(clicked(bool)), this, SLOT(openCurrent()));
|
||||||
|
QIcon icon(QStringLiteral(":/icons/IconForward.svg"));
|
||||||
|
icon.addFile(QStringLiteral(":/icons/IconForwardDisabled.svg"), {}, QIcon::Disabled);
|
||||||
|
m_button.setIcon(icon);
|
||||||
|
QHBoxLayout* layout = new QHBoxLayout;
|
||||||
|
layout->setContentsMargins({});
|
||||||
|
layout->setSpacing(0);
|
||||||
|
layout->addWidget(&m_comboBox);
|
||||||
|
layout->addWidget(&m_button);
|
||||||
|
setLayout(layout);
|
||||||
setGroup(group);
|
setGroup(group);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,13 +184,53 @@ void FieldPageObjectNode::setGroup(ProjectModel::GroupNode* group)
|
||||||
|
|
||||||
if (!group)
|
if (!group)
|
||||||
{
|
{
|
||||||
setModel(new QStandardItemModel(0, 1, this));
|
m_comboBox.setModel(new QStandardItemModel(0, 1, this));
|
||||||
|
m_button.setDisabled(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ProjectModel* model = g_MainWindow->projectModel();
|
ProjectModel* model = g_MainWindow->projectModel();
|
||||||
setModel(model->getPageObjectProxy());
|
m_comboBox.setModel(model->getPageObjectProxy());
|
||||||
setRootModelIndex(model->getPageObjectProxy()->mapFromSource(model->index(group)));
|
m_comboBox.setRootModelIndex(model->getPageObjectProxy()->mapFromSource(model->index(group)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void FieldPageObjectNode::_currentIndexChanged(int index)
|
||||||
|
{
|
||||||
|
m_button.setEnabled(index != 0);
|
||||||
|
emit currentIndexChanged(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectModel::BasePoolObjectNode* FieldPageObjectNode::currentNode() const
|
||||||
|
{
|
||||||
|
int index = m_comboBox.currentIndex();
|
||||||
|
if (index == 0)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ProjectModel* model = g_MainWindow->projectModel();
|
||||||
|
return static_cast<ProjectModel::BasePoolObjectNode*>(
|
||||||
|
model->node(model->getPageObjectProxy()->mapToSource(
|
||||||
|
model->getPageObjectProxy()->index(index, 0, m_comboBox.rootModelIndex()))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FieldPageObjectNode::event(QEvent* ev)
|
||||||
|
{
|
||||||
|
if (ev->type() == QEvent::User)
|
||||||
|
{
|
||||||
|
showPopup();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return QWidget::event(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FieldPageObjectNode::openCurrent()
|
||||||
|
{
|
||||||
|
if (ProjectModel::BasePoolObjectNode* node = currentNode())
|
||||||
|
if (!g_MainWindow->isUiDisabled())
|
||||||
|
g_MainWindow->openEditor(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
AddRemoveButtons::AddRemoveButtons(QWidget* parent)
|
AddRemoveButtons::AddRemoveButtons(QWidget* parent)
|
||||||
|
|
|
@ -23,7 +23,7 @@ public:
|
||||||
virtual void unloadData() {}
|
virtual void unloadData() {}
|
||||||
virtual ProjectModel::INode* currentNode() const { return nullptr; }
|
virtual ProjectModel::INode* currentNode() const { return nullptr; }
|
||||||
virtual void setEditorEnabled(bool en) { setEnabled(en); }
|
virtual void setEditorEnabled(bool en) { setEnabled(en); }
|
||||||
virtual bool isItemEditEnabled() const { return false; }
|
virtual AmuseItemEditFlags itemEditFlags() const { return AmuseItemNone; }
|
||||||
public slots:
|
public slots:
|
||||||
virtual void itemCutAction() {}
|
virtual void itemCutAction() {}
|
||||||
virtual void itemCopyAction() {}
|
virtual void itemCopyAction() {}
|
||||||
|
@ -125,24 +125,51 @@ public:
|
||||||
void wheelEvent(QWheelEvent* event) { event->ignore(); }
|
void wheelEvent(QWheelEvent* event) { event->ignore(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
class FieldProjectNode : public FieldComboBox
|
class FieldProjectNode : public QWidget
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
ProjectModel::CollectionNode* m_collection;
|
ProjectModel::CollectionNode* m_collection;
|
||||||
|
FieldComboBox m_comboBox;
|
||||||
|
QPushButton m_button;
|
||||||
public:
|
public:
|
||||||
explicit FieldProjectNode(ProjectModel::CollectionNode* collection = Q_NULLPTR, QWidget* parent = Q_NULLPTR);
|
explicit FieldProjectNode(ProjectModel::CollectionNode* collection = Q_NULLPTR, QWidget* parent = Q_NULLPTR);
|
||||||
void setCollection(ProjectModel::CollectionNode* collection);
|
void setCollection(ProjectModel::CollectionNode* collection);
|
||||||
ProjectModel::CollectionNode* collection() const { return m_collection; }
|
ProjectModel::CollectionNode* collection() const { return m_collection; }
|
||||||
|
int currentIndex() const { return m_comboBox.currentIndex(); }
|
||||||
|
void setCurrentIndex(int index) { m_comboBox.setCurrentIndex(index); }
|
||||||
|
void showPopup() { m_comboBox.showPopup(); }
|
||||||
|
ProjectModel::BasePoolObjectNode* currentNode() const;
|
||||||
|
bool event(QEvent* ev);
|
||||||
|
private slots:
|
||||||
|
void _currentIndexChanged(int);
|
||||||
|
public slots:
|
||||||
|
void openCurrent();
|
||||||
|
signals:
|
||||||
|
void currentIndexChanged(int);
|
||||||
};
|
};
|
||||||
|
|
||||||
class FieldPageObjectNode : public FieldComboBox
|
class FieldPageObjectNode : public QWidget
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
ProjectModel::GroupNode* m_group;
|
ProjectModel::GroupNode* m_group;
|
||||||
|
FieldComboBox m_comboBox;
|
||||||
|
QPushButton m_button;
|
||||||
public:
|
public:
|
||||||
explicit FieldPageObjectNode(ProjectModel::GroupNode* group = Q_NULLPTR, QWidget* parent = Q_NULLPTR);
|
explicit FieldPageObjectNode(ProjectModel::GroupNode* group = Q_NULLPTR, QWidget* parent = Q_NULLPTR);
|
||||||
void setGroup(ProjectModel::GroupNode* group);
|
void setGroup(ProjectModel::GroupNode* group);
|
||||||
ProjectModel::GroupNode* group() const { return m_group; }
|
ProjectModel::GroupNode* group() const { return m_group; }
|
||||||
|
int currentIndex() const { return m_comboBox.currentIndex(); }
|
||||||
|
void setCurrentIndex(int index) { m_comboBox.setCurrentIndex(index); }
|
||||||
|
QModelIndex rootModelIndex() const { return m_comboBox.rootModelIndex(); }
|
||||||
|
void showPopup() { m_comboBox.showPopup(); }
|
||||||
|
ProjectModel::BasePoolObjectNode* currentNode() const;
|
||||||
|
bool event(QEvent* ev);
|
||||||
|
private slots:
|
||||||
|
void _currentIndexChanged(int);
|
||||||
|
public slots:
|
||||||
|
void openCurrent();
|
||||||
|
signals:
|
||||||
|
void currentIndexChanged(int);
|
||||||
};
|
};
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
|
|
|
@ -118,7 +118,7 @@ void SoundMacroDelegate::setEditorData(QWidget* editor, const QModelIndex& index
|
||||||
ProjectModel::CollectionNode* smColl = group->getCollectionOfType(ProjectModel::INode::Type::SoundMacro);
|
ProjectModel::CollectionNode* smColl = group->getCollectionOfType(ProjectModel::INode::Type::SoundMacro);
|
||||||
static_cast<EditorFieldProjectNode*>(editor)->setCurrentIndex(smColl->indexOfId(layer.macro.id) + 1);
|
static_cast<EditorFieldProjectNode*>(editor)->setCurrentIndex(smColl->indexOfId(layer.macro.id) + 1);
|
||||||
if (static_cast<EditorFieldProjectNode*>(editor)->shouldPopupOpen())
|
if (static_cast<EditorFieldProjectNode*>(editor)->shouldPopupOpen())
|
||||||
static_cast<EditorFieldProjectNode*>(editor)->showPopup();
|
QApplication::postEvent(editor, new QEvent(QEvent::User));
|
||||||
}
|
}
|
||||||
|
|
||||||
void SoundMacroDelegate::setModelData(QWidget* editor, QAbstractItemModel* m, const QModelIndex& index) const
|
void SoundMacroDelegate::setModelData(QWidget* editor, QAbstractItemModel* m, const QModelIndex& index) const
|
||||||
|
@ -636,24 +636,9 @@ void LayersEditor::doSelectionChanged()
|
||||||
g_MainWindow->updateFocus();
|
g_MainWindow->updateFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LayersEditor::isItemEditEnabled() const
|
AmuseItemEditFlags LayersEditor::itemEditFlags() const
|
||||||
{
|
{
|
||||||
return !m_tableView.selectionModel()->selectedRows().isEmpty();
|
return m_tableView.selectionModel()->selectedRows().isEmpty() ? AmuseItemNone : AmuseItemDelete;
|
||||||
}
|
|
||||||
|
|
||||||
void LayersEditor::itemCutAction()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void LayersEditor::itemCopyAction()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void LayersEditor::itemPasteAction()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LayersEditor::itemDeleteAction()
|
void LayersEditor::itemDeleteAction()
|
||||||
|
|
|
@ -78,14 +78,10 @@ public:
|
||||||
void unloadData();
|
void unloadData();
|
||||||
ProjectModel::INode* currentNode() const;
|
ProjectModel::INode* currentNode() const;
|
||||||
void resizeEvent(QResizeEvent* ev);
|
void resizeEvent(QResizeEvent* ev);
|
||||||
bool isItemEditEnabled() const;
|
AmuseItemEditFlags itemEditFlags() const;
|
||||||
public slots:
|
public slots:
|
||||||
void doAdd();
|
void doAdd();
|
||||||
void doSelectionChanged();
|
void doSelectionChanged();
|
||||||
|
|
||||||
void itemCutAction();
|
|
||||||
void itemCopyAction();
|
|
||||||
void itemPasteAction();
|
|
||||||
void itemDeleteAction();
|
void itemDeleteAction();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,9 @@ MIDIReader::MIDIReader(amuse::Engine& engine, bool useLock)
|
||||||
|
|
||||||
void MIDIReader::noteOff(uint8_t chan, uint8_t key, uint8_t velocity)
|
void MIDIReader::noteOff(uint8_t chan, uint8_t key, uint8_t velocity)
|
||||||
{
|
{
|
||||||
|
if (g_MainWindow->m_interactiveSeq)
|
||||||
|
g_MainWindow->m_interactiveSeq->keyOff(chan, key, velocity);
|
||||||
|
|
||||||
auto keySearch = m_chanVoxs.find(key);
|
auto keySearch = m_chanVoxs.find(key);
|
||||||
if (keySearch == m_chanVoxs.cend())
|
if (keySearch == m_chanVoxs.cend())
|
||||||
return;
|
return;
|
||||||
|
@ -19,6 +22,9 @@ void MIDIReader::noteOff(uint8_t chan, uint8_t key, uint8_t velocity)
|
||||||
|
|
||||||
void MIDIReader::noteOn(uint8_t chan, uint8_t key, uint8_t velocity)
|
void MIDIReader::noteOn(uint8_t chan, uint8_t key, uint8_t velocity)
|
||||||
{
|
{
|
||||||
|
if (g_MainWindow->m_interactiveSeq)
|
||||||
|
g_MainWindow->m_interactiveSeq->keyOn(chan, key, velocity);
|
||||||
|
|
||||||
if (m_lastVoice && m_lastVoice->isDestroyed())
|
if (m_lastVoice && m_lastVoice->isDestroyed())
|
||||||
m_lastVoice.reset();
|
m_lastVoice.reset();
|
||||||
|
|
||||||
|
@ -48,13 +54,19 @@ void MIDIReader::noteOn(uint8_t chan, uint8_t key, uint8_t velocity)
|
||||||
|
|
||||||
amuse::ObjToken<amuse::Voice> newVox = g_MainWindow->startEditorVoice(key, velocity);
|
amuse::ObjToken<amuse::Voice> newVox = g_MainWindow->startEditorVoice(key, velocity);
|
||||||
if (newVox)
|
if (newVox)
|
||||||
|
{
|
||||||
m_chanVoxs[key] = newVox;
|
m_chanVoxs[key] = newVox;
|
||||||
|
m_lastVoice = newVox;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MIDIReader::notePressure(uint8_t /*chan*/, uint8_t /*key*/, uint8_t /*pressure*/) {}
|
void MIDIReader::notePressure(uint8_t /*chan*/, uint8_t /*key*/, uint8_t /*pressure*/) {}
|
||||||
|
|
||||||
void MIDIReader::controlChange(uint8_t chan, uint8_t control, uint8_t value)
|
void MIDIReader::controlChange(uint8_t chan, uint8_t control, uint8_t value)
|
||||||
{
|
{
|
||||||
|
if (g_MainWindow->m_interactiveSeq)
|
||||||
|
g_MainWindow->m_interactiveSeq->setCtrlValue(chan, control, value);
|
||||||
|
|
||||||
if (control == 1)
|
if (control == 1)
|
||||||
{
|
{
|
||||||
g_MainWindow->m_ui.modulationSlider->setValue(int(value));
|
g_MainWindow->m_ui.modulationSlider->setValue(int(value));
|
||||||
|
@ -71,17 +83,28 @@ void MIDIReader::controlChange(uint8_t chan, uint8_t control, uint8_t value)
|
||||||
g_MainWindow->m_ctrlVals[control] = value;
|
g_MainWindow->m_ctrlVals[control] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MIDIReader::programChange(uint8_t chan, uint8_t program) {}
|
void MIDIReader::programChange(uint8_t chan, uint8_t program)
|
||||||
|
{
|
||||||
|
if (g_MainWindow->m_interactiveSeq)
|
||||||
|
g_MainWindow->m_interactiveSeq->setChanProgram(chan, program);
|
||||||
|
}
|
||||||
|
|
||||||
void MIDIReader::channelPressure(uint8_t /*chan*/, uint8_t /*pressure*/) {}
|
void MIDIReader::channelPressure(uint8_t /*chan*/, uint8_t /*pressure*/) {}
|
||||||
|
|
||||||
void MIDIReader::pitchBend(uint8_t chan, int16_t pitch)
|
void MIDIReader::pitchBend(uint8_t chan, int16_t pitch)
|
||||||
{
|
{
|
||||||
g_MainWindow->m_ui.pitchSlider->setValue(int((pitch - 0x2000) / float(0x2000) * 2048.f));
|
float pWheel = (pitch - 0x2000) / float(0x2000);
|
||||||
|
if (g_MainWindow->m_interactiveSeq)
|
||||||
|
g_MainWindow->m_interactiveSeq->setPitchWheel(chan, pWheel);
|
||||||
|
else
|
||||||
|
g_MainWindow->m_ui.pitchSlider->setValue(int(pWheel * 2048.f));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MIDIReader::allSoundOff(uint8_t chan)
|
void MIDIReader::allSoundOff(uint8_t chan)
|
||||||
{
|
{
|
||||||
|
if (g_MainWindow->m_interactiveSeq)
|
||||||
|
g_MainWindow->m_interactiveSeq->kill();
|
||||||
|
|
||||||
for (auto& v : m_engine.getActiveVoices())
|
for (auto& v : m_engine.getActiveVoices())
|
||||||
v->kill();
|
v->kill();
|
||||||
}
|
}
|
||||||
|
@ -92,6 +115,9 @@ void MIDIReader::localControl(uint8_t /*chan*/, bool /*on*/) {}
|
||||||
|
|
||||||
void MIDIReader::allNotesOff(uint8_t chan)
|
void MIDIReader::allNotesOff(uint8_t chan)
|
||||||
{
|
{
|
||||||
|
if (g_MainWindow->m_interactiveSeq)
|
||||||
|
g_MainWindow->m_interactiveSeq->kill();
|
||||||
|
|
||||||
for (auto& v : m_engine.getActiveVoices())
|
for (auto& v : m_engine.getActiveVoices())
|
||||||
v->kill();
|
v->kill();
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include <QInputDialog>
|
#include <QInputDialog>
|
||||||
#include <QProgressDialog>
|
#include <QProgressDialog>
|
||||||
#include <QMouseEvent>
|
#include <QMouseEvent>
|
||||||
|
#include <QClipboard>
|
||||||
#include <QtSvg/QtSvg>
|
#include <QtSvg/QtSvg>
|
||||||
#include "amuse/ContainerRegistry.hpp"
|
#include "amuse/ContainerRegistry.hpp"
|
||||||
#include "Common.hpp"
|
#include "Common.hpp"
|
||||||
|
@ -21,6 +22,7 @@
|
||||||
|
|
||||||
MainWindow::MainWindow(QWidget* parent)
|
MainWindow::MainWindow(QWidget* parent)
|
||||||
: QMainWindow(parent),
|
: QMainWindow(parent),
|
||||||
|
m_navIt(m_navList.begin()),
|
||||||
m_treeDelegate(*this, this),
|
m_treeDelegate(*this, this),
|
||||||
m_mainMessenger(this),
|
m_mainMessenger(this),
|
||||||
m_filterProjectModel(this),
|
m_filterProjectModel(this),
|
||||||
|
@ -41,6 +43,17 @@ MainWindow::MainWindow(QWidget* parent)
|
||||||
m_ui.projectOutline->setItemDelegate(&m_treeDelegate);
|
m_ui.projectOutline->setItemDelegate(&m_treeDelegate);
|
||||||
connect(m_ui.projectOutline, SIGNAL(activated(const QModelIndex&)),
|
connect(m_ui.projectOutline, SIGNAL(activated(const QModelIndex&)),
|
||||||
this, SLOT(outlineItemActivated(const QModelIndex&)));
|
this, SLOT(outlineItemActivated(const QModelIndex&)));
|
||||||
|
m_goBack = new QAction(m_ui.backButton->icon(), tr("Go Back"), this);
|
||||||
|
m_goBack->setEnabled(false);
|
||||||
|
connect(m_goBack, SIGNAL(triggered()), this, SLOT(goBack()));
|
||||||
|
m_goBack->setShortcut(QKeySequence::Back);
|
||||||
|
m_ui.backButton->setDefaultAction(m_goBack);
|
||||||
|
m_goForward = new QAction(m_ui.forwardButton->icon(), tr("Go Forward"), this);
|
||||||
|
m_goForward->setEnabled(false);
|
||||||
|
connect(m_goForward, SIGNAL(triggered()), this, SLOT(goForward()));
|
||||||
|
m_goForward->setShortcut(QKeySequence::Forward);
|
||||||
|
m_ui.forwardButton->setDefaultAction(m_goForward);
|
||||||
|
|
||||||
connectMessenger(&m_mainMessenger, Qt::DirectConnection);
|
connectMessenger(&m_mainMessenger, Qt::DirectConnection);
|
||||||
|
|
||||||
m_ui.statusbar->connectKillClicked(this, SLOT(killSounds()));
|
m_ui.statusbar->connectKillClicked(this, SLOT(killSounds()));
|
||||||
|
@ -71,6 +84,10 @@ MainWindow::MainWindow(QWidget* parent)
|
||||||
connect(m_ui.actionImport_Groups, SIGNAL(triggered()), this, SLOT(importAction()));
|
connect(m_ui.actionImport_Groups, SIGNAL(triggered()), this, SLOT(importAction()));
|
||||||
connect(m_ui.actionImport_Songs, SIGNAL(triggered()), this, SLOT(importSongsAction()));
|
connect(m_ui.actionImport_Songs, SIGNAL(triggered()), this, SLOT(importSongsAction()));
|
||||||
connect(m_ui.actionExport_GameCube_Groups, SIGNAL(triggered()), this, SLOT(exportAction()));
|
connect(m_ui.actionExport_GameCube_Groups, SIGNAL(triggered()), this, SLOT(exportAction()));
|
||||||
|
connect(m_ui.actionImport_C_Headers, SIGNAL(triggered()), this, SLOT(importHeadersAction()));
|
||||||
|
connect(m_ui.actionExport_C_Headers, SIGNAL(triggered()), this, SLOT(exportHeadersAction()));
|
||||||
|
m_ui.actionQuit->setShortcut(QKeySequence::Quit);
|
||||||
|
connect(m_ui.actionQuit, SIGNAL(triggered()), qApp, SLOT(quit()));
|
||||||
|
|
||||||
for (int i = 0; i < MaxRecentFiles; ++i)
|
for (int i = 0; i < MaxRecentFiles; ++i)
|
||||||
{
|
{
|
||||||
|
@ -84,21 +101,16 @@ MainWindow::MainWindow(QWidget* parent)
|
||||||
connect(m_clearRecentFileAct, SIGNAL(triggered()), this, SLOT(clearRecentFilesAction()));
|
connect(m_clearRecentFileAct, SIGNAL(triggered()), this, SLOT(clearRecentFilesAction()));
|
||||||
m_ui.menuRecent_Projects->addAction(m_clearRecentFileAct);
|
m_ui.menuRecent_Projects->addAction(m_clearRecentFileAct);
|
||||||
|
|
||||||
#ifndef __APPLE__
|
|
||||||
m_ui.menuFile->addSeparator();
|
|
||||||
QAction* quitAction = m_ui.menuFile->addAction(tr("Quit"));
|
|
||||||
quitAction->setShortcut(QKeySequence::Quit);
|
|
||||||
connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
updateRecentFileActions();
|
updateRecentFileActions();
|
||||||
|
|
||||||
connect(m_undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(cleanChanged(bool)));
|
connect(m_undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(cleanChanged(bool)));
|
||||||
QAction* undoAction = m_undoStack->createUndoAction(this);
|
QAction* undoAction = m_undoStack->createUndoAction(this);
|
||||||
undoAction->setShortcut(QKeySequence::Undo);
|
undoAction->setShortcut(QKeySequence::Undo);
|
||||||
|
undoAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-undo")));
|
||||||
m_ui.menuEdit->insertAction(m_ui.actionCut, undoAction);
|
m_ui.menuEdit->insertAction(m_ui.actionCut, undoAction);
|
||||||
QAction* redoAction = m_undoStack->createRedoAction(this);
|
QAction* redoAction = m_undoStack->createRedoAction(this);
|
||||||
redoAction->setShortcut(QKeySequence::Redo);
|
redoAction->setShortcut(QKeySequence::Redo);
|
||||||
|
redoAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-redo")));
|
||||||
m_ui.menuEdit->insertAction(m_ui.actionCut, redoAction);
|
m_ui.menuEdit->insertAction(m_ui.actionCut, redoAction);
|
||||||
m_ui.menuEdit->insertSeparator(m_ui.actionCut);
|
m_ui.menuEdit->insertSeparator(m_ui.actionCut);
|
||||||
m_ui.actionCut->setShortcut(QKeySequence::Cut);
|
m_ui.actionCut->setShortcut(QKeySequence::Cut);
|
||||||
|
@ -153,6 +165,7 @@ MainWindow::MainWindow(QWidget* parent)
|
||||||
connect(m_ui.menuMIDI, SIGNAL(aboutToShow()), this, SLOT(aboutToShowMIDIIOMenu()));
|
connect(m_ui.menuMIDI, SIGNAL(aboutToShow()), this, SLOT(aboutToShowMIDIIOMenu()));
|
||||||
|
|
||||||
connect(qApp, SIGNAL(focusChanged(QWidget*,QWidget*)), this, SLOT(onFocusChanged(QWidget*,QWidget*)));
|
connect(qApp, SIGNAL(focusChanged(QWidget*,QWidget*)), this, SLOT(onFocusChanged(QWidget*,QWidget*)));
|
||||||
|
connect(QGuiApplication::clipboard(), SIGNAL(dataChanged()), this, SLOT(onClipboardChanged()));
|
||||||
|
|
||||||
m_voxEngine = boo::NewAudioVoiceEngine();
|
m_voxEngine = boo::NewAudioVoiceEngine();
|
||||||
m_voxAllocator = std::make_unique<VoiceAllocator>(*m_voxEngine);
|
m_voxAllocator = std::make_unique<VoiceAllocator>(*m_voxEngine);
|
||||||
|
@ -233,6 +246,7 @@ void MainWindow::updateRecentFileActions()
|
||||||
QString text = QStringLiteral("&%1 %2").arg(i + 1).arg(QDir(files[i]).dirName());
|
QString text = QStringLiteral("&%1 %2").arg(i + 1).arg(QDir(files[i]).dirName());
|
||||||
m_recentFileActs[i]->setText(text);
|
m_recentFileActs[i]->setText(text);
|
||||||
m_recentFileActs[i]->setData(files[i]);
|
m_recentFileActs[i]->setData(files[i]);
|
||||||
|
m_recentFileActs[i]->setToolTip(files[i]);
|
||||||
m_recentFileActs[i]->setVisible(true);
|
m_recentFileActs[i]->setVisible(true);
|
||||||
}
|
}
|
||||||
for (int j = numRecentFiles; j < MaxRecentFiles; ++j)
|
for (int j = numRecentFiles; j < MaxRecentFiles; ++j)
|
||||||
|
@ -282,11 +296,17 @@ bool MainWindow::setProjectPath(const QString& path)
|
||||||
m_ui.actionReload_Sample_Data->setEnabled(true);
|
m_ui.actionReload_Sample_Data->setEnabled(true);
|
||||||
m_ui.actionImport_Songs->setEnabled(true);
|
m_ui.actionImport_Songs->setEnabled(true);
|
||||||
m_ui.actionExport_GameCube_Groups->setEnabled(true);
|
m_ui.actionExport_GameCube_Groups->setEnabled(true);
|
||||||
|
m_ui.actionImport_C_Headers->setEnabled(true);
|
||||||
|
m_ui.actionExport_C_Headers->setEnabled(true);
|
||||||
m_ui.actionNew_Subproject->setEnabled(true);
|
m_ui.actionNew_Subproject->setEnabled(true);
|
||||||
setWindowFilePath(path);
|
setWindowFilePath(path);
|
||||||
updateWindowTitle();
|
updateWindowTitle();
|
||||||
updateFocus();
|
updateFocus();
|
||||||
m_undoStack->clear();
|
m_undoStack->clear();
|
||||||
|
m_navList.clear();
|
||||||
|
m_navIt = m_navList.begin();
|
||||||
|
|
||||||
|
updateNavigationButtons();
|
||||||
|
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
QStringList files = settings.value("recentFileList").toStringList();
|
QStringList files = settings.value("recentFileList").toStringList();
|
||||||
|
@ -399,9 +419,8 @@ void MainWindow::timerEvent(QTimerEvent* ev)
|
||||||
for (int i = 0; i < songTable->model()->rowCount(); ++i)
|
for (int i = 0; i < songTable->model()->rowCount(); ++i)
|
||||||
if (MIDIPlayerWidget* player = qobject_cast<MIDIPlayerWidget*>(
|
if (MIDIPlayerWidget* player = qobject_cast<MIDIPlayerWidget*>(
|
||||||
songTable->indexWidget(songTable->model()->index(i, 1))))
|
songTable->indexWidget(songTable->model()->index(i, 1))))
|
||||||
for (auto& p : m_engine->getActiveSequencers())
|
if (player->sequencer() && player->sequencer()->state() != amuse::SequencerState::Playing)
|
||||||
if (p.get() == player->sequencer() && p->state() != amuse::SequencerState::Playing)
|
player->stopped();
|
||||||
player->stopped();
|
|
||||||
|
|
||||||
QTableView* sfxTable = m_soundGroupEditor->getSFXListView();
|
QTableView* sfxTable = m_soundGroupEditor->getSFXListView();
|
||||||
for (int i = 0; i < sfxTable->model()->rowCount(); ++i)
|
for (int i = 0; i < sfxTable->model()->rowCount(); ++i)
|
||||||
|
@ -481,83 +500,119 @@ void MainWindow::startBackgroundTask(int id, const QString& windowTitle, const Q
|
||||||
QMetaObject::invokeMethod(m_backgroundTask, "run", Qt::QueuedConnection);
|
QMetaObject::invokeMethod(m_backgroundTask, "run", Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MainWindow::_setEditor(EditorWidget* editor)
|
bool MainWindow::_setEditor(EditorWidget* editor, bool appendNav)
|
||||||
{
|
{
|
||||||
|
m_interactiveSeq.reset();
|
||||||
if (editor != m_ui.editorContents->currentWidget() &&
|
if (editor != m_ui.editorContents->currentWidget() &&
|
||||||
m_ui.editorContents->currentWidget() != m_faceSvg)
|
m_ui.editorContents->currentWidget() != m_faceSvg)
|
||||||
getEditorWidget()->unloadData();
|
getEditorWidget()->unloadData();
|
||||||
|
if (appendNav && m_navIt != m_navList.end())
|
||||||
|
m_navList.erase(m_navIt + 1, m_navList.end());
|
||||||
if (!editor || !editor->valid())
|
if (!editor || !editor->valid())
|
||||||
{
|
{
|
||||||
m_ui.editorContents->setCurrentWidget(m_faceSvg);
|
m_ui.editorContents->setCurrentWidget(m_faceSvg);
|
||||||
updateWindowTitle();
|
updateWindowTitle();
|
||||||
|
if (appendNav)
|
||||||
|
{
|
||||||
|
m_navIt = m_navList.end();
|
||||||
|
updateNavigationButtons();
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
m_ui.editorContents->setCurrentWidget(editor);
|
m_ui.editorContents->setCurrentWidget(editor);
|
||||||
m_ui.editorContents->update();
|
m_ui.editorContents->update();
|
||||||
updateWindowTitle();
|
updateWindowTitle();
|
||||||
|
ProjectModel::INode* currentNode = editor->currentNode();
|
||||||
|
if (appendNav)
|
||||||
|
{
|
||||||
|
if (m_navIt == m_navList.end() || *m_navIt != currentNode)
|
||||||
|
m_navIt = m_navList.insert(m_navList.end(), currentNode);
|
||||||
|
updateNavigationButtons();
|
||||||
|
}
|
||||||
|
recursiveExpandAndSelectOutline(m_projectModel->index(currentNode));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MainWindow::openEditor(ProjectModel::SongGroupNode* node)
|
bool MainWindow::openEditor(ProjectModel::SongGroupNode* node, bool appendNav)
|
||||||
{
|
{
|
||||||
return _setEditor(m_songGroupEditor->loadData(node) ? m_songGroupEditor : nullptr);
|
bool ret = _setEditor(m_songGroupEditor->loadData(node) ? m_songGroupEditor : nullptr, appendNav);
|
||||||
|
if (ProjectModel::INode* n = getEditorNode())
|
||||||
|
{
|
||||||
|
if (n->type() == ProjectModel::INode::Type::SongGroup)
|
||||||
|
{
|
||||||
|
ProjectModel::SongGroupNode* cn = static_cast<ProjectModel::SongGroupNode*>(n);
|
||||||
|
ProjectModel::GroupNode* gn = m_projectModel->getGroupNode(n);
|
||||||
|
m_interactiveSeq = m_engine->seqPlay(gn->getAudioGroup(), cn->m_id, {}, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MainWindow::openEditor(ProjectModel::SoundGroupNode* node)
|
bool MainWindow::openEditor(ProjectModel::SoundGroupNode* node, bool appendNav)
|
||||||
{
|
{
|
||||||
return _setEditor(m_soundGroupEditor->loadData(node) ? m_soundGroupEditor : nullptr);
|
bool ret = _setEditor(m_soundGroupEditor->loadData(node) ? m_soundGroupEditor : nullptr, appendNav);
|
||||||
|
if (ProjectModel::INode* n = getEditorNode())
|
||||||
|
{
|
||||||
|
if (n->type() == ProjectModel::INode::Type::SoundGroup)
|
||||||
|
{
|
||||||
|
ProjectModel::SoundGroupNode* cn = static_cast<ProjectModel::SoundGroupNode*>(n);
|
||||||
|
ProjectModel::GroupNode* gn = m_projectModel->getGroupNode(n);
|
||||||
|
m_interactiveSeq = m_engine->seqPlay(gn->getAudioGroup(), cn->m_id, {}, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MainWindow::openEditor(ProjectModel::SoundMacroNode* node)
|
bool MainWindow::openEditor(ProjectModel::SoundMacroNode* node, bool appendNav)
|
||||||
{
|
{
|
||||||
return _setEditor(m_soundMacroEditor->loadData(node) ? m_soundMacroEditor : nullptr);
|
return _setEditor(m_soundMacroEditor->loadData(node) ? m_soundMacroEditor : nullptr, appendNav);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MainWindow::openEditor(ProjectModel::ADSRNode* node)
|
bool MainWindow::openEditor(ProjectModel::ADSRNode* node, bool appendNav)
|
||||||
{
|
{
|
||||||
return _setEditor(m_adsrEditor->loadData(node) ? m_adsrEditor : nullptr);
|
return _setEditor(m_adsrEditor->loadData(node) ? m_adsrEditor : nullptr, appendNav);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MainWindow::openEditor(ProjectModel::CurveNode* node)
|
bool MainWindow::openEditor(ProjectModel::CurveNode* node, bool appendNav)
|
||||||
{
|
{
|
||||||
return _setEditor(m_curveEditor->loadData(node) ? m_curveEditor : nullptr);
|
return _setEditor(m_curveEditor->loadData(node) ? m_curveEditor : nullptr, appendNav);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MainWindow::openEditor(ProjectModel::KeymapNode* node)
|
bool MainWindow::openEditor(ProjectModel::KeymapNode* node, bool appendNav)
|
||||||
{
|
{
|
||||||
return _setEditor(m_keymapEditor->loadData(node) ? m_keymapEditor : nullptr);
|
return _setEditor(m_keymapEditor->loadData(node) ? m_keymapEditor : nullptr, appendNav);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MainWindow::openEditor(ProjectModel::LayersNode* node)
|
bool MainWindow::openEditor(ProjectModel::LayersNode* node, bool appendNav)
|
||||||
{
|
{
|
||||||
return _setEditor(m_layersEditor->loadData(node) ? m_layersEditor : nullptr);
|
return _setEditor(m_layersEditor->loadData(node) ? m_layersEditor : nullptr, appendNav);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MainWindow::openEditor(ProjectModel::SampleNode* node)
|
bool MainWindow::openEditor(ProjectModel::SampleNode* node, bool appendNav)
|
||||||
{
|
{
|
||||||
return _setEditor(m_sampleEditor->loadData(node) ? m_sampleEditor : nullptr);
|
return _setEditor(m_sampleEditor->loadData(node) ? m_sampleEditor : nullptr, appendNav);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MainWindow::openEditor(ProjectModel::INode* node)
|
bool MainWindow::openEditor(ProjectModel::INode* node, bool appendNav)
|
||||||
{
|
{
|
||||||
switch (node->type())
|
switch (node->type())
|
||||||
{
|
{
|
||||||
case ProjectModel::INode::Type::SongGroup:
|
case ProjectModel::INode::Type::SongGroup:
|
||||||
return openEditor(static_cast<ProjectModel::SongGroupNode*>(node));
|
return openEditor(static_cast<ProjectModel::SongGroupNode*>(node), appendNav);
|
||||||
case ProjectModel::INode::Type::SoundGroup:
|
case ProjectModel::INode::Type::SoundGroup:
|
||||||
return openEditor(static_cast<ProjectModel::SoundGroupNode*>(node));
|
return openEditor(static_cast<ProjectModel::SoundGroupNode*>(node), appendNav);
|
||||||
case ProjectModel::INode::Type::SoundMacro:
|
case ProjectModel::INode::Type::SoundMacro:
|
||||||
return openEditor(static_cast<ProjectModel::SoundMacroNode*>(node));
|
return openEditor(static_cast<ProjectModel::SoundMacroNode*>(node), appendNav);
|
||||||
case ProjectModel::INode::Type::ADSR:
|
case ProjectModel::INode::Type::ADSR:
|
||||||
return openEditor(static_cast<ProjectModel::ADSRNode*>(node));
|
return openEditor(static_cast<ProjectModel::ADSRNode*>(node), appendNav);
|
||||||
case ProjectModel::INode::Type::Curve:
|
case ProjectModel::INode::Type::Curve:
|
||||||
return openEditor(static_cast<ProjectModel::CurveNode*>(node));
|
return openEditor(static_cast<ProjectModel::CurveNode*>(node), appendNav);
|
||||||
case ProjectModel::INode::Type::Keymap:
|
case ProjectModel::INode::Type::Keymap:
|
||||||
return openEditor(static_cast<ProjectModel::KeymapNode*>(node));
|
return openEditor(static_cast<ProjectModel::KeymapNode*>(node), appendNav);
|
||||||
case ProjectModel::INode::Type::Layer:
|
case ProjectModel::INode::Type::Layer:
|
||||||
return openEditor(static_cast<ProjectModel::LayersNode*>(node));
|
return openEditor(static_cast<ProjectModel::LayersNode*>(node), appendNav);
|
||||||
case ProjectModel::INode::Type::Sample:
|
case ProjectModel::INode::Type::Sample:
|
||||||
return openEditor(static_cast<ProjectModel::SampleNode*>(node));
|
return openEditor(static_cast<ProjectModel::SampleNode*>(node), appendNav);
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -646,7 +701,7 @@ amuse::ObjToken<amuse::Sequencer> MainWindow::startSong(amuse::GroupId groupId,
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::pushUndoCommand(QUndoCommand* cmd)
|
void MainWindow::pushUndoCommand(EditorUndoCommand* cmd)
|
||||||
{
|
{
|
||||||
m_undoStack->push(cmd);
|
m_undoStack->push(cmd);
|
||||||
}
|
}
|
||||||
|
@ -658,6 +713,21 @@ void MainWindow::updateFocus()
|
||||||
|
|
||||||
void MainWindow::aboutToDeleteNode(ProjectModel::INode* node)
|
void MainWindow::aboutToDeleteNode(ProjectModel::INode* node)
|
||||||
{
|
{
|
||||||
|
if (*m_navIt == node && m_navIt != m_navList.end())
|
||||||
|
{
|
||||||
|
m_navList.erase(m_navIt, m_navList.end());
|
||||||
|
m_navIt = m_navList.end();
|
||||||
|
}
|
||||||
|
for (auto it = m_navList.begin(); it != m_navList.end();)
|
||||||
|
{
|
||||||
|
if (*it == node)
|
||||||
|
{
|
||||||
|
it = m_navList.erase(it);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
updateNavigationButtons();
|
||||||
if (getEditorNode() == node)
|
if (getEditorNode() == node)
|
||||||
closeEditor();
|
closeEditor();
|
||||||
}
|
}
|
||||||
|
@ -813,6 +883,9 @@ void MainWindow::revertAction()
|
||||||
QString path = m_projectModel->path();
|
QString path = m_projectModel->path();
|
||||||
closeEditor();
|
closeEditor();
|
||||||
m_undoStack->clear();
|
m_undoStack->clear();
|
||||||
|
m_navList.clear();
|
||||||
|
m_navIt = m_navList.begin();
|
||||||
|
updateNavigationButtons();
|
||||||
delete m_projectModel;
|
delete m_projectModel;
|
||||||
m_projectModel = nullptr;
|
m_projectModel = nullptr;
|
||||||
openProject(path);
|
openProject(path);
|
||||||
|
@ -936,6 +1009,7 @@ void MainWindow::importAction()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
model->openSongsData();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -978,6 +1052,7 @@ void MainWindow::importAction()
|
||||||
return;
|
return;
|
||||||
task.setValue(++curVal);
|
task.setValue(++curVal);
|
||||||
}
|
}
|
||||||
|
model->openSongsData();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1024,28 +1099,178 @@ void MainWindow::exportAction()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::importHeadersAction()
|
||||||
|
{
|
||||||
|
if (!m_projectModel)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int confirm = m_mainMessenger.warning(tr("Import C Headers"),
|
||||||
|
tr("<p>Importing names from C headers depends on up-to-date, "
|
||||||
|
"consistent names relative to the sound group data.</p>"
|
||||||
|
"<p>Headers are imported on a per-subproject basis from "
|
||||||
|
"a single directory. Headers must be named with the form "
|
||||||
|
"<code><subproject>.h</code>.</p>"
|
||||||
|
"<p>Group, Song and SFX definitions are matched according to the following forms:"
|
||||||
|
"<pre>#define GRP<name> <id>\n#define SNG<name> <id>\n"
|
||||||
|
"#define SFX<name> <id></pre></p>"
|
||||||
|
"<p><strong>This operation cannot be undone! Is is recommended to "
|
||||||
|
"make a backup of the project directory before proceeding.</strong></p>"
|
||||||
|
"<p>Continue?</p>"), QMessageBox::Yes | QMessageBox::No);
|
||||||
|
if (confirm == QMessageBox::No)
|
||||||
|
return;
|
||||||
|
|
||||||
|
QString path = QFileDialog::getExistingDirectory(this, tr("Import C Headers"), m_projectModel->dir().path());
|
||||||
|
if (path.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
closeEditor();
|
||||||
|
|
||||||
|
for (QString group : m_projectModel->getGroupList())
|
||||||
|
m_projectModel->importHeader(path, group, m_mainMessenger);
|
||||||
|
m_projectModel->updateNodeNames();
|
||||||
|
|
||||||
|
/* Samples may have been renamed.. stay consistent! */
|
||||||
|
saveAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::exportHeadersAction()
|
||||||
|
{
|
||||||
|
if (!m_projectModel)
|
||||||
|
return;
|
||||||
|
|
||||||
|
QString path = QFileDialog::getExistingDirectory(this, tr("Export C Headers"), m_projectModel->dir().path());
|
||||||
|
if (path.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool yesToAll = false;
|
||||||
|
for (QString group : m_projectModel->getGroupList())
|
||||||
|
if (!m_projectModel->exportHeader(path, group, yesToAll, m_mainMessenger))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
bool TreeDelegate::editorEvent(QEvent* event,
|
bool TreeDelegate::editorEvent(QEvent* event,
|
||||||
QAbstractItemModel* _model,
|
QAbstractItemModel* __model,
|
||||||
const QStyleOptionViewItem& option,
|
const QStyleOptionViewItem& option,
|
||||||
const QModelIndex& index)
|
const QModelIndex& index)
|
||||||
{
|
{
|
||||||
ProjectModel* model = static_cast<ProjectModel*>(_model);
|
QSortFilterProxyModel* _model = static_cast<QSortFilterProxyModel*>(__model);
|
||||||
ProjectModel::INode* node = model->node(index);
|
ProjectModel* model = static_cast<ProjectModel*>(_model->sourceModel());
|
||||||
if (!node)
|
ProjectModel::INode* node = model->node(_model->mapToSource(index));
|
||||||
|
if (!node || !(node->flags() & Qt::ItemIsEditable))
|
||||||
return false;
|
return false;
|
||||||
|
AmuseItemEditFlags flags = node->editFlags();
|
||||||
|
|
||||||
#if 0
|
if (event->type() == QEvent::MouseButtonPress)
|
||||||
if ((event->type() == QEvent::MouseButtonDblClick &&
|
|
||||||
static_cast<QMouseEvent*>(event)->button() == Qt::LeftButton))
|
|
||||||
{
|
{
|
||||||
// Open in editor
|
QMouseEvent* ev = static_cast<QMouseEvent*>(event);
|
||||||
return m_window.openEditor(node);
|
if (ev->button() == Qt::RightButton)
|
||||||
|
{
|
||||||
|
m_window.m_ui.projectOutline->setCurrentIndex(index);
|
||||||
|
QMenu* menu = new QMenu(g_MainWindow->m_ui.projectOutline);
|
||||||
|
|
||||||
|
QAction* cutAction = new QAction(tr("Cut"), menu);
|
||||||
|
cutAction->setData(index);
|
||||||
|
cutAction->setEnabled(flags & AmuseItemCut);
|
||||||
|
cutAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-cut")));
|
||||||
|
cutAction->setShortcut(QKeySequence::Cut);
|
||||||
|
connect(cutAction, SIGNAL(triggered()), this, SLOT(doCut()));
|
||||||
|
menu->addAction(cutAction);
|
||||||
|
|
||||||
|
QAction* copyAction = new QAction(tr("Copy"), menu);
|
||||||
|
copyAction->setData(index);
|
||||||
|
copyAction->setEnabled(flags & AmuseItemCopy);
|
||||||
|
copyAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy")));
|
||||||
|
copyAction->setShortcut(QKeySequence::Copy);
|
||||||
|
connect(copyAction, SIGNAL(triggered()), this, SLOT(doCopy()));
|
||||||
|
menu->addAction(copyAction);
|
||||||
|
|
||||||
|
QAction* pasteAction = new QAction(tr("Paste"), menu);
|
||||||
|
pasteAction->setData(index);
|
||||||
|
pasteAction->setEnabled(g_MainWindow->m_clipboardAmuseData && flags & AmuseItemPaste);
|
||||||
|
pasteAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-paste")));
|
||||||
|
pasteAction->setShortcut(QKeySequence::Paste);
|
||||||
|
connect(pasteAction, SIGNAL(triggered()), this, SLOT(doPaste()));
|
||||||
|
menu->addAction(pasteAction);
|
||||||
|
|
||||||
|
QAction* dupeAction = new QAction(tr("Duplicate"), menu);
|
||||||
|
dupeAction->setData(index);
|
||||||
|
connect(dupeAction, SIGNAL(triggered()), this, SLOT(doDuplicate()));
|
||||||
|
menu->addAction(dupeAction);
|
||||||
|
|
||||||
|
QAction* deleteAction = new QAction(tr("Delete"), menu);
|
||||||
|
deleteAction->setData(index);
|
||||||
|
deleteAction->setEnabled(flags & AmuseItemDelete);
|
||||||
|
deleteAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete")));
|
||||||
|
deleteAction->setShortcut(QKeySequence::Delete);
|
||||||
|
connect(deleteAction, SIGNAL(triggered()), this, SLOT(doDelete()));
|
||||||
|
menu->addAction(deleteAction);
|
||||||
|
|
||||||
|
QAction* renameAction = new QAction(tr("Rename"), menu);
|
||||||
|
renameAction->setData(index);
|
||||||
|
renameAction->setShortcut(tr("F2"));
|
||||||
|
connect(renameAction, SIGNAL(triggered()), this, SLOT(doRename()));
|
||||||
|
menu->addAction(renameAction);
|
||||||
|
|
||||||
|
menu->popup(ev->globalPos());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TreeDelegate::doCut()
|
||||||
|
{
|
||||||
|
QAction* act = qobject_cast<QAction*>(sender());
|
||||||
|
if (!m_window.m_projectModel)
|
||||||
|
return;
|
||||||
|
m_window.m_projectModel->cut(m_window.m_filterProjectModel.mapToSource(act->data().toModelIndex()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TreeDelegate::doCopy()
|
||||||
|
{
|
||||||
|
QAction* act = qobject_cast<QAction*>(sender());
|
||||||
|
if (!m_window.m_projectModel)
|
||||||
|
return;
|
||||||
|
m_window.m_projectModel->copy(m_window.m_filterProjectModel.mapToSource(act->data().toModelIndex()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TreeDelegate::doPaste()
|
||||||
|
{
|
||||||
|
QAction* act = qobject_cast<QAction*>(sender());
|
||||||
|
if (!m_window.m_projectModel)
|
||||||
|
return;
|
||||||
|
m_window.m_projectModel->paste(m_window.m_filterProjectModel.mapToSource(act->data().toModelIndex()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TreeDelegate::doDuplicate()
|
||||||
|
{
|
||||||
|
QAction* act = qobject_cast<QAction*>(sender());
|
||||||
|
if (!m_window.m_projectModel)
|
||||||
|
return;
|
||||||
|
QModelIndex newIdx =
|
||||||
|
m_window.m_projectModel->duplicate(m_window.m_filterProjectModel.mapToSource(act->data().toModelIndex()));
|
||||||
|
if (newIdx.isValid())
|
||||||
|
{
|
||||||
|
newIdx = m_window.m_filterProjectModel.mapFromSource(newIdx);
|
||||||
|
m_window.m_ui.projectOutline->edit(newIdx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TreeDelegate::doDelete()
|
||||||
|
{
|
||||||
|
QAction* act = qobject_cast<QAction*>(sender());
|
||||||
|
if (!m_window.m_projectModel)
|
||||||
|
return;
|
||||||
|
m_window.m_projectModel->del(m_window.m_filterProjectModel.mapToSource(act->data().toModelIndex()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TreeDelegate::doRename()
|
||||||
|
{
|
||||||
|
QAction* act = qobject_cast<QAction*>(sender());
|
||||||
|
m_window.m_ui.projectOutline->edit(act->data().toModelIndex());
|
||||||
|
}
|
||||||
|
|
||||||
QString MainWindow::getGroupName(ProjectModel::GroupNode* group) const
|
QString MainWindow::getGroupName(ProjectModel::GroupNode* group) const
|
||||||
{
|
{
|
||||||
if (group)
|
if (group)
|
||||||
|
@ -1057,10 +1282,10 @@ ProjectModel::GroupNode* MainWindow::getSelectedGroupNode() const
|
||||||
{
|
{
|
||||||
if (!m_projectModel)
|
if (!m_projectModel)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
if (!m_ui.projectOutline->selectionModel()->currentIndex().isValid())
|
if (!m_ui.projectOutline->currentIndex().isValid())
|
||||||
return nullptr;
|
return nullptr;
|
||||||
return m_projectModel->getGroupNode(m_projectModel->node(
|
return m_projectModel->getGroupNode(m_projectModel->node(
|
||||||
m_filterProjectModel.mapToSource(m_ui.projectOutline->selectionModel()->currentIndex())));
|
m_filterProjectModel.mapToSource(m_ui.projectOutline->currentIndex())));
|
||||||
}
|
}
|
||||||
|
|
||||||
QString MainWindow::getSelectedGroupName() const
|
QString MainWindow::getSelectedGroupName() const
|
||||||
|
@ -1082,10 +1307,7 @@ void MainWindow::recursiveExpandAndSelectOutline(const QModelIndex& index) const
|
||||||
QModelIndex filterIndex = m_filterProjectModel.mapFromSource(index);
|
QModelIndex filterIndex = m_filterProjectModel.mapFromSource(index);
|
||||||
_recursiveExpandOutline(filterIndex);
|
_recursiveExpandOutline(filterIndex);
|
||||||
if (filterIndex.isValid())
|
if (filterIndex.isValid())
|
||||||
{
|
m_ui.projectOutline->setCurrentIndex(filterIndex);
|
||||||
m_ui.projectOutline->selectionModel()->setCurrentIndex(filterIndex, QItemSelectionModel::ClearAndSelect);
|
|
||||||
m_ui.projectOutline->selectionModel()->setCurrentIndex(filterIndex, QItemSelectionModel::Current);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::newSubprojectAction()
|
void MainWindow::newSubprojectAction()
|
||||||
|
@ -1238,6 +1460,30 @@ void MainWindow::newLayersAction()
|
||||||
openEditor(node);
|
openEditor(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::updateNavigationButtons()
|
||||||
|
{
|
||||||
|
m_goForward->setDisabled(m_navIt == m_navList.end() || m_navIt + 1 == m_navList.end());
|
||||||
|
m_goBack->setDisabled(m_navIt == m_navList.begin());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::goForward()
|
||||||
|
{
|
||||||
|
if (m_navIt == m_navList.end() || m_navIt + 1 == m_navList.end())
|
||||||
|
return;
|
||||||
|
++m_navIt;
|
||||||
|
openEditor(*m_navIt, false);
|
||||||
|
updateNavigationButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::goBack()
|
||||||
|
{
|
||||||
|
if (m_navIt == m_navList.begin())
|
||||||
|
return;
|
||||||
|
--m_navIt;
|
||||||
|
openEditor(*m_navIt, false);
|
||||||
|
updateNavigationButtons();
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::aboutToShowAudioIOMenu()
|
void MainWindow::aboutToShowAudioIOMenu()
|
||||||
{
|
{
|
||||||
refreshAudioIO();
|
refreshAudioIO();
|
||||||
|
@ -1413,27 +1659,30 @@ void MainWindow::auxBChanged(int vol)
|
||||||
|
|
||||||
void MainWindow::outlineCutAction()
|
void MainWindow::outlineCutAction()
|
||||||
{
|
{
|
||||||
|
if (!m_projectModel)
|
||||||
|
return;
|
||||||
|
m_projectModel->cut(m_filterProjectModel.mapToSource(m_ui.projectOutline->currentIndex()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::outlineCopyAction()
|
void MainWindow::outlineCopyAction()
|
||||||
{
|
{
|
||||||
|
if (!m_projectModel)
|
||||||
|
return;
|
||||||
|
m_projectModel->copy(m_filterProjectModel.mapToSource(m_ui.projectOutline->currentIndex()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::outlinePasteAction()
|
void MainWindow::outlinePasteAction()
|
||||||
{
|
{
|
||||||
|
if (!m_projectModel)
|
||||||
|
return;
|
||||||
|
m_projectModel->paste(m_filterProjectModel.mapToSource(m_ui.projectOutline->currentIndex()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::outlineDeleteAction()
|
void MainWindow::outlineDeleteAction()
|
||||||
{
|
{
|
||||||
if (!m_projectModel)
|
if (!m_projectModel)
|
||||||
return;
|
return;
|
||||||
QModelIndexList indexes = m_ui.projectOutline->selectionModel()->selectedIndexes();
|
m_projectModel->del(m_filterProjectModel.mapToSource(m_ui.projectOutline->currentIndex()));
|
||||||
if (indexes.empty())
|
|
||||||
return;
|
|
||||||
m_projectModel->del(m_filterProjectModel.mapToSource(indexes.front()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::onFocusChanged(QWidget* old, QWidget* now)
|
void MainWindow::onFocusChanged(QWidget* old, QWidget* now)
|
||||||
|
@ -1460,7 +1709,10 @@ void MainWindow::onFocusChanged(QWidget* old, QWidget* now)
|
||||||
|
|
||||||
if (now == m_ui.projectOutline || m_ui.projectOutline->isAncestorOf(now))
|
if (now == m_ui.projectOutline || m_ui.projectOutline->isAncestorOf(now))
|
||||||
{
|
{
|
||||||
setItemEditEnabled(canEditOutline());
|
AmuseItemEditFlags editFlags = outlineEditFlags();
|
||||||
|
if (!m_clipboardAmuseData)
|
||||||
|
editFlags = AmuseItemEditFlags(editFlags & ~AmuseItemPaste);
|
||||||
|
setItemEditFlags(editFlags);
|
||||||
if (m_projectModel)
|
if (m_projectModel)
|
||||||
{
|
{
|
||||||
m_cutConn = connect(m_ui.actionCut, SIGNAL(triggered()), this, SLOT(outlineCutAction()));
|
m_cutConn = connect(m_ui.actionCut, SIGNAL(triggered()), this, SLOT(outlineCutAction()));
|
||||||
|
@ -1471,20 +1723,40 @@ void MainWindow::onFocusChanged(QWidget* old, QWidget* now)
|
||||||
}
|
}
|
||||||
else if (now == m_ui.editorContents || m_ui.editorContents->isAncestorOf(now))
|
else if (now == m_ui.editorContents || m_ui.editorContents->isAncestorOf(now))
|
||||||
{
|
{
|
||||||
setItemEditEnabled(false);
|
|
||||||
if (EditorWidget* editor = getEditorWidget())
|
if (EditorWidget* editor = getEditorWidget())
|
||||||
{
|
{
|
||||||
if (editor->isItemEditEnabled())
|
setItemEditFlags(editor->itemEditFlags());
|
||||||
|
m_cutConn = connect(m_ui.actionCut, SIGNAL(triggered()), editor, SLOT(itemCutAction()));
|
||||||
|
m_copyConn = connect(m_ui.actionCopy, SIGNAL(triggered()), editor, SLOT(itemCopyAction()));
|
||||||
|
m_pasteConn = connect(m_ui.actionPaste, SIGNAL(triggered()), editor, SLOT(itemPasteAction()));
|
||||||
|
m_deleteConn = connect(m_ui.actionDelete, SIGNAL(triggered()), editor, SLOT(itemDeleteAction()));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setItemEditFlags(AmuseItemNone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::onClipboardChanged()
|
||||||
|
{
|
||||||
|
if (QClipboard* cb = qobject_cast<QClipboard*>(sender()))
|
||||||
|
{
|
||||||
|
if (const QMimeData* md = cb->mimeData())
|
||||||
|
{
|
||||||
|
for (const QString& str : md->formats())
|
||||||
{
|
{
|
||||||
setItemEditEnabled(true);
|
if (str.startsWith(QStringLiteral("application/x-amuse-")))
|
||||||
m_cutConn = connect(m_ui.actionCut, SIGNAL(triggered()), editor, SLOT(itemCutAction()));
|
{
|
||||||
m_copyConn = connect(m_ui.actionCopy, SIGNAL(triggered()), editor, SLOT(itemCopyAction()));
|
m_clipboardAmuseData = true;
|
||||||
m_pasteConn = connect(m_ui.actionPaste, SIGNAL(triggered()), editor, SLOT(itemPasteAction()));
|
updateFocus();
|
||||||
m_deleteConn = connect(m_ui.actionDelete, SIGNAL(triggered()), editor, SLOT(itemDeleteAction()));
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
m_clipboardAmuseData = false;
|
||||||
|
updateFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::outlineItemActivated(const QModelIndex& index)
|
void MainWindow::outlineItemActivated(const QModelIndex& index)
|
||||||
|
@ -1495,12 +1767,12 @@ void MainWindow::outlineItemActivated(const QModelIndex& index)
|
||||||
openEditor(node);
|
openEditor(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::setItemEditEnabled(bool enabled)
|
void MainWindow::setItemEditFlags(AmuseItemEditFlags flags)
|
||||||
{
|
{
|
||||||
m_ui.actionCut->setEnabled(enabled);
|
m_ui.actionCut->setEnabled(flags & AmuseItemCut);
|
||||||
m_ui.actionCopy->setEnabled(enabled);
|
m_ui.actionCopy->setEnabled(flags & AmuseItemCopy);
|
||||||
m_ui.actionPaste->setEnabled(enabled);
|
m_ui.actionPaste->setEnabled(flags & AmuseItemPaste);
|
||||||
m_ui.actionDelete->setEnabled(enabled);
|
m_ui.actionDelete->setEnabled(flags & AmuseItemDelete);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::setItemNewEnabled(bool enabled)
|
void MainWindow::setItemNewEnabled(bool enabled)
|
||||||
|
@ -1514,25 +1786,25 @@ void MainWindow::setItemNewEnabled(bool enabled)
|
||||||
m_ui.actionNew_Layers->setEnabled(enabled);
|
m_ui.actionNew_Layers->setEnabled(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MainWindow::canEditOutline()
|
AmuseItemEditFlags MainWindow::outlineEditFlags()
|
||||||
{
|
{
|
||||||
if (!m_projectModel)
|
if (!m_projectModel)
|
||||||
return false;
|
return AmuseItemNone;
|
||||||
QModelIndex curIndex = m_ui.projectOutline->selectionModel()->currentIndex();
|
QModelIndex curIndex = m_ui.projectOutline->currentIndex();
|
||||||
if (!curIndex.isValid())
|
if (!curIndex.isValid())
|
||||||
return false;
|
return AmuseItemNone;
|
||||||
return m_projectModel->canEdit(m_filterProjectModel.mapToSource(curIndex));
|
return m_projectModel->editFlags(m_filterProjectModel.mapToSource(curIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::onOutlineSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected)
|
void MainWindow::onOutlineSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected)
|
||||||
{
|
{
|
||||||
if (!m_projectModel)
|
if (!m_projectModel)
|
||||||
return;
|
return;
|
||||||
setItemNewEnabled(m_ui.projectOutline->selectionModel()->currentIndex().isValid());
|
setItemNewEnabled(m_ui.projectOutline->currentIndex().isValid());
|
||||||
if (selected.indexes().empty())
|
if (selected.indexes().empty())
|
||||||
setItemEditEnabled(false);
|
setItemEditFlags(AmuseItemNone);
|
||||||
else
|
else
|
||||||
setItemEditEnabled(m_projectModel->canEdit(m_filterProjectModel.mapToSource(selected.indexes().front())));
|
setItemEditFlags(m_projectModel->editFlags(m_filterProjectModel.mapToSource(selected.indexes().front())));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::onTextSelect()
|
void MainWindow::onTextSelect()
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <QStyledItemDelegate>
|
#include <QStyledItemDelegate>
|
||||||
#include <QSortFilterProxyModel>
|
#include <QSortFilterProxyModel>
|
||||||
|
#include <QLinkedList>
|
||||||
#include "ui_MainWindow.h"
|
#include "ui_MainWindow.h"
|
||||||
#include "amuse/Engine.hpp"
|
#include "amuse/Engine.hpp"
|
||||||
#include "amuse/BooBackend.hpp"
|
#include "amuse/BooBackend.hpp"
|
||||||
|
@ -77,13 +78,27 @@ public:
|
||||||
QAbstractItemModel *model,
|
QAbstractItemModel *model,
|
||||||
const QStyleOptionViewItem &option,
|
const QStyleOptionViewItem &option,
|
||||||
const QModelIndex &index);
|
const QModelIndex &index);
|
||||||
|
public slots:
|
||||||
|
void doCut();
|
||||||
|
void doCopy();
|
||||||
|
void doPaste();
|
||||||
|
void doDuplicate();
|
||||||
|
void doDelete();
|
||||||
|
void doRename();
|
||||||
};
|
};
|
||||||
|
|
||||||
class MainWindow : public QMainWindow
|
class MainWindow : public QMainWindow
|
||||||
{
|
{
|
||||||
friend class MIDIReader;
|
friend class MIDIReader;
|
||||||
|
friend class ProjectModel;
|
||||||
|
friend class GroupNodeUndoCommand;
|
||||||
|
friend class TreeDelegate;
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Ui::MainWindow m_ui;
|
Ui::MainWindow m_ui;
|
||||||
|
QAction* m_goBack;
|
||||||
|
QAction* m_goForward;
|
||||||
|
QLinkedList<ProjectModel::INode*> m_navList;
|
||||||
|
QLinkedList<ProjectModel::INode*>::iterator m_navIt;
|
||||||
QAction* m_clearRecentFileAct;
|
QAction* m_clearRecentFileAct;
|
||||||
QAction* m_recentFileActs[MaxRecentFiles];
|
QAction* m_recentFileActs[MaxRecentFiles];
|
||||||
TreeDelegate m_treeDelegate;
|
TreeDelegate m_treeDelegate;
|
||||||
|
@ -105,12 +120,14 @@ class MainWindow : public QMainWindow
|
||||||
std::unique_ptr<VoiceAllocator> m_voxAllocator;
|
std::unique_ptr<VoiceAllocator> m_voxAllocator;
|
||||||
std::unique_ptr<amuse::Engine> m_engine;
|
std::unique_ptr<amuse::Engine> m_engine;
|
||||||
amuse::ObjToken<amuse::Voice> m_lastSound;
|
amuse::ObjToken<amuse::Voice> m_lastSound;
|
||||||
|
amuse::ObjToken<amuse::Sequencer> m_interactiveSeq;
|
||||||
int m_velocity = 90;
|
int m_velocity = 90;
|
||||||
float m_pitch = 0.f;
|
float m_pitch = 0.f;
|
||||||
int8_t m_ctrlVals[128] = {};
|
int8_t m_ctrlVals[128] = {};
|
||||||
float m_auxAVol = 0.f;
|
float m_auxAVol = 0.f;
|
||||||
float m_auxBVol = 0.f;
|
float m_auxBVol = 0.f;
|
||||||
bool m_uiDisabled = false;
|
bool m_uiDisabled = false;
|
||||||
|
bool m_clipboardAmuseData = false;
|
||||||
|
|
||||||
QUndoStack* m_undoStack;
|
QUndoStack* m_undoStack;
|
||||||
|
|
||||||
|
@ -128,6 +145,7 @@ class MainWindow : public QMainWindow
|
||||||
|
|
||||||
void updateWindowTitle();
|
void updateWindowTitle();
|
||||||
void updateRecentFileActions();
|
void updateRecentFileActions();
|
||||||
|
void updateNavigationButtons();
|
||||||
bool setProjectPath(const QString& path);
|
bool setProjectPath(const QString& path);
|
||||||
void refreshAudioIO();
|
void refreshAudioIO();
|
||||||
void refreshMIDIIO();
|
void refreshMIDIIO();
|
||||||
|
@ -139,7 +157,7 @@ class MainWindow : public QMainWindow
|
||||||
void startBackgroundTask(int id, const QString& windowTitle, const QString& label,
|
void startBackgroundTask(int id, const QString& windowTitle, const QString& label,
|
||||||
std::function<void(BackgroundTask&)>&& task);
|
std::function<void(BackgroundTask&)>&& task);
|
||||||
|
|
||||||
bool _setEditor(EditorWidget* widget);
|
bool _setEditor(EditorWidget* widget, bool appendNav = true);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit MainWindow(QWidget* parent = Q_NULLPTR);
|
explicit MainWindow(QWidget* parent = Q_NULLPTR);
|
||||||
|
@ -147,15 +165,15 @@ public:
|
||||||
|
|
||||||
bool openProject(const QString& path);
|
bool openProject(const QString& path);
|
||||||
|
|
||||||
bool openEditor(ProjectModel::SongGroupNode* node);
|
bool openEditor(ProjectModel::SongGroupNode* node, bool appendNav = true);
|
||||||
bool openEditor(ProjectModel::SoundGroupNode* node);
|
bool openEditor(ProjectModel::SoundGroupNode* node, bool appendNav = true);
|
||||||
bool openEditor(ProjectModel::SoundMacroNode* node);
|
bool openEditor(ProjectModel::SoundMacroNode* node, bool appendNav = true);
|
||||||
bool openEditor(ProjectModel::ADSRNode* node);
|
bool openEditor(ProjectModel::ADSRNode* node, bool appendNav = true);
|
||||||
bool openEditor(ProjectModel::CurveNode* node);
|
bool openEditor(ProjectModel::CurveNode* node, bool appendNav = true);
|
||||||
bool openEditor(ProjectModel::KeymapNode* node);
|
bool openEditor(ProjectModel::KeymapNode* node, bool appendNav = true);
|
||||||
bool openEditor(ProjectModel::LayersNode* node);
|
bool openEditor(ProjectModel::LayersNode* node, bool appendNav = true);
|
||||||
bool openEditor(ProjectModel::SampleNode* node);
|
bool openEditor(ProjectModel::SampleNode* node, bool appendNav = true);
|
||||||
bool openEditor(ProjectModel::INode* node);
|
bool openEditor(ProjectModel::INode* node, bool appendNav = true);
|
||||||
void closeEditor();
|
void closeEditor();
|
||||||
|
|
||||||
ProjectModel::INode* getEditorNode() const;
|
ProjectModel::INode* getEditorNode() const;
|
||||||
|
@ -164,7 +182,7 @@ public:
|
||||||
amuse::ObjToken<amuse::Voice> startSFX(amuse::GroupId groupId, amuse::SFXId sfxId);
|
amuse::ObjToken<amuse::Voice> startSFX(amuse::GroupId groupId, amuse::SFXId sfxId);
|
||||||
amuse::ObjToken<amuse::Sequencer> startSong(amuse::GroupId groupId, amuse::SongId songId,
|
amuse::ObjToken<amuse::Sequencer> startSong(amuse::GroupId groupId, amuse::SongId songId,
|
||||||
const unsigned char* arrData);
|
const unsigned char* arrData);
|
||||||
void pushUndoCommand(QUndoCommand* cmd);
|
void pushUndoCommand(EditorUndoCommand* cmd);
|
||||||
void updateFocus();
|
void updateFocus();
|
||||||
void aboutToDeleteNode(ProjectModel::INode* node);
|
void aboutToDeleteNode(ProjectModel::INode* node);
|
||||||
void closeEvent(QCloseEvent* ev);
|
void closeEvent(QCloseEvent* ev);
|
||||||
|
@ -179,6 +197,11 @@ public:
|
||||||
ProjectModel* projectModel() const { return m_projectModel; }
|
ProjectModel* projectModel() const { return m_projectModel; }
|
||||||
UIMessenger& uiMessenger() { return m_mainMessenger; }
|
UIMessenger& uiMessenger() { return m_mainMessenger; }
|
||||||
|
|
||||||
|
void setItemEditFlags(AmuseItemEditFlags flags);
|
||||||
|
void setItemNewEnabled(bool enabled);
|
||||||
|
AmuseItemEditFlags outlineEditFlags();
|
||||||
|
bool isUiDisabled() const { return m_uiDisabled; }
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void newAction();
|
void newAction();
|
||||||
void openAction();
|
void openAction();
|
||||||
|
@ -190,6 +213,8 @@ public slots:
|
||||||
void importAction();
|
void importAction();
|
||||||
void importSongsAction();
|
void importSongsAction();
|
||||||
void exportAction();
|
void exportAction();
|
||||||
|
void importHeadersAction();
|
||||||
|
void exportHeadersAction();
|
||||||
|
|
||||||
void newSubprojectAction();
|
void newSubprojectAction();
|
||||||
void newSFXGroupAction();
|
void newSFXGroupAction();
|
||||||
|
@ -200,6 +225,9 @@ public slots:
|
||||||
void newKeymapAction();
|
void newKeymapAction();
|
||||||
void newLayersAction();
|
void newLayersAction();
|
||||||
|
|
||||||
|
void goForward();
|
||||||
|
void goBack();
|
||||||
|
|
||||||
void aboutToShowAudioIOMenu();
|
void aboutToShowAudioIOMenu();
|
||||||
void aboutToShowMIDIIOMenu();
|
void aboutToShowMIDIIOMenu();
|
||||||
|
|
||||||
|
@ -226,10 +254,8 @@ public slots:
|
||||||
void outlineDeleteAction();
|
void outlineDeleteAction();
|
||||||
|
|
||||||
void onFocusChanged(QWidget* old, QWidget* now);
|
void onFocusChanged(QWidget* old, QWidget* now);
|
||||||
|
void onClipboardChanged();
|
||||||
void outlineItemActivated(const QModelIndex& index);
|
void outlineItemActivated(const QModelIndex& index);
|
||||||
void setItemEditEnabled(bool enabled);
|
|
||||||
void setItemNewEnabled(bool enabled);
|
|
||||||
bool canEditOutline();
|
|
||||||
void onOutlineSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected);
|
void onOutlineSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected);
|
||||||
void onTextSelect();
|
void onTextSelect();
|
||||||
void onTextDelete();
|
void onTextDelete();
|
||||||
|
|
|
@ -53,26 +53,59 @@
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLineEdit" name="projectOutlineFilter">
|
<layout class="QHBoxLayout" name="outlineLayout2">
|
||||||
<property name="sizePolicy">
|
<property name="spacing">
|
||||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
<number>0</number>
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="minimumSize">
|
<item>
|
||||||
<size>
|
<widget class="QLineEdit" name="projectOutlineFilter">
|
||||||
<width>200</width>
|
<property name="sizePolicy">
|
||||||
<height>0</height>
|
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||||
</size>
|
<horstretch>0</horstretch>
|
||||||
</property>
|
<verstretch>0</verstretch>
|
||||||
<property name="placeholderText">
|
</sizepolicy>
|
||||||
<string>Filter</string>
|
</property>
|
||||||
</property>
|
<property name="minimumSize">
|
||||||
<property name="clearButtonEnabled">
|
<size>
|
||||||
<bool>true</bool>
|
<width>200</width>
|
||||||
</property>
|
<height>0</height>
|
||||||
</widget>
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="placeholderText">
|
||||||
|
<string>Filter</string>
|
||||||
|
</property>
|
||||||
|
<property name="clearButtonEnabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="backButton">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="resources/resources.qrc">
|
||||||
|
<normaloff>:/icons/IconBack.svg</normaloff>
|
||||||
|
<disabledoff>:/icons/IconBackDisabled.svg</disabledoff>
|
||||||
|
<disabledon>:/icons/IconBackDisabled.svg</disabledon>:/icons/IconBack.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="forwardButton">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="resources/resources.qrc">
|
||||||
|
<normaloff>:/icons/IconForward.svg</normaloff>
|
||||||
|
<disabledoff>:/icons/IconForwardDisabled.svg</disabledoff>
|
||||||
|
<disabledon>:/icons/IconForwardDisabled.svg</disabledon>:/icons/IconForward.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QTreeView" name="projectOutline">
|
<widget class="QTreeView" name="projectOutline">
|
||||||
|
@ -91,6 +124,9 @@
|
||||||
<property name="focusPolicy">
|
<property name="focusPolicy">
|
||||||
<enum>Qt::WheelFocus</enum>
|
<enum>Qt::WheelFocus</enum>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="editTriggers">
|
||||||
|
<set>QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked</set>
|
||||||
|
</property>
|
||||||
<property name="animated">
|
<property name="animated">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
|
@ -257,7 +293,7 @@
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>1501</width>
|
<width>1501</width>
|
||||||
<height>83</height>
|
<height>85</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
|
@ -294,7 +330,7 @@
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>1360</width>
|
<width>1360</width>
|
||||||
<height>25</height>
|
<height>27</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QMenu" name="menuFile">
|
<widget class="QMenu" name="menuFile">
|
||||||
|
@ -308,6 +344,9 @@
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Recent &Projects</string>
|
<string>Recent &Projects</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset theme="document-open-recent"/>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
<addaction name="actionNew_Project"/>
|
<addaction name="actionNew_Project"/>
|
||||||
<addaction name="actionOpen_Project"/>
|
<addaction name="actionOpen_Project"/>
|
||||||
|
@ -319,6 +358,10 @@
|
||||||
<addaction name="actionImport_Groups"/>
|
<addaction name="actionImport_Groups"/>
|
||||||
<addaction name="actionImport_Songs"/>
|
<addaction name="actionImport_Songs"/>
|
||||||
<addaction name="actionExport_GameCube_Groups"/>
|
<addaction name="actionExport_GameCube_Groups"/>
|
||||||
|
<addaction name="actionImport_C_Headers"/>
|
||||||
|
<addaction name="actionExport_C_Headers"/>
|
||||||
|
<addaction name="separator"/>
|
||||||
|
<addaction name="actionQuit"/>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QMenu" name="menuProject">
|
<widget class="QMenu" name="menuProject">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
|
@ -360,7 +403,7 @@
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QMenu" name="menuHelp">
|
<widget class="QMenu" name="menuHelp">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Help</string>
|
<string>&Help</string>
|
||||||
</property>
|
</property>
|
||||||
<addaction name="actionAbout_Amuse"/>
|
<addaction name="actionAbout_Amuse"/>
|
||||||
<addaction name="actionAbout_Qt"/>
|
<addaction name="actionAbout_Qt"/>
|
||||||
|
@ -374,11 +417,17 @@
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="StatusBarWidget" name="statusbar"/>
|
<widget class="StatusBarWidget" name="statusbar"/>
|
||||||
<action name="actionNew_Project">
|
<action name="actionNew_Project">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset theme="document-new"/>
|
||||||
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&New Project</string>
|
<string>&New Project</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionOpen_Project">
|
<action name="actionOpen_Project">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset theme="document-open"/>
|
||||||
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Open Project</string>
|
<string>&Open Project</string>
|
||||||
</property>
|
</property>
|
||||||
|
@ -387,6 +436,9 @@
|
||||||
<property name="enabled">
|
<property name="enabled">
|
||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset theme="edit-cut"/>
|
||||||
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Cut</string>
|
<string>&Cut</string>
|
||||||
</property>
|
</property>
|
||||||
|
@ -395,6 +447,9 @@
|
||||||
<property name="enabled">
|
<property name="enabled">
|
||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset theme="edit-copy"/>
|
||||||
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>C&opy</string>
|
<string>C&opy</string>
|
||||||
</property>
|
</property>
|
||||||
|
@ -403,6 +458,9 @@
|
||||||
<property name="enabled">
|
<property name="enabled">
|
||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset theme="edit-paste"/>
|
||||||
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Paste</string>
|
<string>&Paste</string>
|
||||||
</property>
|
</property>
|
||||||
|
@ -411,6 +469,9 @@
|
||||||
<property name="enabled">
|
<property name="enabled">
|
||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset theme="edit-delete"/>
|
||||||
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Delete</string>
|
<string>&Delete</string>
|
||||||
</property>
|
</property>
|
||||||
|
@ -550,6 +611,9 @@
|
||||||
<property name="enabled">
|
<property name="enabled">
|
||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset theme="document-save"/>
|
||||||
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Save Project</string>
|
<string>&Save Project</string>
|
||||||
</property>
|
</property>
|
||||||
|
@ -558,6 +622,9 @@
|
||||||
<property name="enabled">
|
<property name="enabled">
|
||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset theme="document-revert"/>
|
||||||
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Revert Project</string>
|
<string>&Revert Project</string>
|
||||||
</property>
|
</property>
|
||||||
|
@ -579,21 +646,54 @@
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionAbout_Amuse">
|
<action name="actionAbout_Amuse">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset theme="help-about"/>
|
||||||
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>About Amuse</string>
|
<string>&About Amuse</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="menuRole">
|
<property name="menuRole">
|
||||||
<enum>QAction::AboutRole</enum>
|
<enum>QAction::AboutRole</enum>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionAbout_Qt">
|
<action name="actionAbout_Qt">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset theme="help-about"/>
|
||||||
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>About Qt</string>
|
<string>About &Qt</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="menuRole">
|
<property name="menuRole">
|
||||||
<enum>QAction::AboutQtRole</enum>
|
<enum>QAction::AboutQtRole</enum>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="actionExport_C_Headers">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Export &C Headers</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionQuit">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset theme="application-exit"/>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>&Quit</string>
|
||||||
|
</property>
|
||||||
|
<property name="menuRole">
|
||||||
|
<enum>QAction::QuitRole</enum>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionImport_C_Headers">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Import C &Headers</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<customwidgets>
|
<customwidgets>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
|
|
|
@ -3,10 +3,14 @@
|
||||||
#include "ProjectModel.hpp"
|
#include "ProjectModel.hpp"
|
||||||
#include "Common.hpp"
|
#include "Common.hpp"
|
||||||
#include "athena/YAMLDocWriter.hpp"
|
#include "athena/YAMLDocWriter.hpp"
|
||||||
|
#include "athena/VectorWriter.hpp"
|
||||||
#include "MainWindow.hpp"
|
#include "MainWindow.hpp"
|
||||||
|
#include "EditorWidget.hpp"
|
||||||
#include "amuse/SongConverter.hpp"
|
#include "amuse/SongConverter.hpp"
|
||||||
#include "amuse/ContainerRegistry.hpp"
|
#include "amuse/ContainerRegistry.hpp"
|
||||||
#include <QUndoCommand>
|
#include <QDate>
|
||||||
|
#include <QMimeData>
|
||||||
|
#include <QClipboard>
|
||||||
|
|
||||||
QIcon ProjectModel::GroupNode::Icon;
|
QIcon ProjectModel::GroupNode::Icon;
|
||||||
QIcon ProjectModel::SongGroupNode::Icon;
|
QIcon ProjectModel::SongGroupNode::Icon;
|
||||||
|
@ -305,6 +309,16 @@ ProjectModel::INode::INode(const QString& name)
|
||||||
m_nullChild = nullNode.get();
|
m_nullChild = nullNode.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ProjectModel::INode::_sortChildren()
|
||||||
|
{
|
||||||
|
std::sort(m_children.begin(), m_children.end(),
|
||||||
|
[](const auto& a, const auto& b)
|
||||||
|
{
|
||||||
|
return a->name() < b->name();
|
||||||
|
});
|
||||||
|
reindexRows(0);
|
||||||
|
}
|
||||||
|
|
||||||
int ProjectModel::INode::hypotheticalIndex(const QString& name) const
|
int ProjectModel::INode::hypotheticalIndex(const QString& name) const
|
||||||
{
|
{
|
||||||
auto search = std::lower_bound(m_children.cbegin(), m_children.cend(), name,
|
auto search = std::lower_bound(m_children.cbegin(), m_children.cend(), name,
|
||||||
|
@ -326,6 +340,17 @@ int ProjectModel::GroupNode::hypotheticalIndex(const QString& name) const
|
||||||
return int(search - m_children.cbegin());
|
return int(search - m_children.cbegin());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ProjectModel::GroupNode::_sortChildren()
|
||||||
|
{
|
||||||
|
/* Sort prior to pool object collections */
|
||||||
|
std::sort(m_children.begin(), m_children.end() - 6,
|
||||||
|
[](const auto& a, const auto& b)
|
||||||
|
{
|
||||||
|
return a->name() < b->name();
|
||||||
|
});
|
||||||
|
reindexRows(0);
|
||||||
|
}
|
||||||
|
|
||||||
ProjectModel::CollectionNode* ProjectModel::GroupNode::getCollectionOfType(Type tp) const
|
ProjectModel::CollectionNode* ProjectModel::GroupNode::getCollectionOfType(Type tp) const
|
||||||
{
|
{
|
||||||
for (auto it = m_children.rbegin(); it != m_children.rend(); ++it)
|
for (auto it = m_children.rbegin(); it != m_children.rend(); ++it)
|
||||||
|
@ -624,6 +649,104 @@ bool ProjectModel::exportGroup(const QString& path, const QString& groupName, UI
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ProjectModel::importHeader(const QString& path, const QString& groupName, UIMessenger& messenger) const
|
||||||
|
{
|
||||||
|
m_projectDatabase.setIdDatabases();
|
||||||
|
auto search = m_groups.find(groupName);
|
||||||
|
if (search == m_groups.cend())
|
||||||
|
{
|
||||||
|
messenger.critical(tr("Import Error"), tr("Unable to find group %1").arg(groupName));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFileInfo fi(QDir(path), QStringLiteral("%1.h").arg(groupName));
|
||||||
|
if (!fi.exists())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
QFile fo(fi.filePath());
|
||||||
|
if (!fo.open(QFile::ReadOnly))
|
||||||
|
{
|
||||||
|
messenger.critical(tr("Export Header Error"), tr("Unable to open %1 for reading").arg(fi.filePath()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto data = fo.readAll();
|
||||||
|
search->second->importCHeader(std::string_view(data.data(), data.size()));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProjectModel::exportHeader(const QString& path, const QString& groupName,
|
||||||
|
bool& yesToAll, UIMessenger& messenger) const
|
||||||
|
{
|
||||||
|
m_projectDatabase.setIdDatabases();
|
||||||
|
auto search = m_groups.find(groupName);
|
||||||
|
if (search == m_groups.cend())
|
||||||
|
{
|
||||||
|
messenger.critical(tr("Export Error"), tr("Unable to find group %1").arg(groupName));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFileInfo fi(QDir(path), QStringLiteral("%1.h").arg(groupName));
|
||||||
|
if (!yesToAll && fi.exists())
|
||||||
|
{
|
||||||
|
auto result = messenger.question(tr("File Exists"),
|
||||||
|
tr("%1 already exists. Overwrite?").arg(fi.filePath()),
|
||||||
|
QMessageBox::Yes | QMessageBox::No | QMessageBox::YesToAll | QMessageBox::NoToAll);
|
||||||
|
if (result == QMessageBox::No)
|
||||||
|
return true;
|
||||||
|
else if (result == QMessageBox::NoToAll)
|
||||||
|
return false;
|
||||||
|
else if (result == QMessageBox::YesToAll)
|
||||||
|
yesToAll = true;
|
||||||
|
}
|
||||||
|
std::string header = search->second->exportCHeader(m_dir.dirName().toUtf8().data(), groupName.toUtf8().data());
|
||||||
|
QFile fo(fi.filePath());
|
||||||
|
if (fo.open(QFile::WriteOnly))
|
||||||
|
fo.write(header.data(), header.size());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectModel::updateNodeNames()
|
||||||
|
{
|
||||||
|
beginResetModel();
|
||||||
|
m_root->oneLevelTraverse([this](INode* n)
|
||||||
|
{
|
||||||
|
GroupNode* gn = static_cast<GroupNode*>(n);
|
||||||
|
setIdDatabases(gn);
|
||||||
|
gn->oneLevelTraverse([](INode* n)
|
||||||
|
{
|
||||||
|
if (n->type() == INode::Type::SongGroup)
|
||||||
|
{
|
||||||
|
SongGroupNode* sgn = static_cast<SongGroupNode*>(n);
|
||||||
|
sgn->m_name = amuse::GroupId::CurNameDB->resolveNameFromId(sgn->m_id).data();
|
||||||
|
}
|
||||||
|
else if (n->type() == INode::Type::SoundGroup)
|
||||||
|
{
|
||||||
|
SoundGroupNode* sgn = static_cast<SoundGroupNode*>(n);
|
||||||
|
sgn->m_name = amuse::GroupId::CurNameDB->resolveNameFromId(sgn->m_id).data();
|
||||||
|
}
|
||||||
|
else if (n->type() == INode::Type::Collection)
|
||||||
|
{
|
||||||
|
CollectionNode* cn = static_cast<CollectionNode*>(n);
|
||||||
|
cn->oneLevelTraverse([](INode* n)
|
||||||
|
{
|
||||||
|
BasePoolObjectNode* on = static_cast<BasePoolObjectNode*>(n);
|
||||||
|
on->m_name = on->getNameDb()->resolveNameFromId(on->m_id).data();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
cn->_sortChildren();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
gn->_sortChildren();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
m_root->_sortChildren();
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
void ProjectModel::_buildGroupNodeCollections(GroupNode& gn)
|
void ProjectModel::_buildGroupNodeCollections(GroupNode& gn)
|
||||||
{
|
{
|
||||||
gn.reserve(6);
|
gn.reserve(6);
|
||||||
|
@ -635,9 +758,8 @@ void ProjectModel::_buildGroupNodeCollections(GroupNode& gn)
|
||||||
gn._appendChild<CollectionNode>(tr("Samples"), QIcon(":/icons/IconSample.svg"), INode::Type::Sample);
|
gn._appendChild<CollectionNode>(tr("Samples"), QIcon(":/icons/IconSample.svg"), INode::Type::Sample);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProjectModel::_buildGroupNode(GroupNode& gn)
|
void ProjectModel::_buildGroupNode(GroupNode& gn, amuse::AudioGroup& group)
|
||||||
{
|
{
|
||||||
amuse::AudioGroup& group = *gn.m_it->second;
|
|
||||||
auto& songGroups = group.getProj().songGroups();
|
auto& songGroups = group.getProj().songGroups();
|
||||||
auto& sfxGroups = group.getProj().sfxGroups();
|
auto& sfxGroups = group.getProj().sfxGroups();
|
||||||
auto& soundMacros = group.getPool().soundMacros();
|
auto& soundMacros = group.getPool().soundMacros();
|
||||||
|
@ -720,7 +842,7 @@ void ProjectModel::_resetModelData()
|
||||||
{
|
{
|
||||||
it->second->setIdDatabases();
|
it->second->setIdDatabases();
|
||||||
GroupNode& gn = m_root->makeChild<GroupNode>(it);
|
GroupNode& gn = m_root->makeChild<GroupNode>(it);
|
||||||
_buildGroupNode(gn);
|
_buildGroupNode(gn, *gn.m_it->second);
|
||||||
}
|
}
|
||||||
endResetModel();
|
endResetModel();
|
||||||
}
|
}
|
||||||
|
@ -835,6 +957,7 @@ QVariant ProjectModel::data(const QModelIndex& index, int role) const
|
||||||
switch (role)
|
switch (role)
|
||||||
{
|
{
|
||||||
case Qt::DisplayRole:
|
case Qt::DisplayRole:
|
||||||
|
case Qt::EditRole:
|
||||||
return item->text();
|
return item->text();
|
||||||
case Qt::DecorationRole:
|
case Qt::DecorationRole:
|
||||||
return item->icon();
|
return item->icon();
|
||||||
|
@ -843,6 +966,87 @@ QVariant ProjectModel::data(const QModelIndex& index, int role) const
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class RenameNodeUndoCommand : public EditorUndoCommand
|
||||||
|
{
|
||||||
|
QString m_redoVal, m_undoVal;
|
||||||
|
public:
|
||||||
|
RenameNodeUndoCommand(const QString& text, ProjectModel::INode* node, const QString& redoVal)
|
||||||
|
: EditorUndoCommand(node, text.arg(node->name())), m_redoVal(redoVal) {}
|
||||||
|
void undo()
|
||||||
|
{
|
||||||
|
g_MainWindow->projectModel()->_renameNode(m_node.get(), m_undoVal);
|
||||||
|
}
|
||||||
|
void redo()
|
||||||
|
{
|
||||||
|
m_undoVal = m_node->name();
|
||||||
|
g_MainWindow->projectModel()->_renameNode(m_node.get(), m_redoVal);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void ProjectModel::_renameNode(INode* node, const QString& name)
|
||||||
|
{
|
||||||
|
INode* parent = node->parent();
|
||||||
|
if (!parent)
|
||||||
|
return;
|
||||||
|
if (parent->findChild(name))
|
||||||
|
return;
|
||||||
|
|
||||||
|
QString oldName = node->m_name;
|
||||||
|
int oldIdx = parent->hypotheticalIndex(oldName);
|
||||||
|
int newIdx = parent->hypotheticalIndex(name);
|
||||||
|
bool moving = beginMoveRows(index(parent), oldIdx, oldIdx, index(parent), newIdx);
|
||||||
|
node->m_name = name;
|
||||||
|
parent->_sortChildren();
|
||||||
|
switch (node->type())
|
||||||
|
{
|
||||||
|
case INode::Type::Group:
|
||||||
|
m_dir.rename(oldName, name);
|
||||||
|
break;
|
||||||
|
case INode::Type::Sample:
|
||||||
|
{
|
||||||
|
ProjectModel::GroupNode* group = getGroupNode(node);
|
||||||
|
auto utf8Name = name.toUtf8();
|
||||||
|
group->getAudioGroup()->renameSample(static_cast<SampleNode*>(node)->id(),
|
||||||
|
std::string_view(utf8Name.data(), utf8Name.length()));
|
||||||
|
g_MainWindow->saveAction();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
QModelIndex idx = index(node);
|
||||||
|
emit dataChanged(idx, idx, {Qt::DisplayRole, Qt::EditRole});
|
||||||
|
if (g_MainWindow->getEditorNode() == node)
|
||||||
|
g_MainWindow->updateWindowTitle();
|
||||||
|
if (moving)
|
||||||
|
endMoveRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProjectModel::setData(const QModelIndex& index, const QVariant& value, int role)
|
||||||
|
{
|
||||||
|
if (!index.isValid() || role != Qt::EditRole)
|
||||||
|
return false;
|
||||||
|
assert(index.model() == this && "Not ProjectModel");
|
||||||
|
|
||||||
|
INode* item = static_cast<INode*>(index.internalPointer());
|
||||||
|
INode* parent = item->parent();
|
||||||
|
if (!parent)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (item->name() == value.toString())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (parent->findChild(value.toString()))
|
||||||
|
{
|
||||||
|
g_MainWindow->uiMessenger().critical(tr("Naming Conflict"),
|
||||||
|
tr("%1 already exists in this context").arg(value.toString()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_MainWindow->pushUndoCommand(new RenameNodeUndoCommand(tr("Rename %1"), item, value.toString()));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
Qt::ItemFlags ProjectModel::flags(const QModelIndex& index) const
|
Qt::ItemFlags ProjectModel::flags(const QModelIndex& index) const
|
||||||
{
|
{
|
||||||
if (!index.isValid())
|
if (!index.isValid())
|
||||||
|
@ -869,12 +1073,12 @@ ProjectModel::GroupNode* ProjectModel::getGroupNode(INode* node) const
|
||||||
return getGroupNode(node->parent());
|
return getGroupNode(node->parent());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ProjectModel::canEdit(const QModelIndex& index) const
|
AmuseItemEditFlags ProjectModel::editFlags(const QModelIndex& index) const
|
||||||
{
|
{
|
||||||
if (!index.isValid())
|
if (!index.isValid())
|
||||||
return false;
|
return AmuseItemNone;
|
||||||
assert(index.model() == this && "Not ProjectModel");
|
assert(index.model() == this && "Not ProjectModel");
|
||||||
return (static_cast<INode*>(index.internalPointer())->flags() & Qt::ItemIsSelectable) != Qt::NoItemFlags;
|
return static_cast<INode*>(index.internalPointer())->editFlags();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProjectModel::_postAddNode(INode* n, const NameUndoRegistry& registry)
|
void ProjectModel::_postAddNode(INode* n, const NameUndoRegistry& registry)
|
||||||
|
@ -897,31 +1101,34 @@ void ProjectModel::_preDelNode(INode* n, NameUndoRegistry& registry)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class GroupNodeUndoCommand : public QUndoCommand
|
class GroupNodeUndoCommand : public EditorUndoCommand
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
std::unique_ptr<amuse::AudioGroupDatabase> m_data;
|
std::unique_ptr<amuse::AudioGroupDatabase> m_data;
|
||||||
amuse::ObjToken<ProjectModel::GroupNode> m_node;
|
|
||||||
ProjectModel::NameUndoRegistry m_nameReg;
|
ProjectModel::NameUndoRegistry m_nameReg;
|
||||||
void add()
|
void add()
|
||||||
{
|
{
|
||||||
g_MainWindow->projectModel()->_addNode(m_node.get(), std::move(m_data), m_nameReg);
|
g_MainWindow->projectModel()->_addNode(static_cast<ProjectModel::GroupNode*>(m_node.get()), std::move(m_data), m_nameReg);
|
||||||
g_MainWindow->recursiveExpandAndSelectOutline(g_MainWindow->projectModel()->index(m_node.get()));
|
g_MainWindow->recursiveExpandAndSelectOutline(g_MainWindow->projectModel()->index(m_node.get()));
|
||||||
}
|
}
|
||||||
void del()
|
void del()
|
||||||
{
|
{
|
||||||
m_data = g_MainWindow->projectModel()->_delNode(m_node.get(), m_nameReg);
|
m_data = g_MainWindow->projectModel()->_delNode(static_cast<ProjectModel::GroupNode*>(m_node.get()), m_nameReg);
|
||||||
|
setObsolete(true);
|
||||||
|
g_MainWindow->m_undoStack->clear();
|
||||||
}
|
}
|
||||||
public:
|
public:
|
||||||
explicit GroupNodeUndoCommand(const QString& text, std::unique_ptr<amuse::AudioGroupDatabase>&& data, ProjectModel::GroupNode* node)
|
explicit GroupNodeUndoCommand(const QString& text, std::unique_ptr<amuse::AudioGroupDatabase>&& data,
|
||||||
: QUndoCommand(text.arg(node->text())), m_data(std::move(data)), m_node(node) {}
|
ProjectModel::GroupNode* node)
|
||||||
|
: EditorUndoCommand(node, text.arg(node->text())), m_data(std::move(data)) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
class GroupNodeAddUndoCommand : public GroupNodeUndoCommand
|
class GroupNodeAddUndoCommand : public GroupNodeUndoCommand
|
||||||
{
|
{
|
||||||
using base = GroupNodeUndoCommand;
|
using base = GroupNodeUndoCommand;
|
||||||
public:
|
public:
|
||||||
explicit GroupNodeAddUndoCommand(const QString& text, std::unique_ptr<amuse::AudioGroupDatabase>&& data, ProjectModel::GroupNode* node)
|
explicit GroupNodeAddUndoCommand(const QString& text, std::unique_ptr<amuse::AudioGroupDatabase>&& data,
|
||||||
|
ProjectModel::GroupNode* node)
|
||||||
: GroupNodeUndoCommand(text, std::move(data), node) {}
|
: GroupNodeUndoCommand(text, std::move(data), node) {}
|
||||||
void undo() { base::del(); }
|
void undo() { base::del(); }
|
||||||
void redo() { base::add(); }
|
void redo() { base::add(); }
|
||||||
|
@ -956,6 +1163,7 @@ std::unique_ptr<amuse::AudioGroupDatabase> ProjectModel::_delNode(GroupNode* nod
|
||||||
std::unique_ptr<amuse::AudioGroupDatabase> ret = std::move(node->m_it->second);
|
std::unique_ptr<amuse::AudioGroupDatabase> ret = std::move(node->m_it->second);
|
||||||
m_groups.erase(node->m_it);
|
m_groups.erase(node->m_it);
|
||||||
node->m_it = {};
|
node->m_it = {};
|
||||||
|
QDir(QFileInfo(m_dir, node->name()).filePath()).removeRecursively();
|
||||||
m_root->removeChild(node);
|
m_root->removeChild(node);
|
||||||
endRemoveRows();
|
endRemoveRows();
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -978,24 +1186,23 @@ ProjectModel::GroupNode* ProjectModel::newSubproject(const QString& name)
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class NT>
|
template <class NT>
|
||||||
class NodeUndoCommand : public QUndoCommand
|
class NodeUndoCommand : public EditorUndoCommand
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
amuse::ObjToken<ProjectModel::GroupNode> m_parent;
|
amuse::ObjToken<ProjectModel::GroupNode> m_parent;
|
||||||
amuse::ObjToken<NT> m_node;
|
|
||||||
ProjectModel::NameUndoRegistry m_nameReg;
|
ProjectModel::NameUndoRegistry m_nameReg;
|
||||||
void add()
|
void add()
|
||||||
{
|
{
|
||||||
g_MainWindow->projectModel()->_addNode(m_node.get(), m_parent.get(), m_nameReg);
|
g_MainWindow->projectModel()->_addNode(static_cast<NT*>(m_node.get()), m_parent.get(), m_nameReg);
|
||||||
g_MainWindow->recursiveExpandAndSelectOutline(g_MainWindow->projectModel()->index(m_node.get()));
|
g_MainWindow->recursiveExpandAndSelectOutline(g_MainWindow->projectModel()->index(m_node.get()));
|
||||||
}
|
}
|
||||||
void del()
|
void del()
|
||||||
{
|
{
|
||||||
g_MainWindow->projectModel()->_delNode(m_node.get(), m_parent.get(), m_nameReg);
|
g_MainWindow->projectModel()->_delNode(static_cast<NT*>(m_node.get()), m_parent.get(), m_nameReg);
|
||||||
}
|
}
|
||||||
public:
|
public:
|
||||||
explicit NodeUndoCommand(const QString& text, NT* node, ProjectModel::GroupNode* parent)
|
explicit NodeUndoCommand(const QString& text, NT* node, ProjectModel::GroupNode* parent)
|
||||||
: QUndoCommand(text.arg(node->text())), m_parent(parent), m_node(node){}
|
: EditorUndoCommand(node, text.arg(node->text())), m_parent(parent) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <class NT>
|
template <class NT>
|
||||||
|
@ -1109,6 +1316,8 @@ static constexpr ProjectModel::INode::Type GetINodeType(ProjectModel::KeymapNode
|
||||||
{ return ProjectModel::INode::Type::Keymap; }
|
{ return ProjectModel::INode::Type::Keymap; }
|
||||||
static constexpr ProjectModel::INode::Type GetINodeType(ProjectModel::LayersNode*)
|
static constexpr ProjectModel::INode::Type GetINodeType(ProjectModel::LayersNode*)
|
||||||
{ return ProjectModel::INode::Type::Layer; }
|
{ return ProjectModel::INode::Type::Layer; }
|
||||||
|
static constexpr ProjectModel::INode::Type GetINodeType(ProjectModel::SampleNode*)
|
||||||
|
{ return ProjectModel::INode::Type::Sample; }
|
||||||
|
|
||||||
static constexpr amuse::NameDB::Type GetNameDBType(ProjectModel::SoundMacroNode*)
|
static constexpr amuse::NameDB::Type GetNameDBType(ProjectModel::SoundMacroNode*)
|
||||||
{ return amuse::NameDB::Type::SoundMacro; }
|
{ return amuse::NameDB::Type::SoundMacro; }
|
||||||
|
@ -1120,12 +1329,23 @@ static constexpr amuse::NameDB::Type GetNameDBType(ProjectModel::KeymapNode*)
|
||||||
{ return amuse::NameDB::Type::Keymap; }
|
{ return amuse::NameDB::Type::Keymap; }
|
||||||
static constexpr amuse::NameDB::Type GetNameDBType(ProjectModel::LayersNode*)
|
static constexpr amuse::NameDB::Type GetNameDBType(ProjectModel::LayersNode*)
|
||||||
{ return amuse::NameDB::Type::Layer; }
|
{ return amuse::NameDB::Type::Layer; }
|
||||||
|
static constexpr amuse::NameDB::Type GetNameDBType(ProjectModel::SampleNode*)
|
||||||
|
{ return amuse::NameDB::Type::Sample; }
|
||||||
|
|
||||||
static amuse::NameDB* GetNameDB(ProjectModel::SoundMacroNode*) { return amuse::SoundMacroId::CurNameDB; }
|
template <class NT>
|
||||||
static amuse::NameDB* GetNameDB(ProjectModel::ADSRNode*) { return amuse::TableId::CurNameDB; }
|
inline amuse::NameDB* GetNameDB() { return nullptr; }
|
||||||
static amuse::NameDB* GetNameDB(ProjectModel::CurveNode*) { return amuse::TableId::CurNameDB; }
|
template <>
|
||||||
static amuse::NameDB* GetNameDB(ProjectModel::KeymapNode*) { return amuse::KeymapId::CurNameDB; }
|
inline amuse::NameDB* GetNameDB<ProjectModel::SoundMacroNode>() { return amuse::SoundMacroId::CurNameDB; }
|
||||||
static amuse::NameDB* GetNameDB(ProjectModel::LayersNode*) { return amuse::LayersId::CurNameDB; }
|
template <>
|
||||||
|
inline amuse::NameDB* GetNameDB<ProjectModel::ADSRNode>() { return amuse::TableId::CurNameDB; }
|
||||||
|
template <>
|
||||||
|
inline amuse::NameDB* GetNameDB<ProjectModel::CurveNode>() { return amuse::TableId::CurNameDB; }
|
||||||
|
template <>
|
||||||
|
inline amuse::NameDB* GetNameDB<ProjectModel::KeymapNode>() { return amuse::KeymapId::CurNameDB; }
|
||||||
|
template <>
|
||||||
|
inline amuse::NameDB* GetNameDB<ProjectModel::LayersNode>() { return amuse::LayersId::CurNameDB; }
|
||||||
|
template <>
|
||||||
|
inline amuse::NameDB* GetNameDB<ProjectModel::SampleNode>() { return amuse::SampleId::CurNameDB; }
|
||||||
|
|
||||||
template <class NT, class T>
|
template <class NT, class T>
|
||||||
void ProjectModel::_addPoolNode(NT* node, GroupNode* parent, const NameUndoRegistry& registry, T& container)
|
void ProjectModel::_addPoolNode(NT* node, GroupNode* parent, const NameUndoRegistry& registry, T& container)
|
||||||
|
@ -1134,8 +1354,8 @@ void ProjectModel::_addPoolNode(NT* node, GroupNode* parent, const NameUndoRegis
|
||||||
CollectionNode* coll = parent->getCollectionOfType(GetINodeType(node));
|
CollectionNode* coll = parent->getCollectionOfType(GetINodeType(node));
|
||||||
int insertIdx = coll->hypotheticalIndex(node->name());
|
int insertIdx = coll->hypotheticalIndex(node->name());
|
||||||
beginInsertRows(index(coll), insertIdx, insertIdx);
|
beginInsertRows(index(coll), insertIdx, insertIdx);
|
||||||
auto newId = GetNameDB(node)->generateId(GetNameDBType(node));
|
auto newId = GetNameDB<NT>()->generateId(GetNameDBType(node));
|
||||||
GetNameDB(node)->registerPair(node->name().toUtf8().data(), newId);
|
GetNameDB<NT>()->registerPair(node->name().toUtf8().data(), newId);
|
||||||
container[newId] = node->m_obj;
|
container[newId] = node->m_obj;
|
||||||
node->m_id = newId;
|
node->m_id = newId;
|
||||||
coll->insertChild(node);
|
coll->insertChild(node);
|
||||||
|
@ -1147,11 +1367,11 @@ template <class NT, class T>
|
||||||
void ProjectModel::_delPoolNode(NT* node, GroupNode* parent, NameUndoRegistry& registry, T& container)
|
void ProjectModel::_delPoolNode(NT* node, GroupNode* parent, NameUndoRegistry& registry, T& container)
|
||||||
{
|
{
|
||||||
int idx = node->row();
|
int idx = node->row();
|
||||||
|
setIdDatabases(parent);
|
||||||
CollectionNode* coll = parent->getCollectionOfType(GetINodeType(node));
|
CollectionNode* coll = parent->getCollectionOfType(GetINodeType(node));
|
||||||
beginRemoveRows(index(coll), idx, idx);
|
beginRemoveRows(index(coll), idx, idx);
|
||||||
_preDelNode(node, registry);
|
_preDelNode(node, registry);
|
||||||
setIdDatabases(parent);
|
GetNameDB<NT>()->remove(node->m_id);
|
||||||
GetNameDB(node)->remove(node->m_id);
|
|
||||||
container.erase(node->m_id);
|
container.erase(node->m_id);
|
||||||
node->m_id = {};
|
node->m_id = {};
|
||||||
coll->removeChild(node);
|
coll->removeChild(node);
|
||||||
|
@ -1296,18 +1516,505 @@ ProjectModel::LayersNode* ProjectModel::newLayers(GroupNode* group, const QStrin
|
||||||
return node.get();
|
return node.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString ProjectModel::MakeDedupedSubprojectName(const QString& origName)
|
||||||
|
{
|
||||||
|
QString dupeName = origName + tr("-copy");
|
||||||
|
QString useName = dupeName;
|
||||||
|
int dupeIdx = 1;
|
||||||
|
while (QFileInfo(m_dir.filePath(useName)).exists())
|
||||||
|
useName = dupeName + QString::number(dupeIdx++);
|
||||||
|
return useName;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ProjectModel::MakeDedupedName(const QString& origName, amuse::NameDB* db)
|
||||||
|
{
|
||||||
|
QString dupeName = origName + tr("-copy");
|
||||||
|
QString useName = dupeName;
|
||||||
|
int dupeIdx = 1;
|
||||||
|
while (db->m_stringToId.find(useName.toUtf8().data()) != db->m_stringToId.cend())
|
||||||
|
useName = dupeName + QString::number(dupeIdx++);
|
||||||
|
return useName;
|
||||||
|
}
|
||||||
|
|
||||||
|
static amuse::ObjToken<std::unique_ptr<amuse::ITable>> DuplicateTable(amuse::ITable* table)
|
||||||
|
{
|
||||||
|
switch (table->Isa())
|
||||||
|
{
|
||||||
|
case amuse::ITable::Type::ADSR:
|
||||||
|
return amuse::MakeObj<std::unique_ptr<amuse::ITable>>
|
||||||
|
(std::make_unique<amuse::ADSR>(static_cast<amuse::ADSR&>(*table)));
|
||||||
|
case amuse::ITable::Type::ADSRDLS:
|
||||||
|
return amuse::MakeObj<std::unique_ptr<amuse::ITable>>
|
||||||
|
(std::make_unique<amuse::ADSRDLS>(static_cast<amuse::ADSRDLS&>(*table)));
|
||||||
|
case amuse::ITable::Type::Curve:
|
||||||
|
return amuse::MakeObj<std::unique_ptr<amuse::ITable>>
|
||||||
|
(std::make_unique<amuse::Curve>(static_cast<amuse::Curve&>(*table)));
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList ProjectModel::mimeTypes() const
|
||||||
|
{
|
||||||
|
return {QStringLiteral("application/x-amuse-subprojectpath"),
|
||||||
|
QStringLiteral("application/x-amuse-songgroup"),
|
||||||
|
QStringLiteral("application/x-amuse-soundgroup"),
|
||||||
|
QStringLiteral("application/x-amuse-soundmacro"),
|
||||||
|
QStringLiteral("application/x-amuse-adsr"),
|
||||||
|
QStringLiteral("application/x-amuse-curve"),
|
||||||
|
QStringLiteral("application/x-amuse-keymap"),
|
||||||
|
QStringLiteral("application/x-amuse-layers"),
|
||||||
|
QStringLiteral("application/x-amuse-samplepath")};
|
||||||
|
}
|
||||||
|
|
||||||
|
static void WriteMimeYAML(athena::io::YAMLDocWriter& w, ProjectModel::SongGroupNode* n) { n->m_index->toYAML(w); }
|
||||||
|
static void WriteMimeYAML(athena::io::YAMLDocWriter& w, ProjectModel::SoundGroupNode* n) { n->m_index->toYAML(w); }
|
||||||
|
static void WriteMimeYAML(athena::io::YAMLDocWriter& w, ProjectModel::SoundMacroNode* n)
|
||||||
|
{
|
||||||
|
if (auto __r2 = w.enterSubVector("cmds"))
|
||||||
|
n->m_obj->toYAML(w);
|
||||||
|
}
|
||||||
|
static void WriteMimeYAML(athena::io::YAMLDocWriter& w, ProjectModel::ADSRNode* n) { n->m_obj->get()->write(w); }
|
||||||
|
static void WriteMimeYAML(athena::io::YAMLDocWriter& w, ProjectModel::CurveNode* n) { n->m_obj->get()->write(w); }
|
||||||
|
static void WriteMimeYAML(athena::io::YAMLDocWriter& w, ProjectModel::KeymapNode* n)
|
||||||
|
{
|
||||||
|
if (auto __v = w.enterSubVector("entries"))
|
||||||
|
{
|
||||||
|
for (const auto& km : *n->m_obj)
|
||||||
|
{
|
||||||
|
if (auto __r2 = w.enterSubRecord(nullptr))
|
||||||
|
{
|
||||||
|
w.setStyle(athena::io::YAMLNodeStyle::Flow);
|
||||||
|
km.write(w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static void WriteMimeYAML(athena::io::YAMLDocWriter& w, ProjectModel::LayersNode* n)
|
||||||
|
{
|
||||||
|
if (auto __v = w.enterSubVector("entries"))
|
||||||
|
{
|
||||||
|
for (const auto& lm : *n->m_obj)
|
||||||
|
{
|
||||||
|
if (auto __r2 = w.enterSubRecord(nullptr))
|
||||||
|
{
|
||||||
|
w.setStyle(athena::io::YAMLNodeStyle::Flow);
|
||||||
|
lm.write(w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class NT>
|
||||||
|
EditorUndoCommand* ProjectModel::readMimeYAML(athena::io::YAMLDocReader& r, const QString& name, GroupNode* gn) {}
|
||||||
|
template <>
|
||||||
|
EditorUndoCommand* ProjectModel::readMimeYAML<ProjectModel::SongGroupNode>
|
||||||
|
(athena::io::YAMLDocReader& r, const QString& name, GroupNode* gn)
|
||||||
|
{
|
||||||
|
auto dataNode = amuse::MakeObj<amuse::SongGroupIndex>();
|
||||||
|
dataNode->fromYAML(r);
|
||||||
|
auto node = amuse::MakeObj<SongGroupNode>(name, dataNode);
|
||||||
|
return new NodeAddUndoCommand(ProjectModel::tr("Add Song Group %1"), node.get(), gn);
|
||||||
|
}
|
||||||
|
template <>
|
||||||
|
EditorUndoCommand* ProjectModel::readMimeYAML<ProjectModel::SoundGroupNode>
|
||||||
|
(athena::io::YAMLDocReader& r, const QString& name, GroupNode* gn)
|
||||||
|
{
|
||||||
|
auto dataNode = amuse::MakeObj<amuse::SFXGroupIndex>();
|
||||||
|
dataNode->fromYAML(r);
|
||||||
|
auto node = amuse::MakeObj<SoundGroupNode>(name, dataNode);
|
||||||
|
return new NodeAddUndoCommand(ProjectModel::tr("Add Sound Group %1"), node.get(), gn);
|
||||||
|
}
|
||||||
|
template <>
|
||||||
|
EditorUndoCommand* ProjectModel::readMimeYAML<ProjectModel::SoundMacroNode>
|
||||||
|
(athena::io::YAMLDocReader& r, const QString& name, GroupNode* gn)
|
||||||
|
{
|
||||||
|
auto dataNode = amuse::MakeObj<amuse::SoundMacro>();
|
||||||
|
size_t cmdCount;
|
||||||
|
if (auto __v = r.enterSubVector("cmds", cmdCount))
|
||||||
|
dataNode->fromYAML(r, cmdCount);
|
||||||
|
auto node = amuse::MakeObj<SoundMacroNode>(name, dataNode);
|
||||||
|
return new NodeAddUndoCommand(ProjectModel::tr("Add SoundMacro %1"), node.get(), gn);
|
||||||
|
}
|
||||||
|
template <>
|
||||||
|
EditorUndoCommand* ProjectModel::readMimeYAML<ProjectModel::ADSRNode>
|
||||||
|
(athena::io::YAMLDocReader& r, const QString& name, GroupNode* gn)
|
||||||
|
{
|
||||||
|
amuse::ObjToken<std::unique_ptr<amuse::ITable>> dataNode;
|
||||||
|
if (auto __vta = r.enterSubRecord("velToAttack"))
|
||||||
|
{
|
||||||
|
__vta.leave();
|
||||||
|
dataNode = amuse::MakeObj<std::unique_ptr<amuse::ITable>>(std::make_unique<amuse::ADSRDLS>());
|
||||||
|
static_cast<amuse::ADSRDLS&>(**dataNode).read(r);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dataNode = amuse::MakeObj<std::unique_ptr<amuse::ITable>>(std::make_unique<amuse::ADSR>());
|
||||||
|
static_cast<amuse::ADSR&>(**dataNode).read(r);
|
||||||
|
}
|
||||||
|
auto node = amuse::MakeObj<ADSRNode>(name, dataNode);
|
||||||
|
return new NodeAddUndoCommand(ProjectModel::tr("Add ADSR %1"), node.get(), gn);
|
||||||
|
}
|
||||||
|
template <>
|
||||||
|
EditorUndoCommand* ProjectModel::readMimeYAML<ProjectModel::CurveNode>
|
||||||
|
(athena::io::YAMLDocReader& r, const QString& name, GroupNode* gn)
|
||||||
|
{
|
||||||
|
auto dataNode = amuse::MakeObj<std::unique_ptr<amuse::ITable>>(std::make_unique<amuse::Curve>());
|
||||||
|
static_cast<amuse::Curve&>(**dataNode).read(r);
|
||||||
|
auto node = amuse::MakeObj<CurveNode>(name, dataNode);
|
||||||
|
return new NodeAddUndoCommand(ProjectModel::tr("Add Curve %1"), node.get(), gn);
|
||||||
|
}
|
||||||
|
template <>
|
||||||
|
EditorUndoCommand* ProjectModel::readMimeYAML<ProjectModel::KeymapNode>
|
||||||
|
(athena::io::YAMLDocReader& r, const QString& name, GroupNode* gn)
|
||||||
|
{
|
||||||
|
auto dataNode = amuse::MakeObj<std::array<amuse::Keymap, 128>>();
|
||||||
|
size_t entryCount;
|
||||||
|
if (auto __v = r.enterSubVector("entries", entryCount))
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < entryCount; ++i)
|
||||||
|
{
|
||||||
|
if (auto __r2 = r.enterSubRecord(nullptr))
|
||||||
|
{
|
||||||
|
(*dataNode)[i].read(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto node = amuse::MakeObj<KeymapNode>(name, dataNode);
|
||||||
|
return new NodeAddUndoCommand(ProjectModel::tr("Add Keymap %1"), node.get(), gn);
|
||||||
|
}
|
||||||
|
template <>
|
||||||
|
EditorUndoCommand* ProjectModel::readMimeYAML<ProjectModel::LayersNode>
|
||||||
|
(athena::io::YAMLDocReader& r, const QString& name, GroupNode* gn)
|
||||||
|
{
|
||||||
|
auto dataNode = amuse::MakeObj<std::vector<amuse::LayerMapping>>();
|
||||||
|
size_t entryCount;
|
||||||
|
if (auto __v = r.enterSubVector("entries", entryCount))
|
||||||
|
{
|
||||||
|
dataNode->resize(entryCount);
|
||||||
|
for (size_t i = 0; i < entryCount; ++i)
|
||||||
|
{
|
||||||
|
if (auto __r2 = r.enterSubRecord(nullptr))
|
||||||
|
{
|
||||||
|
(*dataNode)[i].read(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto node = amuse::MakeObj<LayersNode>(name, dataNode);
|
||||||
|
return new NodeAddUndoCommand(ProjectModel::tr("Add Layers %1"), node.get(), gn);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class NT>
|
||||||
|
void ProjectModel::loadMimeData(const QMimeData* data, const QString& mimeType, GroupNode* gn)
|
||||||
|
{
|
||||||
|
auto d = data->data(mimeType);
|
||||||
|
athena::io::MemoryReader mr(d.data(), atUint64(d.length()));
|
||||||
|
athena::io::YAMLDocReader r;
|
||||||
|
if (r.parse(&mr))
|
||||||
|
{
|
||||||
|
QString newName = MakeDedupedName(QString::fromStdString(r.readString("name")), GetNameDB<NT>());
|
||||||
|
g_MainWindow->pushUndoCommand(readMimeYAML<NT>(r, newName, gn));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class NT>
|
||||||
|
QMimeData* MakeMimeData(NT* n, const QString& mimeType)
|
||||||
|
{
|
||||||
|
QMimeData* data = new QMimeData;
|
||||||
|
athena::io::VectorWriter vw;
|
||||||
|
athena::io::YAMLDocWriter w(nullptr);
|
||||||
|
w.writeString("name", QStringToSysString(n->name()));
|
||||||
|
WriteMimeYAML(w, n);
|
||||||
|
w.finish(&vw);
|
||||||
|
data->setData(mimeType, QByteArray((char*)vw.data().data(), int(vw.data().size())));
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMimeData* ProjectModel::mimeData(const QModelIndexList& indexes) const
|
||||||
|
{
|
||||||
|
QModelIndex index = indexes.first();
|
||||||
|
if (!index.isValid())
|
||||||
|
return nullptr;
|
||||||
|
assert(index.model() == this && "Not ProjectModel");
|
||||||
|
INode* n = node(index);
|
||||||
|
switch (n->type())
|
||||||
|
{
|
||||||
|
case INode::Type::Group:
|
||||||
|
{
|
||||||
|
QDir dir(QFileInfo(m_dir, n->name()).filePath());
|
||||||
|
QMimeData* data = new QMimeData;
|
||||||
|
data->setData(QStringLiteral("application/x-amuse-subprojectpath"), dir.path().toUtf8());
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
case INode::Type::SoundGroup:
|
||||||
|
return MakeMimeData(static_cast<SoundGroupNode*>(n), QStringLiteral("application/x-amuse-soundgroup"));
|
||||||
|
case INode::Type::SongGroup:
|
||||||
|
return MakeMimeData(static_cast<SongGroupNode*>(n), QStringLiteral("application/x-amuse-songgroup"));
|
||||||
|
case INode::Type::SoundMacro:
|
||||||
|
return MakeMimeData(static_cast<SoundMacroNode*>(n), QStringLiteral("application/x-amuse-soundmacro"));
|
||||||
|
case INode::Type::ADSR:
|
||||||
|
return MakeMimeData(static_cast<ADSRNode*>(n), QStringLiteral("application/x-amuse-adsr"));
|
||||||
|
case INode::Type::Curve:
|
||||||
|
return MakeMimeData(static_cast<CurveNode*>(n), QStringLiteral("application/x-amuse-curve"));
|
||||||
|
case INode::Type::Keymap:
|
||||||
|
return MakeMimeData(static_cast<KeymapNode*>(n), QStringLiteral("application/x-amuse-keymap"));
|
||||||
|
case INode::Type::Layer:
|
||||||
|
return MakeMimeData(static_cast<LayersNode*>(n), QStringLiteral("application/x-amuse-layers"));
|
||||||
|
case INode::Type::Sample:
|
||||||
|
{
|
||||||
|
GroupNode* gn = getGroupNode(n);
|
||||||
|
QString path = SysStringToQString(gn->getAudioGroup()->getSampleBasePath(static_cast<SampleNode*>(n)->id()));
|
||||||
|
QMimeData* data = new QMimeData;
|
||||||
|
data->setData(QStringLiteral("application/x-amuse-samplepath"), path.toUtf8());
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProjectModel::dropMimeData(const QMimeData* data, Qt::DropAction action,
|
||||||
|
int row, int column, const QModelIndex& parent)
|
||||||
|
{
|
||||||
|
if (data->hasFormat(QStringLiteral("application/x-amuse-subprojectpath")))
|
||||||
|
{
|
||||||
|
auto path = data->data(QStringLiteral("application/x-amuse-subprojectpath"));
|
||||||
|
QDir oldDir(path);
|
||||||
|
QString newName = MakeDedupedSubprojectName(oldDir.dirName());
|
||||||
|
m_dir.mkdir(newName);
|
||||||
|
QDir newDir(QFileInfo(m_dir, newName).filePath());
|
||||||
|
for (auto ent : oldDir.entryList({"*.wav", "*.dsp", "*.vadpcm", "!pool.yaml", "!project.yaml"}, QDir::Files))
|
||||||
|
QFile::copy(QFileInfo(oldDir, ent).filePath(), QFileInfo(newDir, ent).filePath());
|
||||||
|
auto dataNode = std::make_unique<amuse::AudioGroupDatabase>(QStringToSysString(newDir.path()));
|
||||||
|
auto node = amuse::MakeObj<GroupNode>(newName);
|
||||||
|
_buildGroupNode(*node, *dataNode);
|
||||||
|
g_MainWindow->pushUndoCommand(
|
||||||
|
new GroupNodeAddUndoCommand(tr("Add Subproject %1"), std::move(dataNode), node.get()));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupNode* gn;
|
||||||
|
if (parent.isValid())
|
||||||
|
gn = getGroupNode(node(parent));
|
||||||
|
else
|
||||||
|
gn = static_cast<GroupNode*>(m_root->child(row));
|
||||||
|
setIdDatabases(gn);
|
||||||
|
|
||||||
|
if (data->hasFormat(QStringLiteral("application/x-amuse-soundgroup")))
|
||||||
|
loadMimeData<SoundGroupNode>(data, QStringLiteral("application/x-amuse-soundgroup"), gn);
|
||||||
|
else if (data->hasFormat(QStringLiteral("application/x-amuse-songgroup")))
|
||||||
|
loadMimeData<SongGroupNode>(data, QStringLiteral("application/x-amuse-songgroup"), gn);
|
||||||
|
else if (data->hasFormat(QStringLiteral("application/x-amuse-soundmacro")))
|
||||||
|
loadMimeData<SoundMacroNode>(data, QStringLiteral("application/x-amuse-soundmacro"), gn);
|
||||||
|
else if (data->hasFormat(QStringLiteral("application/x-amuse-adsr")))
|
||||||
|
loadMimeData<ADSRNode>(data, QStringLiteral("application/x-amuse-adsr"), gn);
|
||||||
|
else if (data->hasFormat(QStringLiteral("application/x-amuse-curve")))
|
||||||
|
loadMimeData<CurveNode>(data, QStringLiteral("application/x-amuse-curve"), gn);
|
||||||
|
else if (data->hasFormat(QStringLiteral("application/x-amuse-keymap")))
|
||||||
|
loadMimeData<KeymapNode>(data, QStringLiteral("application/x-amuse-keymap"), gn);
|
||||||
|
else if (data->hasFormat(QStringLiteral("application/x-amuse-layers")))
|
||||||
|
loadMimeData<LayersNode>(data, QStringLiteral("application/x-amuse-layers"), gn);
|
||||||
|
else if (data->hasFormat(QStringLiteral("application/x-amuse-samplepath")))
|
||||||
|
{
|
||||||
|
auto path = data->data(QStringLiteral("application/x-amuse-samplepath"));
|
||||||
|
QString newName = MakeDedupedName(QFileInfo(path).completeBaseName(), amuse::SampleId::CurNameDB);
|
||||||
|
QString newBasePath = QFileInfo(QFileInfo(m_dir, gn->name()).filePath(), newName).filePath();
|
||||||
|
amuse::SystemString newBasePathStr = QStringToSysString(newBasePath);
|
||||||
|
gn->getAudioGroup()->copySampleInto(QStringToSysString(QString::fromUtf8(path)), newBasePathStr);
|
||||||
|
auto dataNode = amuse::MakeObj<amuse::SampleEntry>();
|
||||||
|
dataNode->loadLooseData(newBasePathStr);
|
||||||
|
auto node = amuse::MakeObj<SampleNode>(newName, dataNode);
|
||||||
|
NameUndoRegistry dummy;
|
||||||
|
_addPoolNode(node.get(), gn, dummy, gn->getAudioGroup()->getSdir().sampleEntries());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectModel::cut(const QModelIndex& index)
|
||||||
|
{
|
||||||
|
if (!index.isValid())
|
||||||
|
return;
|
||||||
|
assert(index.model() == this && "Not ProjectModel");
|
||||||
|
QMimeData* data = mimeData({index});
|
||||||
|
if (data)
|
||||||
|
{
|
||||||
|
QGuiApplication::clipboard()->setMimeData(data);
|
||||||
|
INode* n = node(index);
|
||||||
|
EditorUndoCommand* cmd = nullptr;
|
||||||
|
switch (n->type())
|
||||||
|
{
|
||||||
|
case INode::Type::SongGroup:
|
||||||
|
cmd = new NodeDelUndoCommand(tr("Cut SongGroup %1"), static_cast<SongGroupNode*>(n));
|
||||||
|
break;
|
||||||
|
case INode::Type::SoundGroup:
|
||||||
|
cmd = new NodeDelUndoCommand(tr("Cut SFXGroup %1"), static_cast<SoundGroupNode*>(n));
|
||||||
|
break;
|
||||||
|
case INode::Type::SoundMacro:
|
||||||
|
cmd = new NodeDelUndoCommand(tr("Cut SoundMacro %1"), static_cast<SoundMacroNode*>(n));
|
||||||
|
break;
|
||||||
|
case INode::Type::ADSR:
|
||||||
|
cmd = new NodeDelUndoCommand(tr("Cut ADSR %1"), static_cast<ADSRNode*>(n));
|
||||||
|
break;
|
||||||
|
case INode::Type::Curve:
|
||||||
|
cmd = new NodeDelUndoCommand(tr("Cut Curve %1"), static_cast<CurveNode*>(n));
|
||||||
|
break;
|
||||||
|
case INode::Type::Keymap:
|
||||||
|
cmd = new NodeDelUndoCommand(tr("Cut Keymap %1"), static_cast<KeymapNode*>(n));
|
||||||
|
break;
|
||||||
|
case INode::Type::Layer:
|
||||||
|
cmd = new NodeDelUndoCommand(tr("Cut Layers %1"), static_cast<LayersNode*>(n));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (cmd)
|
||||||
|
g_MainWindow->pushUndoCommand(cmd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectModel::copy(const QModelIndex& index)
|
||||||
|
{
|
||||||
|
if (!index.isValid())
|
||||||
|
return;
|
||||||
|
assert(index.model() == this && "Not ProjectModel");
|
||||||
|
QMimeData* data = mimeData({index});
|
||||||
|
if (data)
|
||||||
|
QGuiApplication::clipboard()->setMimeData(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectModel::paste(const QModelIndex& index)
|
||||||
|
{
|
||||||
|
if (!index.isValid())
|
||||||
|
return;
|
||||||
|
assert(index.model() == this && "Not ProjectModel");
|
||||||
|
dropMimeData(QGuiApplication::clipboard()->mimeData(), Qt::DropAction::CopyAction,
|
||||||
|
index.row(), index.column(), index.parent());
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex ProjectModel::duplicate(const QModelIndex& index)
|
||||||
|
{
|
||||||
|
QModelIndex ret;
|
||||||
|
if (!index.isValid())
|
||||||
|
return ret;
|
||||||
|
assert(index.model() == this && "Not ProjectModel");
|
||||||
|
INode* n = node(index);
|
||||||
|
setIdDatabases(n);
|
||||||
|
EditorUndoCommand* cmd = nullptr;
|
||||||
|
amuse::NameDB* nameDb = n->getNameDb();
|
||||||
|
QString newName;
|
||||||
|
if (nameDb)
|
||||||
|
newName = MakeDedupedName(n->name(), nameDb);
|
||||||
|
GroupNode* gn = getGroupNode(n);
|
||||||
|
switch (n->type())
|
||||||
|
{
|
||||||
|
case INode::Type::Group:
|
||||||
|
{
|
||||||
|
GroupNode* cn = static_cast<GroupNode*>(n);
|
||||||
|
newName = MakeDedupedSubprojectName(n->name());
|
||||||
|
m_dir.mkdir(newName);
|
||||||
|
QDir oldDir(QFileInfo(m_dir, n->name()).filePath());
|
||||||
|
QDir newDir(QFileInfo(m_dir, newName).filePath());
|
||||||
|
for (auto ent : oldDir.entryList({"*.wav", "*.dsp", "*.vadpcm", "!pool.yaml"}, QDir::Files))
|
||||||
|
QFile::copy(QFileInfo(oldDir, ent).filePath(), QFileInfo(newDir, ent).filePath());
|
||||||
|
auto data = std::make_unique<amuse::AudioGroupDatabase>(*cn->getAudioGroup(), QStringToSysString(newDir.path()));
|
||||||
|
auto node = amuse::MakeObj<GroupNode>(newName);
|
||||||
|
_buildGroupNode(*node, *data);
|
||||||
|
cmd = new GroupNodeAddUndoCommand(tr("Add Subproject %1"), std::move(data), node.get());
|
||||||
|
ret = ProjectModel::index(node.get());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case INode::Type::SoundGroup:
|
||||||
|
{
|
||||||
|
SoundGroupNode* cn = static_cast<SoundGroupNode*>(n);
|
||||||
|
auto node = amuse::MakeObj<SoundGroupNode>(newName, amuse::MakeObj<amuse::SFXGroupIndex>(*cn->m_index));
|
||||||
|
cmd = new NodeAddUndoCommand(tr("Add Sound Group %1"), node.get(), gn);
|
||||||
|
ret = ProjectModel::index(node.get());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case INode::Type::SongGroup:
|
||||||
|
{
|
||||||
|
SongGroupNode* cn = static_cast<SongGroupNode*>(n);
|
||||||
|
auto node = amuse::MakeObj<SongGroupNode>(newName, amuse::MakeObj<amuse::SongGroupIndex>(*cn->m_index));
|
||||||
|
cmd = new NodeAddUndoCommand(tr("Add Song Group %1"), node.get(), gn);
|
||||||
|
ret = ProjectModel::index(node.get());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case INode::Type::SoundMacro:
|
||||||
|
{
|
||||||
|
SoundMacroNode* cn = static_cast<SoundMacroNode*>(n);
|
||||||
|
auto dataNode = amuse::MakeObj<amuse::SoundMacro>();
|
||||||
|
dataNode->buildFromPrototype(*cn->m_obj);
|
||||||
|
auto node = amuse::MakeObj<SoundMacroNode>(newName, dataNode);
|
||||||
|
cmd = new NodeAddUndoCommand(tr("Add Sound Macro %1"), node.get(), gn);
|
||||||
|
ret = ProjectModel::index(node.get());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case INode::Type::ADSR:
|
||||||
|
{
|
||||||
|
ADSRNode* cn = static_cast<ADSRNode*>(n);
|
||||||
|
auto node = amuse::MakeObj<ADSRNode>(newName, DuplicateTable(cn->m_obj->get()));
|
||||||
|
cmd = new NodeAddUndoCommand(tr("Add ADSR %1"), node.get(), gn);
|
||||||
|
ret = ProjectModel::index(node.get());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case INode::Type::Curve:
|
||||||
|
{
|
||||||
|
CurveNode* cn = static_cast<CurveNode*>(n);
|
||||||
|
auto node = amuse::MakeObj<CurveNode>(newName, DuplicateTable(cn->m_obj->get()));
|
||||||
|
cmd = new NodeAddUndoCommand(tr("Add Curve %1"), node.get(), gn);
|
||||||
|
ret = ProjectModel::index(node.get());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case INode::Type::Keymap:
|
||||||
|
{
|
||||||
|
KeymapNode* cn = static_cast<KeymapNode*>(n);
|
||||||
|
auto node = amuse::MakeObj<KeymapNode>(newName, amuse::MakeObj<std::array<amuse::Keymap, 128>>(*cn->m_obj));
|
||||||
|
cmd = new NodeAddUndoCommand(tr("Add Keymap %1"), node.get(), gn);
|
||||||
|
ret = ProjectModel::index(node.get());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case INode::Type::Layer:
|
||||||
|
{
|
||||||
|
LayersNode* cn = static_cast<LayersNode*>(n);
|
||||||
|
auto node = amuse::MakeObj<LayersNode>(newName, amuse::MakeObj<std::vector<amuse::LayerMapping>>(*cn->m_obj));
|
||||||
|
cmd = new NodeAddUndoCommand(tr("Add Layers %1"), node.get(), gn);
|
||||||
|
ret = ProjectModel::index(node.get());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (cmd)
|
||||||
|
g_MainWindow->pushUndoCommand(cmd);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
void ProjectModel::del(const QModelIndex& index)
|
void ProjectModel::del(const QModelIndex& index)
|
||||||
{
|
{
|
||||||
if (!index.isValid())
|
if (!index.isValid())
|
||||||
return;
|
return;
|
||||||
assert(index.model() == this && "Not ProjectModel");
|
assert(index.model() == this && "Not ProjectModel");
|
||||||
INode* n = node(index);
|
INode* n = node(index);
|
||||||
QUndoCommand* cmd = nullptr;
|
EditorUndoCommand* cmd = nullptr;
|
||||||
switch (n->type())
|
switch (n->type())
|
||||||
{
|
{
|
||||||
case INode::Type::Group:
|
case INode::Type::Group:
|
||||||
cmd = new GroupNodeDelUndoCommand(tr("Delete Subproject %1"), static_cast<GroupNode*>(n));
|
{
|
||||||
|
int result = g_MainWindow->uiMessenger().warning(tr("Delete Subproject"),
|
||||||
|
tr("<p>The subproject %1 will be permanently deleted from the project. "
|
||||||
|
"Sample files will be permanently removed from the file system.</p>"
|
||||||
|
"<p><strong>This action cannot be undone!</strong></p><p>Continue?</p>").arg(n->name()),
|
||||||
|
QMessageBox::Yes, QMessageBox::No);
|
||||||
|
if (result == QMessageBox::No)
|
||||||
|
return;
|
||||||
|
NameUndoRegistry nameReg;
|
||||||
|
g_MainWindow->projectModel()->_delNode(static_cast<GroupNode*>(n), nameReg);
|
||||||
|
g_MainWindow->m_undoStack->clear();
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case INode::Type::SongGroup:
|
case INode::Type::SongGroup:
|
||||||
cmd = new NodeDelUndoCommand(tr("Delete SongGroup %1"), static_cast<SongGroupNode*>(n));
|
cmd = new NodeDelUndoCommand(tr("Delete SongGroup %1"), static_cast<SongGroupNode*>(n));
|
||||||
break;
|
break;
|
||||||
|
@ -1329,6 +2036,21 @@ void ProjectModel::del(const QModelIndex& index)
|
||||||
case INode::Type::Layer:
|
case INode::Type::Layer:
|
||||||
cmd = new NodeDelUndoCommand(tr("Delete Layers %1"), static_cast<LayersNode*>(n));
|
cmd = new NodeDelUndoCommand(tr("Delete Layers %1"), static_cast<LayersNode*>(n));
|
||||||
break;
|
break;
|
||||||
|
case INode::Type::Sample:
|
||||||
|
{
|
||||||
|
int result = g_MainWindow->uiMessenger().warning(tr("Delete Sample"),
|
||||||
|
tr("<p>The sample %1 will be permanently deleted from the file system. "
|
||||||
|
"<p><strong>This action cannot be undone!</strong></p><p>Continue?</p>").arg(n->name()),
|
||||||
|
QMessageBox::Yes, QMessageBox::No);
|
||||||
|
if (result == QMessageBox::No)
|
||||||
|
return;
|
||||||
|
NameUndoRegistry nameReg;
|
||||||
|
GroupNode* gn = getGroupNode(n);
|
||||||
|
gn->getAudioGroup()->deleteSample(static_cast<SampleNode*>(n)->id());
|
||||||
|
_delPoolNode(static_cast<SampleNode*>(n), gn, nameReg, gn->getAudioGroup()->getSdir().sampleEntries());
|
||||||
|
g_MainWindow->m_undoStack->clear();
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,18 @@
|
||||||
#include "amuse/AudioGroupSampleDirectory.hpp"
|
#include "amuse/AudioGroupSampleDirectory.hpp"
|
||||||
|
|
||||||
class ProjectModel;
|
class ProjectModel;
|
||||||
|
class EditorUndoCommand;
|
||||||
|
|
||||||
|
enum AmuseItemEditFlags
|
||||||
|
{
|
||||||
|
AmuseItemNone = 0,
|
||||||
|
AmuseItemCut = 1,
|
||||||
|
AmuseItemCopy = 2,
|
||||||
|
AmuseItemPaste = 4,
|
||||||
|
AmuseItemDelete = 8,
|
||||||
|
AmuseItemNoCut = (AmuseItemCopy | AmuseItemPaste | AmuseItemDelete),
|
||||||
|
AmuseItemAll = (AmuseItemCut | AmuseItemCopy | AmuseItemPaste | AmuseItemDelete)
|
||||||
|
};
|
||||||
|
|
||||||
class NullItemProxyModel : public QIdentityProxyModel
|
class NullItemProxyModel : public QIdentityProxyModel
|
||||||
{
|
{
|
||||||
|
@ -86,6 +98,8 @@ private:
|
||||||
public:
|
public:
|
||||||
class INode : public amuse::IObj
|
class INode : public amuse::IObj
|
||||||
{
|
{
|
||||||
|
friend class ProjectModel;
|
||||||
|
virtual void _sortChildren();
|
||||||
public:
|
public:
|
||||||
enum class Type
|
enum class Type
|
||||||
{
|
{
|
||||||
|
@ -144,13 +158,14 @@ public:
|
||||||
}
|
}
|
||||||
amuse::ObjToken<INode> removeChild(INode* n)
|
amuse::ObjToken<INode> removeChild(INode* n)
|
||||||
{
|
{
|
||||||
int row = n->row();
|
amuse::ObjToken<INode> ret = n;
|
||||||
assert(n == m_children.at(row).get() && "Removing non-child from node");
|
int row = ret->row();
|
||||||
|
assert(ret.get() == m_children.at(row).get() && "Removing non-child from node");
|
||||||
m_children.erase(m_children.begin() + row);
|
m_children.erase(m_children.begin() + row);
|
||||||
reindexRows(row);
|
reindexRows(row);
|
||||||
n->m_parent = nullptr;
|
ret->m_parent = nullptr;
|
||||||
n->m_row = -1;
|
ret->m_row = -1;
|
||||||
return n;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void reserve(size_t sz) { m_children.reserve(sz); }
|
void reserve(size_t sz) { m_children.reserve(sz); }
|
||||||
|
@ -172,6 +187,17 @@ public:
|
||||||
return static_cast<T&>(*tok);
|
return static_cast<T&>(*tok);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
INode* findChild(const QString& name) const
|
||||||
|
{
|
||||||
|
int idx = hypotheticalIndex(name);
|
||||||
|
if (idx >= m_children.size())
|
||||||
|
return nullptr;
|
||||||
|
INode* ret = m_children[idx].get();
|
||||||
|
if (ret->name() == name)
|
||||||
|
return ret;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
bool depthTraverse(const std::function<bool(INode* node)>& func)
|
bool depthTraverse(const std::function<bool(INode* node)>& func)
|
||||||
{
|
{
|
||||||
for (auto& n : m_children)
|
for (auto& n : m_children)
|
||||||
|
@ -191,15 +217,18 @@ public:
|
||||||
const QString& name() const { return m_name; }
|
const QString& name() const { return m_name; }
|
||||||
virtual int hypotheticalIndex(const QString& name) const;
|
virtual int hypotheticalIndex(const QString& name) const;
|
||||||
|
|
||||||
|
virtual amuse::NameDB* getNameDb() const { return nullptr; }
|
||||||
|
|
||||||
virtual Type type() const = 0;
|
virtual Type type() const = 0;
|
||||||
virtual QString text() const = 0;
|
virtual QString text() const = 0;
|
||||||
virtual QIcon icon() const = 0;
|
virtual QIcon icon() const = 0;
|
||||||
virtual Qt::ItemFlags flags() const { return Qt::ItemIsEnabled | Qt::ItemIsSelectable; }
|
virtual Qt::ItemFlags flags() const { return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable; }
|
||||||
|
virtual AmuseItemEditFlags editFlags() const { return AmuseItemNone; }
|
||||||
|
|
||||||
virtual void registerNames(const NameUndoRegistry& registry) const {}
|
virtual void registerNames(const NameUndoRegistry& registry) const {}
|
||||||
virtual void unregisterNames(NameUndoRegistry& registry) const {}
|
virtual void unregisterNames(NameUndoRegistry& registry) const {}
|
||||||
};
|
};
|
||||||
struct NullNode : INode
|
struct NullNode final : INode
|
||||||
{
|
{
|
||||||
NullNode(INode* parent) : INode(parent) {}
|
NullNode(INode* parent) : INode(parent) {}
|
||||||
|
|
||||||
|
@ -207,7 +236,7 @@ public:
|
||||||
QString text() const { return {}; }
|
QString text() const { return {}; }
|
||||||
QIcon icon() const { return {}; }
|
QIcon icon() const { return {}; }
|
||||||
};
|
};
|
||||||
struct RootNode : INode
|
struct RootNode final : INode
|
||||||
{
|
{
|
||||||
RootNode() : INode(QStringLiteral("<root>")) {}
|
RootNode() : INode(QStringLiteral("<root>")) {}
|
||||||
|
|
||||||
|
@ -218,7 +247,7 @@ public:
|
||||||
};
|
};
|
||||||
struct CollectionNode;
|
struct CollectionNode;
|
||||||
struct BasePoolObjectNode;
|
struct BasePoolObjectNode;
|
||||||
struct GroupNode : INode
|
struct GroupNode final : INode
|
||||||
{
|
{
|
||||||
std::unordered_map<QString, std::unique_ptr<amuse::AudioGroupDatabase>>::iterator m_it;
|
std::unordered_map<QString, std::unique_ptr<amuse::AudioGroupDatabase>>::iterator m_it;
|
||||||
GroupNode(const QString& name) : INode(name) {}
|
GroupNode(const QString& name) : INode(name) {}
|
||||||
|
@ -226,17 +255,19 @@ public:
|
||||||
: INode(it->first), m_it(it) {}
|
: INode(it->first), m_it(it) {}
|
||||||
|
|
||||||
int hypotheticalIndex(const QString& name) const;
|
int hypotheticalIndex(const QString& name) const;
|
||||||
|
void _sortChildren();
|
||||||
|
|
||||||
static QIcon Icon;
|
static QIcon Icon;
|
||||||
Type type() const { return Type::Group; }
|
Type type() const { return Type::Group; }
|
||||||
QString text() const { return m_name; }
|
QString text() const { return m_name; }
|
||||||
QIcon icon() const { return Icon; }
|
QIcon icon() const { return Icon; }
|
||||||
|
AmuseItemEditFlags editFlags() const { return AmuseItemNoCut; }
|
||||||
|
|
||||||
CollectionNode* getCollectionOfType(Type tp) const;
|
CollectionNode* getCollectionOfType(Type tp) const;
|
||||||
amuse::AudioGroupDatabase* getAudioGroup() const { return m_it->second.get(); }
|
amuse::AudioGroupDatabase* getAudioGroup() const { return m_it->second.get(); }
|
||||||
BasePoolObjectNode* pageObjectNodeOfId(amuse::ObjectId id) const;
|
BasePoolObjectNode* pageObjectNodeOfId(amuse::ObjectId id) const;
|
||||||
};
|
};
|
||||||
struct SongGroupNode : INode
|
struct SongGroupNode final : INode
|
||||||
{
|
{
|
||||||
amuse::GroupId m_id;
|
amuse::GroupId m_id;
|
||||||
amuse::ObjToken<amuse::SongGroupIndex> m_index;
|
amuse::ObjToken<amuse::SongGroupIndex> m_index;
|
||||||
|
@ -249,6 +280,9 @@ public:
|
||||||
Type type() const { return Type::SongGroup; }
|
Type type() const { return Type::SongGroup; }
|
||||||
QString text() const { return m_name; }
|
QString text() const { return m_name; }
|
||||||
QIcon icon() const { return Icon; }
|
QIcon icon() const { return Icon; }
|
||||||
|
AmuseItemEditFlags editFlags() const { return AmuseItemAll; }
|
||||||
|
|
||||||
|
amuse::NameDB* getNameDb() const { return amuse::GroupId::CurNameDB; }
|
||||||
|
|
||||||
void registerNames(const NameUndoRegistry& registry) const
|
void registerNames(const NameUndoRegistry& registry) const
|
||||||
{
|
{
|
||||||
|
@ -263,7 +297,7 @@ public:
|
||||||
registry.unregisterSongName(p.first);
|
registry.unregisterSongName(p.first);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
struct SoundGroupNode : INode
|
struct SoundGroupNode final : INode
|
||||||
{
|
{
|
||||||
amuse::GroupId m_id;
|
amuse::GroupId m_id;
|
||||||
amuse::ObjToken<amuse::SFXGroupIndex> m_index;
|
amuse::ObjToken<amuse::SFXGroupIndex> m_index;
|
||||||
|
@ -276,6 +310,9 @@ public:
|
||||||
Type type() const { return Type::SoundGroup; }
|
Type type() const { return Type::SoundGroup; }
|
||||||
QString text() const { return m_name; }
|
QString text() const { return m_name; }
|
||||||
QIcon icon() const { return Icon; }
|
QIcon icon() const { return Icon; }
|
||||||
|
AmuseItemEditFlags editFlags() const { return AmuseItemAll; }
|
||||||
|
|
||||||
|
amuse::NameDB* getNameDb() const { return amuse::GroupId::CurNameDB; }
|
||||||
|
|
||||||
void registerNames(const NameUndoRegistry& registry) const
|
void registerNames(const NameUndoRegistry& registry) const
|
||||||
{
|
{
|
||||||
|
@ -290,7 +327,7 @@ public:
|
||||||
registry.unregisterSFXName(p.first);
|
registry.unregisterSFXName(p.first);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
struct CollectionNode : INode
|
struct CollectionNode final : INode
|
||||||
{
|
{
|
||||||
QIcon m_icon;
|
QIcon m_icon;
|
||||||
Type m_collectionType;
|
Type m_collectionType;
|
||||||
|
@ -319,7 +356,7 @@ public:
|
||||||
QIcon icon() const { return {}; }
|
QIcon icon() const { return {}; }
|
||||||
};
|
};
|
||||||
template <class ID, class T, INode::Type TP>
|
template <class ID, class T, INode::Type TP>
|
||||||
struct PoolObjectNode : BasePoolObjectNode
|
struct PoolObjectNode final : BasePoolObjectNode
|
||||||
{
|
{
|
||||||
amuse::ObjToken<T> m_obj;
|
amuse::ObjToken<T> m_obj;
|
||||||
PoolObjectNode(const QString& name, amuse::ObjToken<T> obj) : BasePoolObjectNode(name), m_obj(obj) {}
|
PoolObjectNode(const QString& name, amuse::ObjToken<T> obj) : BasePoolObjectNode(name), m_obj(obj) {}
|
||||||
|
@ -327,6 +364,7 @@ public:
|
||||||
: BasePoolObjectNode(id, ID::CurNameDB->resolveNameFromId(id).data()), m_obj(obj) {}
|
: BasePoolObjectNode(id, ID::CurNameDB->resolveNameFromId(id).data()), m_obj(obj) {}
|
||||||
|
|
||||||
Type type() const { return TP; }
|
Type type() const { return TP; }
|
||||||
|
AmuseItemEditFlags editFlags() const { return TP == INode::Type::Sample ? AmuseItemNoCut : AmuseItemAll; }
|
||||||
|
|
||||||
void registerNames(const NameUndoRegistry& registry) const
|
void registerNames(const NameUndoRegistry& registry) const
|
||||||
{
|
{
|
||||||
|
@ -336,6 +374,10 @@ public:
|
||||||
{
|
{
|
||||||
ID::CurNameDB->remove(m_id);
|
ID::CurNameDB->remove(m_id);
|
||||||
}
|
}
|
||||||
|
amuse::NameDB* getNameDb() const
|
||||||
|
{
|
||||||
|
return ID::CurNameDB;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
using SoundMacroNode = PoolObjectNode<amuse::SoundMacroId, amuse::SoundMacro, INode::Type::SoundMacro>;
|
using SoundMacroNode = PoolObjectNode<amuse::SoundMacroId, amuse::SoundMacro, INode::Type::SoundMacro>;
|
||||||
using ADSRNode = PoolObjectNode<amuse::TableId, std::unique_ptr<amuse::ITable>, INode::Type::ADSR>;
|
using ADSRNode = PoolObjectNode<amuse::TableId, std::unique_ptr<amuse::ITable>, INode::Type::ADSR>;
|
||||||
|
@ -348,9 +390,11 @@ public:
|
||||||
|
|
||||||
bool m_needsReset = false;
|
bool m_needsReset = false;
|
||||||
void _buildGroupNodeCollections(GroupNode& gn);
|
void _buildGroupNodeCollections(GroupNode& gn);
|
||||||
void _buildGroupNode(GroupNode& gn);
|
void _buildGroupNode(GroupNode& gn, amuse::AudioGroup& group);
|
||||||
void _resetModelData();
|
void _resetModelData();
|
||||||
void _resetSongRefCount();
|
void _resetSongRefCount();
|
||||||
|
QString MakeDedupedSubprojectName(const QString& origName);
|
||||||
|
static QString MakeDedupedName(const QString& origName, amuse::NameDB* db);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ProjectModel(const QString& path, QObject* parent = Q_NULLPTR);
|
explicit ProjectModel(const QString& path, QObject* parent = Q_NULLPTR);
|
||||||
|
@ -366,7 +410,10 @@ public:
|
||||||
bool saveToFile(UIMessenger& messenger);
|
bool saveToFile(UIMessenger& messenger);
|
||||||
QStringList getGroupList() const;
|
QStringList getGroupList() const;
|
||||||
bool exportGroup(const QString& path, const QString& groupName, UIMessenger& messenger) const;
|
bool exportGroup(const QString& path, const QString& groupName, UIMessenger& messenger) const;
|
||||||
|
bool importHeader(const QString& path, const QString& groupName, UIMessenger& messenger) const;
|
||||||
|
bool exportHeader(const QString& path, const QString& groupName, bool& yesToAll, UIMessenger& messenger) const;
|
||||||
|
|
||||||
|
void updateNodeNames();
|
||||||
bool ensureModelData();
|
bool ensureModelData();
|
||||||
|
|
||||||
QModelIndex proxyCreateIndex(int arow, int acolumn, void *adata) const;
|
QModelIndex proxyCreateIndex(int arow, int acolumn, void *adata) const;
|
||||||
|
@ -376,10 +423,11 @@ public:
|
||||||
int rowCount(const QModelIndex& parent = QModelIndex()) const;
|
int rowCount(const QModelIndex& parent = QModelIndex()) const;
|
||||||
int columnCount(const QModelIndex& parent = QModelIndex()) const;
|
int columnCount(const QModelIndex& parent = QModelIndex()) const;
|
||||||
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
|
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
|
||||||
|
bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole);
|
||||||
Qt::ItemFlags flags(const QModelIndex& index) const;
|
Qt::ItemFlags flags(const QModelIndex& index) const;
|
||||||
INode* node(const QModelIndex& index) const;
|
INode* node(const QModelIndex& index) const;
|
||||||
GroupNode* getGroupNode(INode* node) const;
|
GroupNode* getGroupNode(INode* node) const;
|
||||||
bool canEdit(const QModelIndex& index) const;
|
AmuseItemEditFlags editFlags(const QModelIndex& index) const;
|
||||||
RootNode* rootNode() const { return m_root.get(); }
|
RootNode* rootNode() const { return m_root.get(); }
|
||||||
|
|
||||||
void _postAddNode(INode* n, const NameUndoRegistry& registry);
|
void _postAddNode(INode* n, const NameUndoRegistry& registry);
|
||||||
|
@ -417,6 +465,21 @@ public:
|
||||||
void _addNode(LayersNode* node, GroupNode* parent, const NameUndoRegistry& registry);
|
void _addNode(LayersNode* node, GroupNode* parent, const NameUndoRegistry& registry);
|
||||||
void _delNode(LayersNode* node, GroupNode* parent, NameUndoRegistry& registry);
|
void _delNode(LayersNode* node, GroupNode* parent, NameUndoRegistry& registry);
|
||||||
LayersNode* newLayers(GroupNode* group, const QString& name);
|
LayersNode* newLayers(GroupNode* group, const QString& name);
|
||||||
|
void _renameNode(INode* node, const QString& name);
|
||||||
|
|
||||||
|
template <class NT>
|
||||||
|
EditorUndoCommand* readMimeYAML(athena::io::YAMLDocReader& r, const QString& name, GroupNode* gn);
|
||||||
|
template <class NT>
|
||||||
|
void loadMimeData(const QMimeData* data, const QString& mimeType, GroupNode* gn);
|
||||||
|
|
||||||
|
QStringList mimeTypes() const;
|
||||||
|
QMimeData* mimeData(const QModelIndexList& indexes) const;
|
||||||
|
bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex &parent);
|
||||||
|
|
||||||
|
void cut(const QModelIndex& index);
|
||||||
|
void copy(const QModelIndex& index);
|
||||||
|
void paste(const QModelIndex& index);
|
||||||
|
QModelIndex duplicate(const QModelIndex& index);
|
||||||
void del(const QModelIndex& index);
|
void del(const QModelIndex& index);
|
||||||
|
|
||||||
const QDir& dir() const { return m_dir; }
|
const QDir& dir() const { return m_dir; }
|
||||||
|
|
|
@ -251,24 +251,17 @@ void PageObjectDelegate::setEditorData(QWidget* editor, const QModelIndex& index
|
||||||
idx = g_MainWindow->projectModel()->getPageObjectProxy()->mapFromSource(g_MainWindow->projectModel()->index(node)).row();
|
idx = g_MainWindow->projectModel()->getPageObjectProxy()->mapFromSource(g_MainWindow->projectModel()->index(node)).row();
|
||||||
static_cast<EditorFieldPageObjectNode*>(editor)->setCurrentIndex(idx);
|
static_cast<EditorFieldPageObjectNode*>(editor)->setCurrentIndex(idx);
|
||||||
if (static_cast<EditorFieldPageObjectNode*>(editor)->shouldPopupOpen())
|
if (static_cast<EditorFieldPageObjectNode*>(editor)->shouldPopupOpen())
|
||||||
static_cast<EditorFieldPageObjectNode*>(editor)->showPopup();
|
QApplication::postEvent(editor, new QEvent(QEvent::User));
|
||||||
}
|
}
|
||||||
|
|
||||||
void PageObjectDelegate::setModelData(QWidget* editor, QAbstractItemModel* m, const QModelIndex& index) const
|
void PageObjectDelegate::setModelData(QWidget* editor, QAbstractItemModel* m, const QModelIndex& index) const
|
||||||
{
|
{
|
||||||
const PageModel* model = static_cast<const PageModel*>(m);
|
const PageModel* model = static_cast<const PageModel*>(m);
|
||||||
auto entry = model->m_sorted[index.row()];
|
auto entry = model->m_sorted[index.row()];
|
||||||
int idx = static_cast<EditorFieldPageObjectNode*>(editor)->currentIndex();
|
ProjectModel::BasePoolObjectNode* node = static_cast<EditorFieldPageObjectNode*>(editor)->currentNode();
|
||||||
amuse::ObjectId id;
|
amuse::ObjectId id;
|
||||||
if (idx != 0)
|
if (node)
|
||||||
{
|
|
||||||
ProjectModel::BasePoolObjectNode* node = static_cast<ProjectModel::BasePoolObjectNode*>(
|
|
||||||
g_MainWindow->projectModel()->node(
|
|
||||||
g_MainWindow->projectModel()->getPageObjectProxy()->mapToSource(
|
|
||||||
g_MainWindow->projectModel()->getPageObjectProxy()->index(idx, 0,
|
|
||||||
static_cast<EditorFieldPageObjectNode*>(editor)->rootModelIndex()))));
|
|
||||||
id = node->id();
|
id = node->id();
|
||||||
}
|
|
||||||
if (id == entry->second.objId.id)
|
if (id == entry->second.objId.id)
|
||||||
{
|
{
|
||||||
emit m->dataChanged(index, index);
|
emit m->dataChanged(index, index);
|
||||||
|
@ -432,7 +425,7 @@ QVariant PageModel::data(const QModelIndex& index, int role) const
|
||||||
switch (index.column())
|
switch (index.column())
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
return entry->first;
|
return entry->first + 1;
|
||||||
case 1:
|
case 1:
|
||||||
{
|
{
|
||||||
ProjectModel::GroupNode* group = g_MainWindow->projectModel()->getGroupNode(m_node.get());
|
ProjectModel::GroupNode* group = g_MainWindow->projectModel()->getGroupNode(m_node.get());
|
||||||
|
@ -464,9 +457,10 @@ bool PageModel::setData(const QModelIndex& index, const QVariant& value, int rol
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
{
|
{
|
||||||
if (value.toInt() == entry->first)
|
int prog = value.toInt() - 1;
|
||||||
|
if (prog == entry->first)
|
||||||
return false;
|
return false;
|
||||||
if (map.find(value.toInt()) != map.cend())
|
if (map.find(prog) != map.cend())
|
||||||
{
|
{
|
||||||
QMessageBox::critical(g_MainWindow, tr("Program Conflict"),
|
QMessageBox::critical(g_MainWindow, tr("Program Conflict"),
|
||||||
tr("Program %1 is already defined in table").arg(value.toInt()));
|
tr("Program %1 is already defined in table").arg(value.toInt()));
|
||||||
|
@ -935,7 +929,7 @@ QVariant SetupModel::data(const QModelIndex& index, int role) const
|
||||||
switch (index.column())
|
switch (index.column())
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
return entry.programNo;
|
return entry.programNo + 1;
|
||||||
case 1:
|
case 1:
|
||||||
return entry.volume;
|
return entry.volume;
|
||||||
case 2:
|
case 2:
|
||||||
|
@ -959,26 +953,28 @@ bool SetupModel::setData(const QModelIndex& index, const QVariant& value, int ro
|
||||||
|
|
||||||
auto& entry = m_data->second[index.row()];
|
auto& entry = m_data->second[index.row()];
|
||||||
|
|
||||||
|
int val = value.toInt();
|
||||||
switch (index.column())
|
switch (index.column())
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
if (entry.programNo == value.toInt())
|
val -= 1;
|
||||||
|
if (entry.programNo == val)
|
||||||
return false;
|
return false;
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
if (entry.volume == value.toInt())
|
if (entry.volume == val)
|
||||||
return false;
|
return false;
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
if (entry.panning == value.toInt())
|
if (entry.panning == val)
|
||||||
return false;
|
return false;
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
if (entry.reverb == value.toInt())
|
if (entry.reverb == val)
|
||||||
return false;
|
return false;
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
if (entry.chorus == value.toInt())
|
if (entry.chorus == val)
|
||||||
return false;
|
return false;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -988,7 +984,7 @@ bool SetupModel::setData(const QModelIndex& index, const QVariant& value, int ro
|
||||||
g_MainWindow->pushUndoCommand(new SetupDataChangeUndoCommand(
|
g_MainWindow->pushUndoCommand(new SetupDataChangeUndoCommand(
|
||||||
static_cast<SongGroupEditor*>(parent())->m_setupList.m_node.get(),
|
static_cast<SongGroupEditor*>(parent())->m_setupList.m_node.get(),
|
||||||
tr("Change %1").arg(headerData(index.column(), Qt::Horizontal).toString()), m_data->first,
|
tr("Change %1").arg(headerData(index.column(), Qt::Horizontal).toString()), m_data->first,
|
||||||
index.row(), index.column(), value.toInt()));
|
index.row(), index.column(), val));
|
||||||
|
|
||||||
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
|
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
|
||||||
|
|
||||||
|
@ -1076,9 +1072,10 @@ PageTableView::PageTableView(QWidget* parent)
|
||||||
setGridStyle(Qt::NoPen);
|
setGridStyle(Qt::NoPen);
|
||||||
|
|
||||||
m_127Delegate.setItemEditorFactory(&m_127Factory);
|
m_127Delegate.setItemEditorFactory(&m_127Factory);
|
||||||
|
m_128Delegate.setItemEditorFactory(&m_128Factory);
|
||||||
m_255Delegate.setItemEditorFactory(&m_255Factory);
|
m_255Delegate.setItemEditorFactory(&m_255Factory);
|
||||||
|
|
||||||
setItemDelegateForColumn(0, &m_127Delegate);
|
setItemDelegateForColumn(0, &m_128Delegate);
|
||||||
setItemDelegateForColumn(1, &m_poDelegate);
|
setItemDelegateForColumn(1, &m_poDelegate);
|
||||||
setItemDelegateForColumn(2, &m_255Delegate);
|
setItemDelegateForColumn(2, &m_255Delegate);
|
||||||
setItemDelegateForColumn(3, &m_255Delegate);
|
setItemDelegateForColumn(3, &m_255Delegate);
|
||||||
|
@ -1095,8 +1092,8 @@ void SetupTableView::setModel(QAbstractItemModel* list, QAbstractItemModel* tabl
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
m_tableView->setModel(table);
|
m_tableView->setModel(table);
|
||||||
auto hheader = m_tableView->horizontalHeader();
|
m_tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
|
||||||
hheader->setSectionResizeMode(QHeaderView::Stretch);
|
m_tableView->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1142,8 +1139,9 @@ SetupTableView::SetupTableView(QWidget* parent)
|
||||||
m_tableView->setGridStyle(Qt::NoPen);
|
m_tableView->setGridStyle(Qt::NoPen);
|
||||||
|
|
||||||
m_127Delegate.setItemEditorFactory(&m_127Factory);
|
m_127Delegate.setItemEditorFactory(&m_127Factory);
|
||||||
|
m_128Delegate.setItemEditorFactory(&m_128Factory);
|
||||||
|
|
||||||
m_tableView->setItemDelegateForColumn(0, &m_127Delegate);
|
m_tableView->setItemDelegateForColumn(0, &m_128Delegate);
|
||||||
m_tableView->setItemDelegateForColumn(1, &m_127Delegate);
|
m_tableView->setItemDelegateForColumn(1, &m_127Delegate);
|
||||||
m_tableView->setItemDelegateForColumn(2, &m_127Delegate);
|
m_tableView->setItemDelegateForColumn(2, &m_127Delegate);
|
||||||
m_tableView->setItemDelegateForColumn(3, &m_127Delegate);
|
m_tableView->setItemDelegateForColumn(3, &m_127Delegate);
|
||||||
|
@ -1434,28 +1432,13 @@ void SongGroupEditor::setupDataChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SongGroupEditor::isItemEditEnabled() const
|
AmuseItemEditFlags SongGroupEditor::itemEditFlags() const
|
||||||
{
|
{
|
||||||
if (PageTableView* table = qobject_cast<PageTableView*>(m_tabs.currentWidget()))
|
if (PageTableView* table = qobject_cast<PageTableView*>(m_tabs.currentWidget()))
|
||||||
return table->hasFocus() && !table->selectionModel()->selectedRows().isEmpty();
|
return (table->hasFocus() && !table->selectionModel()->selectedRows().isEmpty()) ? AmuseItemDelete : AmuseItemNone;
|
||||||
else if (SetupTableView* table = qobject_cast<SetupTableView*>(m_tabs.currentWidget()))
|
else if (SetupTableView* table = qobject_cast<SetupTableView*>(m_tabs.currentWidget()))
|
||||||
return table->m_listView->hasFocus() && !table->m_listView->selectionModel()->selectedRows().isEmpty();
|
return (table->m_listView->hasFocus() && !table->m_listView->selectionModel()->selectedRows().isEmpty()) ? AmuseItemDelete : AmuseItemNone;
|
||||||
return false;
|
return AmuseItemNone;
|
||||||
}
|
|
||||||
|
|
||||||
void SongGroupEditor::itemCutAction()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void SongGroupEditor::itemCopyAction()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void SongGroupEditor::itemPasteAction()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SongGroupEditor::itemDeleteAction()
|
void SongGroupEditor::itemDeleteAction()
|
||||||
|
|
|
@ -170,8 +170,9 @@ class PageTableView : public QTableView
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
PageObjectDelegate m_poDelegate;
|
PageObjectDelegate m_poDelegate;
|
||||||
RangedValueFactory<0, 127> m_127Factory;
|
RangedValueFactory<0, 127> m_127Factory;
|
||||||
|
RangedValueFactory<1, 128> m_128Factory;
|
||||||
RangedValueFactory<0, 255> m_255Factory;
|
RangedValueFactory<0, 255> m_255Factory;
|
||||||
QStyledItemDelegate m_127Delegate, m_255Delegate;
|
QStyledItemDelegate m_127Delegate, m_128Delegate, m_255Delegate;
|
||||||
public:
|
public:
|
||||||
explicit PageTableView(QWidget* parent = Q_NULLPTR);
|
explicit PageTableView(QWidget* parent = Q_NULLPTR);
|
||||||
void setModel(QAbstractItemModel* model);
|
void setModel(QAbstractItemModel* model);
|
||||||
|
@ -187,7 +188,8 @@ class SetupTableView : public QSplitter
|
||||||
QTableView* m_tableView;
|
QTableView* m_tableView;
|
||||||
MIDIFileDelegate m_midiDelegate;
|
MIDIFileDelegate m_midiDelegate;
|
||||||
RangedValueFactory<0, 127> m_127Factory;
|
RangedValueFactory<0, 127> m_127Factory;
|
||||||
QStyledItemDelegate m_127Delegate;
|
RangedValueFactory<1, 128> m_128Factory;
|
||||||
|
QStyledItemDelegate m_127Delegate, m_128Delegate;
|
||||||
public:
|
public:
|
||||||
explicit SetupTableView(QWidget* parent = Q_NULLPTR);
|
explicit SetupTableView(QWidget* parent = Q_NULLPTR);
|
||||||
void setModel(QAbstractItemModel* list, QAbstractItemModel* table);
|
void setModel(QAbstractItemModel* list, QAbstractItemModel* table);
|
||||||
|
@ -262,7 +264,7 @@ public:
|
||||||
void setEditorEnabled(bool en) {}
|
void setEditorEnabled(bool en) {}
|
||||||
void resizeEvent(QResizeEvent* ev);
|
void resizeEvent(QResizeEvent* ev);
|
||||||
QTableView* getSetupListView() const { return m_setupTable->m_listView; }
|
QTableView* getSetupListView() const { return m_setupTable->m_listView; }
|
||||||
bool isItemEditEnabled() const;
|
AmuseItemEditFlags itemEditFlags() const;
|
||||||
public slots:
|
public slots:
|
||||||
void doAdd();
|
void doAdd();
|
||||||
void doSelectionChanged();
|
void doSelectionChanged();
|
||||||
|
@ -271,10 +273,6 @@ public slots:
|
||||||
void rowsAboutToBeRemoved(const QModelIndex &parent, int first, int last);
|
void rowsAboutToBeRemoved(const QModelIndex &parent, int first, int last);
|
||||||
void modelAboutToBeReset();
|
void modelAboutToBeReset();
|
||||||
void setupDataChanged();
|
void setupDataChanged();
|
||||||
|
|
||||||
void itemCutAction();
|
|
||||||
void itemCopyAction();
|
|
||||||
void itemPasteAction();
|
|
||||||
void itemDeleteAction();
|
void itemDeleteAction();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -142,24 +142,17 @@ void SFXObjectDelegate::setEditorData(QWidget* editor, const QModelIndex& index)
|
||||||
idx = g_MainWindow->projectModel()->getPageObjectProxy()->mapFromSource(g_MainWindow->projectModel()->index(node)).row();
|
idx = g_MainWindow->projectModel()->getPageObjectProxy()->mapFromSource(g_MainWindow->projectModel()->index(node)).row();
|
||||||
static_cast<EditorFieldPageObjectNode*>(editor)->setCurrentIndex(idx);
|
static_cast<EditorFieldPageObjectNode*>(editor)->setCurrentIndex(idx);
|
||||||
if (static_cast<EditorFieldPageObjectNode*>(editor)->shouldPopupOpen())
|
if (static_cast<EditorFieldPageObjectNode*>(editor)->shouldPopupOpen())
|
||||||
static_cast<EditorFieldPageObjectNode*>(editor)->showPopup();
|
QApplication::postEvent(editor, new QEvent(QEvent::User));
|
||||||
}
|
}
|
||||||
|
|
||||||
void SFXObjectDelegate::setModelData(QWidget* editor, QAbstractItemModel* m, const QModelIndex& index) const
|
void SFXObjectDelegate::setModelData(QWidget* editor, QAbstractItemModel* m, const QModelIndex& index) const
|
||||||
{
|
{
|
||||||
const SFXModel* model = static_cast<const SFXModel*>(m);
|
const SFXModel* model = static_cast<const SFXModel*>(m);
|
||||||
auto entry = model->m_sorted[index.row()];
|
auto entry = model->m_sorted[index.row()];
|
||||||
int idx = static_cast<EditorFieldPageObjectNode*>(editor)->currentIndex();
|
ProjectModel::BasePoolObjectNode* node = static_cast<EditorFieldPageObjectNode*>(editor)->currentNode();
|
||||||
amuse::ObjectId id;
|
amuse::ObjectId id;
|
||||||
if (idx != 0)
|
if (node)
|
||||||
{
|
|
||||||
ProjectModel::BasePoolObjectNode* node = static_cast<ProjectModel::BasePoolObjectNode*>(
|
|
||||||
g_MainWindow->projectModel()->node(
|
|
||||||
g_MainWindow->projectModel()->getPageObjectProxy()->mapToSource(
|
|
||||||
g_MainWindow->projectModel()->getPageObjectProxy()->index(idx, 0,
|
|
||||||
static_cast<EditorFieldPageObjectNode*>(editor)->rootModelIndex()))));
|
|
||||||
id = node->id();
|
id = node->id();
|
||||||
}
|
|
||||||
if (id == entry->second.objId.id)
|
if (id == entry->second.objId.id)
|
||||||
{
|
{
|
||||||
emit m->dataChanged(index, index);
|
emit m->dataChanged(index, index);
|
||||||
|
@ -614,9 +607,9 @@ void SoundGroupEditor::resizeEvent(QResizeEvent* ev)
|
||||||
m_addRemoveButtons.move(0, ev->size().height() - 32);
|
m_addRemoveButtons.move(0, ev->size().height() - 32);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SoundGroupEditor::isItemEditEnabled() const
|
AmuseItemEditFlags SoundGroupEditor::itemEditFlags() const
|
||||||
{
|
{
|
||||||
return m_sfxTable->hasFocus() && !m_sfxTable->selectionModel()->selectedRows().isEmpty();
|
return (m_sfxTable->hasFocus() && !m_sfxTable->selectionModel()->selectedRows().isEmpty()) ? AmuseItemDelete : AmuseItemNone;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SoundGroupEditor::doAdd()
|
void SoundGroupEditor::doAdd()
|
||||||
|
@ -652,21 +645,6 @@ void SoundGroupEditor::sfxDataChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SoundGroupEditor::itemCutAction()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void SoundGroupEditor::itemCopyAction()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void SoundGroupEditor::itemPasteAction()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void SoundGroupEditor::itemDeleteAction()
|
void SoundGroupEditor::itemDeleteAction()
|
||||||
{
|
{
|
||||||
m_sfxTable->deleteSelection();
|
m_sfxTable->deleteSelection();
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
class SFXObjectDelegate : public QStyledItemDelegate
|
class SFXObjectDelegate : public QStyledItemDelegate
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit SFXObjectDelegate(QObject* parent = Q_NULLPTR);
|
explicit SFXObjectDelegate(QObject* parent = Q_NULLPTR);
|
||||||
QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const;
|
QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const;
|
||||||
|
@ -20,7 +20,7 @@ private slots:
|
||||||
|
|
||||||
class SFXModel : public QAbstractTableModel
|
class SFXModel : public QAbstractTableModel
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
friend class SoundGroupEditor;
|
friend class SoundGroupEditor;
|
||||||
friend class SFXObjectDelegate;
|
friend class SFXObjectDelegate;
|
||||||
friend class SFXTableView;
|
friend class SFXTableView;
|
||||||
|
@ -69,7 +69,7 @@ public:
|
||||||
|
|
||||||
class SFXTableView : public QTableView
|
class SFXTableView : public QTableView
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
SFXObjectDelegate m_sfxDelegate;
|
SFXObjectDelegate m_sfxDelegate;
|
||||||
RangedValueFactory<0, 127> m_127Factory;
|
RangedValueFactory<0, 127> m_127Factory;
|
||||||
RangedValueFactory<0, 255> m_255Factory;
|
RangedValueFactory<0, 255> m_255Factory;
|
||||||
|
@ -104,7 +104,7 @@ public slots:
|
||||||
|
|
||||||
class SoundGroupEditor : public EditorWidget
|
class SoundGroupEditor : public EditorWidget
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
SFXModel m_sfxs;
|
SFXModel m_sfxs;
|
||||||
SFXTableView* m_sfxTable;
|
SFXTableView* m_sfxTable;
|
||||||
AddRemoveButtons m_addRemoveButtons;
|
AddRemoveButtons m_addRemoveButtons;
|
||||||
|
@ -116,15 +116,11 @@ public:
|
||||||
void setEditorEnabled(bool en) {}
|
void setEditorEnabled(bool en) {}
|
||||||
void resizeEvent(QResizeEvent* ev);
|
void resizeEvent(QResizeEvent* ev);
|
||||||
QTableView* getSFXListView() const { return m_sfxTable; }
|
QTableView* getSFXListView() const { return m_sfxTable; }
|
||||||
bool isItemEditEnabled() const;
|
AmuseItemEditFlags itemEditFlags() const;
|
||||||
public slots:
|
public slots:
|
||||||
void doAdd();
|
void doAdd();
|
||||||
void doSelectionChanged();
|
void doSelectionChanged();
|
||||||
void sfxDataChanged();
|
void sfxDataChanged();
|
||||||
|
|
||||||
void itemCutAction();
|
|
||||||
void itemCopyAction();
|
|
||||||
void itemPasteAction();
|
|
||||||
void itemDeleteAction();
|
void itemDeleteAction();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -37,11 +37,7 @@ void FieldSoundMacroStep::targetPressed()
|
||||||
{
|
{
|
||||||
ProjectModel::SoundMacroNode* node = nullptr;
|
ProjectModel::SoundMacroNode* node = nullptr;
|
||||||
if (m_macroField)
|
if (m_macroField)
|
||||||
{
|
node = static_cast<ProjectModel::SoundMacroNode*>(m_macroField->currentNode());
|
||||||
int val = m_macroField->currentIndex();
|
|
||||||
if (val != 0)
|
|
||||||
node = static_cast<ProjectModel::SoundMacroNode*>(m_macroField->collection()->nodeOfIndex(val - 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!m_macroField || node == getListing()->currentNode())
|
if (!m_macroField || node == getListing()->currentNode())
|
||||||
if (SoundMacroEditor* editor = getEditor())
|
if (SoundMacroEditor* editor = getEditor())
|
||||||
|
@ -60,8 +56,8 @@ void FieldSoundMacroStep::updateMacroField()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int val = m_macroField->currentIndex();
|
ProjectModel::SoundMacroNode* node = static_cast<ProjectModel::SoundMacroNode*>(m_macroField->currentNode());
|
||||||
if (val == 0)
|
if (node == nullptr)
|
||||||
{
|
{
|
||||||
m_spinBox.setValue(0);
|
m_spinBox.setValue(0);
|
||||||
m_spinBox.setDisabled(true);
|
m_spinBox.setDisabled(true);
|
||||||
|
@ -69,8 +65,6 @@ void FieldSoundMacroStep::updateMacroField()
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ProjectModel::SoundMacroNode* node = static_cast<ProjectModel::SoundMacroNode*>(
|
|
||||||
m_macroField->collection()->nodeOfIndex(val - 1));
|
|
||||||
int numCmds = int(node->m_obj->m_cmds.size());
|
int numCmds = int(node->m_obj->m_cmds.size());
|
||||||
m_spinBox.setMaximum(numCmds - 1);
|
m_spinBox.setMaximum(numCmds - 1);
|
||||||
m_spinBox.setDisabled(false);
|
m_spinBox.setDisabled(false);
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 5.2916665 5.2916669"
|
||||||
|
id="svg2"
|
||||||
|
version="1.1"
|
||||||
|
inkscape:version="0.92.2 2405546, 2018-03-11"
|
||||||
|
sodipodi:docname="IconBack.svg">
|
||||||
|
<defs
|
||||||
|
id="defs4" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#393939"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="39.383707"
|
||||||
|
inkscape:cx="6.3383887"
|
||||||
|
inkscape:cy="7.7203454"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="true"
|
||||||
|
units="px"
|
||||||
|
inkscape:window-width="3840"
|
||||||
|
inkscape:window-height="2079"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="40"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
showborder="true"
|
||||||
|
objecttolerance="4"
|
||||||
|
gridtolerance="4"
|
||||||
|
guidetolerance="5">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid4173"
|
||||||
|
empspacing="1"
|
||||||
|
visible="true" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<metadata
|
||||||
|
id="metadata7">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(0,-291.70832)">
|
||||||
|
<path
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="m 0,294.35415 2.6458333,-2.64583 v 1.5875 h 2.6458334 v 1.85208 H 2.6458333 l 0,1.85209 z"
|
||||||
|
id="path841"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="cccccccc" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
|
@ -0,0 +1,74 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 5.2916665 5.2916669"
|
||||||
|
id="svg2"
|
||||||
|
version="1.1"
|
||||||
|
inkscape:version="0.92.2 2405546, 2018-03-11"
|
||||||
|
sodipodi:docname="IconBackDisabled.svg">
|
||||||
|
<defs
|
||||||
|
id="defs4" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#393939"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="39.383707"
|
||||||
|
inkscape:cx="1.7552752"
|
||||||
|
inkscape:cy="7.7203454"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="true"
|
||||||
|
units="px"
|
||||||
|
inkscape:window-width="3840"
|
||||||
|
inkscape:window-height="2079"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="40"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
showborder="true"
|
||||||
|
objecttolerance="4"
|
||||||
|
gridtolerance="4"
|
||||||
|
guidetolerance="5">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid4173"
|
||||||
|
empspacing="1"
|
||||||
|
visible="true" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<metadata
|
||||||
|
id="metadata7">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(0,-291.70832)">
|
||||||
|
<path
|
||||||
|
style="fill:#ffffff;fill-opacity:0.39215687;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="m 0,294.35415 2.6458333,-2.64583 v 1.5875 h 2.6458334 v 1.85208 H 2.6458333 v 1.85209 z"
|
||||||
|
id="path841"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="cccccccc" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
|
@ -0,0 +1,74 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 5.2916665 5.2916669"
|
||||||
|
id="svg2"
|
||||||
|
version="1.1"
|
||||||
|
inkscape:version="0.92.2 2405546, 2018-03-11"
|
||||||
|
sodipodi:docname="IconForward.svg">
|
||||||
|
<defs
|
||||||
|
id="defs4" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#393939"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="39.383707"
|
||||||
|
inkscape:cx="6.3383887"
|
||||||
|
inkscape:cy="7.7203454"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="true"
|
||||||
|
units="px"
|
||||||
|
inkscape:window-width="3840"
|
||||||
|
inkscape:window-height="2079"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="40"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
showborder="true"
|
||||||
|
objecttolerance="4"
|
||||||
|
gridtolerance="4"
|
||||||
|
guidetolerance="5">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid4173"
|
||||||
|
empspacing="1"
|
||||||
|
visible="true" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<metadata
|
||||||
|
id="metadata7">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(0,-291.70832)">
|
||||||
|
<path
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="m 5.2916667,294.35415 -2.6458333,-2.64583 v 1.5875 H 0 v 1.85208 h 2.6458334 v 1.85209 z"
|
||||||
|
id="path841"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="cccccccc" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
|
@ -0,0 +1,74 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 5.2916665 5.2916669"
|
||||||
|
id="svg2"
|
||||||
|
version="1.1"
|
||||||
|
inkscape:version="0.92.2 2405546, 2018-03-11"
|
||||||
|
sodipodi:docname="IconForwardDisabled.svg">
|
||||||
|
<defs
|
||||||
|
id="defs4" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#393939"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="39.383707"
|
||||||
|
inkscape:cx="6.3383887"
|
||||||
|
inkscape:cy="7.7203454"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="true"
|
||||||
|
units="px"
|
||||||
|
inkscape:window-width="3840"
|
||||||
|
inkscape:window-height="2079"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="40"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
showborder="true"
|
||||||
|
objecttolerance="4"
|
||||||
|
gridtolerance="4"
|
||||||
|
guidetolerance="5">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid4173"
|
||||||
|
empspacing="1"
|
||||||
|
visible="true" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<metadata
|
||||||
|
id="metadata7">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(0,-291.70832)">
|
||||||
|
<path
|
||||||
|
style="fill:#ffffff;fill-opacity:0.39170507;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="m 5.2916667,294.35415 -2.6458333,-2.64583 v 1.5875 H 0 v 1.85208 h 2.6458334 v 1.85209 z"
|
||||||
|
id="path841"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="cccccccc" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
File diff suppressed because it is too large
Load Diff
|
@ -36,6 +36,10 @@
|
||||||
<file>IconFX.svg</file>
|
<file>IconFX.svg</file>
|
||||||
<file>IconB.svg</file>
|
<file>IconB.svg</file>
|
||||||
<file>IconA.svg</file>
|
<file>IconA.svg</file>
|
||||||
|
<file>IconBack.svg</file>
|
||||||
|
<file>IconForward.svg</file>
|
||||||
|
<file>IconBackDisabled.svg</file>
|
||||||
|
<file>IconForwardDisabled.svg</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
<qresource prefix="/bg">
|
<qresource prefix="/bg">
|
||||||
<file>FaceGrey.svg</file>
|
<file>FaceGrey.svg</file>
|
||||||
|
|
|
@ -4,15 +4,18 @@
|
||||||
#include "AudioGroupPool.hpp"
|
#include "AudioGroupPool.hpp"
|
||||||
#include "AudioGroupProject.hpp"
|
#include "AudioGroupProject.hpp"
|
||||||
#include "AudioGroupSampleDirectory.hpp"
|
#include "AudioGroupSampleDirectory.hpp"
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
namespace amuse
|
namespace amuse
|
||||||
{
|
{
|
||||||
class AudioGroupData;
|
class AudioGroupData;
|
||||||
|
class ProjectDatabase;
|
||||||
|
|
||||||
/** Runtime audio group index container */
|
/** Runtime audio group index container */
|
||||||
class AudioGroup
|
class AudioGroup
|
||||||
{
|
{
|
||||||
friend class AudioGroupSampleDirectory;
|
friend class AudioGroupSampleDirectory;
|
||||||
|
protected:
|
||||||
AudioGroupProject m_proj;
|
AudioGroupProject m_proj;
|
||||||
AudioGroupPool m_pool;
|
AudioGroupPool m_pool;
|
||||||
AudioGroupSampleDirectory m_sdir;
|
AudioGroupSampleDirectory m_sdir;
|
||||||
|
@ -20,16 +23,17 @@ class AudioGroup
|
||||||
SystemString m_groupPath; /* Typically only set by editor */
|
SystemString m_groupPath; /* Typically only set by editor */
|
||||||
bool m_valid;
|
bool m_valid;
|
||||||
|
|
||||||
SystemString getSampleBasePath(SampleId sfxId) const;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
SystemString getSampleBasePath(SampleId sfxId) const;
|
||||||
operator bool() const { return m_valid; }
|
operator bool() const { return m_valid; }
|
||||||
AudioGroup() = default;
|
AudioGroup() = default;
|
||||||
explicit AudioGroup(const AudioGroupData& data) { assign(data); }
|
explicit AudioGroup(const AudioGroupData& data) { assign(data); }
|
||||||
explicit AudioGroup(SystemStringView groupPath) { assign(groupPath); }
|
explicit AudioGroup(SystemStringView groupPath) { assign(groupPath); }
|
||||||
|
explicit AudioGroup(const AudioGroup& data, SystemStringView groupPath) { assign(data, groupPath); }
|
||||||
|
|
||||||
void assign(const AudioGroupData& data);
|
void assign(const AudioGroupData& data);
|
||||||
void assign(SystemStringView groupPath);
|
void assign(SystemStringView groupPath);
|
||||||
|
void assign(const AudioGroup& data, SystemStringView groupPath);
|
||||||
void setGroupPath(SystemStringView groupPath) { m_groupPath = groupPath; }
|
void setGroupPath(SystemStringView groupPath) { m_groupPath = groupPath; }
|
||||||
|
|
||||||
const SampleEntry* getSample(SampleId sfxId) const;
|
const SampleEntry* getSample(SampleId sfxId) const;
|
||||||
|
@ -58,6 +62,9 @@ class AudioGroupDatabase final : public AudioGroup
|
||||||
NameDB m_keymapDb;
|
NameDB m_keymapDb;
|
||||||
NameDB m_layersDb;
|
NameDB m_layersDb;
|
||||||
|
|
||||||
|
void _recursiveRenameMacro(SoundMacroId id, std::string_view str, int& macroIdx,
|
||||||
|
std::unordered_set<SoundMacroId>& renamedIds);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AudioGroupDatabase() = default;
|
AudioGroupDatabase() = default;
|
||||||
explicit AudioGroupDatabase(const AudioGroupData& data)
|
explicit AudioGroupDatabase(const AudioGroupData& data)
|
||||||
|
@ -70,6 +77,11 @@ public:
|
||||||
setIdDatabases();
|
setIdDatabases();
|
||||||
assign(groupPath);
|
assign(groupPath);
|
||||||
}
|
}
|
||||||
|
explicit AudioGroupDatabase(const AudioGroupDatabase& data, SystemStringView groupPath)
|
||||||
|
{
|
||||||
|
setIdDatabases();
|
||||||
|
assign(data, groupPath);
|
||||||
|
}
|
||||||
|
|
||||||
void setIdDatabases() const
|
void setIdDatabases() const
|
||||||
{
|
{
|
||||||
|
@ -79,6 +91,13 @@ public:
|
||||||
KeymapId::CurNameDB = const_cast<NameDB*>(&m_keymapDb);
|
KeymapId::CurNameDB = const_cast<NameDB*>(&m_keymapDb);
|
||||||
LayersId::CurNameDB = const_cast<NameDB*>(&m_layersDb);
|
LayersId::CurNameDB = const_cast<NameDB*>(&m_layersDb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void renameSample(SampleId id, std::string_view str);
|
||||||
|
void deleteSample(SampleId id);
|
||||||
|
void copySampleInto(const SystemString& basePath, const SystemString& newBasePath);
|
||||||
|
|
||||||
|
void importCHeader(std::string_view header);
|
||||||
|
std::string exportCHeader(std::string_view projectName, std::string_view groupName) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ProjectDatabase
|
class ProjectDatabase
|
||||||
|
|
|
@ -1157,6 +1157,9 @@ struct SoundMacro
|
||||||
std::swap(m_cmds[a], m_cmds[b]);
|
std::swap(m_cmds[a], m_cmds[b]);
|
||||||
}
|
}
|
||||||
void buildFromPrototype(const SoundMacro& other);
|
void buildFromPrototype(const SoundMacro& other);
|
||||||
|
|
||||||
|
void toYAML(athena::io::YAMLDocWriter& w) const;
|
||||||
|
void fromYAML(athena::io::YAMLDocReader& r, size_t cmdCount);
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
|
|
@ -125,6 +125,9 @@ struct SongGroupIndex : AudioGroupIndex
|
||||||
reverb(setup.reverb), chorus(setup.chorus) {}
|
reverb(setup.reverb), chorus(setup.chorus) {}
|
||||||
};
|
};
|
||||||
std::unordered_map<SongId, std::array<MIDISetup, 16>> m_midiSetups;
|
std::unordered_map<SongId, std::array<MIDISetup, 16>> m_midiSetups;
|
||||||
|
|
||||||
|
void toYAML(athena::io::YAMLDocWriter& w) const;
|
||||||
|
void fromYAML(athena::io::YAMLDocReader& r);
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Root index of SFXGroup */
|
/** Root index of SFXGroup */
|
||||||
|
@ -177,6 +180,12 @@ struct SFXGroupIndex : AudioGroupIndex
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
std::unordered_map<SFXId, SFXEntry> m_sfxEntries;
|
std::unordered_map<SFXId, SFXEntry> m_sfxEntries;
|
||||||
|
|
||||||
|
SFXGroupIndex() = default;
|
||||||
|
SFXGroupIndex(const SFXGroupIndex& other);
|
||||||
|
|
||||||
|
void toYAML(athena::io::YAMLDocWriter& w) const;
|
||||||
|
void fromYAML(athena::io::YAMLDocReader& r);
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Collection of SongGroup and SFXGroup indexes */
|
/** Collection of SongGroup and SFXGroup indexes */
|
||||||
|
@ -196,6 +205,7 @@ public:
|
||||||
AudioGroupProject() = default;
|
AudioGroupProject() = default;
|
||||||
static AudioGroupProject CreateAudioGroupProject(const AudioGroupData& data);
|
static AudioGroupProject CreateAudioGroupProject(const AudioGroupData& data);
|
||||||
static AudioGroupProject CreateAudioGroupProject(SystemStringView groupPath);
|
static AudioGroupProject CreateAudioGroupProject(SystemStringView groupPath);
|
||||||
|
static AudioGroupProject CreateAudioGroupProject(const AudioGroupProject& oldProj);
|
||||||
static void BootstrapObjectIDs(const AudioGroupData& data);
|
static void BootstrapObjectIDs(const AudioGroupData& data);
|
||||||
|
|
||||||
const SongGroupIndex* getSongGroupIndex(int groupId) const;
|
const SongGroupIndex* getSongGroupIndex(int groupId) const;
|
||||||
|
|
|
@ -349,6 +349,7 @@ public:
|
||||||
static AudioGroupSampleDirectory CreateAudioGroupSampleDirectory(SystemStringView groupPath);
|
static AudioGroupSampleDirectory CreateAudioGroupSampleDirectory(SystemStringView groupPath);
|
||||||
|
|
||||||
const std::unordered_map<SampleId, ObjToken<Entry>>& sampleEntries() const { return m_entries; }
|
const std::unordered_map<SampleId, ObjToken<Entry>>& sampleEntries() const { return m_entries; }
|
||||||
|
std::unordered_map<SampleId, ObjToken<Entry>>& sampleEntries() { return m_entries; }
|
||||||
|
|
||||||
void extractWAV(SampleId id, amuse::SystemStringView destDir, const unsigned char* samp) const;
|
void extractWAV(SampleId id, amuse::SystemStringView destDir, const unsigned char* samp) const;
|
||||||
void extractAllWAV(amuse::SystemStringView destDir, const unsigned char* samp) const;
|
void extractAllWAV(amuse::SystemStringView destDir, const unsigned char* samp) const;
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include <strings.h>
|
#include <strings.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
#else
|
#else
|
||||||
#ifndef WIN32_LEAN_AND_MEAN
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
#define WIN32_LEAN_AND_MEAN 1
|
#define WIN32_LEAN_AND_MEAN 1
|
||||||
|
@ -176,6 +177,10 @@ public:
|
||||||
ObjTokenBase& operator=(ObjTokenBase&& other)
|
ObjTokenBase& operator=(ObjTokenBase&& other)
|
||||||
{ if (m_obj) m_obj->decrement(); m_obj = other.m_obj; other.m_obj = nullptr; return *this; }
|
{ if (m_obj) m_obj->decrement(); m_obj = other.m_obj; other.m_obj = nullptr; return *this; }
|
||||||
~ObjTokenBase() { if (m_obj) m_obj->decrement(); }
|
~ObjTokenBase() { if (m_obj) m_obj->decrement(); }
|
||||||
|
bool operator==(const ObjTokenBase& other) const { return m_obj == other.m_obj; }
|
||||||
|
bool operator!=(const ObjTokenBase& other) const { return m_obj != other.m_obj; }
|
||||||
|
bool operator<(const ObjTokenBase& other) const { return m_obj < other.m_obj; }
|
||||||
|
bool operator>(const ObjTokenBase& other) const { return m_obj > other.m_obj; }
|
||||||
operator bool() const { return m_obj != nullptr; }
|
operator bool() const { return m_obj != nullptr; }
|
||||||
void reset() { if (m_obj) m_obj->decrement(); m_obj = nullptr; }
|
void reset() { if (m_obj) m_obj->decrement(); m_obj = nullptr; }
|
||||||
};
|
};
|
||||||
|
@ -276,6 +281,16 @@ static inline int Mkdir(const char* path, mode_t mode) { return mkdir(path, mode
|
||||||
static inline int Stat(const char* path, Sstat* statout) { return stat(path, statout); }
|
static inline int Stat(const char* path, Sstat* statout) { return stat(path, statout); }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static inline int Rename(const SystemChar* oldpath, const SystemChar* newpath)
|
||||||
|
{
|
||||||
|
#if _WIN32
|
||||||
|
//return _wrename(oldpath, newpath);
|
||||||
|
return MoveFileExW(oldpath, newpath, MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH) == 0;
|
||||||
|
#else
|
||||||
|
return rename(oldpath, newpath);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
#if _WIN32
|
#if _WIN32
|
||||||
static inline int CompareCaseInsensitive(const char* a, const char* b) { return _stricmp(a, b); }
|
static inline int CompareCaseInsensitive(const char* a, const char* b) { return _stricmp(a, b); }
|
||||||
#endif
|
#endif
|
||||||
|
@ -415,6 +430,8 @@ static inline void Unlink(const SystemChar* file)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Copy(const SystemChar* from, const SystemChar* to);
|
||||||
|
|
||||||
#undef bswap16
|
#undef bswap16
|
||||||
#undef bswap32
|
#undef bswap32
|
||||||
#undef bswap64
|
#undef bswap64
|
||||||
|
@ -618,7 +635,7 @@ DECL_ID_HASH(GroupId)
|
||||||
template<class T>
|
template<class T>
|
||||||
struct hash<amuse::ObjToken<T>>
|
struct hash<amuse::ObjToken<T>>
|
||||||
{
|
{
|
||||||
size_t operator()(const amuse::ObjToken<T>& val) const noexcept { return reinterpret_cast<size_t>(val.get()); }
|
size_t operator()(const amuse::ObjToken<T>& val) const noexcept { return hash<T*>()(val.get()); }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,10 +28,10 @@ protected:
|
||||||
}
|
}
|
||||||
Engine& m_engine;
|
Engine& m_engine;
|
||||||
const AudioGroup& m_audioGroup;
|
const AudioGroup& m_audioGroup;
|
||||||
int m_groupId;
|
GroupId m_groupId;
|
||||||
ObjectId m_objectId; /* if applicable */
|
ObjectId m_objectId; /* if applicable */
|
||||||
public:
|
public:
|
||||||
Entity(Engine& engine, const AudioGroup& group, int groupId, ObjectId oid = ObjectId())
|
Entity(Engine& engine, const AudioGroup& group, GroupId groupId, ObjectId oid = ObjectId())
|
||||||
: m_engine(engine), m_audioGroup(group), m_groupId(groupId), m_objectId(oid)
|
: m_engine(engine), m_audioGroup(group), m_groupId(groupId), m_objectId(oid)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ public:
|
||||||
|
|
||||||
Engine& getEngine() { return m_engine; }
|
Engine& getEngine() { return m_engine; }
|
||||||
const AudioGroup& getAudioGroup() const { return m_audioGroup; }
|
const AudioGroup& getAudioGroup() const { return m_audioGroup; }
|
||||||
int getGroupId() const { return m_groupId; }
|
GroupId getGroupId() const { return m_groupId; }
|
||||||
ObjectId getObjectId() const { return m_objectId; }
|
ObjectId getObjectId() const { return m_objectId; }
|
||||||
bool isDestroyed() const { return m_destroyed; }
|
bool isDestroyed() const { return m_destroyed; }
|
||||||
};
|
};
|
||||||
|
|
|
@ -92,9 +92,9 @@ class Sequencer : public Entity
|
||||||
|
|
||||||
public:
|
public:
|
||||||
~Sequencer();
|
~Sequencer();
|
||||||
Sequencer(Engine& engine, const AudioGroup& group, int groupId, const SongGroupIndex* songGroup, int setupId,
|
Sequencer(Engine& engine, const AudioGroup& group, GroupId groupId, const SongGroupIndex* songGroup, SongId setupId,
|
||||||
ObjToken<Studio> studio);
|
ObjToken<Studio> studio);
|
||||||
Sequencer(Engine& engine, const AudioGroup& group, int groupId, const SFXGroupIndex* sfxGroup,
|
Sequencer(Engine& engine, const AudioGroup& group, GroupId groupId, const SFXGroupIndex* sfxGroup,
|
||||||
ObjToken<Studio> studio);
|
ObjToken<Studio> studio);
|
||||||
|
|
||||||
/** Advance current song data (if any) */
|
/** Advance current song data (if any) */
|
||||||
|
|
|
@ -84,7 +84,7 @@ class SongState
|
||||||
int32_t m_modVal = 0; /**< Accumulated value of mod */
|
int32_t m_modVal = 0; /**< Accumulated value of mod */
|
||||||
uint32_t m_nextModTick = 0; /**< Upcoming position of mod wheel change */
|
uint32_t m_nextModTick = 0; /**< Upcoming position of mod wheel change */
|
||||||
int32_t m_nextModDelta = 0; /**< Upcoming delta value of mod */
|
int32_t m_nextModDelta = 0; /**< Upcoming delta value of mod */
|
||||||
std::array<int, 128> m_remNoteLengths; /**< Remaining ticks per note */
|
std::array<int, 128> m_remNoteLengths = {}; /**< Remaining ticks per note */
|
||||||
|
|
||||||
int32_t m_eventWaitCountdown = 0; /**< Current wait in ticks */
|
int32_t m_eventWaitCountdown = 0; /**< Current wait in ticks */
|
||||||
int32_t m_lastN64EventTick =
|
int32_t m_lastN64EventTick =
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
#include "amuse/AudioGroup.hpp"
|
#include "amuse/AudioGroup.hpp"
|
||||||
#include "amuse/AudioGroupData.hpp"
|
#include "amuse/AudioGroupData.hpp"
|
||||||
|
#include <regex>
|
||||||
|
#include <athena/FileReader.hpp>
|
||||||
|
|
||||||
|
using namespace std::literals;
|
||||||
|
|
||||||
namespace amuse
|
namespace amuse
|
||||||
{
|
{
|
||||||
|
@ -20,6 +24,15 @@ void AudioGroup::assign(SystemStringView groupPath)
|
||||||
m_proj = AudioGroupProject::CreateAudioGroupProject(groupPath);
|
m_proj = AudioGroupProject::CreateAudioGroupProject(groupPath);
|
||||||
m_samp = nullptr;
|
m_samp = nullptr;
|
||||||
}
|
}
|
||||||
|
void AudioGroup::assign(const AudioGroup& data, SystemStringView groupPath)
|
||||||
|
{
|
||||||
|
/* Reverse order when loading intermediates */
|
||||||
|
m_groupPath = groupPath;
|
||||||
|
m_sdir = AudioGroupSampleDirectory::CreateAudioGroupSampleDirectory(groupPath);
|
||||||
|
m_pool = AudioGroupPool::CreateAudioGroupPool(groupPath);
|
||||||
|
m_proj = AudioGroupProject::CreateAudioGroupProject(data.getProj());
|
||||||
|
m_samp = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
const SampleEntry* AudioGroup::getSample(SampleId sfxId) const
|
const SampleEntry* AudioGroup::getSample(SampleId sfxId) const
|
||||||
{
|
{
|
||||||
|
@ -93,4 +106,249 @@ void AudioGroup::makeCompressedVersion(SampleId sfxId, const SampleEntry* sample
|
||||||
m_sdir._extractCompressed(sfxId, *sample->m_data, m_groupPath, sample->m_data->m_looseData.get(), true);
|
m_sdir._extractCompressed(sfxId, *sample->m_data, m_groupPath, sample->m_data->m_looseData.get(), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AudioGroupDatabase::renameSample(SampleId id, std::string_view str)
|
||||||
|
{
|
||||||
|
SystemString oldBasePath = getSampleBasePath(id);
|
||||||
|
SampleId::CurNameDB->rename(id, str);
|
||||||
|
SystemString newBasePath = getSampleBasePath(id);
|
||||||
|
Rename((oldBasePath + _S(".wav")).c_str(), (newBasePath + _S(".wav")).c_str());
|
||||||
|
Rename((oldBasePath + _S(".dsp")).c_str(), (newBasePath + _S(".dsp")).c_str());
|
||||||
|
Rename((oldBasePath + _S(".vadpcm")).c_str(), (newBasePath + _S(".vadpcm")).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioGroupDatabase::deleteSample(SampleId id)
|
||||||
|
{
|
||||||
|
SystemString basePath = getSampleBasePath(id);
|
||||||
|
Unlink((basePath + _S(".wav")).c_str());
|
||||||
|
Unlink((basePath + _S(".dsp")).c_str());
|
||||||
|
Unlink((basePath + _S(".vadpcm")).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioGroupDatabase::copySampleInto(const SystemString& basePath, const SystemString& newBasePath)
|
||||||
|
{
|
||||||
|
Copy((basePath + _S(".wav")).c_str(), (newBasePath + _S(".wav")).c_str());
|
||||||
|
Copy((basePath + _S(".dsp")).c_str(), (newBasePath + _S(".dsp")).c_str());
|
||||||
|
Copy((basePath + _S(".vadpcm")).c_str(), (newBasePath + _S(".vadpcm")).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioGroupDatabase::_recursiveRenameMacro(SoundMacroId id, std::string_view str, int& macroIdx,
|
||||||
|
std::unordered_set<SoundMacroId>& renamedIds)
|
||||||
|
{
|
||||||
|
if (renamedIds.find(id) != renamedIds.cend())
|
||||||
|
return;
|
||||||
|
if (const SoundMacro* macro = getPool().soundMacro(id))
|
||||||
|
{
|
||||||
|
if (!strncmp(SoundMacroId::CurNameDB->resolveNameFromId(id).data(), "macro", 5))
|
||||||
|
{
|
||||||
|
std::string macroName("macro"sv);
|
||||||
|
if (macroIdx)
|
||||||
|
{
|
||||||
|
char num[16];
|
||||||
|
snprintf(num, 16, "%d", macroIdx);
|
||||||
|
macroName += num;
|
||||||
|
}
|
||||||
|
macroName += '_';
|
||||||
|
macroName += str;
|
||||||
|
++macroIdx;
|
||||||
|
SoundMacroId::CurNameDB->rename(id, macroName);
|
||||||
|
renamedIds.insert(id);
|
||||||
|
int sampleIdx = 0;
|
||||||
|
for (const auto& cmd : macro->m_cmds)
|
||||||
|
{
|
||||||
|
switch (cmd->Isa())
|
||||||
|
{
|
||||||
|
case SoundMacro::CmdOp::StartSample:
|
||||||
|
{
|
||||||
|
SoundMacro::CmdStartSample* ss = static_cast<SoundMacro::CmdStartSample*>(cmd.get());
|
||||||
|
if (!strncmp(SampleId::CurNameDB->resolveNameFromId(ss->sample.id).data(), "sample", 6))
|
||||||
|
{
|
||||||
|
std::string sampleName("sample"sv);
|
||||||
|
if (sampleIdx)
|
||||||
|
{
|
||||||
|
char num[16];
|
||||||
|
snprintf(num, 16, "%d", sampleIdx);
|
||||||
|
sampleName += num;
|
||||||
|
}
|
||||||
|
sampleName += '_';
|
||||||
|
sampleName += macroName;
|
||||||
|
++sampleIdx;
|
||||||
|
renameSample(ss->sample.id, sampleName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case SoundMacro::CmdOp::SplitKey:
|
||||||
|
_recursiveRenameMacro(static_cast<SoundMacro::CmdSplitKey*>(cmd.get())->macro, str, macroIdx, renamedIds);
|
||||||
|
break;
|
||||||
|
case SoundMacro::CmdOp::SplitVel:
|
||||||
|
_recursiveRenameMacro(static_cast<SoundMacro::CmdSplitVel*>(cmd.get())->macro, str, macroIdx, renamedIds);
|
||||||
|
break;
|
||||||
|
case SoundMacro::CmdOp::Goto:
|
||||||
|
_recursiveRenameMacro(static_cast<SoundMacro::CmdGoto*>(cmd.get())->macro, str, macroIdx, renamedIds);
|
||||||
|
break;
|
||||||
|
case SoundMacro::CmdOp::PlayMacro:
|
||||||
|
_recursiveRenameMacro(static_cast<SoundMacro::CmdPlayMacro*>(cmd.get())->macro, str, macroIdx, renamedIds);
|
||||||
|
break;
|
||||||
|
case SoundMacro::CmdOp::SplitMod:
|
||||||
|
_recursiveRenameMacro(static_cast<SoundMacro::CmdSplitMod*>(cmd.get())->macro, str, macroIdx, renamedIds);
|
||||||
|
break;
|
||||||
|
case SoundMacro::CmdOp::SplitRnd:
|
||||||
|
_recursiveRenameMacro(static_cast<SoundMacro::CmdSplitRnd*>(cmd.get())->macro, str, macroIdx, renamedIds);
|
||||||
|
break;
|
||||||
|
case SoundMacro::CmdOp::GoSub:
|
||||||
|
_recursiveRenameMacro(static_cast<SoundMacro::CmdGoSub*>(cmd.get())->macro, str, macroIdx, renamedIds);
|
||||||
|
break;
|
||||||
|
case SoundMacro::CmdOp::TrapEvent:
|
||||||
|
_recursiveRenameMacro(static_cast<SoundMacro::CmdTrapEvent*>(cmd.get())->macro, str, macroIdx, renamedIds);
|
||||||
|
break;
|
||||||
|
case SoundMacro::CmdOp::SendMessage:
|
||||||
|
_recursiveRenameMacro(static_cast<SoundMacro::CmdSendMessage*>(cmd.get())->macro, str, macroIdx, renamedIds);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const std::regex DefineGRPEntry(R"(#define\s+GRP(\S+)\s+(\S+))",
|
||||||
|
std::regex::ECMAScript|std::regex::optimize);
|
||||||
|
static const std::regex DefineSNGEntry(R"(#define\s+SNG(\S+)\s+(\S+))",
|
||||||
|
std::regex::ECMAScript|std::regex::optimize);
|
||||||
|
static const std::regex DefineSFXEntry(R"(#define\s+SFX(\S+)\s+(\S+))",
|
||||||
|
std::regex::ECMAScript|std::regex::optimize);
|
||||||
|
|
||||||
|
void AudioGroupDatabase::importCHeader(std::string_view header)
|
||||||
|
{
|
||||||
|
setIdDatabases();
|
||||||
|
std::match_results<std::string_view::const_iterator> dirMatch;
|
||||||
|
auto begin = header.cbegin();
|
||||||
|
auto end = header.cend();
|
||||||
|
while (std::regex_search(begin, end, dirMatch, DefineGRPEntry))
|
||||||
|
{
|
||||||
|
std::string key = dirMatch[1].str();
|
||||||
|
std::string value = dirMatch[2].str();
|
||||||
|
char* endPtr;
|
||||||
|
amuse::ObjectId id;
|
||||||
|
id.id = strtoul(value.c_str(), &endPtr, 0);
|
||||||
|
if (endPtr == value.c_str())
|
||||||
|
continue;
|
||||||
|
GroupId::CurNameDB->rename(id, key);
|
||||||
|
begin = dirMatch.suffix().first;
|
||||||
|
}
|
||||||
|
begin = header.cbegin();
|
||||||
|
end = header.cend();
|
||||||
|
while (std::regex_search(begin, end, dirMatch, DefineSNGEntry))
|
||||||
|
{
|
||||||
|
std::string key = dirMatch[1].str();
|
||||||
|
std::string value = dirMatch[2].str();
|
||||||
|
char* endPtr;
|
||||||
|
amuse::ObjectId id;
|
||||||
|
id.id = strtoul(value.c_str(), &endPtr, 0);
|
||||||
|
if (endPtr == value.c_str())
|
||||||
|
continue;
|
||||||
|
SongId::CurNameDB->rename(id, key);
|
||||||
|
begin = dirMatch.suffix().first;
|
||||||
|
}
|
||||||
|
begin = header.cbegin();
|
||||||
|
end = header.cend();
|
||||||
|
std::unordered_set<SoundMacroId> renamedMacroIDs;
|
||||||
|
while (std::regex_search(begin, end, dirMatch, DefineSFXEntry))
|
||||||
|
{
|
||||||
|
std::string key = dirMatch[1].str();
|
||||||
|
std::string value = dirMatch[2].str();
|
||||||
|
char* endPtr;
|
||||||
|
amuse::ObjectId id;
|
||||||
|
id.id = strtoul(value.c_str(), &endPtr, 0);
|
||||||
|
if (endPtr == value.c_str())
|
||||||
|
continue;
|
||||||
|
SFXId::CurNameDB->rename(id, key);
|
||||||
|
int macroIdx = 0;
|
||||||
|
for (auto& sfxGrp : getProj().sfxGroups())
|
||||||
|
{
|
||||||
|
for (auto& sfx : sfxGrp.second->m_sfxEntries)
|
||||||
|
{
|
||||||
|
if (sfx.first == id)
|
||||||
|
{
|
||||||
|
ObjectId sfxObjId = sfx.second.objId;
|
||||||
|
if (sfxObjId == ObjectId() || sfxObjId.id & 0xc000)
|
||||||
|
continue;
|
||||||
|
_recursiveRenameMacro(sfxObjId, key, macroIdx, renamedMacroIDs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
begin = dirMatch.suffix().first;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void WriteDefineLine(std::string& ret, std::string_view typeStr, std::string_view name, ObjectId id)
|
||||||
|
{
|
||||||
|
ret += "#define "sv;
|
||||||
|
ret += typeStr;
|
||||||
|
ret += name;
|
||||||
|
ret += ' ';
|
||||||
|
char idStr[16];
|
||||||
|
snprintf(idStr, 16, "%d", id.id);
|
||||||
|
ret += idStr;
|
||||||
|
ret += '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string AudioGroupDatabase::exportCHeader(std::string_view projectName, std::string_view groupName) const
|
||||||
|
{
|
||||||
|
setIdDatabases();
|
||||||
|
std::string ret;
|
||||||
|
ret += "/* Auto-generated Amuse Defines\n"
|
||||||
|
" *\n"
|
||||||
|
" * Project: "sv;
|
||||||
|
ret += projectName;
|
||||||
|
ret += "\n"
|
||||||
|
" * Subproject: "sv;
|
||||||
|
ret += groupName;
|
||||||
|
ret += "\n"
|
||||||
|
" * Date: "sv;
|
||||||
|
time_t curTime = time(nullptr);
|
||||||
|
struct tm curTm;
|
||||||
|
localtime_r(&curTime, &curTm);
|
||||||
|
char curTmStr[26];
|
||||||
|
asctime_r(&curTm, curTmStr);
|
||||||
|
ret += curTmStr;
|
||||||
|
ret += "\n"
|
||||||
|
" */\n\n\n"sv;
|
||||||
|
|
||||||
|
for (const auto& sg : SortUnorderedMap(getProj().songGroups()))
|
||||||
|
{
|
||||||
|
auto name = amuse::GroupId::CurNameDB->resolveNameFromId(sg.first);
|
||||||
|
WriteDefineLine(ret, "GRP"sv, name, sg.first);
|
||||||
|
}
|
||||||
|
for (const auto& sg : SortUnorderedMap(getProj().sfxGroups()))
|
||||||
|
{
|
||||||
|
auto name = amuse::GroupId::CurNameDB->resolveNameFromId(sg.first);
|
||||||
|
WriteDefineLine(ret, "GRP"sv, name, sg.first);
|
||||||
|
}
|
||||||
|
|
||||||
|
ret += "\n\n"sv;
|
||||||
|
|
||||||
|
std::unordered_set<amuse::SongId> songIds;
|
||||||
|
for (const auto& sg : getProj().songGroups())
|
||||||
|
for (const auto& song : sg.second->m_midiSetups)
|
||||||
|
songIds.insert(song.first);
|
||||||
|
for (amuse::SongId id : SortUnorderedSet(songIds))
|
||||||
|
{
|
||||||
|
auto name = amuse::SongId::CurNameDB->resolveNameFromId(id);
|
||||||
|
WriteDefineLine(ret, "SNG"sv, name, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
ret += "\n\n"sv;
|
||||||
|
|
||||||
|
for (const auto& sg : SortUnorderedMap(getProj().sfxGroups()))
|
||||||
|
{
|
||||||
|
for (const auto& sfx : SortUnorderedMap(sg.second.get()->m_sfxEntries))
|
||||||
|
{
|
||||||
|
auto name = amuse::SFXId::CurNameDB->resolveNameFromId(sfx.first);
|
||||||
|
WriteDefineLine(ret, "SFX"sv, name, sfx.first.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -279,12 +279,7 @@ AudioGroupPool AudioGroupPool::CreateAudioGroupPool(SystemStringView groupPath)
|
||||||
smOut = MakeObj<SoundMacro>();
|
smOut = MakeObj<SoundMacro>();
|
||||||
size_t cmdCount;
|
size_t cmdCount;
|
||||||
if (auto __v = r.enterSubVector(sm.first.c_str(), cmdCount))
|
if (auto __v = r.enterSubVector(sm.first.c_str(), cmdCount))
|
||||||
{
|
smOut->fromYAML(r, cmdCount);
|
||||||
smOut->m_cmds.reserve(cmdCount);
|
|
||||||
for (int c = 0; c < cmdCount; ++c)
|
|
||||||
if (auto __r2 = r.enterSubRecord(nullptr))
|
|
||||||
smOut->m_cmds.push_back(SoundMacro::CmdDo<MakeCmdOp, std::unique_ptr<SoundMacro::ICmd>>(r));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -416,6 +411,27 @@ void SoundMacro::buildFromPrototype(const SoundMacro& other)
|
||||||
m_cmds.push_back(CmdDo<MakeCopyCmdOp, std::unique_ptr<SoundMacro::ICmd>>(*cmd));
|
m_cmds.push_back(CmdDo<MakeCopyCmdOp, std::unique_ptr<SoundMacro::ICmd>>(*cmd));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SoundMacro::toYAML(athena::io::YAMLDocWriter& w) const
|
||||||
|
{
|
||||||
|
for (const auto& c : m_cmds)
|
||||||
|
{
|
||||||
|
if (auto __r2 = w.enterSubRecord(nullptr))
|
||||||
|
{
|
||||||
|
w.setStyle(athena::io::YAMLNodeStyle::Flow);
|
||||||
|
w.writeString("cmdOp", SoundMacro::CmdOpToStr(c->Isa()));
|
||||||
|
c->write(w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SoundMacro::fromYAML(athena::io::YAMLDocReader& r, size_t cmdCount)
|
||||||
|
{
|
||||||
|
m_cmds.reserve(cmdCount);
|
||||||
|
for (int c = 0; c < cmdCount; ++c)
|
||||||
|
if (auto __r2 = r.enterSubRecord(nullptr))
|
||||||
|
m_cmds.push_back(SoundMacro::CmdDo<MakeCmdOp, std::unique_ptr<SoundMacro::ICmd>>(r));
|
||||||
|
}
|
||||||
|
|
||||||
const SoundMacro* AudioGroupPool::soundMacro(ObjectId id) const
|
const SoundMacro* AudioGroupPool::soundMacro(ObjectId id) const
|
||||||
{
|
{
|
||||||
auto search = m_soundMacros.find(id);
|
auto search = m_soundMacros.find(id);
|
||||||
|
@ -1002,15 +1018,7 @@ bool AudioGroupPool::toYAML(SystemStringView groupPath) const
|
||||||
{
|
{
|
||||||
if (auto __v = w.enterSubVector(SoundMacroId::CurNameDB->resolveNameFromId(p.first).data()))
|
if (auto __v = w.enterSubVector(SoundMacroId::CurNameDB->resolveNameFromId(p.first).data()))
|
||||||
{
|
{
|
||||||
for (const auto& c : p.second.get()->m_cmds)
|
p.second.get()->toYAML(w);
|
||||||
{
|
|
||||||
if (auto __r2 = w.enterSubRecord(nullptr))
|
|
||||||
{
|
|
||||||
w.setStyle(athena::io::YAMLNodeStyle::Flow);
|
|
||||||
w.writeString("cmdOp", SoundMacro::CmdOpToStr(c->Isa()));
|
|
||||||
c->write(w);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -353,6 +353,59 @@ std::string ParseStringSlashId(const std::string& str, uint16_t& idOut)
|
||||||
return {str.begin(), str.begin() + slashPos};
|
return {str.begin(), str.begin() + slashPos};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SongGroupIndex::fromYAML(athena::io::YAMLDocReader& r)
|
||||||
|
{
|
||||||
|
if (auto __v2 = r.enterSubRecord("normPages"))
|
||||||
|
{
|
||||||
|
m_normPages.reserve(r.getCurNode()->m_mapChildren.size());
|
||||||
|
for (const auto& pg : r.getCurNode()->m_mapChildren)
|
||||||
|
if (auto __r2 = r.enterSubRecord(pg.first.c_str()))
|
||||||
|
m_normPages[strtoul(pg.first.c_str(), nullptr, 0)].read(r);
|
||||||
|
}
|
||||||
|
if (auto __v2 = r.enterSubRecord("drumPages"))
|
||||||
|
{
|
||||||
|
m_drumPages.reserve(r.getCurNode()->m_mapChildren.size());
|
||||||
|
for (const auto& pg : r.getCurNode()->m_mapChildren)
|
||||||
|
if (auto __r2 = r.enterSubRecord(pg.first.c_str()))
|
||||||
|
m_drumPages[strtoul(pg.first.c_str(), nullptr, 0)].read(r);
|
||||||
|
}
|
||||||
|
if (auto __v2 = r.enterSubRecord("songs"))
|
||||||
|
{
|
||||||
|
m_midiSetups.reserve(r.getCurNode()->m_mapChildren.size());
|
||||||
|
for (const auto& song : r.getCurNode()->m_mapChildren)
|
||||||
|
{
|
||||||
|
size_t chanCount;
|
||||||
|
if (auto __v3 = r.enterSubVector(song.first.c_str(), chanCount))
|
||||||
|
{
|
||||||
|
uint16_t songId;
|
||||||
|
std::string songName = ParseStringSlashId(song.first, songId);
|
||||||
|
if (songName.empty() || songId == 0xffff)
|
||||||
|
continue;
|
||||||
|
SongId::CurNameDB->registerPair(songName, songId);
|
||||||
|
|
||||||
|
std::array<SongGroupIndex::MIDISetup, 16>& setup = m_midiSetups[songId];
|
||||||
|
for (int i = 0; i < 16 && i < chanCount; ++i)
|
||||||
|
if (auto __r2 = r.enterSubRecord(nullptr))
|
||||||
|
setup[i].read(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SFXGroupIndex::fromYAML(athena::io::YAMLDocReader& r)
|
||||||
|
{
|
||||||
|
for (const auto& sfx : r.getCurNode()->m_mapChildren)
|
||||||
|
if (auto __r2 = r.enterSubRecord(sfx.first.c_str()))
|
||||||
|
{
|
||||||
|
uint16_t sfxId;
|
||||||
|
std::string sfxName = ParseStringSlashId(sfx.first, sfxId);
|
||||||
|
if (sfxName.empty() || sfxId == 0xffff)
|
||||||
|
continue;
|
||||||
|
SFXId::CurNameDB->registerPair(sfxName, sfxId);
|
||||||
|
m_sfxEntries[sfxId].read(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
AudioGroupProject AudioGroupProject::CreateAudioGroupProject(SystemStringView groupPath)
|
AudioGroupProject AudioGroupProject::CreateAudioGroupProject(SystemStringView groupPath)
|
||||||
{
|
{
|
||||||
AudioGroupProject ret;
|
AudioGroupProject ret;
|
||||||
|
@ -380,41 +433,7 @@ AudioGroupProject AudioGroupProject::CreateAudioGroupProject(SystemStringView gr
|
||||||
|
|
||||||
auto& idx = ret.m_songGroups[groupId];
|
auto& idx = ret.m_songGroups[groupId];
|
||||||
idx = MakeObj<SongGroupIndex>();
|
idx = MakeObj<SongGroupIndex>();
|
||||||
if (auto __v2 = r.enterSubRecord("normPages"))
|
idx->fromYAML(r);
|
||||||
{
|
|
||||||
idx->m_normPages.reserve(r.getCurNode()->m_mapChildren.size());
|
|
||||||
for (const auto& pg : r.getCurNode()->m_mapChildren)
|
|
||||||
if (auto __r2 = r.enterSubRecord(pg.first.c_str()))
|
|
||||||
idx->m_normPages[strtoul(pg.first.c_str(), nullptr, 0)].read(r);
|
|
||||||
}
|
|
||||||
if (auto __v2 = r.enterSubRecord("drumPages"))
|
|
||||||
{
|
|
||||||
idx->m_drumPages.reserve(r.getCurNode()->m_mapChildren.size());
|
|
||||||
for (const auto& pg : r.getCurNode()->m_mapChildren)
|
|
||||||
if (auto __r2 = r.enterSubRecord(pg.first.c_str()))
|
|
||||||
idx->m_drumPages[strtoul(pg.first.c_str(), nullptr, 0)].read(r);
|
|
||||||
}
|
|
||||||
if (auto __v2 = r.enterSubRecord("songs"))
|
|
||||||
{
|
|
||||||
idx->m_midiSetups.reserve(r.getCurNode()->m_mapChildren.size());
|
|
||||||
for (const auto& song : r.getCurNode()->m_mapChildren)
|
|
||||||
{
|
|
||||||
size_t chanCount;
|
|
||||||
if (auto __v3 = r.enterSubVector(song.first.c_str(), chanCount))
|
|
||||||
{
|
|
||||||
uint16_t songId;
|
|
||||||
std::string songName = ParseStringSlashId(song.first, songId);
|
|
||||||
if (songName.empty() || songId == 0xffff)
|
|
||||||
continue;
|
|
||||||
SongId::CurNameDB->registerPair(songName, songId);
|
|
||||||
|
|
||||||
std::array<SongGroupIndex::MIDISetup, 16>& setup = idx->m_midiSetups[songId];
|
|
||||||
for (int i = 0; i < 16 && i < chanCount; ++i)
|
|
||||||
if (auto __r2 = r.enterSubRecord(nullptr))
|
|
||||||
setup[i].read(r);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -434,16 +453,7 @@ AudioGroupProject AudioGroupProject::CreateAudioGroupProject(SystemStringView gr
|
||||||
|
|
||||||
auto& idx = ret.m_sfxGroups[groupId];
|
auto& idx = ret.m_sfxGroups[groupId];
|
||||||
idx = MakeObj<SFXGroupIndex>();
|
idx = MakeObj<SFXGroupIndex>();
|
||||||
for (const auto& sfx : r.getCurNode()->m_mapChildren)
|
idx->fromYAML(r);
|
||||||
if (auto __r2 = r.enterSubRecord(sfx.first.c_str()))
|
|
||||||
{
|
|
||||||
uint16_t sfxId;
|
|
||||||
std::string sfxName = ParseStringSlashId(sfx.first, sfxId);
|
|
||||||
if (sfxName.empty() || sfxId == 0xffff)
|
|
||||||
continue;
|
|
||||||
SFXId::CurNameDB->registerPair(sfxName, sfxId);
|
|
||||||
idx->m_sfxEntries[sfxId].read(r);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -453,6 +463,42 @@ AudioGroupProject AudioGroupProject::CreateAudioGroupProject(SystemStringView gr
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static ObjectId RegisterDedupedName(ObjectId origId, amuse::NameDB* db, NameDB::Type tp)
|
||||||
|
{
|
||||||
|
std::string dupeName = std::string(db->resolveNameFromId(origId)) + "-copy";
|
||||||
|
std::string useName = dupeName;
|
||||||
|
int dupeIdx = 1;
|
||||||
|
while (db->m_stringToId.find(useName) != db->m_stringToId.cend())
|
||||||
|
{
|
||||||
|
char num[16];
|
||||||
|
snprintf(num, 16, "%d", dupeIdx++);
|
||||||
|
useName = dupeName + num;
|
||||||
|
}
|
||||||
|
ObjectId ret = db->generateId(tp);
|
||||||
|
db->registerPair(useName, ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
SFXGroupIndex::SFXGroupIndex(const SFXGroupIndex& other)
|
||||||
|
{
|
||||||
|
for (const auto& sfx : other.m_sfxEntries)
|
||||||
|
m_sfxEntries[RegisterDedupedName(sfx.first, SFXId::CurNameDB, NameDB::Type::SFX)] = sfx.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioGroupProject AudioGroupProject::CreateAudioGroupProject(const AudioGroupProject& oldProj)
|
||||||
|
{
|
||||||
|
AudioGroupProject ret;
|
||||||
|
|
||||||
|
for (const auto& grp : oldProj.songGroups())
|
||||||
|
ret.m_songGroups[RegisterDedupedName(grp.first, GroupId::CurNameDB, NameDB::Type::Group)] =
|
||||||
|
MakeObj<SongGroupIndex>(*grp.second);
|
||||||
|
for (const auto& grp : oldProj.sfxGroups())
|
||||||
|
ret.m_sfxGroups[RegisterDedupedName(grp.first, GroupId::CurNameDB, NameDB::Type::Group)] =
|
||||||
|
MakeObj<SFXGroupIndex>(*grp.second);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
void AudioGroupProject::BootstrapObjectIDs(athena::io::IStreamReader& r, GCNDataTag)
|
void AudioGroupProject::BootstrapObjectIDs(athena::io::IStreamReader& r, GCNDataTag)
|
||||||
{
|
{
|
||||||
while (!AtEnd32(r))
|
while (!AtEnd32(r))
|
||||||
|
@ -638,6 +684,76 @@ const SFXGroupIndex* AudioGroupProject::getSFXGroupIndex(int groupId) const
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SongGroupIndex::toYAML(athena::io::YAMLDocWriter& w) const
|
||||||
|
{
|
||||||
|
if (!m_normPages.empty())
|
||||||
|
{
|
||||||
|
if (auto __v2 = w.enterSubRecord("normPages"))
|
||||||
|
{
|
||||||
|
for (const auto& pg : SortUnorderedMap(m_normPages))
|
||||||
|
{
|
||||||
|
char name[16];
|
||||||
|
snprintf(name, 16, "%d", pg.first);
|
||||||
|
if (auto __r2 = w.enterSubRecord(name))
|
||||||
|
{
|
||||||
|
w.setStyle(athena::io::YAMLNodeStyle::Flow);
|
||||||
|
pg.second.get().write(w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!m_drumPages.empty())
|
||||||
|
{
|
||||||
|
if (auto __v2 = w.enterSubRecord("drumPages"))
|
||||||
|
{
|
||||||
|
for (const auto& pg : SortUnorderedMap(m_drumPages))
|
||||||
|
{
|
||||||
|
char name[16];
|
||||||
|
snprintf(name, 16, "%d", pg.first);
|
||||||
|
if (auto __r2 = w.enterSubRecord(name))
|
||||||
|
{
|
||||||
|
w.setStyle(athena::io::YAMLNodeStyle::Flow);
|
||||||
|
pg.second.get().write(w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!m_midiSetups.empty())
|
||||||
|
{
|
||||||
|
if (auto __v2 = w.enterSubRecord("songs"))
|
||||||
|
{
|
||||||
|
for (const auto& song : SortUnorderedMap(m_midiSetups))
|
||||||
|
{
|
||||||
|
char songString[64];
|
||||||
|
snprintf(songString, 64, "%s/0x%04X",
|
||||||
|
SongId::CurNameDB->resolveNameFromId(song.first).data(), int(song.first.id));
|
||||||
|
if (auto __v3 = w.enterSubVector(songString))
|
||||||
|
for (int i = 0; i < 16; ++i)
|
||||||
|
if (auto __r2 = w.enterSubRecord(nullptr))
|
||||||
|
{
|
||||||
|
w.setStyle(athena::io::YAMLNodeStyle::Flow);
|
||||||
|
song.second.get()[i].write(w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SFXGroupIndex::toYAML(athena::io::YAMLDocWriter& w) const
|
||||||
|
{
|
||||||
|
for (const auto& sfx : SortUnorderedMap(m_sfxEntries))
|
||||||
|
{
|
||||||
|
char sfxString[64];
|
||||||
|
snprintf(sfxString, 64, "%s/0x%04X",
|
||||||
|
SFXId::CurNameDB->resolveNameFromId(sfx.first).data(), int(sfx.first.id));
|
||||||
|
if (auto __r2 = w.enterSubRecord(sfxString))
|
||||||
|
{
|
||||||
|
w.setStyle(athena::io::YAMLNodeStyle::Flow);
|
||||||
|
sfx.second.get().write(w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool AudioGroupProject::toYAML(SystemStringView groupPath) const
|
bool AudioGroupProject::toYAML(SystemStringView groupPath) const
|
||||||
{
|
{
|
||||||
athena::io::YAMLDocWriter w("amuse::Project");
|
athena::io::YAMLDocWriter w("amuse::Project");
|
||||||
|
@ -652,56 +768,7 @@ bool AudioGroupProject::toYAML(SystemStringView groupPath) const
|
||||||
snprintf(groupString, 64, "%s/0x%04X", GroupId::CurNameDB->resolveNameFromId(p.first).data(), int(p.first.id));
|
snprintf(groupString, 64, "%s/0x%04X", GroupId::CurNameDB->resolveNameFromId(p.first).data(), int(p.first.id));
|
||||||
if (auto __r = w.enterSubRecord(groupString))
|
if (auto __r = w.enterSubRecord(groupString))
|
||||||
{
|
{
|
||||||
if (!p.second.get()->m_normPages.empty())
|
p.second.get()->toYAML(w);
|
||||||
{
|
|
||||||
if (auto __v2 = w.enterSubRecord("normPages"))
|
|
||||||
{
|
|
||||||
for (const auto& pg : SortUnorderedMap(p.second.get()->m_normPages))
|
|
||||||
{
|
|
||||||
char name[16];
|
|
||||||
snprintf(name, 16, "%d", pg.first);
|
|
||||||
if (auto __r2 = w.enterSubRecord(name))
|
|
||||||
{
|
|
||||||
w.setStyle(athena::io::YAMLNodeStyle::Flow);
|
|
||||||
pg.second.get().write(w);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!p.second.get()->m_drumPages.empty())
|
|
||||||
{
|
|
||||||
if (auto __v2 = w.enterSubRecord("drumPages"))
|
|
||||||
{
|
|
||||||
for (const auto& pg : SortUnorderedMap(p.second.get()->m_drumPages))
|
|
||||||
{
|
|
||||||
char name[16];
|
|
||||||
snprintf(name, 16, "%d", pg.first);
|
|
||||||
if (auto __r2 = w.enterSubRecord(name))
|
|
||||||
{
|
|
||||||
w.setStyle(athena::io::YAMLNodeStyle::Flow);
|
|
||||||
pg.second.get().write(w);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!p.second.get()->m_midiSetups.empty())
|
|
||||||
{
|
|
||||||
if (auto __v2 = w.enterSubRecord("songs"))
|
|
||||||
{
|
|
||||||
for (const auto& song : SortUnorderedMap(p.second.get()->m_midiSetups))
|
|
||||||
{
|
|
||||||
char songString[64];
|
|
||||||
snprintf(songString, 64, "%s/0x%04X", SongId::CurNameDB->resolveNameFromId(song.first).data(), int(song.first.id));
|
|
||||||
if (auto __v3 = w.enterSubVector(songString))
|
|
||||||
for (int i = 0; i < 16; ++i)
|
|
||||||
if (auto __r2 = w.enterSubRecord(nullptr))
|
|
||||||
{
|
|
||||||
w.setStyle(athena::io::YAMLNodeStyle::Flow);
|
|
||||||
song.second.get()[i].write(w);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -717,16 +784,7 @@ bool AudioGroupProject::toYAML(SystemStringView groupPath) const
|
||||||
snprintf(groupString, 64, "%s/0x%04X", GroupId::CurNameDB->resolveNameFromId(p.first).data(), int(p.first.id));
|
snprintf(groupString, 64, "%s/0x%04X", GroupId::CurNameDB->resolveNameFromId(p.first).data(), int(p.first.id));
|
||||||
if (auto __r = w.enterSubRecord(groupString))
|
if (auto __r = w.enterSubRecord(groupString))
|
||||||
{
|
{
|
||||||
for (const auto& sfx : SortUnorderedMap(p.second.get()->m_sfxEntries))
|
p.second.get()->toYAML(w);
|
||||||
{
|
|
||||||
char sfxString[64];
|
|
||||||
snprintf(sfxString, 64, "%s/0x%04X", SFXId::CurNameDB->resolveNameFromId(sfx.first).data(), int(sfx.first.id));
|
|
||||||
if (auto __r2 = w.enterSubRecord(sfxString))
|
|
||||||
{
|
|
||||||
w.setStyle(athena::io::YAMLNodeStyle::Flow);
|
|
||||||
sfx.second.get().write(w);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,35 @@ namespace amuse
|
||||||
{
|
{
|
||||||
static logvisor::Module Log("amuse");
|
static logvisor::Module Log("amuse");
|
||||||
|
|
||||||
|
bool Copy(const SystemChar* from, const SystemChar* to)
|
||||||
|
{
|
||||||
|
#if _WIN32
|
||||||
|
return CopyFileW(from, to, FALSE) != 0;
|
||||||
|
#else
|
||||||
|
FILE* fi = fopen(from, "rb");
|
||||||
|
if (!fi)
|
||||||
|
return false;
|
||||||
|
FILE* fo = fopen(to, "wb");
|
||||||
|
if (!fo)
|
||||||
|
{
|
||||||
|
fclose(fi);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::unique_ptr<uint8_t[]> buf(new uint8_t[65536]);
|
||||||
|
size_t readSz = 0;
|
||||||
|
while ((readSz = fread(buf.get(), 1, 65536, fi)))
|
||||||
|
fwrite(buf.get(), 1, readSz, fo);
|
||||||
|
fclose(fi);
|
||||||
|
fclose(fo);
|
||||||
|
struct stat theStat;
|
||||||
|
if (::stat(from, &theStat))
|
||||||
|
return true;
|
||||||
|
struct timespec times[] = { theStat.st_atim, theStat.st_mtim };
|
||||||
|
utimensat(AT_FDCWD, to, times, 0);
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
#define DEFINE_ID_TYPE(type, typeName) \
|
#define DEFINE_ID_TYPE(type, typeName) \
|
||||||
thread_local NameDB* type::CurNameDB = nullptr; \
|
thread_local NameDB* type::CurNameDB = nullptr; \
|
||||||
template<> template<> \
|
template<> template<> \
|
||||||
|
|
|
@ -32,6 +32,8 @@ void Sequencer::ChannelState::_bringOutYourDead()
|
||||||
}
|
}
|
||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
|
std::unordered_set<ObjToken<Voice>> newSet = m_keyoffVoxs;
|
||||||
|
m_keyoffVoxs = newSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sequencer::_bringOutYourDead()
|
void Sequencer::_bringOutYourDead()
|
||||||
|
@ -57,8 +59,8 @@ Sequencer::~Sequencer()
|
||||||
m_studio.reset();
|
m_studio.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
Sequencer::Sequencer(Engine& engine, const AudioGroup& group, int groupId, const SongGroupIndex* songGroup, int setupId,
|
Sequencer::Sequencer(Engine& engine, const AudioGroup& group, GroupId groupId, const SongGroupIndex* songGroup,
|
||||||
ObjToken<Studio> studio)
|
SongId setupId, ObjToken<Studio> studio)
|
||||||
: Entity(engine, group, groupId), m_songGroup(songGroup), m_studio(studio)
|
: Entity(engine, group, groupId), m_songGroup(songGroup), m_studio(studio)
|
||||||
{
|
{
|
||||||
auto it = m_songGroup->m_midiSetups.find(setupId);
|
auto it = m_songGroup->m_midiSetups.find(setupId);
|
||||||
|
@ -66,7 +68,7 @@ Sequencer::Sequencer(Engine& engine, const AudioGroup& group, int groupId, const
|
||||||
m_midiSetup = it->second.data();
|
m_midiSetup = it->second.data();
|
||||||
}
|
}
|
||||||
|
|
||||||
Sequencer::Sequencer(Engine& engine, const AudioGroup& group, int groupId, const SFXGroupIndex* sfxGroup,
|
Sequencer::Sequencer(Engine& engine, const AudioGroup& group, GroupId groupId, const SFXGroupIndex* sfxGroup,
|
||||||
ObjToken<Studio> studio)
|
ObjToken<Studio> studio)
|
||||||
: Entity(engine, group, groupId), m_sfxGroup(sfxGroup), m_studio(studio)
|
: Entity(engine, group, groupId), m_sfxGroup(sfxGroup), m_studio(studio)
|
||||||
{
|
{
|
||||||
|
@ -238,7 +240,7 @@ ObjToken<Voice> Sequencer::ChannelState::keyOn(uint8_t note, uint8_t velocity)
|
||||||
m_lastVoice.reset();
|
m_lastVoice.reset();
|
||||||
keySearch->second->keyOff();
|
keySearch->second->keyOff();
|
||||||
keySearch->second->setPedal(false);
|
keySearch->second->setPedal(false);
|
||||||
m_keyoffVoxs.emplace(std::move(keySearch->second));
|
m_keyoffVoxs.emplace(keySearch->second);
|
||||||
m_chanVoxs.erase(keySearch);
|
m_chanVoxs.erase(keySearch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -310,7 +312,7 @@ void Sequencer::ChannelState::keyOff(uint8_t note, uint8_t velocity)
|
||||||
if ((m_lastVoice && m_lastVoice->isDestroyed()) || keySearch->second == m_lastVoice)
|
if ((m_lastVoice && m_lastVoice->isDestroyed()) || keySearch->second == m_lastVoice)
|
||||||
m_lastVoice.reset();
|
m_lastVoice.reset();
|
||||||
keySearch->second->keyOff();
|
keySearch->second->keyOff();
|
||||||
m_keyoffVoxs.emplace(std::move(keySearch->second));
|
m_keyoffVoxs.emplace(keySearch->second);
|
||||||
m_chanVoxs.erase(keySearch);
|
m_chanVoxs.erase(keySearch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -455,7 +457,7 @@ void Sequencer::ChannelState::allOff()
|
||||||
if (it->second == m_lastVoice)
|
if (it->second == m_lastVoice)
|
||||||
m_lastVoice.reset();
|
m_lastVoice.reset();
|
||||||
it->second->keyOff();
|
it->second->keyOff();
|
||||||
m_keyoffVoxs.emplace(std::move(it->second));
|
m_keyoffVoxs.emplace(it->second);
|
||||||
it = m_chanVoxs.erase(it);
|
it = m_chanVoxs.erase(it);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -517,7 +519,7 @@ void Sequencer::ChannelState::killKeygroup(uint8_t kg, bool now)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
vox->keyOff();
|
vox->keyOff();
|
||||||
m_keyoffVoxs.emplace(std::move(it->second));
|
m_keyoffVoxs.emplace(it->second);
|
||||||
it = m_chanVoxs.erase(it);
|
it = m_chanVoxs.erase(it);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,15 +62,15 @@ struct Event
|
||||||
uint8_t noteOrCtrl;
|
uint8_t noteOrCtrl;
|
||||||
uint8_t velOrVal;
|
uint8_t velOrVal;
|
||||||
uint8_t program;
|
uint8_t program;
|
||||||
uint16_t length;
|
int length;
|
||||||
int pitchBend;
|
int pitchBend;
|
||||||
|
|
||||||
Event(NoteEvent, uint8_t chan, uint8_t note, uint8_t vel, uint16_t len)
|
Event(NoteEvent, uint8_t chan, uint8_t note, uint8_t vel, int len)
|
||||||
: m_type(Type::Note), channel(chan), noteOrCtrl(note), velOrVal(vel), length(len)
|
: m_type(Type::Note), channel(chan), noteOrCtrl(note), velOrVal(vel), length(len)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
Event(CtrlEvent, uint8_t chan, uint8_t note, uint8_t vel, uint16_t len)
|
Event(CtrlEvent, uint8_t chan, uint8_t note, uint8_t vel, int len)
|
||||||
: m_type(Type::Control), channel(chan), noteOrCtrl(note), velOrVal(vel), length(len)
|
: m_type(Type::Control), channel(chan), noteOrCtrl(note), velOrVal(vel), length(len)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -83,16 +83,17 @@ struct Event
|
||||||
class MIDIDecoder
|
class MIDIDecoder
|
||||||
{
|
{
|
||||||
int m_tick = 0;
|
int m_tick = 0;
|
||||||
std::vector<std::pair<int, std::multimap<int, Event>>> m_results;
|
std::vector<std::pair<int, std::multimap<int, Event>>> m_results[16];
|
||||||
std::multimap<int, int> m_tempos;
|
std::multimap<int, int> m_tempos;
|
||||||
std::array<std::multimap<int, Event>::iterator, 128> m_notes;
|
std::array<std::multimap<int, Event>::iterator, 128> m_notes[16];
|
||||||
|
|
||||||
void _addProgramChange(int prog)
|
void _addProgramChange(int chan, int prog)
|
||||||
{
|
{
|
||||||
m_results.emplace_back();
|
auto& results = m_results[chan];
|
||||||
m_results.back().first = prog;
|
results.emplace_back();
|
||||||
|
results.back().first = prog;
|
||||||
for (size_t i = 0; i < 128; ++i)
|
for (size_t i = 0; i < 128; ++i)
|
||||||
m_notes[i] = m_results.back().second.end();
|
m_notes[chan][i] = results.back().second.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t m_status = 0;
|
uint8_t m_status = 0;
|
||||||
|
@ -128,154 +129,93 @@ public:
|
||||||
std::vector<uint8_t>::const_iterator end)
|
std::vector<uint8_t>::const_iterator end)
|
||||||
{
|
{
|
||||||
std::vector<uint8_t>::const_iterator it = begin;
|
std::vector<uint8_t>::const_iterator it = begin;
|
||||||
if (it == end)
|
while (it != end)
|
||||||
return begin;
|
|
||||||
|
|
||||||
uint32_t deltaTime;
|
|
||||||
_readContinuedValue(it, end, deltaTime);
|
|
||||||
m_tick += deltaTime;
|
|
||||||
|
|
||||||
uint8_t a = *it++;
|
|
||||||
uint8_t b;
|
|
||||||
if (a & 0x80)
|
|
||||||
m_status = a;
|
|
||||||
else
|
|
||||||
it--;
|
|
||||||
|
|
||||||
/* Not actually used as such for now */
|
|
||||||
if (m_results.empty())
|
|
||||||
_addProgramChange(0);
|
|
||||||
std::multimap<int, Event>& res = m_results.back().second;
|
|
||||||
|
|
||||||
if (m_status == 0xff)
|
|
||||||
{
|
{
|
||||||
/* Meta events */
|
uint32_t deltaTime;
|
||||||
if (it == end)
|
_readContinuedValue(it, end, deltaTime);
|
||||||
return begin;
|
m_tick += deltaTime;
|
||||||
a = *it++;
|
|
||||||
|
|
||||||
uint32_t length;
|
uint8_t a = *it++;
|
||||||
_readContinuedValue(it, end, length);
|
uint8_t b;
|
||||||
|
if (a & 0x80)
|
||||||
|
m_status = a;
|
||||||
|
else
|
||||||
|
it--;
|
||||||
|
|
||||||
switch (a)
|
if (m_status == 0xff)
|
||||||
{
|
|
||||||
case 0x51:
|
|
||||||
{
|
|
||||||
uint32_t tempo = 0;
|
|
||||||
memcpy(&reinterpret_cast<uint8_t*>(&tempo)[1], &*it, 3);
|
|
||||||
m_tempos.emplace(m_tick, 60000000 / SBig(tempo));
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
it += length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
uint8_t chan = m_status & 0xf;
|
|
||||||
switch (Status(m_status & 0xf0))
|
|
||||||
{
|
|
||||||
case Status::NoteOff:
|
|
||||||
{
|
{
|
||||||
|
/* Meta events */
|
||||||
if (it == end)
|
if (it == end)
|
||||||
return begin;
|
return begin;
|
||||||
a = *it++;
|
a = *it++;
|
||||||
if (it == end)
|
|
||||||
return begin;
|
|
||||||
b = *it++;
|
|
||||||
|
|
||||||
uint8_t notenum = clamp7(a);
|
uint32_t length;
|
||||||
std::multimap<int, Event>::iterator note = m_notes[notenum];
|
_readContinuedValue(it, end, length);
|
||||||
if (note != res.end())
|
|
||||||
|
switch (a)
|
||||||
{
|
{
|
||||||
note->second.length = uint16_t(m_tick - note->first);
|
case 0x51:
|
||||||
m_notes[notenum] = res.end();
|
{
|
||||||
|
uint32_t tempo = 0;
|
||||||
|
memcpy(&reinterpret_cast<uint8_t*>(&tempo)[1], &*it, 3);
|
||||||
|
m_tempos.emplace(m_tick, 60000000 / SBig(tempo));
|
||||||
}
|
}
|
||||||
break;
|
default:
|
||||||
}
|
it += length;
|
||||||
case Status::NoteOn:
|
|
||||||
{
|
|
||||||
if (it == end)
|
|
||||||
return begin;
|
|
||||||
a = *it++;
|
|
||||||
if (it == end)
|
|
||||||
return begin;
|
|
||||||
b = *it++;
|
|
||||||
|
|
||||||
uint8_t notenum = clamp7(a);
|
|
||||||
uint8_t vel = clamp7(b);
|
|
||||||
std::multimap<int, Event>::iterator note = m_notes[notenum];
|
|
||||||
if (note != res.end())
|
|
||||||
note->second.length = uint16_t(m_tick - note->first);
|
|
||||||
|
|
||||||
m_notes[notenum] = res.emplace(m_tick, Event{NoteEvent{}, chan, notenum, vel, 0});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Status::NotePressure:
|
|
||||||
{
|
|
||||||
if (it == end)
|
|
||||||
return begin;
|
|
||||||
a = *it++;
|
|
||||||
if (it == end)
|
|
||||||
return begin;
|
|
||||||
b = *it++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Status::ControlChange:
|
|
||||||
{
|
|
||||||
if (it == end)
|
|
||||||
return begin;
|
|
||||||
a = *it++;
|
|
||||||
if (it == end)
|
|
||||||
return begin;
|
|
||||||
b = *it++;
|
|
||||||
res.emplace(m_tick, Event{CtrlEvent{}, chan, clamp7(a), clamp7(b), 0});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Status::ProgramChange:
|
|
||||||
{
|
|
||||||
if (it == end)
|
|
||||||
return begin;
|
|
||||||
a = *it++;
|
|
||||||
res.emplace(m_tick, Event{ProgEvent{}, chan, a});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Status::ChannelPressure:
|
|
||||||
{
|
|
||||||
if (it == end)
|
|
||||||
return begin;
|
|
||||||
a = *it++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Status::PitchBend:
|
|
||||||
{
|
|
||||||
if (it == end)
|
|
||||||
return begin;
|
|
||||||
a = *it++;
|
|
||||||
if (it == end)
|
|
||||||
return begin;
|
|
||||||
b = *it++;
|
|
||||||
res.emplace(m_tick, Event{PitchEvent{}, chan, clamp7(b) * 128 + clamp7(a)});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Status::SysEx:
|
|
||||||
{
|
|
||||||
switch (Status(m_status & 0xff))
|
|
||||||
{
|
|
||||||
case Status::SysEx:
|
|
||||||
{
|
|
||||||
uint32_t len;
|
|
||||||
if (!_readContinuedValue(it, end, len) || end - it < len)
|
|
||||||
return begin;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case Status::TimecodeQuarterFrame:
|
} else
|
||||||
|
{
|
||||||
|
uint8_t chan = m_status & 0xf;
|
||||||
|
auto& results = m_results[chan];
|
||||||
|
|
||||||
|
/* Not actually used as such for now */
|
||||||
|
if (results.empty())
|
||||||
|
_addProgramChange(chan, 0);
|
||||||
|
std::multimap<int, Event>& res = results.back().second;
|
||||||
|
|
||||||
|
switch (Status(m_status & 0xf0))
|
||||||
|
{
|
||||||
|
case Status::NoteOff:
|
||||||
{
|
{
|
||||||
if (it == end)
|
if (it == end)
|
||||||
return begin;
|
return begin;
|
||||||
a = *it++;
|
a = *it++;
|
||||||
|
if (it == end)
|
||||||
|
return begin;
|
||||||
|
b = *it++;
|
||||||
|
|
||||||
|
uint8_t notenum = clamp7(a);
|
||||||
|
std::multimap<int, Event>::iterator note = m_notes[chan][notenum];
|
||||||
|
if (note != res.end())
|
||||||
|
{
|
||||||
|
note->second.length = m_tick - note->first;
|
||||||
|
m_notes[chan][notenum] = res.end();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Status::SongPositionPointer:
|
case Status::NoteOn:
|
||||||
|
{
|
||||||
|
if (it == end)
|
||||||
|
return begin;
|
||||||
|
a = *it++;
|
||||||
|
if (it == end)
|
||||||
|
return begin;
|
||||||
|
b = *it++;
|
||||||
|
|
||||||
|
uint8_t notenum = clamp7(a);
|
||||||
|
uint8_t vel = clamp7(b);
|
||||||
|
std::multimap<int, Event>::iterator note = m_notes[chan][notenum];
|
||||||
|
if (note != res.end())
|
||||||
|
note->second.length = m_tick - note->first;
|
||||||
|
|
||||||
|
if (vel != 0)
|
||||||
|
m_notes[chan][notenum] = res.emplace(m_tick, Event{NoteEvent{}, chan, notenum, vel, 0});
|
||||||
|
else
|
||||||
|
m_notes[chan][notenum] = res.end();
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Status::NotePressure:
|
||||||
{
|
{
|
||||||
if (it == end)
|
if (it == end)
|
||||||
return begin;
|
return begin;
|
||||||
|
@ -285,35 +225,100 @@ public:
|
||||||
b = *it++;
|
b = *it++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Status::SongSelect:
|
case Status::ControlChange:
|
||||||
|
{
|
||||||
|
if (it == end)
|
||||||
|
return begin;
|
||||||
|
a = *it++;
|
||||||
|
if (it == end)
|
||||||
|
return begin;
|
||||||
|
b = *it++;
|
||||||
|
res.emplace(m_tick, Event{CtrlEvent{}, chan, clamp7(a), clamp7(b), 0});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Status::ProgramChange:
|
||||||
|
{
|
||||||
|
if (it == end)
|
||||||
|
return begin;
|
||||||
|
a = *it++;
|
||||||
|
res.emplace(m_tick, Event{ProgEvent{}, chan, a});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Status::ChannelPressure:
|
||||||
{
|
{
|
||||||
if (it == end)
|
if (it == end)
|
||||||
return begin;
|
return begin;
|
||||||
a = *it++;
|
a = *it++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Status::TuneRequest:
|
case Status::PitchBend:
|
||||||
case Status::Start:
|
{
|
||||||
case Status::Continue:
|
if (it == end)
|
||||||
case Status::Stop:
|
return begin;
|
||||||
case Status::Reset:
|
a = *it++;
|
||||||
case Status::SysExTerm:
|
if (it == end)
|
||||||
case Status::TimingClock:
|
return begin;
|
||||||
case Status::ActiveSensing:
|
b = *it++;
|
||||||
|
res.emplace(m_tick, Event{PitchEvent{}, chan, clamp7(b) * 128 + clamp7(a)});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Status::SysEx:
|
||||||
|
{
|
||||||
|
switch (Status(m_status & 0xff))
|
||||||
|
{
|
||||||
|
case Status::SysEx:
|
||||||
|
{
|
||||||
|
uint32_t len;
|
||||||
|
if (!_readContinuedValue(it, end, len) || end - it < len)
|
||||||
|
return begin;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Status::TimecodeQuarterFrame:
|
||||||
|
{
|
||||||
|
if (it == end)
|
||||||
|
return begin;
|
||||||
|
a = *it++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Status::SongPositionPointer:
|
||||||
|
{
|
||||||
|
if (it == end)
|
||||||
|
return begin;
|
||||||
|
a = *it++;
|
||||||
|
if (it == end)
|
||||||
|
return begin;
|
||||||
|
b = *it++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Status::SongSelect:
|
||||||
|
{
|
||||||
|
if (it == end)
|
||||||
|
return begin;
|
||||||
|
a = *it++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Status::TuneRequest:
|
||||||
|
case Status::Start:
|
||||||
|
case Status::Continue:
|
||||||
|
case Status::Stop:
|
||||||
|
case Status::Reset:
|
||||||
|
case Status::SysExTerm:
|
||||||
|
case Status::TimingClock:
|
||||||
|
case Status::ActiveSensing:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return it;
|
return it;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::pair<int, std::multimap<int, Event>>>& getResults() { return m_results; }
|
std::vector<std::pair<int, std::multimap<int, Event>>>& getResults(int chan) { return m_results[chan]; }
|
||||||
std::multimap<int, int>& getTempos() { return m_tempos; }
|
std::multimap<int, int>& getTempos() { return m_tempos; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1031,8 +1036,7 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
|
||||||
std::vector<uint8_t>::const_iterator end = it + length;
|
std::vector<uint8_t>::const_iterator end = it + length;
|
||||||
|
|
||||||
MIDIDecoder dec;
|
MIDIDecoder dec;
|
||||||
while (begin != end)
|
dec.receiveBytes(begin, end);
|
||||||
begin = dec.receiveBytes(begin, end);
|
|
||||||
|
|
||||||
std::multimap<int, int>& tempos = dec.getTempos();
|
std::multimap<int, int>& tempos = dec.getTempos();
|
||||||
if (tempos.size() == 1)
|
if (tempos.size() == 1)
|
||||||
|
@ -1065,12 +1069,11 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
|
||||||
it = end;
|
it = end;
|
||||||
|
|
||||||
MIDIDecoder dec;
|
MIDIDecoder dec;
|
||||||
while (begin != end)
|
dec.receiveBytes(begin, end);
|
||||||
begin = dec.receiveBytes(begin, end);
|
|
||||||
|
|
||||||
std::vector<std::pair<int, std::multimap<int, Event>>>& results = dec.getResults();
|
|
||||||
for (int c = 0; c < 16; ++c)
|
for (int c = 0; c < 16; ++c)
|
||||||
{
|
{
|
||||||
|
std::vector<std::pair<int, std::multimap<int, Event>>>& results = dec.getResults(c);
|
||||||
int lastTrackStartTick = 0;
|
int lastTrackStartTick = 0;
|
||||||
bool didChanInit = false;
|
bool didChanInit = false;
|
||||||
for (auto& prog : results)
|
for (auto& prog : results)
|
||||||
|
@ -1177,20 +1180,21 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
|
||||||
case Event::Type::Pitch:
|
case Event::Type::Pitch:
|
||||||
{
|
{
|
||||||
int newPitch = event.second.pitchBend - 0x2000;
|
int newPitch = event.second.pitchBend - 0x2000;
|
||||||
EncodeDelta(region.modBuf, eventTick - lastPitchTick, newPitch - lastPitchVal);
|
EncodeDelta(region.pitchBuf, eventTick - lastPitchTick, newPitch - lastPitchVal);
|
||||||
lastPitchTick = eventTick;
|
lastPitchTick = eventTick;
|
||||||
lastPitchVal = newPitch;
|
lastPitchVal = newPitch;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Event::Type::Note:
|
case Event::Type::Note:
|
||||||
{
|
{
|
||||||
|
int lenTicks = event.second.length * 384 / header.div;
|
||||||
if (version == 1)
|
if (version == 1)
|
||||||
{
|
{
|
||||||
EncodeTime(region.eventBuf, uint32_t(eventTick - lastEventTick));
|
EncodeTime(region.eventBuf, uint32_t(eventTick - lastEventTick));
|
||||||
lastEventTick = eventTick;
|
lastEventTick = eventTick;
|
||||||
region.eventBuf.push_back(event.second.noteOrCtrl);
|
region.eventBuf.push_back(event.second.noteOrCtrl);
|
||||||
region.eventBuf.push_back(event.second.velOrVal);
|
region.eventBuf.push_back(event.second.velOrVal);
|
||||||
uint16_t lenBig = SBig(uint16_t(event.second.length));
|
uint16_t lenBig = SBig(uint16_t(lenTicks));
|
||||||
region.eventBuf.push_back(reinterpret_cast<const uint8_t*>(&lenBig)[0]);
|
region.eventBuf.push_back(reinterpret_cast<const uint8_t*>(&lenBig)[0]);
|
||||||
region.eventBuf.push_back(reinterpret_cast<const uint8_t*>(&lenBig)[1]);
|
region.eventBuf.push_back(reinterpret_cast<const uint8_t*>(&lenBig)[1]);
|
||||||
}
|
}
|
||||||
|
@ -1201,7 +1205,7 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
|
||||||
uint32_t tickBig = SBig(uint32_t(eventTick - startTick));
|
uint32_t tickBig = SBig(uint32_t(eventTick - startTick));
|
||||||
for (int i = 0; i < 4; ++i)
|
for (int i = 0; i < 4; ++i)
|
||||||
region.eventBuf.push_back(reinterpret_cast<const uint8_t*>(&tickBig)[i]);
|
region.eventBuf.push_back(reinterpret_cast<const uint8_t*>(&tickBig)[i]);
|
||||||
uint16_t lenBig = SBig(uint16_t(event.second.length));
|
uint16_t lenBig = SBig(uint16_t(lenTicks));
|
||||||
region.eventBuf.push_back(reinterpret_cast<const uint8_t*>(&lenBig)[0]);
|
region.eventBuf.push_back(reinterpret_cast<const uint8_t*>(&lenBig)[0]);
|
||||||
region.eventBuf.push_back(reinterpret_cast<const uint8_t*>(&lenBig)[1]);
|
region.eventBuf.push_back(reinterpret_cast<const uint8_t*>(&lenBig)[1]);
|
||||||
region.eventBuf.push_back(event.second.noteOrCtrl);
|
region.eventBuf.push_back(event.second.noteOrCtrl);
|
||||||
|
@ -1212,7 +1216,7 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
|
||||||
uint32_t tick = uint32_t(eventTick - startTick);
|
uint32_t tick = uint32_t(eventTick - startTick);
|
||||||
for (int i = 0; i < 4; ++i)
|
for (int i = 0; i < 4; ++i)
|
||||||
region.eventBuf.push_back(reinterpret_cast<const uint8_t*>(&tick)[i]);
|
region.eventBuf.push_back(reinterpret_cast<const uint8_t*>(&tick)[i]);
|
||||||
uint16_t len = uint16_t(event.second.length);
|
uint16_t len = uint16_t(lenTicks);
|
||||||
region.eventBuf.push_back(reinterpret_cast<const uint8_t*>(&len)[0]);
|
region.eventBuf.push_back(reinterpret_cast<const uint8_t*>(&len)[0]);
|
||||||
region.eventBuf.push_back(reinterpret_cast<const uint8_t*>(&len)[1]);
|
region.eventBuf.push_back(reinterpret_cast<const uint8_t*>(&len)[1]);
|
||||||
region.eventBuf.push_back(event.second.noteOrCtrl);
|
region.eventBuf.push_back(event.second.noteOrCtrl);
|
||||||
|
|
|
@ -106,8 +106,6 @@ void SongState::Track::Header::swapBig()
|
||||||
SongState::Track::Track(SongState& parent, uint8_t midiChan, const TrackRegion* regions)
|
SongState::Track::Track(SongState& parent, uint8_t midiChan, const TrackRegion* regions)
|
||||||
: m_parent(&parent), m_midiChan(midiChan), m_curRegion(nullptr), m_nextRegion(regions)
|
: m_parent(&parent), m_midiChan(midiChan), m_curRegion(nullptr), m_nextRegion(regions)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < 128; ++i)
|
|
||||||
m_remNoteLengths[i] = std::numeric_limits<decltype(m_remNoteLengths)::value_type>::min();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SongState::Track::setRegion(Sequencer* seq, const TrackRegion* region)
|
void SongState::Track::setRegion(Sequencer* seq, const TrackRegion* region)
|
||||||
|
@ -406,15 +404,11 @@ bool SongState::Track::advance(Sequencer& seq, int32_t ticks)
|
||||||
/* Stop finished notes */
|
/* Stop finished notes */
|
||||||
for (int i = 0; i < 128; ++i)
|
for (int i = 0; i < 128; ++i)
|
||||||
{
|
{
|
||||||
constexpr decltype(m_remNoteLengths)::value_type MIN = std::numeric_limits<decltype(MIN)>::min();
|
if (m_remNoteLengths[i] > 0)
|
||||||
if (m_remNoteLengths[i] != MIN)
|
|
||||||
{
|
{
|
||||||
m_remNoteLengths[i] -= ticks;
|
m_remNoteLengths[i] -= ticks;
|
||||||
if (m_remNoteLengths[i] <= 0)
|
if (m_remNoteLengths[i] <= 0)
|
||||||
{
|
|
||||||
seq.keyOff(m_midiChan, i, 0);
|
seq.keyOff(m_midiChan, i, 0);
|
||||||
m_remNoteLengths[i] = MIN;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -280,7 +280,8 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdWaitTicks::Introspective =
|
||||||
CmdType::Structure,
|
CmdType::Structure,
|
||||||
"Wait Ticks"sv,
|
"Wait Ticks"sv,
|
||||||
"Suspend SoundMacro execution for specified length of time. Value of 65535 "
|
"Suspend SoundMacro execution for specified length of time. Value of 65535 "
|
||||||
"will wait indefinitely, relying on Key Off or Sample End to signal stop."sv,
|
"will wait indefinitely, relying on Key Off or Sample End to signal stop. "
|
||||||
|
"Absolute mode waits relative to the start of the SoundMacro."sv,
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
FIELD_HEAD(SoundMacro::CmdWaitTicks, keyOff),
|
FIELD_HEAD(SoundMacro::CmdWaitTicks, keyOff),
|
||||||
|
@ -442,7 +443,8 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdWaitMs::Introspective =
|
||||||
CmdType::Structure,
|
CmdType::Structure,
|
||||||
"Wait Millisec"sv,
|
"Wait Millisec"sv,
|
||||||
"Suspend SoundMacro execution for specified length of time. Value of 65535 "
|
"Suspend SoundMacro execution for specified length of time. Value of 65535 "
|
||||||
"will wait indefinitely, relying on Key Off or Sample End to signal stop."sv,
|
"will wait indefinitely, relying on Key Off or Sample End to signal stop. "
|
||||||
|
"Absolute mode waits relative to the start of the SoundMacro."sv,
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
FIELD_HEAD(SoundMacro::CmdWaitMs, keyOff),
|
FIELD_HEAD(SoundMacro::CmdWaitMs, keyOff),
|
||||||
|
@ -1468,7 +1470,7 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdSetPitchAdsr::Introspective =
|
||||||
{
|
{
|
||||||
CmdType::Pitch,
|
CmdType::Pitch,
|
||||||
"Set Pitch ADSR"sv,
|
"Set Pitch ADSR"sv,
|
||||||
"Define the pitch ADSR from a pool object. The pitch range is "
|
"Define the pitch ADSR from a DLS ADSR pool object. The pitch range is "
|
||||||
"specified using Note and Cents parameters."sv,
|
"specified using Note and Cents parameters."sv,
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
|
|
|
@ -117,6 +117,7 @@ bool Voice::_checkSamplePos(bool& looped)
|
||||||
|
|
||||||
void Voice::_doKeyOff()
|
void Voice::_doKeyOff()
|
||||||
{
|
{
|
||||||
|
m_voxState = VoiceState::KeyOff;
|
||||||
if (m_state.m_inWait && m_state.m_keyoffWait)
|
if (m_state.m_inWait && m_state.m_keyoffWait)
|
||||||
{
|
{
|
||||||
if (m_volAdsr.isAdsrSet() || m_state.m_useAdsrControllers)
|
if (m_volAdsr.isAdsrSet() || m_state.m_useAdsrControllers)
|
||||||
|
@ -480,7 +481,7 @@ void Voice::preSupplyAudio(double dt)
|
||||||
}
|
}
|
||||||
if (m_pitchEnv)
|
if (m_pitchEnv)
|
||||||
{
|
{
|
||||||
newPitch *= m_pitchAdsr.advance(dt);
|
newPitch += m_pitchAdsr.advance(dt) * m_pitchEnvRange;
|
||||||
refresh = true;
|
refresh = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -934,7 +935,6 @@ void Voice::_macroKeyOff()
|
||||||
m_sustainKeyOff = true;
|
m_sustainKeyOff = true;
|
||||||
else
|
else
|
||||||
_doKeyOff();
|
_doKeyOff();
|
||||||
m_voxState = VoiceState::KeyOff;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1456,10 +1456,7 @@ void Voice::_notifyCtrlChange(uint8_t ctrl, int8_t val)
|
||||||
{
|
{
|
||||||
if (ctrl == 0x40)
|
if (ctrl == 0x40)
|
||||||
{
|
{
|
||||||
if (val >= 0x40)
|
setPedal(val >= 0x40);
|
||||||
setPedal(true);
|
|
||||||
else
|
|
||||||
setPedal(false);
|
|
||||||
}
|
}
|
||||||
else if (ctrl == 0x5b)
|
else if (ctrl == 0x5b)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue