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)
|
||||
: 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);
|
||||
}
|
||||
|
||||
|
@ -102,18 +116,65 @@ void FieldProjectNode::setCollection(ProjectModel::CollectionNode* collection)
|
|||
|
||||
if (!collection)
|
||||
{
|
||||
setModel(new QStandardItemModel(0, 1, this));
|
||||
m_comboBox.setModel(new QStandardItemModel(0, 1, this));
|
||||
m_button.setDisabled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
ProjectModel* model = g_MainWindow->projectModel();
|
||||
setModel(model->getNullProxy());
|
||||
setRootModelIndex(model->getNullProxy()->mapFromSource(model->index(collection)));
|
||||
m_comboBox.setModel(model->getNullProxy());
|
||||
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)
|
||||
: 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);
|
||||
}
|
||||
|
||||
|
@ -123,13 +184,53 @@ void FieldPageObjectNode::setGroup(ProjectModel::GroupNode* group)
|
|||
|
||||
if (!group)
|
||||
{
|
||||
setModel(new QStandardItemModel(0, 1, this));
|
||||
m_comboBox.setModel(new QStandardItemModel(0, 1, this));
|
||||
m_button.setDisabled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
ProjectModel* model = g_MainWindow->projectModel();
|
||||
setModel(model->getPageObjectProxy());
|
||||
setRootModelIndex(model->getPageObjectProxy()->mapFromSource(model->index(group)));
|
||||
m_comboBox.setModel(model->getPageObjectProxy());
|
||||
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)
|
||||
|
|
|
@ -23,7 +23,7 @@ public:
|
|||
virtual void unloadData() {}
|
||||
virtual ProjectModel::INode* currentNode() const { return nullptr; }
|
||||
virtual void setEditorEnabled(bool en) { setEnabled(en); }
|
||||
virtual bool isItemEditEnabled() const { return false; }
|
||||
virtual AmuseItemEditFlags itemEditFlags() const { return AmuseItemNone; }
|
||||
public slots:
|
||||
virtual void itemCutAction() {}
|
||||
virtual void itemCopyAction() {}
|
||||
|
@ -125,24 +125,51 @@ public:
|
|||
void wheelEvent(QWheelEvent* event) { event->ignore(); }
|
||||
};
|
||||
|
||||
class FieldProjectNode : public FieldComboBox
|
||||
class FieldProjectNode : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
ProjectModel::CollectionNode* m_collection;
|
||||
FieldComboBox m_comboBox;
|
||||
QPushButton m_button;
|
||||
public:
|
||||
explicit FieldProjectNode(ProjectModel::CollectionNode* collection = Q_NULLPTR, QWidget* parent = Q_NULLPTR);
|
||||
void setCollection(ProjectModel::CollectionNode* 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
|
||||
ProjectModel::GroupNode* m_group;
|
||||
FieldComboBox m_comboBox;
|
||||
QPushButton m_button;
|
||||
public:
|
||||
explicit FieldPageObjectNode(ProjectModel::GroupNode* group = Q_NULLPTR, QWidget* parent = Q_NULLPTR);
|
||||
void setGroup(ProjectModel::GroupNode* 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>
|
||||
|
|
|
@ -118,7 +118,7 @@ void SoundMacroDelegate::setEditorData(QWidget* editor, const QModelIndex& index
|
|||
ProjectModel::CollectionNode* smColl = group->getCollectionOfType(ProjectModel::INode::Type::SoundMacro);
|
||||
static_cast<EditorFieldProjectNode*>(editor)->setCurrentIndex(smColl->indexOfId(layer.macro.id) + 1);
|
||||
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
|
||||
|
@ -636,24 +636,9 @@ void LayersEditor::doSelectionChanged()
|
|||
g_MainWindow->updateFocus();
|
||||
}
|
||||
|
||||
bool LayersEditor::isItemEditEnabled() const
|
||||
AmuseItemEditFlags LayersEditor::itemEditFlags() const
|
||||
{
|
||||
return !m_tableView.selectionModel()->selectedRows().isEmpty();
|
||||
}
|
||||
|
||||
void LayersEditor::itemCutAction()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void LayersEditor::itemCopyAction()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void LayersEditor::itemPasteAction()
|
||||
{
|
||||
|
||||
return m_tableView.selectionModel()->selectedRows().isEmpty() ? AmuseItemNone : AmuseItemDelete;
|
||||
}
|
||||
|
||||
void LayersEditor::itemDeleteAction()
|
||||
|
|
|
@ -78,14 +78,10 @@ public:
|
|||
void unloadData();
|
||||
ProjectModel::INode* currentNode() const;
|
||||
void resizeEvent(QResizeEvent* ev);
|
||||
bool isItemEditEnabled() const;
|
||||
AmuseItemEditFlags itemEditFlags() const;
|
||||
public slots:
|
||||
void doAdd();
|
||||
void doSelectionChanged();
|
||||
|
||||
void itemCutAction();
|
||||
void itemCopyAction();
|
||||
void itemPasteAction();
|
||||
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)
|
||||
{
|
||||
if (g_MainWindow->m_interactiveSeq)
|
||||
g_MainWindow->m_interactiveSeq->keyOff(chan, key, velocity);
|
||||
|
||||
auto keySearch = m_chanVoxs.find(key);
|
||||
if (keySearch == m_chanVoxs.cend())
|
||||
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)
|
||||
{
|
||||
if (g_MainWindow->m_interactiveSeq)
|
||||
g_MainWindow->m_interactiveSeq->keyOn(chan, key, velocity);
|
||||
|
||||
if (m_lastVoice && m_lastVoice->isDestroyed())
|
||||
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);
|
||||
if (newVox)
|
||||
{
|
||||
m_chanVoxs[key] = newVox;
|
||||
m_lastVoice = newVox;
|
||||
}
|
||||
}
|
||||
|
||||
void MIDIReader::notePressure(uint8_t /*chan*/, uint8_t /*key*/, uint8_t /*pressure*/) {}
|
||||
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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::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)
|
||||
{
|
||||
if (g_MainWindow->m_interactiveSeq)
|
||||
g_MainWindow->m_interactiveSeq->kill();
|
||||
|
||||
for (auto& v : m_engine.getActiveVoices())
|
||||
v->kill();
|
||||
}
|
||||
|
@ -92,6 +115,9 @@ void MIDIReader::localControl(uint8_t /*chan*/, bool /*on*/) {}
|
|||
|
||||
void MIDIReader::allNotesOff(uint8_t chan)
|
||||
{
|
||||
if (g_MainWindow->m_interactiveSeq)
|
||||
g_MainWindow->m_interactiveSeq->kill();
|
||||
|
||||
for (auto& v : m_engine.getActiveVoices())
|
||||
v->kill();
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <QInputDialog>
|
||||
#include <QProgressDialog>
|
||||
#include <QMouseEvent>
|
||||
#include <QClipboard>
|
||||
#include <QtSvg/QtSvg>
|
||||
#include "amuse/ContainerRegistry.hpp"
|
||||
#include "Common.hpp"
|
||||
|
@ -21,6 +22,7 @@
|
|||
|
||||
MainWindow::MainWindow(QWidget* parent)
|
||||
: QMainWindow(parent),
|
||||
m_navIt(m_navList.begin()),
|
||||
m_treeDelegate(*this, this),
|
||||
m_mainMessenger(this),
|
||||
m_filterProjectModel(this),
|
||||
|
@ -41,6 +43,17 @@ MainWindow::MainWindow(QWidget* parent)
|
|||
m_ui.projectOutline->setItemDelegate(&m_treeDelegate);
|
||||
connect(m_ui.projectOutline, SIGNAL(activated(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);
|
||||
|
||||
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_Songs, SIGNAL(triggered()), this, SLOT(importSongsAction()));
|
||||
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)
|
||||
{
|
||||
|
@ -84,21 +101,16 @@ MainWindow::MainWindow(QWidget* parent)
|
|||
connect(m_clearRecentFileAct, SIGNAL(triggered()), this, SLOT(clearRecentFilesAction()));
|
||||
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();
|
||||
|
||||
connect(m_undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(cleanChanged(bool)));
|
||||
QAction* undoAction = m_undoStack->createUndoAction(this);
|
||||
undoAction->setShortcut(QKeySequence::Undo);
|
||||
undoAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-undo")));
|
||||
m_ui.menuEdit->insertAction(m_ui.actionCut, undoAction);
|
||||
QAction* redoAction = m_undoStack->createRedoAction(this);
|
||||
redoAction->setShortcut(QKeySequence::Redo);
|
||||
redoAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-redo")));
|
||||
m_ui.menuEdit->insertAction(m_ui.actionCut, redoAction);
|
||||
m_ui.menuEdit->insertSeparator(m_ui.actionCut);
|
||||
m_ui.actionCut->setShortcut(QKeySequence::Cut);
|
||||
|
@ -153,6 +165,7 @@ MainWindow::MainWindow(QWidget* parent)
|
|||
connect(m_ui.menuMIDI, SIGNAL(aboutToShow()), this, SLOT(aboutToShowMIDIIOMenu()));
|
||||
|
||||
connect(qApp, SIGNAL(focusChanged(QWidget*,QWidget*)), this, SLOT(onFocusChanged(QWidget*,QWidget*)));
|
||||
connect(QGuiApplication::clipboard(), SIGNAL(dataChanged()), this, SLOT(onClipboardChanged()));
|
||||
|
||||
m_voxEngine = boo::NewAudioVoiceEngine();
|
||||
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());
|
||||
m_recentFileActs[i]->setText(text);
|
||||
m_recentFileActs[i]->setData(files[i]);
|
||||
m_recentFileActs[i]->setToolTip(files[i]);
|
||||
m_recentFileActs[i]->setVisible(true);
|
||||
}
|
||||
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.actionImport_Songs->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);
|
||||
setWindowFilePath(path);
|
||||
updateWindowTitle();
|
||||
updateFocus();
|
||||
m_undoStack->clear();
|
||||
m_navList.clear();
|
||||
m_navIt = m_navList.begin();
|
||||
|
||||
updateNavigationButtons();
|
||||
|
||||
QSettings settings;
|
||||
QStringList files = settings.value("recentFileList").toStringList();
|
||||
|
@ -399,9 +419,8 @@ void MainWindow::timerEvent(QTimerEvent* ev)
|
|||
for (int i = 0; i < songTable->model()->rowCount(); ++i)
|
||||
if (MIDIPlayerWidget* player = qobject_cast<MIDIPlayerWidget*>(
|
||||
songTable->indexWidget(songTable->model()->index(i, 1))))
|
||||
for (auto& p : m_engine->getActiveSequencers())
|
||||
if (p.get() == player->sequencer() && p->state() != amuse::SequencerState::Playing)
|
||||
player->stopped();
|
||||
if (player->sequencer() && player->sequencer()->state() != amuse::SequencerState::Playing)
|
||||
player->stopped();
|
||||
|
||||
QTableView* sfxTable = m_soundGroupEditor->getSFXListView();
|
||||
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);
|
||||
}
|
||||
|
||||
bool MainWindow::_setEditor(EditorWidget* editor)
|
||||
bool MainWindow::_setEditor(EditorWidget* editor, bool appendNav)
|
||||
{
|
||||
m_interactiveSeq.reset();
|
||||
if (editor != m_ui.editorContents->currentWidget() &&
|
||||
m_ui.editorContents->currentWidget() != m_faceSvg)
|
||||
getEditorWidget()->unloadData();
|
||||
if (appendNav && m_navIt != m_navList.end())
|
||||
m_navList.erase(m_navIt + 1, m_navList.end());
|
||||
if (!editor || !editor->valid())
|
||||
{
|
||||
m_ui.editorContents->setCurrentWidget(m_faceSvg);
|
||||
updateWindowTitle();
|
||||
if (appendNav)
|
||||
{
|
||||
m_navIt = m_navList.end();
|
||||
updateNavigationButtons();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
m_ui.editorContents->setCurrentWidget(editor);
|
||||
m_ui.editorContents->update();
|
||||
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;
|
||||
}
|
||||
|
||||
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())
|
||||
{
|
||||
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:
|
||||
return openEditor(static_cast<ProjectModel::SoundGroupNode*>(node));
|
||||
return openEditor(static_cast<ProjectModel::SoundGroupNode*>(node), appendNav);
|
||||
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:
|
||||
return openEditor(static_cast<ProjectModel::ADSRNode*>(node));
|
||||
return openEditor(static_cast<ProjectModel::ADSRNode*>(node), appendNav);
|
||||
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:
|
||||
return openEditor(static_cast<ProjectModel::KeymapNode*>(node));
|
||||
return openEditor(static_cast<ProjectModel::KeymapNode*>(node), appendNav);
|
||||
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:
|
||||
return openEditor(static_cast<ProjectModel::SampleNode*>(node));
|
||||
return openEditor(static_cast<ProjectModel::SampleNode*>(node), appendNav);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@ -646,7 +701,7 @@ amuse::ObjToken<amuse::Sequencer> MainWindow::startSong(amuse::GroupId groupId,
|
|||
return {};
|
||||
}
|
||||
|
||||
void MainWindow::pushUndoCommand(QUndoCommand* cmd)
|
||||
void MainWindow::pushUndoCommand(EditorUndoCommand* cmd)
|
||||
{
|
||||
m_undoStack->push(cmd);
|
||||
}
|
||||
|
@ -658,6 +713,21 @@ void MainWindow::updateFocus()
|
|||
|
||||
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)
|
||||
closeEditor();
|
||||
}
|
||||
|
@ -813,6 +883,9 @@ void MainWindow::revertAction()
|
|||
QString path = m_projectModel->path();
|
||||
closeEditor();
|
||||
m_undoStack->clear();
|
||||
m_navList.clear();
|
||||
m_navIt = m_navList.begin();
|
||||
updateNavigationButtons();
|
||||
delete m_projectModel;
|
||||
m_projectModel = nullptr;
|
||||
openProject(path);
|
||||
|
@ -936,6 +1009,7 @@ void MainWindow::importAction()
|
|||
return;
|
||||
}
|
||||
}
|
||||
model->openSongsData();
|
||||
});
|
||||
|
||||
|
||||
|
@ -978,6 +1052,7 @@ void MainWindow::importAction()
|
|||
return;
|
||||
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,
|
||||
QAbstractItemModel* _model,
|
||||
QAbstractItemModel* __model,
|
||||
const QStyleOptionViewItem& option,
|
||||
const QModelIndex& index)
|
||||
{
|
||||
ProjectModel* model = static_cast<ProjectModel*>(_model);
|
||||
ProjectModel::INode* node = model->node(index);
|
||||
if (!node)
|
||||
QSortFilterProxyModel* _model = static_cast<QSortFilterProxyModel*>(__model);
|
||||
ProjectModel* model = static_cast<ProjectModel*>(_model->sourceModel());
|
||||
ProjectModel::INode* node = model->node(_model->mapToSource(index));
|
||||
if (!node || !(node->flags() & Qt::ItemIsEditable))
|
||||
return false;
|
||||
AmuseItemEditFlags flags = node->editFlags();
|
||||
|
||||
#if 0
|
||||
if ((event->type() == QEvent::MouseButtonDblClick &&
|
||||
static_cast<QMouseEvent*>(event)->button() == Qt::LeftButton))
|
||||
if (event->type() == QEvent::MouseButtonPress)
|
||||
{
|
||||
// Open in editor
|
||||
return m_window.openEditor(node);
|
||||
QMouseEvent* ev = static_cast<QMouseEvent*>(event);
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
if (group)
|
||||
|
@ -1057,10 +1282,10 @@ ProjectModel::GroupNode* MainWindow::getSelectedGroupNode() const
|
|||
{
|
||||
if (!m_projectModel)
|
||||
return nullptr;
|
||||
if (!m_ui.projectOutline->selectionModel()->currentIndex().isValid())
|
||||
if (!m_ui.projectOutline->currentIndex().isValid())
|
||||
return nullptr;
|
||||
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
|
||||
|
@ -1082,10 +1307,7 @@ void MainWindow::recursiveExpandAndSelectOutline(const QModelIndex& index) const
|
|||
QModelIndex filterIndex = m_filterProjectModel.mapFromSource(index);
|
||||
_recursiveExpandOutline(filterIndex);
|
||||
if (filterIndex.isValid())
|
||||
{
|
||||
m_ui.projectOutline->selectionModel()->setCurrentIndex(filterIndex, QItemSelectionModel::ClearAndSelect);
|
||||
m_ui.projectOutline->selectionModel()->setCurrentIndex(filterIndex, QItemSelectionModel::Current);
|
||||
}
|
||||
m_ui.projectOutline->setCurrentIndex(filterIndex);
|
||||
}
|
||||
|
||||
void MainWindow::newSubprojectAction()
|
||||
|
@ -1238,6 +1460,30 @@ void MainWindow::newLayersAction()
|
|||
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()
|
||||
{
|
||||
refreshAudioIO();
|
||||
|
@ -1413,27 +1659,30 @@ void MainWindow::auxBChanged(int vol)
|
|||
|
||||
void MainWindow::outlineCutAction()
|
||||
{
|
||||
|
||||
if (!m_projectModel)
|
||||
return;
|
||||
m_projectModel->cut(m_filterProjectModel.mapToSource(m_ui.projectOutline->currentIndex()));
|
||||
}
|
||||
|
||||
void MainWindow::outlineCopyAction()
|
||||
{
|
||||
|
||||
if (!m_projectModel)
|
||||
return;
|
||||
m_projectModel->copy(m_filterProjectModel.mapToSource(m_ui.projectOutline->currentIndex()));
|
||||
}
|
||||
|
||||
void MainWindow::outlinePasteAction()
|
||||
{
|
||||
|
||||
if (!m_projectModel)
|
||||
return;
|
||||
m_projectModel->paste(m_filterProjectModel.mapToSource(m_ui.projectOutline->currentIndex()));
|
||||
}
|
||||
|
||||
void MainWindow::outlineDeleteAction()
|
||||
{
|
||||
if (!m_projectModel)
|
||||
return;
|
||||
QModelIndexList indexes = m_ui.projectOutline->selectionModel()->selectedIndexes();
|
||||
if (indexes.empty())
|
||||
return;
|
||||
m_projectModel->del(m_filterProjectModel.mapToSource(indexes.front()));
|
||||
m_projectModel->del(m_filterProjectModel.mapToSource(m_ui.projectOutline->currentIndex()));
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
setItemEditEnabled(canEditOutline());
|
||||
AmuseItemEditFlags editFlags = outlineEditFlags();
|
||||
if (!m_clipboardAmuseData)
|
||||
editFlags = AmuseItemEditFlags(editFlags & ~AmuseItemPaste);
|
||||
setItemEditFlags(editFlags);
|
||||
if (m_projectModel)
|
||||
{
|
||||
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))
|
||||
{
|
||||
setItemEditEnabled(false);
|
||||
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);
|
||||
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()));
|
||||
if (str.startsWith(QStringLiteral("application/x-amuse-")))
|
||||
{
|
||||
m_clipboardAmuseData = true;
|
||||
updateFocus();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_clipboardAmuseData = false;
|
||||
updateFocus();
|
||||
}
|
||||
|
||||
void MainWindow::outlineItemActivated(const QModelIndex& index)
|
||||
|
@ -1495,12 +1767,12 @@ void MainWindow::outlineItemActivated(const QModelIndex& index)
|
|||
openEditor(node);
|
||||
}
|
||||
|
||||
void MainWindow::setItemEditEnabled(bool enabled)
|
||||
void MainWindow::setItemEditFlags(AmuseItemEditFlags flags)
|
||||
{
|
||||
m_ui.actionCut->setEnabled(enabled);
|
||||
m_ui.actionCopy->setEnabled(enabled);
|
||||
m_ui.actionPaste->setEnabled(enabled);
|
||||
m_ui.actionDelete->setEnabled(enabled);
|
||||
m_ui.actionCut->setEnabled(flags & AmuseItemCut);
|
||||
m_ui.actionCopy->setEnabled(flags & AmuseItemCopy);
|
||||
m_ui.actionPaste->setEnabled(flags & AmuseItemPaste);
|
||||
m_ui.actionDelete->setEnabled(flags & AmuseItemDelete);
|
||||
}
|
||||
|
||||
void MainWindow::setItemNewEnabled(bool enabled)
|
||||
|
@ -1514,25 +1786,25 @@ void MainWindow::setItemNewEnabled(bool enabled)
|
|||
m_ui.actionNew_Layers->setEnabled(enabled);
|
||||
}
|
||||
|
||||
bool MainWindow::canEditOutline()
|
||||
AmuseItemEditFlags MainWindow::outlineEditFlags()
|
||||
{
|
||||
if (!m_projectModel)
|
||||
return false;
|
||||
QModelIndex curIndex = m_ui.projectOutline->selectionModel()->currentIndex();
|
||||
return AmuseItemNone;
|
||||
QModelIndex curIndex = m_ui.projectOutline->currentIndex();
|
||||
if (!curIndex.isValid())
|
||||
return false;
|
||||
return m_projectModel->canEdit(m_filterProjectModel.mapToSource(curIndex));
|
||||
return AmuseItemNone;
|
||||
return m_projectModel->editFlags(m_filterProjectModel.mapToSource(curIndex));
|
||||
}
|
||||
|
||||
void MainWindow::onOutlineSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected)
|
||||
{
|
||||
if (!m_projectModel)
|
||||
return;
|
||||
setItemNewEnabled(m_ui.projectOutline->selectionModel()->currentIndex().isValid());
|
||||
setItemNewEnabled(m_ui.projectOutline->currentIndex().isValid());
|
||||
if (selected.indexes().empty())
|
||||
setItemEditEnabled(false);
|
||||
setItemEditFlags(AmuseItemNone);
|
||||
else
|
||||
setItemEditEnabled(m_projectModel->canEdit(m_filterProjectModel.mapToSource(selected.indexes().front())));
|
||||
setItemEditFlags(m_projectModel->editFlags(m_filterProjectModel.mapToSource(selected.indexes().front())));
|
||||
}
|
||||
|
||||
void MainWindow::onTextSelect()
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <QThread>
|
||||
#include <QStyledItemDelegate>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QLinkedList>
|
||||
#include "ui_MainWindow.h"
|
||||
#include "amuse/Engine.hpp"
|
||||
#include "amuse/BooBackend.hpp"
|
||||
|
@ -77,13 +78,27 @@ public:
|
|||
QAbstractItemModel *model,
|
||||
const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index);
|
||||
public slots:
|
||||
void doCut();
|
||||
void doCopy();
|
||||
void doPaste();
|
||||
void doDuplicate();
|
||||
void doDelete();
|
||||
void doRename();
|
||||
};
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
friend class MIDIReader;
|
||||
friend class ProjectModel;
|
||||
friend class GroupNodeUndoCommand;
|
||||
friend class TreeDelegate;
|
||||
Q_OBJECT
|
||||
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_recentFileActs[MaxRecentFiles];
|
||||
TreeDelegate m_treeDelegate;
|
||||
|
@ -105,12 +120,14 @@ class MainWindow : public QMainWindow
|
|||
std::unique_ptr<VoiceAllocator> m_voxAllocator;
|
||||
std::unique_ptr<amuse::Engine> m_engine;
|
||||
amuse::ObjToken<amuse::Voice> m_lastSound;
|
||||
amuse::ObjToken<amuse::Sequencer> m_interactiveSeq;
|
||||
int m_velocity = 90;
|
||||
float m_pitch = 0.f;
|
||||
int8_t m_ctrlVals[128] = {};
|
||||
float m_auxAVol = 0.f;
|
||||
float m_auxBVol = 0.f;
|
||||
bool m_uiDisabled = false;
|
||||
bool m_clipboardAmuseData = false;
|
||||
|
||||
QUndoStack* m_undoStack;
|
||||
|
||||
|
@ -128,6 +145,7 @@ class MainWindow : public QMainWindow
|
|||
|
||||
void updateWindowTitle();
|
||||
void updateRecentFileActions();
|
||||
void updateNavigationButtons();
|
||||
bool setProjectPath(const QString& path);
|
||||
void refreshAudioIO();
|
||||
void refreshMIDIIO();
|
||||
|
@ -139,7 +157,7 @@ class MainWindow : public QMainWindow
|
|||
void startBackgroundTask(int id, const QString& windowTitle, const QString& label,
|
||||
std::function<void(BackgroundTask&)>&& task);
|
||||
|
||||
bool _setEditor(EditorWidget* widget);
|
||||
bool _setEditor(EditorWidget* widget, bool appendNav = true);
|
||||
|
||||
public:
|
||||
explicit MainWindow(QWidget* parent = Q_NULLPTR);
|
||||
|
@ -147,15 +165,15 @@ public:
|
|||
|
||||
bool openProject(const QString& path);
|
||||
|
||||
bool openEditor(ProjectModel::SongGroupNode* node);
|
||||
bool openEditor(ProjectModel::SoundGroupNode* node);
|
||||
bool openEditor(ProjectModel::SoundMacroNode* node);
|
||||
bool openEditor(ProjectModel::ADSRNode* node);
|
||||
bool openEditor(ProjectModel::CurveNode* node);
|
||||
bool openEditor(ProjectModel::KeymapNode* node);
|
||||
bool openEditor(ProjectModel::LayersNode* node);
|
||||
bool openEditor(ProjectModel::SampleNode* node);
|
||||
bool openEditor(ProjectModel::INode* node);
|
||||
bool openEditor(ProjectModel::SongGroupNode* node, bool appendNav = true);
|
||||
bool openEditor(ProjectModel::SoundGroupNode* node, bool appendNav = true);
|
||||
bool openEditor(ProjectModel::SoundMacroNode* node, bool appendNav = true);
|
||||
bool openEditor(ProjectModel::ADSRNode* node, bool appendNav = true);
|
||||
bool openEditor(ProjectModel::CurveNode* node, bool appendNav = true);
|
||||
bool openEditor(ProjectModel::KeymapNode* node, bool appendNav = true);
|
||||
bool openEditor(ProjectModel::LayersNode* node, bool appendNav = true);
|
||||
bool openEditor(ProjectModel::SampleNode* node, bool appendNav = true);
|
||||
bool openEditor(ProjectModel::INode* node, bool appendNav = true);
|
||||
void closeEditor();
|
||||
|
||||
ProjectModel::INode* getEditorNode() const;
|
||||
|
@ -164,7 +182,7 @@ public:
|
|||
amuse::ObjToken<amuse::Voice> startSFX(amuse::GroupId groupId, amuse::SFXId sfxId);
|
||||
amuse::ObjToken<amuse::Sequencer> startSong(amuse::GroupId groupId, amuse::SongId songId,
|
||||
const unsigned char* arrData);
|
||||
void pushUndoCommand(QUndoCommand* cmd);
|
||||
void pushUndoCommand(EditorUndoCommand* cmd);
|
||||
void updateFocus();
|
||||
void aboutToDeleteNode(ProjectModel::INode* node);
|
||||
void closeEvent(QCloseEvent* ev);
|
||||
|
@ -179,6 +197,11 @@ public:
|
|||
ProjectModel* projectModel() const { return m_projectModel; }
|
||||
UIMessenger& uiMessenger() { return m_mainMessenger; }
|
||||
|
||||
void setItemEditFlags(AmuseItemEditFlags flags);
|
||||
void setItemNewEnabled(bool enabled);
|
||||
AmuseItemEditFlags outlineEditFlags();
|
||||
bool isUiDisabled() const { return m_uiDisabled; }
|
||||
|
||||
public slots:
|
||||
void newAction();
|
||||
void openAction();
|
||||
|
@ -190,6 +213,8 @@ public slots:
|
|||
void importAction();
|
||||
void importSongsAction();
|
||||
void exportAction();
|
||||
void importHeadersAction();
|
||||
void exportHeadersAction();
|
||||
|
||||
void newSubprojectAction();
|
||||
void newSFXGroupAction();
|
||||
|
@ -200,6 +225,9 @@ public slots:
|
|||
void newKeymapAction();
|
||||
void newLayersAction();
|
||||
|
||||
void goForward();
|
||||
void goBack();
|
||||
|
||||
void aboutToShowAudioIOMenu();
|
||||
void aboutToShowMIDIIOMenu();
|
||||
|
||||
|
@ -226,10 +254,8 @@ public slots:
|
|||
void outlineDeleteAction();
|
||||
|
||||
void onFocusChanged(QWidget* old, QWidget* now);
|
||||
void onClipboardChanged();
|
||||
void outlineItemActivated(const QModelIndex& index);
|
||||
void setItemEditEnabled(bool enabled);
|
||||
void setItemNewEnabled(bool enabled);
|
||||
bool canEditOutline();
|
||||
void onOutlineSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected);
|
||||
void onTextSelect();
|
||||
void onTextDelete();
|
||||
|
|
|
@ -53,26 +53,59 @@
|
|||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="projectOutlineFilter">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
<layout class="QHBoxLayout" name="outlineLayout2">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>Filter</string>
|
||||
</property>
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="projectOutlineFilter">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>0</height>
|
||||
</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>
|
||||
<widget class="QTreeView" name="projectOutline">
|
||||
|
@ -91,6 +124,9 @@
|
|||
<property name="focusPolicy">
|
||||
<enum>Qt::WheelFocus</enum>
|
||||
</property>
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked</set>
|
||||
</property>
|
||||
<property name="animated">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
|
@ -257,7 +293,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1501</width>
|
||||
<height>83</height>
|
||||
<height>85</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
|
@ -294,7 +330,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1360</width>
|
||||
<height>25</height>
|
||||
<height>27</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuFile">
|
||||
|
@ -308,6 +344,9 @@
|
|||
<property name="title">
|
||||
<string>Recent &Projects</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="document-open-recent"/>
|
||||
</property>
|
||||
</widget>
|
||||
<addaction name="actionNew_Project"/>
|
||||
<addaction name="actionOpen_Project"/>
|
||||
|
@ -319,6 +358,10 @@
|
|||
<addaction name="actionImport_Groups"/>
|
||||
<addaction name="actionImport_Songs"/>
|
||||
<addaction name="actionExport_GameCube_Groups"/>
|
||||
<addaction name="actionImport_C_Headers"/>
|
||||
<addaction name="actionExport_C_Headers"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionQuit"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuProject">
|
||||
<property name="title">
|
||||
|
@ -360,7 +403,7 @@
|
|||
</widget>
|
||||
<widget class="QMenu" name="menuHelp">
|
||||
<property name="title">
|
||||
<string>Help</string>
|
||||
<string>&Help</string>
|
||||
</property>
|
||||
<addaction name="actionAbout_Amuse"/>
|
||||
<addaction name="actionAbout_Qt"/>
|
||||
|
@ -374,11 +417,17 @@
|
|||
</widget>
|
||||
<widget class="StatusBarWidget" name="statusbar"/>
|
||||
<action name="actionNew_Project">
|
||||
<property name="icon">
|
||||
<iconset theme="document-new"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&New Project</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionOpen_Project">
|
||||
<property name="icon">
|
||||
<iconset theme="document-open"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Open Project</string>
|
||||
</property>
|
||||
|
@ -387,6 +436,9 @@
|
|||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="edit-cut"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Cut</string>
|
||||
</property>
|
||||
|
@ -395,6 +447,9 @@
|
|||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="edit-copy"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>C&opy</string>
|
||||
</property>
|
||||
|
@ -403,6 +458,9 @@
|
|||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="edit-paste"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Paste</string>
|
||||
</property>
|
||||
|
@ -411,6 +469,9 @@
|
|||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="edit-delete"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Delete</string>
|
||||
</property>
|
||||
|
@ -550,6 +611,9 @@
|
|||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="document-save"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Save Project</string>
|
||||
</property>
|
||||
|
@ -558,6 +622,9 @@
|
|||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="document-revert"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Revert Project</string>
|
||||
</property>
|
||||
|
@ -579,21 +646,54 @@
|
|||
</property>
|
||||
</action>
|
||||
<action name="actionAbout_Amuse">
|
||||
<property name="icon">
|
||||
<iconset theme="help-about"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>About Amuse</string>
|
||||
<string>&About Amuse</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::AboutRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionAbout_Qt">
|
||||
<property name="icon">
|
||||
<iconset theme="help-about"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>About Qt</string>
|
||||
<string>About &Qt</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::AboutQtRole</enum>
|
||||
</property>
|
||||
</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>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
|
|
|
@ -3,10 +3,14 @@
|
|||
#include "ProjectModel.hpp"
|
||||
#include "Common.hpp"
|
||||
#include "athena/YAMLDocWriter.hpp"
|
||||
#include "athena/VectorWriter.hpp"
|
||||
#include "MainWindow.hpp"
|
||||
#include "EditorWidget.hpp"
|
||||
#include "amuse/SongConverter.hpp"
|
||||
#include "amuse/ContainerRegistry.hpp"
|
||||
#include <QUndoCommand>
|
||||
#include <QDate>
|
||||
#include <QMimeData>
|
||||
#include <QClipboard>
|
||||
|
||||
QIcon ProjectModel::GroupNode::Icon;
|
||||
QIcon ProjectModel::SongGroupNode::Icon;
|
||||
|
@ -305,6 +309,16 @@ ProjectModel::INode::INode(const QString& name)
|
|||
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
|
||||
{
|
||||
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());
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
gn.reserve(6);
|
||||
|
@ -635,9 +758,8 @@ void ProjectModel::_buildGroupNodeCollections(GroupNode& gn)
|
|||
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& sfxGroups = group.getProj().sfxGroups();
|
||||
auto& soundMacros = group.getPool().soundMacros();
|
||||
|
@ -720,7 +842,7 @@ void ProjectModel::_resetModelData()
|
|||
{
|
||||
it->second->setIdDatabases();
|
||||
GroupNode& gn = m_root->makeChild<GroupNode>(it);
|
||||
_buildGroupNode(gn);
|
||||
_buildGroupNode(gn, *gn.m_it->second);
|
||||
}
|
||||
endResetModel();
|
||||
}
|
||||
|
@ -835,6 +957,7 @@ QVariant ProjectModel::data(const QModelIndex& index, int role) const
|
|||
switch (role)
|
||||
{
|
||||
case Qt::DisplayRole:
|
||||
case Qt::EditRole:
|
||||
return item->text();
|
||||
case Qt::DecorationRole:
|
||||
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
|
||||
{
|
||||
if (!index.isValid())
|
||||
|
@ -869,12 +1073,12 @@ ProjectModel::GroupNode* ProjectModel::getGroupNode(INode* node) const
|
|||
return getGroupNode(node->parent());
|
||||
}
|
||||
|
||||
bool ProjectModel::canEdit(const QModelIndex& index) const
|
||||
AmuseItemEditFlags ProjectModel::editFlags(const QModelIndex& index) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return false;
|
||||
return AmuseItemNone;
|
||||
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)
|
||||
|
@ -897,31 +1101,34 @@ void ProjectModel::_preDelNode(INode* n, NameUndoRegistry& registry)
|
|||
});
|
||||
}
|
||||
|
||||
class GroupNodeUndoCommand : public QUndoCommand
|
||||
class GroupNodeUndoCommand : public EditorUndoCommand
|
||||
{
|
||||
protected:
|
||||
std::unique_ptr<amuse::AudioGroupDatabase> m_data;
|
||||
amuse::ObjToken<ProjectModel::GroupNode> m_node;
|
||||
ProjectModel::NameUndoRegistry m_nameReg;
|
||||
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()));
|
||||
}
|
||||
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:
|
||||
explicit GroupNodeUndoCommand(const QString& text, std::unique_ptr<amuse::AudioGroupDatabase>&& data, ProjectModel::GroupNode* node)
|
||||
: QUndoCommand(text.arg(node->text())), m_data(std::move(data)), m_node(node) {}
|
||||
explicit GroupNodeUndoCommand(const QString& text, std::unique_ptr<amuse::AudioGroupDatabase>&& data,
|
||||
ProjectModel::GroupNode* node)
|
||||
: EditorUndoCommand(node, text.arg(node->text())), m_data(std::move(data)) {}
|
||||
};
|
||||
|
||||
class GroupNodeAddUndoCommand : public GroupNodeUndoCommand
|
||||
{
|
||||
using base = GroupNodeUndoCommand;
|
||||
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) {}
|
||||
void undo() { base::del(); }
|
||||
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);
|
||||
m_groups.erase(node->m_it);
|
||||
node->m_it = {};
|
||||
QDir(QFileInfo(m_dir, node->name()).filePath()).removeRecursively();
|
||||
m_root->removeChild(node);
|
||||
endRemoveRows();
|
||||
return ret;
|
||||
|
@ -978,24 +1186,23 @@ ProjectModel::GroupNode* ProjectModel::newSubproject(const QString& name)
|
|||
}
|
||||
|
||||
template <class NT>
|
||||
class NodeUndoCommand : public QUndoCommand
|
||||
class NodeUndoCommand : public EditorUndoCommand
|
||||
{
|
||||
protected:
|
||||
amuse::ObjToken<ProjectModel::GroupNode> m_parent;
|
||||
amuse::ObjToken<NT> m_node;
|
||||
ProjectModel::NameUndoRegistry m_nameReg;
|
||||
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()));
|
||||
}
|
||||
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:
|
||||
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>
|
||||
|
@ -1109,6 +1316,8 @@ static constexpr ProjectModel::INode::Type GetINodeType(ProjectModel::KeymapNode
|
|||
{ return ProjectModel::INode::Type::Keymap; }
|
||||
static constexpr ProjectModel::INode::Type GetINodeType(ProjectModel::LayersNode*)
|
||||
{ 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*)
|
||||
{ return amuse::NameDB::Type::SoundMacro; }
|
||||
|
@ -1120,12 +1329,23 @@ static constexpr amuse::NameDB::Type GetNameDBType(ProjectModel::KeymapNode*)
|
|||
{ return amuse::NameDB::Type::Keymap; }
|
||||
static constexpr amuse::NameDB::Type GetNameDBType(ProjectModel::LayersNode*)
|
||||
{ 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; }
|
||||
static amuse::NameDB* GetNameDB(ProjectModel::ADSRNode*) { return amuse::TableId::CurNameDB; }
|
||||
static amuse::NameDB* GetNameDB(ProjectModel::CurveNode*) { return amuse::TableId::CurNameDB; }
|
||||
static amuse::NameDB* GetNameDB(ProjectModel::KeymapNode*) { return amuse::KeymapId::CurNameDB; }
|
||||
static amuse::NameDB* GetNameDB(ProjectModel::LayersNode*) { return amuse::LayersId::CurNameDB; }
|
||||
template <class NT>
|
||||
inline amuse::NameDB* GetNameDB() { return nullptr; }
|
||||
template <>
|
||||
inline amuse::NameDB* GetNameDB<ProjectModel::SoundMacroNode>() { return amuse::SoundMacroId::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>
|
||||
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));
|
||||
int insertIdx = coll->hypotheticalIndex(node->name());
|
||||
beginInsertRows(index(coll), insertIdx, insertIdx);
|
||||
auto newId = GetNameDB(node)->generateId(GetNameDBType(node));
|
||||
GetNameDB(node)->registerPair(node->name().toUtf8().data(), newId);
|
||||
auto newId = GetNameDB<NT>()->generateId(GetNameDBType(node));
|
||||
GetNameDB<NT>()->registerPair(node->name().toUtf8().data(), newId);
|
||||
container[newId] = node->m_obj;
|
||||
node->m_id = newId;
|
||||
coll->insertChild(node);
|
||||
|
@ -1147,11 +1367,11 @@ template <class NT, class T>
|
|||
void ProjectModel::_delPoolNode(NT* node, GroupNode* parent, NameUndoRegistry& registry, T& container)
|
||||
{
|
||||
int idx = node->row();
|
||||
setIdDatabases(parent);
|
||||
CollectionNode* coll = parent->getCollectionOfType(GetINodeType(node));
|
||||
beginRemoveRows(index(coll), idx, idx);
|
||||
_preDelNode(node, registry);
|
||||
setIdDatabases(parent);
|
||||
GetNameDB(node)->remove(node->m_id);
|
||||
GetNameDB<NT>()->remove(node->m_id);
|
||||
container.erase(node->m_id);
|
||||
node->m_id = {};
|
||||
coll->removeChild(node);
|
||||
|
@ -1296,18 +1516,505 @@ ProjectModel::LayersNode* ProjectModel::newLayers(GroupNode* group, const QStrin
|
|||
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)
|
||||
{
|
||||
if (!index.isValid())
|
||||
return;
|
||||
assert(index.model() == this && "Not ProjectModel");
|
||||
INode* n = node(index);
|
||||
QUndoCommand* cmd = nullptr;
|
||||
EditorUndoCommand* cmd = nullptr;
|
||||
switch (n->type())
|
||||
{
|
||||
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;
|
||||
}
|
||||
case INode::Type::SongGroup:
|
||||
cmd = new NodeDelUndoCommand(tr("Delete SongGroup %1"), static_cast<SongGroupNode*>(n));
|
||||
break;
|
||||
|
@ -1329,6 +2036,21 @@ void ProjectModel::del(const QModelIndex& index)
|
|||
case INode::Type::Layer:
|
||||
cmd = new NodeDelUndoCommand(tr("Delete Layers %1"), static_cast<LayersNode*>(n));
|
||||
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:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -15,6 +15,18 @@
|
|||
#include "amuse/AudioGroupSampleDirectory.hpp"
|
||||
|
||||
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
|
||||
{
|
||||
|
@ -86,6 +98,8 @@ private:
|
|||
public:
|
||||
class INode : public amuse::IObj
|
||||
{
|
||||
friend class ProjectModel;
|
||||
virtual void _sortChildren();
|
||||
public:
|
||||
enum class Type
|
||||
{
|
||||
|
@ -144,13 +158,14 @@ public:
|
|||
}
|
||||
amuse::ObjToken<INode> removeChild(INode* n)
|
||||
{
|
||||
int row = n->row();
|
||||
assert(n == m_children.at(row).get() && "Removing non-child from node");
|
||||
amuse::ObjToken<INode> ret = n;
|
||||
int row = ret->row();
|
||||
assert(ret.get() == m_children.at(row).get() && "Removing non-child from node");
|
||||
m_children.erase(m_children.begin() + row);
|
||||
reindexRows(row);
|
||||
n->m_parent = nullptr;
|
||||
n->m_row = -1;
|
||||
return n;
|
||||
ret->m_parent = nullptr;
|
||||
ret->m_row = -1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void reserve(size_t sz) { m_children.reserve(sz); }
|
||||
|
@ -172,6 +187,17 @@ public:
|
|||
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)
|
||||
{
|
||||
for (auto& n : m_children)
|
||||
|
@ -191,15 +217,18 @@ public:
|
|||
const QString& name() const { return m_name; }
|
||||
virtual int hypotheticalIndex(const QString& name) const;
|
||||
|
||||
virtual amuse::NameDB* getNameDb() const { return nullptr; }
|
||||
|
||||
virtual Type type() const = 0;
|
||||
virtual QString text() 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 unregisterNames(NameUndoRegistry& registry) const {}
|
||||
};
|
||||
struct NullNode : INode
|
||||
struct NullNode final : INode
|
||||
{
|
||||
NullNode(INode* parent) : INode(parent) {}
|
||||
|
||||
|
@ -207,7 +236,7 @@ public:
|
|||
QString text() const { return {}; }
|
||||
QIcon icon() const { return {}; }
|
||||
};
|
||||
struct RootNode : INode
|
||||
struct RootNode final : INode
|
||||
{
|
||||
RootNode() : INode(QStringLiteral("<root>")) {}
|
||||
|
||||
|
@ -218,7 +247,7 @@ public:
|
|||
};
|
||||
struct CollectionNode;
|
||||
struct BasePoolObjectNode;
|
||||
struct GroupNode : INode
|
||||
struct GroupNode final : INode
|
||||
{
|
||||
std::unordered_map<QString, std::unique_ptr<amuse::AudioGroupDatabase>>::iterator m_it;
|
||||
GroupNode(const QString& name) : INode(name) {}
|
||||
|
@ -226,17 +255,19 @@ public:
|
|||
: INode(it->first), m_it(it) {}
|
||||
|
||||
int hypotheticalIndex(const QString& name) const;
|
||||
void _sortChildren();
|
||||
|
||||
static QIcon Icon;
|
||||
Type type() const { return Type::Group; }
|
||||
QString text() const { return m_name; }
|
||||
QIcon icon() const { return Icon; }
|
||||
AmuseItemEditFlags editFlags() const { return AmuseItemNoCut; }
|
||||
|
||||
CollectionNode* getCollectionOfType(Type tp) const;
|
||||
amuse::AudioGroupDatabase* getAudioGroup() const { return m_it->second.get(); }
|
||||
BasePoolObjectNode* pageObjectNodeOfId(amuse::ObjectId id) const;
|
||||
};
|
||||
struct SongGroupNode : INode
|
||||
struct SongGroupNode final : INode
|
||||
{
|
||||
amuse::GroupId m_id;
|
||||
amuse::ObjToken<amuse::SongGroupIndex> m_index;
|
||||
|
@ -249,6 +280,9 @@ public:
|
|||
Type type() const { return Type::SongGroup; }
|
||||
QString text() const { return m_name; }
|
||||
QIcon icon() const { return Icon; }
|
||||
AmuseItemEditFlags editFlags() const { return AmuseItemAll; }
|
||||
|
||||
amuse::NameDB* getNameDb() const { return amuse::GroupId::CurNameDB; }
|
||||
|
||||
void registerNames(const NameUndoRegistry& registry) const
|
||||
{
|
||||
|
@ -263,7 +297,7 @@ public:
|
|||
registry.unregisterSongName(p.first);
|
||||
}
|
||||
};
|
||||
struct SoundGroupNode : INode
|
||||
struct SoundGroupNode final : INode
|
||||
{
|
||||
amuse::GroupId m_id;
|
||||
amuse::ObjToken<amuse::SFXGroupIndex> m_index;
|
||||
|
@ -276,6 +310,9 @@ public:
|
|||
Type type() const { return Type::SoundGroup; }
|
||||
QString text() const { return m_name; }
|
||||
QIcon icon() const { return Icon; }
|
||||
AmuseItemEditFlags editFlags() const { return AmuseItemAll; }
|
||||
|
||||
amuse::NameDB* getNameDb() const { return amuse::GroupId::CurNameDB; }
|
||||
|
||||
void registerNames(const NameUndoRegistry& registry) const
|
||||
{
|
||||
|
@ -290,7 +327,7 @@ public:
|
|||
registry.unregisterSFXName(p.first);
|
||||
}
|
||||
};
|
||||
struct CollectionNode : INode
|
||||
struct CollectionNode final : INode
|
||||
{
|
||||
QIcon m_icon;
|
||||
Type m_collectionType;
|
||||
|
@ -319,7 +356,7 @@ public:
|
|||
QIcon icon() const { return {}; }
|
||||
};
|
||||
template <class ID, class T, INode::Type TP>
|
||||
struct PoolObjectNode : BasePoolObjectNode
|
||||
struct PoolObjectNode final : BasePoolObjectNode
|
||||
{
|
||||
amuse::ObjToken<T> m_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) {}
|
||||
|
||||
Type type() const { return TP; }
|
||||
AmuseItemEditFlags editFlags() const { return TP == INode::Type::Sample ? AmuseItemNoCut : AmuseItemAll; }
|
||||
|
||||
void registerNames(const NameUndoRegistry& registry) const
|
||||
{
|
||||
|
@ -336,6 +374,10 @@ public:
|
|||
{
|
||||
ID::CurNameDB->remove(m_id);
|
||||
}
|
||||
amuse::NameDB* getNameDb() const
|
||||
{
|
||||
return ID::CurNameDB;
|
||||
}
|
||||
};
|
||||
using SoundMacroNode = PoolObjectNode<amuse::SoundMacroId, amuse::SoundMacro, INode::Type::SoundMacro>;
|
||||
using ADSRNode = PoolObjectNode<amuse::TableId, std::unique_ptr<amuse::ITable>, INode::Type::ADSR>;
|
||||
|
@ -348,9 +390,11 @@ public:
|
|||
|
||||
bool m_needsReset = false;
|
||||
void _buildGroupNodeCollections(GroupNode& gn);
|
||||
void _buildGroupNode(GroupNode& gn);
|
||||
void _buildGroupNode(GroupNode& gn, amuse::AudioGroup& group);
|
||||
void _resetModelData();
|
||||
void _resetSongRefCount();
|
||||
QString MakeDedupedSubprojectName(const QString& origName);
|
||||
static QString MakeDedupedName(const QString& origName, amuse::NameDB* db);
|
||||
|
||||
public:
|
||||
explicit ProjectModel(const QString& path, QObject* parent = Q_NULLPTR);
|
||||
|
@ -366,7 +410,10 @@ public:
|
|||
bool saveToFile(UIMessenger& messenger);
|
||||
QStringList getGroupList() 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();
|
||||
|
||||
QModelIndex proxyCreateIndex(int arow, int acolumn, void *adata) const;
|
||||
|
@ -376,10 +423,11 @@ public:
|
|||
int rowCount(const QModelIndex& parent = QModelIndex()) const;
|
||||
int columnCount(const QModelIndex& parent = QModelIndex()) 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;
|
||||
INode* node(const QModelIndex& index) 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(); }
|
||||
|
||||
void _postAddNode(INode* n, const NameUndoRegistry& registry);
|
||||
|
@ -417,6 +465,21 @@ public:
|
|||
void _addNode(LayersNode* node, GroupNode* parent, const NameUndoRegistry& registry);
|
||||
void _delNode(LayersNode* node, GroupNode* parent, NameUndoRegistry& registry);
|
||||
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);
|
||||
|
||||
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();
|
||||
static_cast<EditorFieldPageObjectNode*>(editor)->setCurrentIndex(idx);
|
||||
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
|
||||
{
|
||||
const PageModel* model = static_cast<const PageModel*>(m);
|
||||
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;
|
||||
if (idx != 0)
|
||||
{
|
||||
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()))));
|
||||
if (node)
|
||||
id = node->id();
|
||||
}
|
||||
if (id == entry->second.objId.id)
|
||||
{
|
||||
emit m->dataChanged(index, index);
|
||||
|
@ -432,7 +425,7 @@ QVariant PageModel::data(const QModelIndex& index, int role) const
|
|||
switch (index.column())
|
||||
{
|
||||
case 0:
|
||||
return entry->first;
|
||||
return entry->first + 1;
|
||||
case 1:
|
||||
{
|
||||
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:
|
||||
{
|
||||
if (value.toInt() == entry->first)
|
||||
int prog = value.toInt() - 1;
|
||||
if (prog == entry->first)
|
||||
return false;
|
||||
if (map.find(value.toInt()) != map.cend())
|
||||
if (map.find(prog) != map.cend())
|
||||
{
|
||||
QMessageBox::critical(g_MainWindow, tr("Program Conflict"),
|
||||
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())
|
||||
{
|
||||
case 0:
|
||||
return entry.programNo;
|
||||
return entry.programNo + 1;
|
||||
case 1:
|
||||
return entry.volume;
|
||||
case 2:
|
||||
|
@ -959,26 +953,28 @@ bool SetupModel::setData(const QModelIndex& index, const QVariant& value, int ro
|
|||
|
||||
auto& entry = m_data->second[index.row()];
|
||||
|
||||
int val = value.toInt();
|
||||
switch (index.column())
|
||||
{
|
||||
case 0:
|
||||
if (entry.programNo == value.toInt())
|
||||
val -= 1;
|
||||
if (entry.programNo == val)
|
||||
return false;
|
||||
break;
|
||||
case 1:
|
||||
if (entry.volume == value.toInt())
|
||||
if (entry.volume == val)
|
||||
return false;
|
||||
break;
|
||||
case 2:
|
||||
if (entry.panning == value.toInt())
|
||||
if (entry.panning == val)
|
||||
return false;
|
||||
break;
|
||||
case 3:
|
||||
if (entry.reverb == value.toInt())
|
||||
if (entry.reverb == val)
|
||||
return false;
|
||||
break;
|
||||
case 4:
|
||||
if (entry.chorus == value.toInt())
|
||||
if (entry.chorus == val)
|
||||
return false;
|
||||
break;
|
||||
default:
|
||||
|
@ -988,7 +984,7 @@ bool SetupModel::setData(const QModelIndex& index, const QVariant& value, int ro
|
|||
g_MainWindow->pushUndoCommand(new SetupDataChangeUndoCommand(
|
||||
static_cast<SongGroupEditor*>(parent())->m_setupList.m_node.get(),
|
||||
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});
|
||||
|
||||
|
@ -1076,9 +1072,10 @@ PageTableView::PageTableView(QWidget* parent)
|
|||
setGridStyle(Qt::NoPen);
|
||||
|
||||
m_127Delegate.setItemEditorFactory(&m_127Factory);
|
||||
m_128Delegate.setItemEditorFactory(&m_128Factory);
|
||||
m_255Delegate.setItemEditorFactory(&m_255Factory);
|
||||
|
||||
setItemDelegateForColumn(0, &m_127Delegate);
|
||||
setItemDelegateForColumn(0, &m_128Delegate);
|
||||
setItemDelegateForColumn(1, &m_poDelegate);
|
||||
setItemDelegateForColumn(2, &m_255Delegate);
|
||||
setItemDelegateForColumn(3, &m_255Delegate);
|
||||
|
@ -1095,8 +1092,8 @@ void SetupTableView::setModel(QAbstractItemModel* list, QAbstractItemModel* tabl
|
|||
}
|
||||
{
|
||||
m_tableView->setModel(table);
|
||||
auto hheader = m_tableView->horizontalHeader();
|
||||
hheader->setSectionResizeMode(QHeaderView::Stretch);
|
||||
m_tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
|
||||
m_tableView->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1142,8 +1139,9 @@ SetupTableView::SetupTableView(QWidget* parent)
|
|||
m_tableView->setGridStyle(Qt::NoPen);
|
||||
|
||||
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(2, &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()))
|
||||
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()))
|
||||
return table->m_listView->hasFocus() && !table->m_listView->selectionModel()->selectedRows().isEmpty();
|
||||
return false;
|
||||
}
|
||||
|
||||
void SongGroupEditor::itemCutAction()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void SongGroupEditor::itemCopyAction()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void SongGroupEditor::itemPasteAction()
|
||||
{
|
||||
|
||||
return (table->m_listView->hasFocus() && !table->m_listView->selectionModel()->selectedRows().isEmpty()) ? AmuseItemDelete : AmuseItemNone;
|
||||
return AmuseItemNone;
|
||||
}
|
||||
|
||||
void SongGroupEditor::itemDeleteAction()
|
||||
|
|
|
@ -170,8 +170,9 @@ class PageTableView : public QTableView
|
|||
Q_OBJECT
|
||||
PageObjectDelegate m_poDelegate;
|
||||
RangedValueFactory<0, 127> m_127Factory;
|
||||
RangedValueFactory<1, 128> m_128Factory;
|
||||
RangedValueFactory<0, 255> m_255Factory;
|
||||
QStyledItemDelegate m_127Delegate, m_255Delegate;
|
||||
QStyledItemDelegate m_127Delegate, m_128Delegate, m_255Delegate;
|
||||
public:
|
||||
explicit PageTableView(QWidget* parent = Q_NULLPTR);
|
||||
void setModel(QAbstractItemModel* model);
|
||||
|
@ -187,7 +188,8 @@ class SetupTableView : public QSplitter
|
|||
QTableView* m_tableView;
|
||||
MIDIFileDelegate m_midiDelegate;
|
||||
RangedValueFactory<0, 127> m_127Factory;
|
||||
QStyledItemDelegate m_127Delegate;
|
||||
RangedValueFactory<1, 128> m_128Factory;
|
||||
QStyledItemDelegate m_127Delegate, m_128Delegate;
|
||||
public:
|
||||
explicit SetupTableView(QWidget* parent = Q_NULLPTR);
|
||||
void setModel(QAbstractItemModel* list, QAbstractItemModel* table);
|
||||
|
@ -262,7 +264,7 @@ public:
|
|||
void setEditorEnabled(bool en) {}
|
||||
void resizeEvent(QResizeEvent* ev);
|
||||
QTableView* getSetupListView() const { return m_setupTable->m_listView; }
|
||||
bool isItemEditEnabled() const;
|
||||
AmuseItemEditFlags itemEditFlags() const;
|
||||
public slots:
|
||||
void doAdd();
|
||||
void doSelectionChanged();
|
||||
|
@ -271,10 +273,6 @@ public slots:
|
|||
void rowsAboutToBeRemoved(const QModelIndex &parent, int first, int last);
|
||||
void modelAboutToBeReset();
|
||||
void setupDataChanged();
|
||||
|
||||
void itemCutAction();
|
||||
void itemCopyAction();
|
||||
void itemPasteAction();
|
||||
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();
|
||||
static_cast<EditorFieldPageObjectNode*>(editor)->setCurrentIndex(idx);
|
||||
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
|
||||
{
|
||||
const SFXModel* model = static_cast<const SFXModel*>(m);
|
||||
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;
|
||||
if (idx != 0)
|
||||
{
|
||||
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()))));
|
||||
if (node)
|
||||
id = node->id();
|
||||
}
|
||||
if (id == entry->second.objId.id)
|
||||
{
|
||||
emit m->dataChanged(index, index);
|
||||
|
@ -614,9 +607,9 @@ void SoundGroupEditor::resizeEvent(QResizeEvent* ev)
|
|||
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()
|
||||
|
@ -652,21 +645,6 @@ void SoundGroupEditor::sfxDataChanged()
|
|||
}
|
||||
}
|
||||
|
||||
void SoundGroupEditor::itemCutAction()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void SoundGroupEditor::itemCopyAction()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void SoundGroupEditor::itemPasteAction()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void SoundGroupEditor::itemDeleteAction()
|
||||
{
|
||||
m_sfxTable->deleteSelection();
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
class SFXObjectDelegate : public QStyledItemDelegate
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SFXObjectDelegate(QObject* parent = Q_NULLPTR);
|
||||
QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const;
|
||||
|
@ -20,7 +20,7 @@ private slots:
|
|||
|
||||
class SFXModel : public QAbstractTableModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
friend class SoundGroupEditor;
|
||||
friend class SFXObjectDelegate;
|
||||
friend class SFXTableView;
|
||||
|
@ -69,7 +69,7 @@ public:
|
|||
|
||||
class SFXTableView : public QTableView
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
SFXObjectDelegate m_sfxDelegate;
|
||||
RangedValueFactory<0, 127> m_127Factory;
|
||||
RangedValueFactory<0, 255> m_255Factory;
|
||||
|
@ -104,7 +104,7 @@ public slots:
|
|||
|
||||
class SoundGroupEditor : public EditorWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
SFXModel m_sfxs;
|
||||
SFXTableView* m_sfxTable;
|
||||
AddRemoveButtons m_addRemoveButtons;
|
||||
|
@ -116,15 +116,11 @@ public:
|
|||
void setEditorEnabled(bool en) {}
|
||||
void resizeEvent(QResizeEvent* ev);
|
||||
QTableView* getSFXListView() const { return m_sfxTable; }
|
||||
bool isItemEditEnabled() const;
|
||||
AmuseItemEditFlags itemEditFlags() const;
|
||||
public slots:
|
||||
void doAdd();
|
||||
void doSelectionChanged();
|
||||
void sfxDataChanged();
|
||||
|
||||
void itemCutAction();
|
||||
void itemCopyAction();
|
||||
void itemPasteAction();
|
||||
void itemDeleteAction();
|
||||
};
|
||||
|
||||
|
|
|
@ -37,11 +37,7 @@ void FieldSoundMacroStep::targetPressed()
|
|||
{
|
||||
ProjectModel::SoundMacroNode* node = nullptr;
|
||||
if (m_macroField)
|
||||
{
|
||||
int val = m_macroField->currentIndex();
|
||||
if (val != 0)
|
||||
node = static_cast<ProjectModel::SoundMacroNode*>(m_macroField->collection()->nodeOfIndex(val - 1));
|
||||
}
|
||||
node = static_cast<ProjectModel::SoundMacroNode*>(m_macroField->currentNode());
|
||||
|
||||
if (!m_macroField || node == getListing()->currentNode())
|
||||
if (SoundMacroEditor* editor = getEditor())
|
||||
|
@ -60,8 +56,8 @@ void FieldSoundMacroStep::updateMacroField()
|
|||
return;
|
||||
}
|
||||
|
||||
int val = m_macroField->currentIndex();
|
||||
if (val == 0)
|
||||
ProjectModel::SoundMacroNode* node = static_cast<ProjectModel::SoundMacroNode*>(m_macroField->currentNode());
|
||||
if (node == nullptr)
|
||||
{
|
||||
m_spinBox.setValue(0);
|
||||
m_spinBox.setDisabled(true);
|
||||
|
@ -69,8 +65,6 @@ void FieldSoundMacroStep::updateMacroField()
|
|||
}
|
||||
else
|
||||
{
|
||||
ProjectModel::SoundMacroNode* node = static_cast<ProjectModel::SoundMacroNode*>(
|
||||
m_macroField->collection()->nodeOfIndex(val - 1));
|
||||
int numCmds = int(node->m_obj->m_cmds.size());
|
||||
m_spinBox.setMaximum(numCmds - 1);
|
||||
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>IconB.svg</file>
|
||||
<file>IconA.svg</file>
|
||||
<file>IconBack.svg</file>
|
||||
<file>IconForward.svg</file>
|
||||
<file>IconBackDisabled.svg</file>
|
||||
<file>IconForwardDisabled.svg</file>
|
||||
</qresource>
|
||||
<qresource prefix="/bg">
|
||||
<file>FaceGrey.svg</file>
|
||||
|
|
|
@ -4,15 +4,18 @@
|
|||
#include "AudioGroupPool.hpp"
|
||||
#include "AudioGroupProject.hpp"
|
||||
#include "AudioGroupSampleDirectory.hpp"
|
||||
#include <unordered_set>
|
||||
|
||||
namespace amuse
|
||||
{
|
||||
class AudioGroupData;
|
||||
class ProjectDatabase;
|
||||
|
||||
/** Runtime audio group index container */
|
||||
class AudioGroup
|
||||
{
|
||||
friend class AudioGroupSampleDirectory;
|
||||
protected:
|
||||
AudioGroupProject m_proj;
|
||||
AudioGroupPool m_pool;
|
||||
AudioGroupSampleDirectory m_sdir;
|
||||
|
@ -20,16 +23,17 @@ class AudioGroup
|
|||
SystemString m_groupPath; /* Typically only set by editor */
|
||||
bool m_valid;
|
||||
|
||||
SystemString getSampleBasePath(SampleId sfxId) const;
|
||||
|
||||
public:
|
||||
SystemString getSampleBasePath(SampleId sfxId) const;
|
||||
operator bool() const { return m_valid; }
|
||||
AudioGroup() = default;
|
||||
explicit AudioGroup(const AudioGroupData& data) { assign(data); }
|
||||
explicit AudioGroup(SystemStringView groupPath) { assign(groupPath); }
|
||||
explicit AudioGroup(const AudioGroup& data, SystemStringView groupPath) { assign(data, groupPath); }
|
||||
|
||||
void assign(const AudioGroupData& data);
|
||||
void assign(SystemStringView groupPath);
|
||||
void assign(const AudioGroup& data, SystemStringView groupPath);
|
||||
void setGroupPath(SystemStringView groupPath) { m_groupPath = groupPath; }
|
||||
|
||||
const SampleEntry* getSample(SampleId sfxId) const;
|
||||
|
@ -58,6 +62,9 @@ class AudioGroupDatabase final : public AudioGroup
|
|||
NameDB m_keymapDb;
|
||||
NameDB m_layersDb;
|
||||
|
||||
void _recursiveRenameMacro(SoundMacroId id, std::string_view str, int& macroIdx,
|
||||
std::unordered_set<SoundMacroId>& renamedIds);
|
||||
|
||||
public:
|
||||
AudioGroupDatabase() = default;
|
||||
explicit AudioGroupDatabase(const AudioGroupData& data)
|
||||
|
@ -70,6 +77,11 @@ public:
|
|||
setIdDatabases();
|
||||
assign(groupPath);
|
||||
}
|
||||
explicit AudioGroupDatabase(const AudioGroupDatabase& data, SystemStringView groupPath)
|
||||
{
|
||||
setIdDatabases();
|
||||
assign(data, groupPath);
|
||||
}
|
||||
|
||||
void setIdDatabases() const
|
||||
{
|
||||
|
@ -79,6 +91,13 @@ public:
|
|||
KeymapId::CurNameDB = const_cast<NameDB*>(&m_keymapDb);
|
||||
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
|
||||
|
|
|
@ -1157,6 +1157,9 @@ struct SoundMacro
|
|||
std::swap(m_cmds[a], m_cmds[b]);
|
||||
}
|
||||
void buildFromPrototype(const SoundMacro& other);
|
||||
|
||||
void toYAML(athena::io::YAMLDocWriter& w) const;
|
||||
void fromYAML(athena::io::YAMLDocReader& r, size_t cmdCount);
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
|
|
|
@ -125,6 +125,9 @@ struct SongGroupIndex : AudioGroupIndex
|
|||
reverb(setup.reverb), chorus(setup.chorus) {}
|
||||
};
|
||||
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 */
|
||||
|
@ -177,6 +180,12 @@ struct SFXGroupIndex : AudioGroupIndex
|
|||
}
|
||||
};
|
||||
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 */
|
||||
|
@ -196,6 +205,7 @@ public:
|
|||
AudioGroupProject() = default;
|
||||
static AudioGroupProject CreateAudioGroupProject(const AudioGroupData& data);
|
||||
static AudioGroupProject CreateAudioGroupProject(SystemStringView groupPath);
|
||||
static AudioGroupProject CreateAudioGroupProject(const AudioGroupProject& oldProj);
|
||||
static void BootstrapObjectIDs(const AudioGroupData& data);
|
||||
|
||||
const SongGroupIndex* getSongGroupIndex(int groupId) const;
|
||||
|
|
|
@ -349,6 +349,7 @@ public:
|
|||
static AudioGroupSampleDirectory CreateAudioGroupSampleDirectory(SystemStringView groupPath);
|
||||
|
||||
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 extractAllWAV(amuse::SystemStringView destDir, const unsigned char* samp) const;
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include <strings.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#else
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN 1
|
||||
|
@ -176,6 +177,10 @@ public:
|
|||
ObjTokenBase& operator=(ObjTokenBase&& other)
|
||||
{ if (m_obj) m_obj->decrement(); m_obj = other.m_obj; other.m_obj = nullptr; return *this; }
|
||||
~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; }
|
||||
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); }
|
||||
#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
|
||||
static inline int CompareCaseInsensitive(const char* a, const char* b) { return _stricmp(a, b); }
|
||||
#endif
|
||||
|
@ -415,6 +430,8 @@ static inline void Unlink(const SystemChar* file)
|
|||
#endif
|
||||
}
|
||||
|
||||
bool Copy(const SystemChar* from, const SystemChar* to);
|
||||
|
||||
#undef bswap16
|
||||
#undef bswap32
|
||||
#undef bswap64
|
||||
|
@ -618,7 +635,7 @@ DECL_ID_HASH(GroupId)
|
|||
template<class 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;
|
||||
const AudioGroup& m_audioGroup;
|
||||
int m_groupId;
|
||||
GroupId m_groupId;
|
||||
ObjectId m_objectId; /* if applicable */
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ public:
|
|||
|
||||
Engine& getEngine() { return m_engine; }
|
||||
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; }
|
||||
bool isDestroyed() const { return m_destroyed; }
|
||||
};
|
||||
|
|
|
@ -92,9 +92,9 @@ class Sequencer : public Entity
|
|||
|
||||
public:
|
||||
~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);
|
||||
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);
|
||||
|
||||
/** Advance current song data (if any) */
|
||||
|
|
|
@ -84,7 +84,7 @@ class SongState
|
|||
int32_t m_modVal = 0; /**< Accumulated value of mod */
|
||||
uint32_t m_nextModTick = 0; /**< Upcoming position of mod wheel change */
|
||||
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_lastN64EventTick =
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
#include "amuse/AudioGroup.hpp"
|
||||
#include "amuse/AudioGroupData.hpp"
|
||||
#include <regex>
|
||||
#include <athena/FileReader.hpp>
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace amuse
|
||||
{
|
||||
|
@ -20,6 +24,15 @@ void AudioGroup::assign(SystemStringView groupPath)
|
|||
m_proj = AudioGroupProject::CreateAudioGroupProject(groupPath);
|
||||
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
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
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>();
|
||||
size_t cmdCount;
|
||||
if (auto __v = r.enterSubVector(sm.first.c_str(), 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));
|
||||
}
|
||||
smOut->fromYAML(r, cmdCount);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -416,6 +411,27 @@ void SoundMacro::buildFromPrototype(const SoundMacro& other)
|
|||
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
|
||||
{
|
||||
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()))
|
||||
{
|
||||
for (const auto& c : p.second.get()->m_cmds)
|
||||
{
|
||||
if (auto __r2 = w.enterSubRecord(nullptr))
|
||||
{
|
||||
w.setStyle(athena::io::YAMLNodeStyle::Flow);
|
||||
w.writeString("cmdOp", SoundMacro::CmdOpToStr(c->Isa()));
|
||||
c->write(w);
|
||||
}
|
||||
}
|
||||
p.second.get()->toYAML(w);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -353,6 +353,59 @@ std::string ParseStringSlashId(const std::string& str, uint16_t& idOut)
|
|||
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 ret;
|
||||
|
@ -380,41 +433,7 @@ AudioGroupProject AudioGroupProject::CreateAudioGroupProject(SystemStringView gr
|
|||
|
||||
auto& idx = ret.m_songGroups[groupId];
|
||||
idx = MakeObj<SongGroupIndex>();
|
||||
if (auto __v2 = r.enterSubRecord("normPages"))
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
idx->fromYAML(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -434,16 +453,7 @@ AudioGroupProject AudioGroupProject::CreateAudioGroupProject(SystemStringView gr
|
|||
|
||||
auto& idx = ret.m_sfxGroups[groupId];
|
||||
idx = MakeObj<SFXGroupIndex>();
|
||||
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);
|
||||
idx->m_sfxEntries[sfxId].read(r);
|
||||
}
|
||||
idx->fromYAML(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -453,6 +463,42 @@ AudioGroupProject AudioGroupProject::CreateAudioGroupProject(SystemStringView gr
|
|||
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)
|
||||
{
|
||||
while (!AtEnd32(r))
|
||||
|
@ -638,6 +684,76 @@ const SFXGroupIndex* AudioGroupProject::getSFXGroupIndex(int groupId) const
|
|||
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
|
||||
{
|
||||
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));
|
||||
if (auto __r = w.enterSubRecord(groupString))
|
||||
{
|
||||
if (!p.second.get()->m_normPages.empty())
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
p.second.get()->toYAML(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));
|
||||
if (auto __r = w.enterSubRecord(groupString))
|
||||
{
|
||||
for (const auto& sfx : SortUnorderedMap(p.second.get()->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);
|
||||
}
|
||||
}
|
||||
p.second.get()->toYAML(w);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,35 @@ namespace 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) \
|
||||
thread_local NameDB* type::CurNameDB = nullptr; \
|
||||
template<> template<> \
|
||||
|
|
|
@ -32,6 +32,8 @@ void Sequencer::ChannelState::_bringOutYourDead()
|
|||
}
|
||||
++it;
|
||||
}
|
||||
std::unordered_set<ObjToken<Voice>> newSet = m_keyoffVoxs;
|
||||
m_keyoffVoxs = newSet;
|
||||
}
|
||||
|
||||
void Sequencer::_bringOutYourDead()
|
||||
|
@ -57,8 +59,8 @@ Sequencer::~Sequencer()
|
|||
m_studio.reset();
|
||||
}
|
||||
|
||||
Sequencer::Sequencer(Engine& engine, const AudioGroup& group, int groupId, const SongGroupIndex* songGroup, int setupId,
|
||||
ObjToken<Studio> studio)
|
||||
Sequencer::Sequencer(Engine& engine, const AudioGroup& group, GroupId groupId, const SongGroupIndex* songGroup,
|
||||
SongId setupId, ObjToken<Studio> studio)
|
||||
: Entity(engine, group, groupId), m_songGroup(songGroup), m_studio(studio)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
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)
|
||||
: 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();
|
||||
keySearch->second->keyOff();
|
||||
keySearch->second->setPedal(false);
|
||||
m_keyoffVoxs.emplace(std::move(keySearch->second));
|
||||
m_keyoffVoxs.emplace(keySearch->second);
|
||||
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)
|
||||
m_lastVoice.reset();
|
||||
keySearch->second->keyOff();
|
||||
m_keyoffVoxs.emplace(std::move(keySearch->second));
|
||||
m_keyoffVoxs.emplace(keySearch->second);
|
||||
m_chanVoxs.erase(keySearch);
|
||||
}
|
||||
|
||||
|
@ -455,7 +457,7 @@ void Sequencer::ChannelState::allOff()
|
|||
if (it->second == m_lastVoice)
|
||||
m_lastVoice.reset();
|
||||
it->second->keyOff();
|
||||
m_keyoffVoxs.emplace(std::move(it->second));
|
||||
m_keyoffVoxs.emplace(it->second);
|
||||
it = m_chanVoxs.erase(it);
|
||||
}
|
||||
}
|
||||
|
@ -517,7 +519,7 @@ void Sequencer::ChannelState::killKeygroup(uint8_t kg, bool now)
|
|||
continue;
|
||||
}
|
||||
vox->keyOff();
|
||||
m_keyoffVoxs.emplace(std::move(it->second));
|
||||
m_keyoffVoxs.emplace(it->second);
|
||||
it = m_chanVoxs.erase(it);
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -62,15 +62,15 @@ struct Event
|
|||
uint8_t noteOrCtrl;
|
||||
uint8_t velOrVal;
|
||||
uint8_t program;
|
||||
uint16_t length;
|
||||
int length;
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
@ -83,16 +83,17 @@ struct Event
|
|||
class MIDIDecoder
|
||||
{
|
||||
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::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();
|
||||
m_results.back().first = prog;
|
||||
auto& results = m_results[chan];
|
||||
results.emplace_back();
|
||||
results.back().first = prog;
|
||||
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;
|
||||
|
@ -128,154 +129,93 @@ public:
|
|||
std::vector<uint8_t>::const_iterator end)
|
||||
{
|
||||
std::vector<uint8_t>::const_iterator it = begin;
|
||||
if (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)
|
||||
while (it != end)
|
||||
{
|
||||
/* Meta events */
|
||||
if (it == end)
|
||||
return begin;
|
||||
a = *it++;
|
||||
uint32_t deltaTime;
|
||||
_readContinuedValue(it, end, deltaTime);
|
||||
m_tick += deltaTime;
|
||||
|
||||
uint32_t length;
|
||||
_readContinuedValue(it, end, length);
|
||||
uint8_t a = *it++;
|
||||
uint8_t b;
|
||||
if (a & 0x80)
|
||||
m_status = a;
|
||||
else
|
||||
it--;
|
||||
|
||||
switch (a)
|
||||
{
|
||||
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:
|
||||
if (m_status == 0xff)
|
||||
{
|
||||
/* Meta events */
|
||||
if (it == end)
|
||||
return begin;
|
||||
a = *it++;
|
||||
if (it == end)
|
||||
return begin;
|
||||
b = *it++;
|
||||
|
||||
uint8_t notenum = clamp7(a);
|
||||
std::multimap<int, Event>::iterator note = m_notes[notenum];
|
||||
if (note != res.end())
|
||||
uint32_t length;
|
||||
_readContinuedValue(it, end, length);
|
||||
|
||||
switch (a)
|
||||
{
|
||||
note->second.length = uint16_t(m_tick - note->first);
|
||||
m_notes[notenum] = res.end();
|
||||
case 0x51:
|
||||
{
|
||||
uint32_t tempo = 0;
|
||||
memcpy(&reinterpret_cast<uint8_t*>(&tempo)[1], &*it, 3);
|
||||
m_tempos.emplace(m_tick, 60000000 / SBig(tempo));
|
||||
}
|
||||
break;
|
||||
}
|
||||
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;
|
||||
default:
|
||||
it += length;
|
||||
}
|
||||
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)
|
||||
return begin;
|
||||
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;
|
||||
}
|
||||
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)
|
||||
return begin;
|
||||
|
@ -285,35 +225,100 @@ public:
|
|||
b = *it++;
|
||||
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)
|
||||
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:
|
||||
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:
|
||||
{
|
||||
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:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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; }
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
||||
MIDIDecoder dec;
|
||||
while (begin != end)
|
||||
begin = dec.receiveBytes(begin, end);
|
||||
dec.receiveBytes(begin, end);
|
||||
|
||||
std::multimap<int, int>& tempos = dec.getTempos();
|
||||
if (tempos.size() == 1)
|
||||
|
@ -1065,12 +1069,11 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
|
|||
it = end;
|
||||
|
||||
MIDIDecoder dec;
|
||||
while (begin != end)
|
||||
begin = dec.receiveBytes(begin, end);
|
||||
dec.receiveBytes(begin, end);
|
||||
|
||||
std::vector<std::pair<int, std::multimap<int, Event>>>& results = dec.getResults();
|
||||
for (int c = 0; c < 16; ++c)
|
||||
{
|
||||
std::vector<std::pair<int, std::multimap<int, Event>>>& results = dec.getResults(c);
|
||||
int lastTrackStartTick = 0;
|
||||
bool didChanInit = false;
|
||||
for (auto& prog : results)
|
||||
|
@ -1177,20 +1180,21 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
|
|||
case Event::Type::Pitch:
|
||||
{
|
||||
int newPitch = event.second.pitchBend - 0x2000;
|
||||
EncodeDelta(region.modBuf, eventTick - lastPitchTick, newPitch - lastPitchVal);
|
||||
EncodeDelta(region.pitchBuf, eventTick - lastPitchTick, newPitch - lastPitchVal);
|
||||
lastPitchTick = eventTick;
|
||||
lastPitchVal = newPitch;
|
||||
break;
|
||||
}
|
||||
case Event::Type::Note:
|
||||
{
|
||||
int lenTicks = event.second.length * 384 / header.div;
|
||||
if (version == 1)
|
||||
{
|
||||
EncodeTime(region.eventBuf, uint32_t(eventTick - lastEventTick));
|
||||
lastEventTick = eventTick;
|
||||
region.eventBuf.push_back(event.second.noteOrCtrl);
|
||||
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)[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));
|
||||
for (int i = 0; i < 4; ++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)[1]);
|
||||
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);
|
||||
for (int i = 0; i < 4; ++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)[1]);
|
||||
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)
|
||||
: 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)
|
||||
|
@ -406,15 +404,11 @@ bool SongState::Track::advance(Sequencer& seq, int32_t ticks)
|
|||
/* Stop finished notes */
|
||||
for (int i = 0; i < 128; ++i)
|
||||
{
|
||||
constexpr decltype(m_remNoteLengths)::value_type MIN = std::numeric_limits<decltype(MIN)>::min();
|
||||
if (m_remNoteLengths[i] != MIN)
|
||||
if (m_remNoteLengths[i] > 0)
|
||||
{
|
||||
m_remNoteLengths[i] -= ticks;
|
||||
if (m_remNoteLengths[i] <= 0)
|
||||
{
|
||||
seq.keyOff(m_midiChan, i, 0);
|
||||
m_remNoteLengths[i] = MIN;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -280,7 +280,8 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdWaitTicks::Introspective =
|
|||
CmdType::Structure,
|
||||
"Wait Ticks"sv,
|
||||
"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),
|
||||
|
@ -442,7 +443,8 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdWaitMs::Introspective =
|
|||
CmdType::Structure,
|
||||
"Wait Millisec"sv,
|
||||
"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),
|
||||
|
@ -1468,7 +1470,7 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdSetPitchAdsr::Introspective =
|
|||
{
|
||||
CmdType::Pitch,
|
||||
"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,
|
||||
{
|
||||
{
|
||||
|
|
|
@ -117,6 +117,7 @@ bool Voice::_checkSamplePos(bool& looped)
|
|||
|
||||
void Voice::_doKeyOff()
|
||||
{
|
||||
m_voxState = VoiceState::KeyOff;
|
||||
if (m_state.m_inWait && m_state.m_keyoffWait)
|
||||
{
|
||||
if (m_volAdsr.isAdsrSet() || m_state.m_useAdsrControllers)
|
||||
|
@ -480,7 +481,7 @@ void Voice::preSupplyAudio(double dt)
|
|||
}
|
||||
if (m_pitchEnv)
|
||||
{
|
||||
newPitch *= m_pitchAdsr.advance(dt);
|
||||
newPitch += m_pitchAdsr.advance(dt) * m_pitchEnvRange;
|
||||
refresh = true;
|
||||
}
|
||||
|
||||
|
@ -934,7 +935,6 @@ void Voice::_macroKeyOff()
|
|||
m_sustainKeyOff = true;
|
||||
else
|
||||
_doKeyOff();
|
||||
m_voxState = VoiceState::KeyOff;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1456,10 +1456,7 @@ void Voice::_notifyCtrlChange(uint8_t ctrl, int8_t val)
|
|||
{
|
||||
if (ctrl == 0x40)
|
||||
{
|
||||
if (val >= 0x40)
|
||||
setPedal(true);
|
||||
else
|
||||
setPedal(false);
|
||||
setPedal(val >= 0x40);
|
||||
}
|
||||
else if (ctrl == 0x5b)
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue