Implement SongGroupEditor

This commit is contained in:
Jack Andersen 2018-08-08 21:42:17 -10:00
parent 32deea8341
commit eff832bb8c
15 changed files with 1997 additions and 218 deletions

View File

@ -38,3 +38,24 @@ void FieldProjectNode::setCollection(ProjectModel::CollectionNode* collection)
setModel(model->getNullProxy());
setRootModelIndex(model->getNullProxy()->mapFromSource(model->index(collection)));
}
FieldPageObjectNode::FieldPageObjectNode(ProjectModel::GroupNode* group, QWidget* parent)
: FieldComboBox(parent)
{
setGroup(group);
}
void FieldPageObjectNode::setGroup(ProjectModel::GroupNode* group)
{
m_group = group;
if (!group)
{
setModel(new QStandardItemModel(0, 1, this));
return;
}
ProjectModel* model = g_MainWindow->projectModel();
setModel(model->getPageObjectProxy());
setRootModelIndex(model->getPageObjectProxy()->mapFromSource(model->index(group)));
}

View File

@ -7,6 +7,7 @@
#include <QSpinBox>
#include <QComboBox>
#include <QWheelEvent>
#include <QItemEditorFactory>
#include "ProjectModel.hpp"
class EditorWidget : public QWidget
@ -85,4 +86,45 @@ public:
ProjectModel::CollectionNode* collection() const { return m_collection; }
};
class FieldPageObjectNode : public FieldComboBox
{
Q_OBJECT
ProjectModel::GroupNode* m_group;
public:
explicit FieldPageObjectNode(ProjectModel::GroupNode* group = Q_NULLPTR, QWidget* parent = Q_NULLPTR);
void setGroup(ProjectModel::GroupNode* group);
ProjectModel::GroupNode* group() const { return m_group; }
};
template <class T>
class EditorFieldNode : public T
{
bool m_deferPopupOpen = true;
public:
using T::T;
bool shouldPopupOpen()
{
bool ret = m_deferPopupOpen;
m_deferPopupOpen = false;
return ret;
}
};
using EditorFieldProjectNode = EditorFieldNode<FieldProjectNode>;
using EditorFieldPageObjectNode = EditorFieldNode<FieldPageObjectNode>;
template <int MIN, int MAX>
class RangedValueFactory : public QItemEditorFactory
{
public:
QWidget* createEditor(int userType, QWidget *parent) const
{
QSpinBox* sb = new QSpinBox(parent);
sb->setFrame(false);
sb->setMinimum(MIN);
sb->setMaximum(MAX);
return sb;
}
};
#endif //AMUSE_EDITOR_WIDGET_HPP

View File

@ -4,28 +4,6 @@
#include <QScrollBar>
#include <QMimeData>
QWidget* SignedValueFactory::createEditor(int userType, QWidget *parent) const
{
QSpinBox* sb = new QSpinBox(parent);
sb->setFrame(false);
sb->setMinimum(-128);
sb->setMaximum(127);
return sb;
}
QWidget* UnsignedValueFactory::createEditor(int userType, QWidget *parent) const
{
QSpinBox* sb = new QSpinBox(parent);
sb->setFrame(false);
sb->setMinimum(0);
sb->setMaximum(127);
return sb;
}
EditorFieldProjectNode::EditorFieldProjectNode(ProjectModel::CollectionNode* collection, QWidget* parent)
: FieldProjectNode(collection, parent)
{}
SoundMacroDelegate::SoundMacroDelegate(QObject* parent)
: QStyledItemDelegate(parent) {}
@ -61,6 +39,7 @@ void SoundMacroDelegate::setModelData(QWidget* editor, QAbstractItemModel* m, co
layer.macro.id = amuse::SoundMacroId();
else
layer.macro.id = smColl->idOfIndex(idx - 1);
emit m->dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
}
void SoundMacroDelegate::smIndexChanged()
@ -150,24 +129,31 @@ bool LayersModel::setData(const QModelIndex& index, const QVariant& value, int r
{
case 1:
layer.keyLo = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
case 2:
layer.keyHi = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
case 3:
layer.transpose = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
case 4:
layer.volume = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
case 5:
layer.prioOffset = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
case 6:
layer.span = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
case 7:
layer.pan = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
default:
break;
@ -341,8 +327,9 @@ void LayersTableView::deleteSelection()
model()->removeRow(list.back().row());
}
void LayersTableView::doItemsLayout()
void LayersTableView::setModel(QAbstractItemModel* model)
{
QTableView::setModel(model);
horizontalHeader()->setMinimumSectionSize(75);
horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
horizontalHeader()->setSectionResizeMode(1, QHeaderView::Fixed);
@ -359,7 +346,6 @@ void LayersTableView::doItemsLayout()
horizontalHeader()->resizeSection(6, 75);
horizontalHeader()->setSectionResizeMode(7, QHeaderView::Fixed);
horizontalHeader()->resizeSection(7, 75);
QTableView::doItemsLayout();
}
LayersTableView::LayersTableView(QWidget* parent)
@ -371,8 +357,19 @@ LayersTableView::LayersTableView(QWidget* parent)
setDefaultDropAction(Qt::MoveAction);
setDragEnabled(true);
setGridStyle(Qt::NoPen);
}
m_signedDelegate.setItemEditorFactory(&m_signedFactory);
m_unsignedDelegate.setItemEditorFactory(&m_unsignedFactory);
setItemDelegateForColumn(0, &m_smDelegate);
setItemDelegateForColumn(1, &m_unsignedDelegate);
setItemDelegateForColumn(2, &m_unsignedDelegate);
setItemDelegateForColumn(3, &m_signedDelegate);
setItemDelegateForColumn(4, &m_unsignedDelegate);
setItemDelegateForColumn(5, &m_signedDelegate);
setItemDelegateForColumn(6, &m_unsignedDelegate);
setItemDelegateForColumn(7, &m_unsignedDelegate);
}
bool LayersEditor::loadData(ProjectModel::LayersNode* node)
{
@ -438,31 +435,21 @@ void LayersEditor::itemDeleteAction()
}
LayersEditor::LayersEditor(QWidget* parent)
: EditorWidget(parent), m_tableView(this),
: EditorWidget(parent), m_model(this), m_tableView(this),
m_addAction(tr("Add Row")), m_addButton(this), m_removeAction(tr("Remove Row")), m_removeButton(this)
{
m_signedDelegate.setItemEditorFactory(&m_signedFactory);
m_unsignedDelegate.setItemEditorFactory(&m_unsignedFactory);
m_tableView.setItemDelegateForColumn(1, &m_unsignedDelegate);
m_tableView.setItemDelegateForColumn(2, &m_unsignedDelegate);
m_tableView.setItemDelegateForColumn(3, &m_signedDelegate);
m_tableView.setItemDelegateForColumn(4, &m_unsignedDelegate);
m_tableView.setItemDelegateForColumn(5, &m_signedDelegate);
m_tableView.setItemDelegateForColumn(6, &m_unsignedDelegate);
m_tableView.setItemDelegateForColumn(7, &m_unsignedDelegate);
m_tableView.setModel(&m_model);
m_tableView.setItemDelegateForColumn(0, &m_smDelegate);
connect(m_tableView.selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
this, SLOT(doSelectionChanged(const QItemSelection&)));
m_addAction.setIcon(QIcon(QStringLiteral(":/icons/IconAdd.svg")));
m_addAction.setToolTip(tr("Add new layer mapping"));
m_addButton.setDefaultAction(&m_addAction);
m_addButton.setFixedSize(32, 32);
connect(&m_addAction, SIGNAL(triggered(bool)), this, SLOT(doAdd()));
m_removeAction.setIcon(QIcon(QStringLiteral(":/icons/IconRemove.svg")));
m_removeAction.setToolTip(tr("Remove selected layer mappings"));
m_removeButton.setDefaultAction(&m_removeAction);
m_removeButton.setFixedSize(32, 32);
connect(&m_removeAction, SIGNAL(triggered(bool)), this, SLOT(itemDeleteAction()));

View File

@ -7,33 +7,6 @@
#include <QAction>
#include <QToolButton>
#include <QStyledItemDelegate>
#include <QItemEditorFactory>
class SignedValueFactory : public QItemEditorFactory
{
public:
QWidget* createEditor(int userType, QWidget *parent) const;
};
class UnsignedValueFactory : public QItemEditorFactory
{
public:
QWidget* createEditor(int userType, QWidget *parent) const;
};
class EditorFieldProjectNode : public FieldProjectNode
{
Q_OBJECT
bool m_deferPopupOpen = true;
public:
explicit EditorFieldProjectNode(ProjectModel::CollectionNode* collection = Q_NULLPTR, QWidget* parent = Q_NULLPTR);
bool shouldPopupOpen()
{
bool ret = m_deferPopupOpen;
m_deferPopupOpen = false;
return ret;
}
};
class SoundMacroDelegate : public QStyledItemDelegate
{
@ -78,9 +51,13 @@ public:
class LayersTableView : public QTableView
{
Q_OBJECT
SoundMacroDelegate m_smDelegate;
RangedValueFactory<-128, 127> m_signedFactory;
RangedValueFactory<0, 127> m_unsignedFactory;
QStyledItemDelegate m_signedDelegate, m_unsignedDelegate;
public:
explicit LayersTableView(QWidget* parent = Q_NULLPTR);
void doItemsLayout();
void setModel(QAbstractItemModel* model);
void deleteSelection();
};
@ -88,10 +65,6 @@ class LayersEditor : public EditorWidget
{
Q_OBJECT
LayersModel m_model;
SoundMacroDelegate m_smDelegate;
SignedValueFactory m_signedFactory;
UnsignedValueFactory m_unsignedFactory;
QStyledItemDelegate m_signedDelegate, m_unsignedDelegate;
LayersTableView m_tableView;
QAction m_addAction;
QToolButton m_addButton;

View File

@ -30,6 +30,8 @@ MainWindow::MainWindow(QWidget* parent)
m_ui.setupUi(this);
m_ui.splitter->setCollapsible(1, false);
m_ui.projectOutline->setItemDelegate(&m_treeDelegate);
connect(m_ui.projectOutline, SIGNAL(activated(const QModelIndex&)),
this, SLOT(outlineItemActivated(const QModelIndex&)));
connectMessenger(&m_mainMessenger, Qt::DirectConnection);
m_ui.statusbar->connectKillClicked(this, SLOT(killSounds()));
@ -232,11 +234,13 @@ bool MainWindow::setProjectPath(const QString& path)
QMessageBox::critical(this, tr("Directory does not exist"), msg);
return false;
}
QString testWritePath = dir.filePath(tr("test"));
QString testWritePath = dir.filePath(tr("__amuse_test__"));
QFile testWriteFile(testWritePath);
if (!testWriteFile.open(QFile::ReadWrite))
{
QString msg = QString(tr("The directory at '%1' must be writable for the Amuse editor.")).arg(path);
QString msg = QString(tr("The directory at '%1' must be writable for the Amuse editor: %2")).arg(path).
arg(testWriteFile.errorString());
QMessageBox::critical(this, tr("Unable to write to directory"), msg);
return false;
}
@ -617,6 +621,7 @@ bool MainWindow::openProject(const QString& path)
return;
}
}
model->openSongsData();
});
return true;
@ -841,12 +846,14 @@ bool TreeDelegate::editorEvent(QEvent* event,
if (!node)
return false;
#if 0
if ((event->type() == QEvent::MouseButtonDblClick &&
static_cast<QMouseEvent*>(event)->button() == Qt::LeftButton))
{
// Open in editor
return m_window.openEditor(node);
}
#endif
return false;
}
@ -1032,6 +1039,14 @@ void MainWindow::onFocusChanged(QWidget* old, QWidget* now)
}
void MainWindow::outlineItemActivated(const QModelIndex& index)
{
ProjectModel::INode* node = m_projectModel->node(index);
if (!node)
return;
openEditor(node);
}
void MainWindow::setItemEditEnabled(bool enabled)
{
m_ui.actionCut->setEnabled(enabled);

View File

@ -191,6 +191,7 @@ public slots:
void outlineDeleteAction();
void onFocusChanged(QWidget* old, QWidget* now);
void outlineItemActivated(const QModelIndex& index);
void setItemEditEnabled(bool enabled);
bool canEditOutline();
void onOutlineSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected);

View File

@ -77,44 +77,15 @@
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QScrollArea" name="editorScrollArea">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
<widget class="QStackedWidget" name="editorContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1103</width>
<height>606</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>500</width>
<height>400</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<property name="midLineWidth">
<number>-2</number>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QStackedWidget" name="editorContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1103</width>
<height>606</height>
</rect>
</property>
</widget>
</widget>
<widget class="QWidget" name="keyboard" native="true">
<property name="sizePolicy">

View File

@ -52,6 +52,219 @@ QVariant NullItemProxyModel::data(const QModelIndex& proxyIndex, int role) const
return QIdentityProxyModel::data(proxyIndex, role);
}
PageObjectProxyModel::PageObjectProxyModel(ProjectModel* source)
: QIdentityProxyModel(source)
{
setSourceModel(source);
}
QModelIndex PageObjectProxyModel::mapFromSource(const QModelIndex& sourceIndex) const
{
if (!sourceIndex.isValid())
return QModelIndex();
ProjectModel::INode* node = static_cast<ProjectModel::INode*>(sourceIndex.internalPointer());
auto tp = node->type();
if ((tp != ProjectModel::INode::Type::SoundMacro &&
tp != ProjectModel::INode::Type::Keymap &&
tp != ProjectModel::INode::Type::Layer &&
tp != ProjectModel::INode::Type::Null) ||
(tp == ProjectModel::INode::Type::Null &&
node->parent() == static_cast<ProjectModel*>(sourceModel())->rootNode()))
return createIndex(sourceIndex.row(), sourceIndex.column(), node);
ProjectModel::GroupNode* group = static_cast<ProjectModel*>(sourceModel())->getGroupNode(node);
ProjectModel::CollectionNode* smCol = group->getCollectionOfType(ProjectModel::INode::Type::SoundMacro);
ProjectModel::CollectionNode* kmCol = group->getCollectionOfType(ProjectModel::INode::Type::Keymap);
ProjectModel::CollectionNode* layCol = group->getCollectionOfType(ProjectModel::INode::Type::Layer);
switch (tp)
{
case ProjectModel::INode::Type::Null:
if (node->parent() == group)
return createIndex(0, sourceIndex.column(), sourceIndex.internalPointer());
else if (node->parent() == smCol)
return createIndex(1, sourceIndex.column(), sourceIndex.internalPointer());
else if (node->parent() == kmCol)
return createIndex(2 + smCol->childCount(), sourceIndex.column(), sourceIndex.internalPointer());
else if (node->parent() == layCol)
return createIndex(3 + smCol->childCount() + kmCol->childCount(), sourceIndex.column(), sourceIndex.internalPointer());
break;
case ProjectModel::INode::Type::SoundMacro:
return createIndex(2 + node->row(), sourceIndex.column(), sourceIndex.internalPointer());
case ProjectModel::INode::Type::Keymap:
return createIndex(3 + smCol->childCount() + node->row(), sourceIndex.column(), sourceIndex.internalPointer());
case ProjectModel::INode::Type::Layer:
return createIndex(4 + smCol->childCount() + kmCol->childCount() + node->row(), sourceIndex.column(), sourceIndex.internalPointer());
default:
break;
}
return QModelIndex();
}
QModelIndex PageObjectProxyModel::mapToSource(const QModelIndex& proxyIndex) const
{
if (!proxyIndex.isValid())
return QModelIndex();
ProjectModel::INode* node = static_cast<ProjectModel::INode*>(proxyIndex.internalPointer());
auto tp = node->type();
if ((tp != ProjectModel::INode::Type::SoundMacro &&
tp != ProjectModel::INode::Type::Keymap &&
tp != ProjectModel::INode::Type::Layer &&
tp != ProjectModel::INode::Type::Null) ||
(tp == ProjectModel::INode::Type::Null &&
node->parent() == static_cast<ProjectModel*>(sourceModel())->rootNode()))
return static_cast<ProjectModel*>(sourceModel())->
proxyCreateIndex(proxyIndex.row(), proxyIndex.column(), proxyIndex.internalPointer());
ProjectModel::GroupNode* group = static_cast<ProjectModel*>(sourceModel())->getGroupNode(node);
ProjectModel::CollectionNode* smCol = group->getCollectionOfType(ProjectModel::INode::Type::SoundMacro);
ProjectModel::CollectionNode* kmCol = group->getCollectionOfType(ProjectModel::INode::Type::Keymap);
ProjectModel::CollectionNode* layCol = group->getCollectionOfType(ProjectModel::INode::Type::Layer);
switch (tp)
{
case ProjectModel::INode::Type::Null:
if (node->parent() == group)
return static_cast<ProjectModel*>(sourceModel())->
proxyCreateIndex(group->childCount(), proxyIndex.column(), proxyIndex.internalPointer());
else if (node->parent() == smCol)
return static_cast<ProjectModel*>(sourceModel())->
proxyCreateIndex(smCol->childCount(), proxyIndex.column(), proxyIndex.internalPointer());
else if (node->parent() == kmCol)
return static_cast<ProjectModel*>(sourceModel())->
proxyCreateIndex(kmCol->childCount(), proxyIndex.column(), proxyIndex.internalPointer());
else if (node->parent() == layCol)
return static_cast<ProjectModel*>(sourceModel())->
proxyCreateIndex(layCol->childCount(), proxyIndex.column(), proxyIndex.internalPointer());
break;
case ProjectModel::INode::Type::SoundMacro:
return static_cast<ProjectModel*>(sourceModel())->
proxyCreateIndex(node->row() - 2, proxyIndex.column(), proxyIndex.internalPointer());
case ProjectModel::INode::Type::Keymap:
return static_cast<ProjectModel*>(sourceModel())->
proxyCreateIndex(node->row() - smCol->childCount() - 3, proxyIndex.column(), proxyIndex.internalPointer());
case ProjectModel::INode::Type::Layer:
return static_cast<ProjectModel*>(sourceModel())->
proxyCreateIndex(node->row() - kmCol->childCount() - smCol->childCount() - 4, proxyIndex.column(), proxyIndex.internalPointer());
default:
break;
}
return QModelIndex();
}
QModelIndex PageObjectProxyModel::parent(const QModelIndex& child) const
{
if (!child.isValid())
return QModelIndex();
ProjectModel::INode* node = static_cast<ProjectModel::INode*>(child.internalPointer());
auto tp = node->type();
if ((tp != ProjectModel::INode::Type::SoundMacro &&
tp != ProjectModel::INode::Type::Keymap &&
tp != ProjectModel::INode::Type::Layer &&
tp != ProjectModel::INode::Type::Null) ||
(tp == ProjectModel::INode::Type::Null &&
node->parent() == static_cast<ProjectModel*>(sourceModel())->rootNode()))
return QIdentityProxyModel::parent(child);
ProjectModel::INode* group = node->parent();
if (group->type() == ProjectModel::INode::Type::Collection)
group = group->parent();
return createIndex(group->row(), 0, group);
}
int PageObjectProxyModel::rowCount(const QModelIndex& parent) const
{
ProjectModel::INode* node = static_cast<ProjectModel*>(sourceModel())->node(parent);
auto tp = node->type();
if (tp != ProjectModel::INode::Type::Group)
return static_cast<ProjectModel*>(sourceModel())->rowCount(parent);
ProjectModel::GroupNode* group = static_cast<ProjectModel::GroupNode*>(node);
ProjectModel::CollectionNode* smCol = group->getCollectionOfType(ProjectModel::INode::Type::SoundMacro);
ProjectModel::CollectionNode* kmCol = group->getCollectionOfType(ProjectModel::INode::Type::Keymap);
ProjectModel::CollectionNode* layCol = group->getCollectionOfType(ProjectModel::INode::Type::Layer);
return 4 + smCol->childCount() + kmCol->childCount() + layCol->childCount();
}
QModelIndex PageObjectProxyModel::index(int row, int column, const QModelIndex& parent) const
{
if (!parent.isValid())
return QIdentityProxyModel::index(row, column, parent);
ProjectModel::INode* parentNode = static_cast<ProjectModel::INode*>(parent.internalPointer());
auto ptp = parentNode->type();
if (ptp != ProjectModel::INode::Type::Group)
return QIdentityProxyModel::index(row, column, parent);
ProjectModel::GroupNode* group = static_cast<ProjectModel::GroupNode*>(parentNode);
ProjectModel::CollectionNode* smCol = group->getCollectionOfType(ProjectModel::INode::Type::SoundMacro);
ProjectModel::CollectionNode* kmCol = group->getCollectionOfType(ProjectModel::INode::Type::Keymap);
ProjectModel::CollectionNode* layCol = group->getCollectionOfType(ProjectModel::INode::Type::Layer);
if (row == 0)
return createIndex(row, column, group->nullChild());
else if (row == 1)
return createIndex(row, column, smCol->nullChild());
else if (row < 2 + smCol->childCount())
return createIndex(row, column, smCol->child(row - 2));
else if (row == 2 + smCol->childCount())
return createIndex(row, column, kmCol->nullChild());
else if (row < 3 + smCol->childCount() + kmCol->childCount())
return createIndex(row, column, kmCol->child(row - smCol->childCount() - 3));
else if (row == 3 + smCol->childCount() + kmCol->childCount())
return createIndex(row, column, layCol->nullChild());
else if (row < 4 + smCol->childCount() + kmCol->childCount() + layCol->childCount())
return createIndex(row, column, layCol->child(row - kmCol->childCount() - smCol->childCount() - 4));
return QModelIndex();
}
QVariant PageObjectProxyModel::data(const QModelIndex& proxyIndex, int role) const
{
if (role != Qt::DisplayRole || !proxyIndex.isValid() || proxyIndex.row() == 0)
return QVariant();
ProjectModel::INode* node = static_cast<ProjectModel::INode*>(proxyIndex.internalPointer());
auto tp = node->type();
if ((tp != ProjectModel::INode::Type::SoundMacro &&
tp != ProjectModel::INode::Type::Keymap &&
tp != ProjectModel::INode::Type::Layer &&
tp != ProjectModel::INode::Type::Null) ||
(tp == ProjectModel::INode::Type::Null &&
node->parent() == static_cast<ProjectModel*>(sourceModel())->rootNode()))
return QVariant();
ProjectModel::GroupNode* group = static_cast<ProjectModel*>(sourceModel())->getGroupNode(node);
ProjectModel::CollectionNode* smCol = group->getCollectionOfType(ProjectModel::INode::Type::SoundMacro);
ProjectModel::CollectionNode* kmCol = group->getCollectionOfType(ProjectModel::INode::Type::Keymap);
ProjectModel::CollectionNode* layCol = group->getCollectionOfType(ProjectModel::INode::Type::Layer);
switch (tp)
{
case ProjectModel::INode::Type::Null:
if (node->parent() == group)
return QVariant();
else if (node->parent() == smCol)
return tr("SoundMacros:");
else if (node->parent() == kmCol)
return tr("Keymaps:");
else if (node->parent() == layCol)
return tr("Layers:");
break;
case ProjectModel::INode::Type::SoundMacro:
case ProjectModel::INode::Type::Keymap:
case ProjectModel::INode::Type::Layer:
return node->text();
default:
break;
}
return QVariant();
}
Qt::ItemFlags PageObjectProxyModel::flags(const QModelIndex& proxyIndex) const
{
if (!proxyIndex.isValid())
return Qt::NoItemFlags;
if (proxyIndex.row() == 0)
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
ProjectModel::INode* node = static_cast<ProjectModel::INode*>(proxyIndex.internalPointer());
auto tp = node->type();
if (tp == ProjectModel::INode::Type::Null)
return Qt::NoItemFlags;
if (tp != ProjectModel::INode::Type::SoundMacro &&
tp != ProjectModel::INode::Type::Keymap &&
tp != ProjectModel::INode::Type::Layer)
return Qt::NoItemFlags;
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
ProjectModel::INode::INode(INode* parent, int row) : m_parent(parent), m_row(row)
{
auto nullNode = amuse::MakeObj<NullNode>(this);
@ -72,6 +285,17 @@ ProjectModel::CollectionNode* ProjectModel::GroupNode::getCollectionOfType(Type
return nullptr;
}
ProjectModel::BasePoolObjectNode* ProjectModel::GroupNode::pageObjectNodeOfId(amuse::ObjectId id) const
{
if (ProjectModel::BasePoolObjectNode* ret = getCollectionOfType(Type::SoundMacro)->nodeOfId(id))
return ret;
if (ProjectModel::BasePoolObjectNode* ret = getCollectionOfType(Type::Keymap)->nodeOfId(id))
return ret;
if (ProjectModel::BasePoolObjectNode* ret = getCollectionOfType(Type::Layer)->nodeOfId(id))
return ret;
return nullptr;
}
int ProjectModel::CollectionNode::indexOfId(amuse::ObjectId id) const
{
int ret = 0;
@ -103,7 +327,7 @@ ProjectModel::BasePoolObjectNode* ProjectModel::CollectionNode::nodeOfId(amuse::
}
ProjectModel::ProjectModel(const QString& path, QObject* parent)
: QAbstractItemModel(parent), m_dir(path), m_nullProxy(this)
: QAbstractItemModel(parent), m_dir(path), m_nullProxy(this), m_pageObjectProxy(this)
{
m_root = amuse::MakeObj<RootNode>();
@ -116,6 +340,7 @@ bool ProjectModel::clearProjectData()
{
m_projectDatabase = amuse::ProjectDatabase();
m_groups.clear();
m_midiFiles.clear();
m_needsReset = true;
return true;
@ -131,6 +356,33 @@ bool ProjectModel::openGroupData(const QString& groupName, UIMessenger& messenge
return true;
}
bool ProjectModel::openSongsData()
{
m_midiFiles.clear();
QFileInfo songsFile(m_dir, QStringLiteral("!songs.yaml"));
if (songsFile.exists())
{
athena::io::FileReader r(QStringToSysString(songsFile.path()));
if (!r.hasError())
{
athena::io::YAMLDocReader dr;
if (dr.parse(&r))
{
m_midiFiles.reserve(dr.getRootNode()->m_mapChildren.size());
for (auto& p : dr.getRootNode()->m_mapChildren)
{
char* endPtr;
amuse::SongId id = uint16_t(strtoul(p.first.c_str(), &endPtr, 0));
if (endPtr == p.first.c_str() || id.id == 0xffff)
continue;
m_midiFiles[id] = QString::fromStdString(p.second->m_scalarString);
}
}
}
}
return true;
}
bool ProjectModel::reloadSampleData(const QString& groupName, UIMessenger& messenger)
{
m_projectDatabase.setIdDatabases();
@ -203,6 +455,21 @@ bool ProjectModel::saveToFile(UIMessenger& messenger)
g.second.getPool().toYAML(groupPath);
}
if (!m_midiFiles.empty())
{
QFileInfo songsFile(m_dir, QStringLiteral("!songs.yaml"));
athena::io::YAMLDocWriter dw("amuse::Songs");
for (auto& p : m_midiFiles)
{
char id[16];
snprintf(id, 16, "%04X", p.first.id);
dw.writeString(id, p.second.toUtf8().data());
}
athena::io::FileWriter w(QStringToSysString(songsFile.path()));
if (!w.hasError())
dw.finish(&w);
}
return true;
}
@ -405,7 +672,7 @@ QVariant ProjectModel::data(const QModelIndex& index, int role) const
Qt::ItemFlags ProjectModel::flags(const QModelIndex& index) const
{
if (!index.isValid())
return 0;
return Qt::NoItemFlags;
return static_cast<INode*>(index.internalPointer())->flags();
}
@ -477,3 +744,56 @@ void ProjectModel::del(const QModelIndex& index)
return;
g_MainWindow->pushUndoCommand(new DeleteNodeUndoCommand(index));
}
ProjectModel::GroupNode* ProjectModel::getGroupOfSfx(amuse::SFXId id) const
{
ProjectModel::GroupNode* ret = nullptr;
m_root->oneLevelTraverse([id, &ret](INode* n)
{
GroupNode* gn = static_cast<GroupNode*>(n);
amuse::AudioGroupDatabase* db = gn->getAudioGroup();
for (const auto& p : db->getProj().sfxGroups())
{
if (p.second->m_sfxEntries.find(id) != p.second->m_sfxEntries.cend())
{
ret = gn;
return false;
}
}
return true;
});
return ret;
}
ProjectModel::GroupNode* ProjectModel::getGroupOfSong(amuse::SongId id) const
{
ProjectModel::GroupNode* ret = nullptr;
m_root->oneLevelTraverse([id, &ret](INode* n)
{
GroupNode* gn = static_cast<GroupNode*>(n);
amuse::AudioGroupDatabase* db = gn->getAudioGroup();
for (const auto& p : db->getProj().songGroups())
{
if (p.second->m_midiSetups.find(id) != p.second->m_midiSetups.cend())
{
ret = gn;
return false;
}
}
return true;
});
return ret;
}
QString ProjectModel::getMIDIPathOfSong(amuse::SongId id) const
{
auto search = m_midiFiles.find(id);
if (search == m_midiFiles.cend())
return {};
return search->second;
}
void ProjectModel::setMIDIPathOfSong(amuse::SongId id, const QString& path)
{
m_midiFiles[id] = path;
}

View File

@ -27,6 +27,20 @@ public:
QVariant data(const QModelIndex& proxyIndex, int role) const;
};
class PageObjectProxyModel : public QIdentityProxyModel
{
Q_OBJECT
public:
explicit PageObjectProxyModel(ProjectModel* source);
QModelIndex mapFromSource(const QModelIndex& sourceIndex) const;
QModelIndex mapToSource(const QModelIndex& proxyIndex) const;
QModelIndex parent(const QModelIndex& child) const;
int rowCount(const QModelIndex& parent) const;
QModelIndex index(int row, int column, const QModelIndex& parent) const;
QVariant data(const QModelIndex& proxyIndex, int role) const;
Qt::ItemFlags flags(const QModelIndex& proxyIndex) const;
};
class ProjectModel : public QAbstractItemModel
{
Q_OBJECT
@ -41,9 +55,11 @@ public:
private:
QDir m_dir;
NullItemProxyModel m_nullProxy;
PageObjectProxyModel m_pageObjectProxy;
amuse::ProjectDatabase m_projectDatabase;
std::map<QString, amuse::AudioGroupDatabase> m_groups;
std::unordered_map<amuse::SongId, QString> m_midiFiles;
public:
class INode : public amuse::IObj
@ -126,6 +142,14 @@ public:
return func(this);
}
bool oneLevelTraverse(const std::function<bool(INode* node)>& func)
{
for (auto& n : m_children)
if (!func(n.get()))
return false;
return true;
}
virtual Type type() const = 0;
virtual QString text() const = 0;
virtual QIcon icon() const = 0;
@ -149,6 +173,7 @@ public:
Qt::ItemFlags flags() const { return Qt::ItemIsEnabled; }
};
struct CollectionNode;
struct BasePoolObjectNode;
struct GroupNode : INode
{
std::map<QString, amuse::AudioGroupDatabase>::iterator m_it;
@ -162,6 +187,7 @@ public:
CollectionNode* getCollectionOfType(Type tp) const;
amuse::AudioGroupDatabase* getAudioGroup() const { return &m_it->second; }
BasePoolObjectNode* pageObjectNodeOfId(amuse::ObjectId id) const;
};
struct SongGroupNode : INode
{
@ -189,7 +215,6 @@ public:
QString text() const { return m_name; }
QIcon icon() const { return Icon; }
};
struct BasePoolObjectNode;
struct CollectionNode : INode
{
QString m_name;
@ -245,6 +270,7 @@ public:
bool clearProjectData();
bool openGroupData(const QString& groupName, UIMessenger& messenger);
bool openSongsData();
bool reloadSampleData(const QString& groupName, UIMessenger& messenger);
bool importGroupData(const QString& groupName, const amuse::AudioGroupData& data,
ImportMode mode, UIMessenger& messenger);
@ -266,9 +292,17 @@ public:
void _undoDel(const QModelIndex& index, amuse::ObjToken<ProjectModel::INode> node);
amuse::ObjToken<ProjectModel::INode> _redoDel(const QModelIndex& index);
void del(const QModelIndex& index);
RootNode* rootNode() const { return m_root.get(); }
const QDir& dir() const { return m_dir; }
QString path() const { return m_dir.path(); }
NullItemProxyModel* getNullProxy() { return &m_nullProxy; }
PageObjectProxyModel* getPageObjectProxy() { return &m_pageObjectProxy; }
GroupNode* getGroupOfSfx(amuse::SFXId id) const;
GroupNode* getGroupOfSong(amuse::SongId id) const;
QString getMIDIPathOfSong(amuse::SongId id) const;
void setMIDIPathOfSong(amuse::SongId id, const QString& path);
};

View File

@ -1,12 +1,987 @@
#include "SongGroupEditor.hpp"
#include "MainWindow.hpp"
bool SongGroupEditor::loadData(ProjectModel::SongGroupNode* node)
PageObjectDelegate::PageObjectDelegate(QObject* parent)
: QStyledItemDelegate(parent) {}
QWidget* PageObjectDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
const PageModel* model = static_cast<const PageModel*>(index.model());
ProjectModel::GroupNode* group = g_MainWindow->projectModel()->getGroupNode(model->m_node.get());
EditorFieldPageObjectNode* cb = new EditorFieldPageObjectNode(group, parent);
connect(cb, SIGNAL(currentIndexChanged(int)), this, SLOT(objIndexChanged()));
return cb;
}
void PageObjectDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const
{
const PageModel* model = static_cast<const PageModel*>(index.model());
auto entry = model->m_sorted[index.row()];
ProjectModel::GroupNode* group = g_MainWindow->projectModel()->getGroupNode(model->m_node.get());
ProjectModel::BasePoolObjectNode* node = group->pageObjectNodeOfId(entry->second.objId.id);
int idx = 0;
if (node)
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();
}
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();
if (idx == 0)
{
entry->second.objId.id = amuse::ObjectId();
}
else
{
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()))));
entry->second.objId.id = node->id();
}
emit m->dataChanged(index, index);
}
void PageObjectDelegate::objIndexChanged()
{
emit commitData(static_cast<QWidget*>(sender()));
}
void MIDIFileFieldWidget::buttonPressed()
{
m_dialog.setDirectory(QFileInfo(g_MainWindow->projectModel()->dir().absoluteFilePath(m_le.text())).path());
m_dialog.open(this, SLOT(fileDialogOpened(const QString&)));
}
void MIDIFileFieldWidget::fileDialogOpened(const QString& path)
{
m_le.setText(g_MainWindow->projectModel()->dir().relativeFilePath(path));
emit pathChanged();
}
MIDIFileFieldWidget::MIDIFileFieldWidget(QWidget* parent)
: QWidget(parent), m_button(tr("Browse")),
m_dialog(this, tr("Open Song File"), {}, QStringLiteral("Songs(*.mid *.son *.sng)"))
{
QHBoxLayout* layout = new QHBoxLayout;
layout->addWidget(&m_le);
layout->addWidget(&m_button);
layout->setContentsMargins(QMargins());
layout->setSpacing(0);
setLayout(layout);
connect(&m_le, SIGNAL(returnPressed()), this, SIGNAL(pathChanged()));
connect(&m_button, SIGNAL(clicked(bool)), this, SLOT(buttonPressed()));
m_dialog.setFileMode(QFileDialog::ExistingFile);
}
QWidget* MIDIFileDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
MIDIFileFieldWidget* field = new MIDIFileFieldWidget(parent);
connect(field, SIGNAL(pathChanged()), this, SLOT(pathChanged()));
return field;
}
void MIDIFileDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const
{
MIDIFileFieldWidget* widget = static_cast<MIDIFileFieldWidget*>(editor);
const SetupListModel* model = static_cast<const SetupListModel*>(index.model());
auto entry = model->m_sorted[index.row()];
widget->setPath(g_MainWindow->projectModel()->getMIDIPathOfSong(entry->first));
}
void MIDIFileDelegate::setModelData(QWidget* editor, QAbstractItemModel* m, const QModelIndex& index) const
{
MIDIFileFieldWidget* widget = static_cast<MIDIFileFieldWidget*>(editor);
const SetupListModel* model = static_cast<const SetupListModel*>(index.model());
auto entry = model->m_sorted[index.row()];
g_MainWindow->projectModel()->setMIDIPathOfSong(entry->first, widget->path());
emit m->dataChanged(index, index);
}
void MIDIFileDelegate::pathChanged()
{
emit commitData(static_cast<MIDIFileFieldWidget*>(sender()));
}
MIDIFileDelegate::MIDIFileDelegate(QObject* parent)
: QStyledItemDelegate(parent) {}
std::unordered_map<uint8_t, amuse::SongGroupIndex::PageEntry>& PageModel::_getMap() const
{
return m_drum ? m_node->m_index->m_drumPages : m_node->m_index->m_normPages;
}
void PageModel::_buildSortedList()
{
m_sorted.clear();
if (!m_node)
return;
auto& map = _getMap();
m_sorted.reserve(map.size());
for (auto it = map.begin() ; it != map.end() ; ++it)
m_sorted.emplace_back(it);
std::sort(m_sorted.begin(), m_sorted.end());
}
QModelIndex PageModel::_indexOfProgram(uint8_t prog) const
{
auto search = std::lower_bound(m_sorted.cbegin(), m_sorted.cend(), prog);
if (search == m_sorted.cend() || search->m_it->first != prog)
return QModelIndex();
else
return createIndex(search - m_sorted.begin(), 0);
}
int PageModel::_hypotheticalIndexOfProgram(uint8_t prog) const
{
auto search = std::lower_bound(m_sorted.cbegin(), m_sorted.cend(), prog);
return search - m_sorted.begin();
}
void PageModel::loadData(ProjectModel::SongGroupNode* node)
{
beginResetModel();
m_node = node;
_buildSortedList();
endResetModel();
}
void PageModel::unloadData()
{
beginResetModel();
m_node.reset();
m_sorted.clear();
endResetModel();
}
int PageModel::rowCount(const QModelIndex& parent) const
{
if (parent.isValid())
return 0;
if (!m_node)
return 0;
return int(m_sorted.size()) + 1;
}
int PageModel::columnCount(const QModelIndex& parent) const
{
if (parent.isValid())
return 0;
return 4;
}
QVariant PageModel::data(const QModelIndex& index, int role) const
{
if (!m_node)
return QVariant();
if (index.row() == m_sorted.size())
return QVariant();
auto entry = m_sorted[index.row()];
if (role == Qt::DisplayRole || role == Qt::EditRole)
{
switch (index.column())
{
case 0:
return entry->first;
case 1:
{
ProjectModel::GroupNode* group = g_MainWindow->projectModel()->getGroupNode(m_node.get());
if (ProjectModel::BasePoolObjectNode* node = group->pageObjectNodeOfId(entry->second.objId.id))
return node->text();
return QVariant();
}
case 2:
return entry->second.priority;
case 3:
return entry->second.maxVoices;
default:
break;
}
}
return QVariant();
}
bool PageModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
if (!m_node || role != Qt::EditRole)
return false;
auto& map = _getMap();
auto entry = m_sorted[index.row()];
switch (index.column())
{
case 0:
{
if (value.toInt() == entry->first)
return false;
if (map.find(value.toInt()) != map.cend())
{
QMessageBox::critical(g_MainWindow, tr("Program Conflict"),
tr("Program %1 is already defined in table").arg(value.toInt()));
return false;
}
emit layoutAboutToBeChanged();
auto nh = map.extract(entry->first);
nh.key() = value.toInt();
map.insert(std::move(nh));
_buildSortedList();
QModelIndex newIndex = _indexOfProgram(value.toInt());
changePersistentIndex(index, newIndex);
emit layoutChanged();
emit dataChanged(newIndex, newIndex);
return true;
}
case 2:
entry->second.priority = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
case 3:
entry->second.maxVoices = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
default:
break;
}
return false;
}
SongGroupEditor::SongGroupEditor(QWidget* parent)
: EditorWidget(parent)
QVariant PageModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
{
switch (section)
{
case 0:
return tr("Program");
case 1:
return tr("Object");
case 2:
return tr("Priority");
case 3:
return tr("Max Voices");
default:
break;
}
}
return QVariant();
}
Qt::ItemFlags PageModel::flags(const QModelIndex& index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
if (index.row() == m_sorted.size())
return Qt::NoItemFlags;
return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable;
}
bool PageModel::insertRows(int row, int count, const QModelIndex& parent)
{
if (!m_node)
return false;
if (m_sorted.size() >= 128)
return false;
auto& map = _getMap();
if (m_sorted.empty())
{
beginInsertRows(parent, 0, count - 1);
for (int i = 0; i < count; ++i)
map.emplace(std::make_pair(i, amuse::SongGroupIndex::PageEntry{}));
_buildSortedList();
endInsertRows();
return true;
}
for (int i = 0; i < count; ++i)
{
int prog = -1;
if (row < m_sorted.size())
{
prog = m_sorted[row].m_it->first;
while (prog >= 0 && _indexOfProgram(prog).isValid())
--prog;
}
if (prog == -1)
{
prog = 0;
while (prog < 128 && _indexOfProgram(prog).isValid())
++prog;
}
if (prog == 128)
return true;
int insertIdx = _hypotheticalIndexOfProgram(prog);
beginInsertRows(parent, insertIdx, insertIdx);
map.emplace(std::make_pair(prog, amuse::SongGroupIndex::PageEntry{}));
_buildSortedList();
endInsertRows();
++row;
}
return true;
}
bool PageModel::removeRows(int row, int count, const QModelIndex& parent)
{
if (!m_node)
return false;
auto& map = _getMap();
beginRemoveRows(parent, row, row + count - 1);
std::vector<uint8_t> removeProgs;
removeProgs.reserve(count);
for (int i = 0; i < count; ++i)
removeProgs.push_back(m_sorted[row+i].m_it->first);
for (uint8_t prog : removeProgs)
map.erase(prog);
_buildSortedList();
endRemoveRows();
return true;
}
PageModel::PageModel(bool drum, QObject* parent)
: QAbstractTableModel(parent), m_drum(drum)
{}
std::unordered_map<amuse::SongId, std::array<amuse::SongGroupIndex::MIDISetup, 16>>& SetupListModel::_getMap() const
{
return m_node->m_index->m_midiSetups;
}
void SetupListModel::_buildSortedList()
{
m_sorted.clear();
if (!m_node)
return;
auto& map = _getMap();
m_sorted.reserve(map.size());
for (auto it = map.begin() ; it != map.end() ; ++it)
m_sorted.emplace_back(it);
std::sort(m_sorted.begin(), m_sorted.end());
}
QModelIndex SetupListModel::_indexOfSong(amuse::SongId id) const
{
auto search = std::lower_bound(m_sorted.cbegin(), m_sorted.cend(), id);
if (search == m_sorted.cend() || search->m_it->first != id)
return QModelIndex();
else
return createIndex(search - m_sorted.begin(), 0);
}
int SetupListModel::_hypotheticalIndexOfSong(const std::string& songName) const
{
auto search = std::lower_bound(m_sorted.cbegin(), m_sorted.cend(), songName);
return search - m_sorted.begin();
}
void SetupListModel::loadData(ProjectModel::SongGroupNode* node)
{
beginResetModel();
m_node = node;
g_MainWindow->projectModel()->getGroupNode(m_node.get())->getAudioGroup()->setIdDatabases();
_buildSortedList();
endResetModel();
}
void SetupListModel::unloadData()
{
beginResetModel();
m_node = nullptr;
m_sorted.clear();
endResetModel();
}
int SetupListModel::rowCount(const QModelIndex& parent) const
{
if (parent.isValid())
return 0;
if (!m_node)
return 0;
return int(m_sorted.size()) + 1;
}
int SetupListModel::columnCount(const QModelIndex& parent) const
{
if (parent.isValid())
return 0;
return 2;
}
QVariant SetupListModel::data(const QModelIndex& index, int role) const
{
if (!m_node)
return QVariant();
if (index.row() == m_sorted.size())
return QVariant();
auto entry = m_sorted[index.row()];
if (role == Qt::DisplayRole || role == Qt::EditRole)
{
if (index.column() == 0)
{
g_MainWindow->projectModel()->getGroupNode(m_node.get())->getAudioGroup()->setIdDatabases();
return amuse::SongId::CurNameDB->resolveNameFromId(entry->first.id).data();
}
else if (index.column() == 1)
{
return g_MainWindow->projectModel()->getMIDIPathOfSong(entry.m_it->first);
}
}
return QVariant();
}
bool SetupListModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
if (!m_node || role != Qt::EditRole || index.column() != 0)
return false;
auto& map = _getMap();
auto entry = m_sorted[index.row()];
g_MainWindow->projectModel()->getGroupNode(m_node.get())->getAudioGroup()->setIdDatabases();
auto utf8key = value.toString().toUtf8();
std::unordered_map<std::string, amuse::ObjectId>::iterator idIt;
if ((idIt = amuse::SongId::CurNameDB->m_stringToId.find(utf8key.data())) != amuse::SongId::CurNameDB->m_stringToId.cend())
{
if (idIt->second == entry->first)
return false;
QMessageBox::critical(g_MainWindow, tr("Song Conflict"),
tr("Song %1 is already defined in project").arg(value.toString()));
return false;
}
emit layoutAboutToBeChanged();
amuse::SongId::CurNameDB->rename(entry.m_it->first, utf8key.data());
_buildSortedList();
QModelIndex newIndex = _indexOfSong(entry.m_it->first);
changePersistentIndex(index, newIndex);
emit layoutChanged();
emit dataChanged(newIndex, newIndex, {Qt::DisplayRole, Qt::EditRole});
return true;
}
QVariant SetupListModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
{
switch (section)
{
case 0:
return tr("Song");
case 1:
return tr("MIDI File");
default:
break;
}
}
return QVariant();
}
Qt::ItemFlags SetupListModel::flags(const QModelIndex& index) const
{
if (index.row() == m_sorted.size())
return Qt::NoItemFlags;
return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable;
}
bool SetupListModel::insertRows(int row, int count, const QModelIndex& parent)
{
if (!m_node)
return false;
auto& map = _getMap();
g_MainWindow->projectModel()->getGroupNode(m_node.get())->getAudioGroup()->setIdDatabases();
for (int i = 0; i < count; ++i)
{
amuse::ObjectId songId = amuse::SongId::CurNameDB->generateId(amuse::NameDB::Type::Song);
std::string songName = amuse::SongId::CurNameDB->generateName(songId, amuse::NameDB::Type::Song);
int insertIdx = _hypotheticalIndexOfSong(songName);
beginInsertRows(parent, insertIdx, insertIdx);
amuse::SongId::CurNameDB->registerPair(songName, songId);
map.emplace(std::make_pair(songId, std::array<amuse::SongGroupIndex::MIDISetup, 16>{}));
_buildSortedList();
endInsertRows();
++row;
}
return true;
}
bool SetupListModel::removeRows(int row, int count, const QModelIndex& parent)
{
if (!m_node)
return false;
auto& map = _getMap();
g_MainWindow->projectModel()->getGroupNode(m_node.get())->getAudioGroup()->setIdDatabases();
beginRemoveRows(parent, row, row + count - 1);
std::vector<amuse::SongId> removeSongs;
removeSongs.reserve(count);
for (int i = 0; i < count; ++i)
removeSongs.push_back(m_sorted[row+i].m_it->first);
for (amuse::SongId song : removeSongs)
{
amuse::SongId::CurNameDB->remove(song);
map.erase(song);
}
_buildSortedList();
endRemoveRows();
return true;
}
SetupListModel::SetupListModel(QObject* parent)
: QAbstractTableModel(parent)
{}
void SetupModel::loadData(std::pair<const amuse::SongId, std::array<amuse::SongGroupIndex::MIDISetup, 16>>* data)
{
beginResetModel();
m_data = data;
endResetModel();
}
void SetupModel::unloadData()
{
beginResetModel();
m_data = nullptr;
endResetModel();
}
int SetupModel::rowCount(const QModelIndex& parent) const
{
if (parent.isValid())
return 0;
if (!m_data)
return 0;
return 16;
}
int SetupModel::columnCount(const QModelIndex& parent) const
{
if (parent.isValid())
return 0;
return 5;
}
QVariant SetupModel::data(const QModelIndex& index, int role) const
{
if (!m_data)
return QVariant();
auto& entry = m_data->second[index.row()];
if (role == Qt::DisplayRole || role == Qt::EditRole)
{
switch (index.column())
{
case 0:
return entry.programNo;
case 1:
return entry.volume;
case 2:
return entry.panning;
case 3:
return entry.reverb;
case 4:
return entry.chorus;
default:
break;
}
}
return QVariant();
}
bool SetupModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
if (!m_data || role != Qt::EditRole)
return false;
auto& entry = m_data->second[index.row()];
switch (index.column())
{
case 0:
entry.programNo = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
case 1:
entry.volume = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
case 2:
entry.panning = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
case 3:
entry.reverb = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
case 4:
entry.chorus = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
default:
break;
}
return false;
}
QVariant SetupModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role == Qt::DisplayRole)
{
if (orientation == Qt::Horizontal)
{
switch (section)
{
case 0:
return tr("Program");
case 1:
return tr("Volume");
case 2:
return tr("Panning");
case 3:
return tr("Reverb");
case 4:
return tr("Chorus");
default:
break;
}
}
else
{
return section + 1;
}
}
else if (role == Qt::BackgroundColorRole && orientation == Qt::Vertical)
{
if (section == 9)
return QColor(64, 0, 0);
return QColor(0, 64, 0);
}
return QVariant();
}
Qt::ItemFlags SetupModel::flags(const QModelIndex& index) const
{
return Qt::ItemIsEnabled | Qt::ItemIsEditable;
}
SetupModel::SetupModel(QObject* parent)
: QAbstractTableModel(parent)
{}
void PageTableView::deleteSelection()
{
QModelIndexList list;
while (!(list = selectionModel()->selectedRows()).isEmpty())
model()->removeRow(list.back().row());
}
void PageTableView::setModel(QAbstractItemModel* model)
{
QTableView::setModel(model);
horizontalHeader()->setMinimumSectionSize(75);
horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed);
horizontalHeader()->resizeSection(0, 75);
horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
horizontalHeader()->setSectionResizeMode(2, QHeaderView::Fixed);
horizontalHeader()->resizeSection(2, 75);
horizontalHeader()->setSectionResizeMode(3, QHeaderView::Fixed);
horizontalHeader()->resizeSection(3, 100);
}
PageTableView::PageTableView(QWidget* parent)
: QTableView(parent)
{
setSelectionBehavior(QAbstractItemView::SelectRows);
setSelectionMode(QAbstractItemView::ExtendedSelection);
setGridStyle(Qt::NoPen);
m_127Delegate.setItemEditorFactory(&m_127Factory);
m_255Delegate.setItemEditorFactory(&m_255Factory);
setItemDelegateForColumn(0, &m_127Delegate);
setItemDelegateForColumn(1, &m_poDelegate);
setItemDelegateForColumn(2, &m_255Delegate);
setItemDelegateForColumn(3, &m_255Delegate);
}
void SetupTableView::setModel(QAbstractItemModel* list, QAbstractItemModel* table)
{
{
m_listView.setModel(list);
auto hheader = m_listView.horizontalHeader();
hheader->setMinimumSectionSize(200);
hheader->resizeSection(0, 200);
hheader->setSectionResizeMode(1, QHeaderView::Stretch);
}
{
m_tableView.setModel(table);
auto hheader = m_tableView.horizontalHeader();
hheader->setSectionResizeMode(QHeaderView::Stretch);
}
}
void SetupTableView::deleteSelection()
{
QModelIndexList list;
while (!(list = m_listView.selectionModel()->selectedRows()).isEmpty())
m_listView.model()->removeRow(list.back().row());
}
void SetupTableView::showEvent(QShowEvent* event)
{
setSizes({width() - 375, 375});
}
SetupTableView::SetupTableView(QWidget* parent)
: QSplitter(parent), m_listView(this), m_tableView(this)
{
setChildrenCollapsible(false);
setStretchFactor(0, 1);
setStretchFactor(1, 0);
addWidget(&m_listView);
addWidget(&m_tableView);
m_listView.setSelectionBehavior(QAbstractItemView::SelectRows);
m_listView.setSelectionMode(QAbstractItemView::ExtendedSelection);
m_listView.setGridStyle(Qt::NoPen);
m_listView.setItemDelegateForColumn(1, &m_midiDelegate);
m_tableView.setSelectionMode(QAbstractItemView::NoSelection);
m_tableView.setGridStyle(Qt::NoPen);
m_127Delegate.setItemEditorFactory(&m_127Factory);
m_tableView.setItemDelegateForColumn(0, &m_127Delegate);
m_tableView.setItemDelegateForColumn(1, &m_127Delegate);
m_tableView.setItemDelegateForColumn(2, &m_127Delegate);
m_tableView.setItemDelegateForColumn(3, &m_127Delegate);
m_tableView.setItemDelegateForColumn(4, &m_127Delegate);
}
void ColoredTabBarStyle::drawControl(QStyle::ControlElement element, const QStyleOption *option,
QPainter *painter, const QWidget *widget) const
{
if (element == QStyle::CE_TabBarTab)
{
QStyleOptionTab optionTab = *static_cast<const QStyleOptionTab*>(option);
switch (optionTab.position)
{
case QStyleOptionTab::Beginning:
optionTab.palette.setColor(QPalette::Button, QColor(0, 64, 0));
break;
case QStyleOptionTab::Middle:
optionTab.palette.setColor(QPalette::Button, QColor(64, 0, 0));
break;
default:
break;
}
QProxyStyle::drawControl(element, &optionTab, painter, widget);
}
else
QProxyStyle::drawControl(element, option, painter, widget);
}
ColoredTabBar::ColoredTabBar(QWidget* parent)
: QTabBar(parent), m_style(style())
{
setDrawBase(false);
setStyle(&m_style);
}
ColoredTabWidget::ColoredTabWidget(QWidget* parent)
: QTabWidget(parent)
{
setTabBar(&m_tabBar);
}
bool SongGroupEditor::loadData(ProjectModel::SongGroupNode* node)
{
m_normPages.loadData(node);
m_drumPages.loadData(node);
m_setupList.loadData(node);
m_setup.unloadData();
return true;
}
void SongGroupEditor::unloadData()
{
m_normPages.unloadData();
m_drumPages.unloadData();
m_setupList.unloadData();
m_setup.unloadData();
}
ProjectModel::INode* SongGroupEditor::currentNode() const
{
return m_normPages.m_node.get();
}
void SongGroupEditor::resizeEvent(QResizeEvent* ev)
{
m_tabs.setGeometry(QRect({}, ev->size()));
m_addButton.move(0, ev->size().height() - 32);
m_removeButton.move(32, ev->size().height() - 32);
}
void SongGroupEditor::doAdd()
{
if (PageTableView* table = qobject_cast<PageTableView*>(m_tabs.currentWidget()))
{
QModelIndex idx = table->selectionModel()->currentIndex();
if (!idx.isValid())
table->model()->insertRow(table->model()->rowCount() - 1);
else
table->model()->insertRow(idx.row());
if (PageTableView* ctable = qobject_cast<PageTableView*>(table))
m_addAction.setDisabled(ctable->model()->rowCount() >= 128);
}
else if (SetupTableView* table = qobject_cast<SetupTableView*>(m_tabs.currentWidget()))
{
QModelIndex idx = table->m_listView.selectionModel()->currentIndex();
if (!idx.isValid())
table->m_listView.model()->insertRow(table->m_listView.model()->rowCount() - 1);
else
table->m_listView.model()->insertRow(idx.row());
}
}
void SongGroupEditor::doSelectionChanged(const QItemSelection& selected)
{
m_removeAction.setDisabled(selected.isEmpty());
g_MainWindow->updateFocus();
}
void SongGroupEditor::doSetupSelectionChanged(const QItemSelection& selected)
{
doSelectionChanged(selected);
if (selected.indexes().isEmpty() || m_setupList.m_sorted.empty())
{
m_setup.unloadData();
}
else
{
auto entry = m_setupList.m_sorted[selected.indexes().last().row()];
m_setup.loadData(&*entry.m_it);
}
}
void SongGroupEditor::currentTabChanged(int idx)
{
if (PageTableView* table = qobject_cast<PageTableView*>(m_tabs.currentWidget()))
{
m_addAction.setDisabled(table->model()->rowCount() >= 128);
doSelectionChanged(table->selectionModel()->selection());
}
else if (SetupTableView* table = qobject_cast<SetupTableView*>(m_tabs.currentWidget()))
{
m_addAction.setDisabled(false);
doSelectionChanged(table->m_listView.selectionModel()->selection());
}
}
void SongGroupEditor::rowsAboutToBeRemoved(const QModelIndex &parent, int first, int last)
{
for (int i = first; i <= last; ++i)
{
auto entry = m_setupList.m_sorted[i];
if (&*entry.m_it == m_setup.m_data)
{
m_setup.unloadData();
return;
}
}
}
void SongGroupEditor::modelAboutToBeReset()
{
m_setup.unloadData();
}
bool SongGroupEditor::isItemEditEnabled() const
{
if (PageTableView* table = qobject_cast<PageTableView*>(m_tabs.currentWidget()))
return table->hasFocus() && !table->selectionModel()->selectedRows().isEmpty();
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()
{
}
void SongGroupEditor::itemDeleteAction()
{
if (PageTableView* table = qobject_cast<PageTableView*>(m_tabs.currentWidget()))
table->deleteSelection();
else if (SetupTableView* table = qobject_cast<SetupTableView*>(m_tabs.currentWidget()))
table->deleteSelection();
}
SongGroupEditor::SongGroupEditor(QWidget* parent)
: EditorWidget(parent), m_normPages(false, this), m_drumPages(true, this), m_setup(this),
m_normTable(this), m_drumTable(this), m_setupTable(this), m_tabs(this),
m_addAction(tr("Add Row")), m_addButton(this), m_removeAction(tr("Remove Row")), m_removeButton(this)
{
m_tabs.addTab(&m_normTable, tr("Normal Pages"));
m_tabs.addTab(&m_drumTable, tr("Drum Pages"));
m_tabs.addTab(&m_setupTable, tr("MIDI Setups"));
connect(&m_tabs, SIGNAL(currentChanged(int)), this, SLOT(currentTabChanged(int)));
connect(&m_setupList, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)),
this, SLOT(rowsAboutToBeRemoved(const QModelIndex&, int, int)));
connect(&m_setupList, SIGNAL(modelAboutToBeReset()),
this, SLOT(modelAboutToBeReset()));
m_normTable.setModel(&m_normPages);
m_drumTable.setModel(&m_drumPages);
m_setupTable.setModel(&m_setupList, &m_setup);
connect(m_normTable.selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
this, SLOT(doSelectionChanged(const QItemSelection&)));
connect(m_drumTable.selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
this, SLOT(doSelectionChanged(const QItemSelection&)));
connect(m_setupTable.m_listView.selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
this, SLOT(doSetupSelectionChanged(const QItemSelection&)));
m_addAction.setIcon(QIcon(QStringLiteral(":/icons/IconAdd.svg")));
m_addButton.setDefaultAction(&m_addAction);
m_addAction.setToolTip(tr("Add new page entry"));
m_addButton.setFixedSize(32, 32);
connect(&m_addAction, SIGNAL(triggered(bool)), this, SLOT(doAdd()));
m_removeAction.setIcon(QIcon(QStringLiteral(":/icons/IconRemove.svg")));
m_removeButton.setDefaultAction(&m_removeAction);
m_removeAction.setToolTip(tr("Remove selected page entries"));
m_removeButton.setFixedSize(32, 32);
connect(&m_removeAction, SIGNAL(triggered(bool)), this, SLOT(itemDeleteAction()));
m_removeAction.setEnabled(false);
m_tabs.setCurrentIndex(0);
}

View File

@ -2,13 +2,250 @@
#define AMUSE_SONG_GROUP_EDITOR_HPP
#include "EditorWidget.hpp"
#include <QTabWidget>
#include <QAbstractTableModel>
#include <QStyledItemDelegate>
#include <QTableView>
#include <QToolButton>
#include <QAction>
#include <QSplitter>
#include <QListView>
#include <QLineEdit>
#include <QPushButton>
#include <QFileDialog>
#include <QProxyStyle>
class PageObjectDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
explicit PageObjectDelegate(QObject* parent = Q_NULLPTR);
QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const;
void setEditorData(QWidget* editor, const QModelIndex& index) const;
void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const;
private slots:
void objIndexChanged();
};
class MIDIFileFieldWidget : public QWidget
{
Q_OBJECT
QLineEdit m_le;
QPushButton m_button;
QFileDialog m_dialog;
public:
explicit MIDIFileFieldWidget(QWidget* parent = Q_NULLPTR);
QString path() const { return m_le.text(); }
void setPath(const QString& path) { m_le.setText(path); }
public slots:
void buttonPressed();
void fileDialogOpened(const QString& path);
signals:
void pathChanged();
};
class MIDIFileDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
explicit MIDIFileDelegate(QObject* parent = Q_NULLPTR);
QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const;
void setEditorData(QWidget* editor, const QModelIndex& index) const;
void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const;
public slots:
void pathChanged();
};
class PageModel : public QAbstractTableModel
{
Q_OBJECT
friend class SongGroupEditor;
friend class PageObjectDelegate;
amuse::ObjToken<ProjectModel::SongGroupNode> m_node;
struct Iterator
{
using ItTp = std::unordered_map<uint8_t, amuse::SongGroupIndex::PageEntry>::iterator;
ItTp m_it;
Iterator(ItTp it) : m_it(it) {}
ItTp::pointer operator->() { return m_it.operator->(); }
bool operator<(const Iterator& other) const { return m_it->first < other.m_it->first; }
bool operator<(uint8_t other) const { return m_it->first < other; }
};
std::vector<Iterator> m_sorted;
bool m_drum;
std::unordered_map<uint8_t, amuse::SongGroupIndex::PageEntry>& _getMap() const;
void _buildSortedList();
QModelIndex _indexOfProgram(uint8_t prog) const;
int _hypotheticalIndexOfProgram(uint8_t prog) const;
public:
explicit PageModel(bool drum, QObject* parent = Q_NULLPTR);
void loadData(ProjectModel::SongGroupNode* node);
void unloadData();
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);
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
Qt::ItemFlags flags(const QModelIndex& index) const;
bool insertRows(int row, int count, const QModelIndex& parent = QModelIndex());
bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex());
};
class SetupListModel : public QAbstractTableModel
{
Q_OBJECT
friend class SongGroupEditor;
friend class MIDIFileDelegate;
amuse::ObjToken<ProjectModel::SongGroupNode> m_node;
struct Iterator
{
using ItTp = std::unordered_map<amuse::SongId, std::array<amuse::SongGroupIndex::MIDISetup, 16>>::iterator;
ItTp m_it;
Iterator(ItTp it) : m_it(it) {}
ItTp::pointer operator->() { return m_it.operator->(); }
bool operator<(const Iterator& other) const
{
return amuse::SongId::CurNameDB->resolveNameFromId(m_it->first) <
amuse::SongId::CurNameDB->resolveNameFromId(other.m_it->first);
}
bool operator<(amuse::SongId other) const
{
return amuse::SongId::CurNameDB->resolveNameFromId(m_it->first) <
amuse::SongId::CurNameDB->resolveNameFromId(other);
}
bool operator<(const std::string& name) const
{
return amuse::SongId::CurNameDB->resolveNameFromId(m_it->first) < name;
}
};
std::vector<Iterator> m_sorted;
std::unordered_map<amuse::SongId, std::array<amuse::SongGroupIndex::MIDISetup, 16>>& _getMap() const;
void _buildSortedList();
QModelIndex _indexOfSong(amuse::SongId id) const;
int _hypotheticalIndexOfSong(const std::string& songName) const;
public:
explicit SetupListModel(QObject* parent = Q_NULLPTR);
void loadData(ProjectModel::SongGroupNode* node);
void unloadData();
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);
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
Qt::ItemFlags flags(const QModelIndex& index) const;
bool insertRows(int row, int count, const QModelIndex& parent = QModelIndex());
bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex());
};
class SetupModel : public QAbstractTableModel
{
Q_OBJECT
friend class SongGroupEditor;
std::pair<const amuse::SongId, std::array<amuse::SongGroupIndex::MIDISetup, 16>>* m_data = nullptr;
public:
explicit SetupModel(QObject* parent = Q_NULLPTR);
void loadData(std::pair<const amuse::SongId, std::array<amuse::SongGroupIndex::MIDISetup, 16>>* data);
void unloadData();
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);
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
Qt::ItemFlags flags(const QModelIndex& index) const;
};
class PageTableView : public QTableView
{
Q_OBJECT
PageObjectDelegate m_poDelegate;
RangedValueFactory<0, 127> m_127Factory;
RangedValueFactory<0, 255> m_255Factory;
QStyledItemDelegate m_127Delegate, m_255Delegate;
public:
explicit PageTableView(QWidget* parent = Q_NULLPTR);
void setModel(QAbstractItemModel* model);
void deleteSelection();
};
class SetupTableView : public QSplitter
{
Q_OBJECT
friend class SongGroupEditor;
QTableView m_listView;
QTableView m_tableView;
MIDIFileDelegate m_midiDelegate;
RangedValueFactory<0, 127> m_127Factory;
QStyledItemDelegate m_127Delegate;
public:
explicit SetupTableView(QWidget* parent = Q_NULLPTR);
void setModel(QAbstractItemModel* list, QAbstractItemModel* table);
void deleteSelection();
void showEvent(QShowEvent* event);
};
class ColoredTabBarStyle : public QProxyStyle
{
public:
using QProxyStyle::QProxyStyle;
void drawControl(QStyle::ControlElement element, const QStyleOption *option,
QPainter *painter, const QWidget *widget = nullptr) const;
};
class ColoredTabBar : public QTabBar
{
Q_OBJECT
ColoredTabBarStyle m_style;
public:
explicit ColoredTabBar(QWidget* parent = Q_NULLPTR);
};
class ColoredTabWidget : public QTabWidget
{
Q_OBJECT
ColoredTabBar m_tabBar;
public:
explicit ColoredTabWidget(QWidget* parent = Q_NULLPTR);
};
class SongGroupEditor : public EditorWidget
{
Q_OBJECT
PageModel m_normPages;
PageModel m_drumPages;
SetupListModel m_setupList;
SetupModel m_setup;
PageTableView m_normTable;
PageTableView m_drumTable;
SetupTableView m_setupTable;
ColoredTabWidget m_tabs;
QAction m_addAction;
QToolButton m_addButton;
QAction m_removeAction;
QToolButton m_removeButton;
public:
explicit SongGroupEditor(QWidget* parent = Q_NULLPTR);
bool loadData(ProjectModel::SongGroupNode* node);
void unloadData();
ProjectModel::INode* currentNode() const;
void resizeEvent(QResizeEvent* ev);
public slots:
void doAdd();
void doSelectionChanged(const QItemSelection& selected);
void doSetupSelectionChanged(const QItemSelection& selected);
void currentTabChanged(int idx);
void rowsAboutToBeRemoved(const QModelIndex &parent, int first, int last);
void modelAboutToBeReset();
bool isItemEditEnabled() const;
void itemCutAction();
void itemCopyAction();
void itemPasteAction();
void itemDeleteAction();
};

View File

@ -184,214 +184,237 @@
<context>
<name>LayersEditor</name>
<message>
<location filename="../LayersEditor.cpp" line="442"/>
<location filename="../LayersEditor.cpp" line="439"/>
<source>Add Row</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../LayersEditor.cpp" line="442"/>
<location filename="../LayersEditor.cpp" line="439"/>
<source>Remove Row</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../LayersEditor.cpp" line="446"/>
<source>Add new layer mapping</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../LayersEditor.cpp" line="452"/>
<source>Remove selected layer mappings</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>LayersModel</name>
<message>
<location filename="../LayersEditor.cpp" line="186"/>
<location filename="../LayersEditor.cpp" line="172"/>
<source>SoundMacro</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../LayersEditor.cpp" line="188"/>
<location filename="../LayersEditor.cpp" line="174"/>
<source>Key Lo</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../LayersEditor.cpp" line="190"/>
<location filename="../LayersEditor.cpp" line="176"/>
<source>Key Hi</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../LayersEditor.cpp" line="192"/>
<location filename="../LayersEditor.cpp" line="178"/>
<source>Transpose</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../LayersEditor.cpp" line="194"/>
<location filename="../LayersEditor.cpp" line="180"/>
<source>Volume</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../LayersEditor.cpp" line="196"/>
<location filename="../LayersEditor.cpp" line="182"/>
<source>Prio Off</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../LayersEditor.cpp" line="198"/>
<location filename="../LayersEditor.cpp" line="184"/>
<source>Span</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../LayersEditor.cpp" line="200"/>
<location filename="../LayersEditor.cpp" line="186"/>
<source>Pan</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MIDIFileFieldWidget</name>
<message>
<location filename="../SongGroupEditor.cpp" line="69"/>
<source>Browse</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="70"/>
<source>Open Song File</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MainWindow</name>
<message>
<location filename="../MainWindow.ui" line="14"/>
<location filename="../MainWindow.cpp" line="181"/>
<location filename="../MainWindow.cpp" line="183"/>
<source>Amuse</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.ui" line="306"/>
<location filename="../MainWindow.ui" line="277"/>
<source>&amp;File</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.ui" line="313"/>
<location filename="../MainWindow.ui" line="284"/>
<source>Recent &amp;Projects</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.ui" line="328"/>
<location filename="../MainWindow.ui" line="299"/>
<source>Pro&amp;ject</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.ui" line="343"/>
<location filename="../MainWindow.ui" line="314"/>
<source>&amp;Audio</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.ui" line="351"/>
<location filename="../MainWindow.ui" line="322"/>
<source>&amp;MIDI</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.ui" line="358"/>
<location filename="../MainWindow.ui" line="329"/>
<source>&amp;Edit</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.ui" line="374"/>
<location filename="../MainWindow.ui" line="345"/>
<source>&amp;New Project</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.ui" line="379"/>
<location filename="../MainWindow.ui" line="350"/>
<source>&amp;Open Project</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.ui" line="387"/>
<location filename="../MainWindow.ui" line="358"/>
<source>&amp;Cut</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.ui" line="395"/>
<location filename="../MainWindow.ui" line="366"/>
<source>C&amp;opy</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.ui" line="403"/>
<location filename="../MainWindow.ui" line="374"/>
<source>&amp;Paste</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.ui" line="411"/>
<location filename="../MainWindow.ui" line="382"/>
<source>&amp;Delete</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.ui" line="416"/>
<location filename="../MainWindow.ui" line="387"/>
<source>&amp;Import Groups</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.ui" line="419"/>
<location filename="../MainWindow.ui" line="390"/>
<source>Ctrl+I</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.ui" line="431"/>
<location filename="../MainWindow.ui" line="402"/>
<source>New SF&amp;X Group</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.ui" line="443"/>
<location filename="../MainWindow.ui" line="414"/>
<source>New Son&amp;g Group</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.ui" line="455"/>
<location filename="../MainWindow.ui" line="426"/>
<source>New Sound &amp;Macro</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.ui" line="467"/>
<location filename="../MainWindow.ui" line="438"/>
<source>New &amp;Keymap</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.ui" line="479"/>
<location filename="../MainWindow.ui" line="450"/>
<source>New &amp;Layers</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.ui" line="490"/>
<location filename="../MainWindow.ui" line="461"/>
<source>&amp;Auto-Play</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.ui" line="498"/>
<location filename="../MainWindow.ui" line="469"/>
<source>&amp;Output Device:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.ui" line="506"/>
<location filename="../MainWindow.ui" line="477"/>
<source>&amp;Input Device:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.ui" line="514"/>
<location filename="../MainWindow.ui" line="485"/>
<source>&amp;Export GameCube Groups</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.ui" line="517"/>
<location filename="../MainWindow.ui" line="488"/>
<source>Ctrl+E</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.ui" line="529"/>
<location filename="../MainWindow.ui" line="500"/>
<source>&amp;New Subproject</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.ui" line="541"/>
<location filename="../MainWindow.ui" line="512"/>
<source>New &amp;ADSR</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.ui" line="553"/>
<location filename="../MainWindow.ui" line="524"/>
<source>New &amp;Curve</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.ui" line="561"/>
<location filename="../MainWindow.ui" line="532"/>
<source>&amp;Save Project</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.ui" line="569"/>
<location filename="../MainWindow.ui" line="540"/>
<source>&amp;Revert Project</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.ui" line="577"/>
<location filename="../MainWindow.ui" line="548"/>
<source>Reload Sample &amp;Data</source>
<translation type="unfinished"></translation>
</message>
@ -431,194 +454,194 @@
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="66"/>
<location filename="../MainWindow.cpp" line="68"/>
<source>Clear Recent Projects</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="72"/>
<location filename="../MainWindow.cpp" line="74"/>
<source>Quit</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="189"/>
<location filename="../MainWindow.cpp" line="191"/>
<source>Amuse [%1/%2/%3]</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="194"/>
<location filename="../MainWindow.cpp" line="196"/>
<source>Amuse [%1]</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="225"/>
<location filename="../MainWindow.cpp" line="581"/>
<location filename="../MainWindow.cpp" line="227"/>
<location filename="../MainWindow.cpp" line="585"/>
<source>The directory at &apos;%1&apos; must not be empty.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="226"/>
<location filename="../MainWindow.cpp" line="582"/>
<location filename="../MainWindow.cpp" line="228"/>
<location filename="../MainWindow.cpp" line="586"/>
<source>Directory empty</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="231"/>
<location filename="../MainWindow.cpp" line="233"/>
<source>The directory at &apos;%1&apos; must exist for the Amuse editor.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="232"/>
<location filename="../MainWindow.cpp" line="234"/>
<source>Directory does not exist</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="235"/>
<source>test</source>
<location filename="../MainWindow.cpp" line="237"/>
<source>__amuse_test__</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="239"/>
<source>The directory at &apos;%1&apos; must be writable for the Amuse editor.</source>
<location filename="../MainWindow.cpp" line="242"/>
<source>The directory at &apos;%1&apos; must be writable for the Amuse editor: %2</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="240"/>
<location filename="../MainWindow.cpp" line="244"/>
<source>Unable to write to directory</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="286"/>
<location filename="../MainWindow.cpp" line="290"/>
<source>No Audio Devices Found</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="309"/>
<location filename="../MainWindow.cpp" line="313"/>
<source>No MIDI Devices Found</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="348"/>
<location filename="../MainWindow.cpp" line="352"/>
<source>SUSTAIN</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="564"/>
<location filename="../MainWindow.cpp" line="568"/>
<source>New Project</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="587"/>
<location filename="../MainWindow.cpp" line="591"/>
<source>The directory at &apos;%1&apos; does not exist.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="588"/>
<location filename="../MainWindow.cpp" line="592"/>
<source>Bad Directory</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="603"/>
<location filename="../MainWindow.cpp" line="607"/>
<source>Opening</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="603"/>
<location filename="../MainWindow.cpp" line="681"/>
<location filename="../MainWindow.cpp" line="764"/>
<location filename="../MainWindow.cpp" line="809"/>
<location filename="../MainWindow.cpp" line="607"/>
<location filename="../MainWindow.cpp" line="686"/>
<location filename="../MainWindow.cpp" line="769"/>
<location filename="../MainWindow.cpp" line="814"/>
<source>Scanning Project</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="615"/>
<location filename="../MainWindow.cpp" line="619"/>
<source>Opening %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="627"/>
<location filename="../MainWindow.cpp" line="632"/>
<source>Open Project</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="681"/>
<location filename="../MainWindow.cpp" line="686"/>
<source>Reloading Samples</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="693"/>
<location filename="../MainWindow.cpp" line="698"/>
<source>Scanning %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="703"/>
<location filename="../MainWindow.cpp" line="708"/>
<source>Import Project</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="712"/>
<location filename="../MainWindow.cpp" line="717"/>
<source>The file at &apos;%1&apos; could not be interpreted as a MusyX container.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="713"/>
<location filename="../MainWindow.cpp" line="718"/>
<source>Unsupported MusyX Container</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="718"/>
<location filename="../MainWindow.cpp" line="723"/>
<source>Sample Import Mode</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="719"/>
<location filename="../MainWindow.cpp" line="724"/>
<source>Amuse can import samples as WAV files for ease of editing, import original compressed data for lossless repacking, or both. Exporting the project will prefer whichever version was modified most recently.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="723"/>
<location filename="../MainWindow.cpp" line="728"/>
<source>Import Compressed</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="723"/>
<location filename="../MainWindow.cpp" line="728"/>
<source>Import WAVs</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="723"/>
<location filename="../MainWindow.cpp" line="728"/>
<source>Import Both</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="739"/>
<location filename="../MainWindow.cpp" line="744"/>
<source>Raw Import Mode</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="740"/>
<location filename="../MainWindow.cpp" line="745"/>
<source>Would you like to scan for all MusyX group files in this directory?</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="750"/>
<location filename="../MainWindow.cpp" line="755"/>
<source>Project Name</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="750"/>
<location filename="../MainWindow.cpp" line="755"/>
<source>What should this project be named?</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="764"/>
<location filename="../MainWindow.cpp" line="809"/>
<location filename="../MainWindow.cpp" line="769"/>
<location filename="../MainWindow.cpp" line="814"/>
<source>Importing</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="776"/>
<location filename="../MainWindow.cpp" line="818"/>
<location filename="../MainWindow.cpp" line="781"/>
<location filename="../MainWindow.cpp" line="823"/>
<source>Importing %1</source>
<translation type="unfinished"></translation>
</message>
@ -631,6 +654,57 @@
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>PageModel</name>
<message>
<location filename="../SongGroupEditor.cpp" line="231"/>
<source>Program Conflict</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="232"/>
<source>Program %1 is already defined in table</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="268"/>
<source>Program</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="270"/>
<source>Object</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="272"/>
<source>Priority</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="274"/>
<source>Max Voices</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>PageObjectProxyModel</name>
<message>
<location filename="../ProjectModel.cpp" line="235"/>
<source>SoundMacros:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ProjectModel.cpp" line="237"/>
<source>Keymaps:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ProjectModel.cpp" line="239"/>
<source>Layers:</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>PaintButton</name>
<message>
@ -650,37 +724,37 @@
<context>
<name>ProjectModel</name>
<message>
<location filename="../ProjectModel.cpp" line="234"/>
<location filename="../ProjectModel.cpp" line="501"/>
<source>Sound Macros</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ProjectModel.cpp" line="253"/>
<location filename="../ProjectModel.cpp" line="520"/>
<source>ADSRs</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ProjectModel.cpp" line="264"/>
<location filename="../ProjectModel.cpp" line="531"/>
<source>Curves</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ProjectModel.cpp" line="276"/>
<location filename="../ProjectModel.cpp" line="543"/>
<source>Keymaps</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ProjectModel.cpp" line="283"/>
<location filename="../ProjectModel.cpp" line="550"/>
<source>Layers</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ProjectModel.cpp" line="290"/>
<location filename="../ProjectModel.cpp" line="557"/>
<source>Samples</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ProjectModel.cpp" line="442"/>
<location filename="../ProjectModel.cpp" line="709"/>
<source>Delete %1</source>
<translation type="unfinished"></translation>
</message>
@ -756,6 +830,95 @@
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SetupListModel</name>
<message>
<location filename="../SongGroupEditor.cpp" line="460"/>
<source>Song Conflict</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="461"/>
<source>Song %1 is already defined in project</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="482"/>
<source>Song</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="484"/>
<source>MIDI File</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SetupModel</name>
<message>
<location filename="../SongGroupEditor.cpp" line="649"/>
<source>Program</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="651"/>
<source>Volume</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="653"/>
<source>Panning</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="655"/>
<source>Reverb</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="657"/>
<source>Chorus</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SongGroupEditor</name>
<message>
<location filename="../SongGroupEditor.cpp" line="975"/>
<source>Add new page entry</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="981"/>
<source>Remove selected page entries</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="950"/>
<source>Add Row</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="950"/>
<source>Remove Row</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="952"/>
<source>Normal Pages</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="953"/>
<source>Drum Pages</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="954"/>
<source>MIDI Setups</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SoundMacroCatalogue</name>
<message>

View File

@ -71,8 +71,8 @@ struct SongGroupIndex : AudioGroupIndex
{
AT_DECL_DNA_YAML
PageObjectIdDNA<athena::Big> objId;
Value<atUint8> priority;
Value<atUint8> maxVoices;
Value<atUint8> priority = 0;
Value<atUint8> maxVoices = 255;
PageEntry() = default;
@ -112,11 +112,11 @@ struct SongGroupIndex : AudioGroupIndex
struct MIDISetup : BigDNA
{
AT_DECL_DNA_YAML
Value<atUint8> programNo;
Value<atUint8> volume;
Value<atUint8> panning;
Value<atUint8> reverb;
Value<atUint8> chorus;
Value<atUint8> programNo = 0;
Value<atUint8> volume = 127;
Value<atUint8> panning = 64;
Value<atUint8> reverb = 0;
Value<atUint8> chorus = 0;
MIDISetup() = default;
MIDISetup(const MusyX1MIDISetup& setup)
: programNo(setup.programNo), volume(setup.volume), panning(setup.panning),

View File

@ -620,6 +620,8 @@ struct NameDB
std::string_view registerPair(std::string_view str, ObjectId id);
std::string_view resolveNameFromId(ObjectId id) const;
ObjectId resolveIdFromName(std::string_view str) const;
void remove(ObjectId id);
void rename(ObjectId id, std::string_view str);
};
}

View File

@ -166,7 +166,15 @@ void PageObjectIdDNA<DNAE>::_read(athena::io::YAMLDocReader& r)
{
search = LayersId::CurNameDB->m_stringToId.find(name);
if (search == LayersId::CurNameDB->m_stringToId.cend())
Log.report(logvisor::Fatal, "Unable to resolve name %s", name.c_str());
{
search = SoundMacroId::CurNameDB->m_stringToId.find(name);
if (search == SoundMacroId::CurNameDB->m_stringToId.cend())
{
Log.report(logvisor::Error, "Unable to resolve name %s", name.c_str());
id.id = 0xffff;
return;
}
}
}
id = search->second;
}
@ -261,7 +269,11 @@ template struct SoundMacroStepDNA<athena::Little>;
ObjectId NameDB::generateId(Type tp) const
{
uint16_t maxMatch = uint16_t(tp == Type::Layer ? 0x8000 : 0);
uint16_t maxMatch = 0;
if (tp == Type::Layer)
maxMatch = 0x8000;
else if (tp == Type::Keymap)
maxMatch = 0x4000;
for (const auto& p : m_idToString)
if (p.first >= maxMatch)
maxMatch = p.first + 1;
@ -332,6 +344,32 @@ ObjectId NameDB::resolveIdFromName(std::string_view str) const
return search->second;
}
void NameDB::remove(ObjectId id)
{
auto search = m_idToString.find(id);
if (search == m_idToString.cend())
return;
auto search2 = m_stringToId.find(search->second);
if (search2 == m_stringToId.cend())
return;
m_idToString.erase(search);
m_stringToId.erase(search2);
}
void NameDB::rename(ObjectId id, std::string_view str)
{
auto search = m_idToString.find(id);
if (search == m_idToString.cend())
return;
auto search2 = m_stringToId.find(search->second);
if (search2 == m_stringToId.cend())
return;
auto nh = m_stringToId.extract(search2);
nh.key() = str;
m_stringToId.insert(std::move(nh));
m_idToString[id] = str;
}
template<>
void LittleUInt24::Enumerate<LittleDNA::Read>(athena::io::IStreamReader& reader)
{