Implement LayersEditor

This commit is contained in:
Jack Andersen 2018-08-06 21:09:23 -10:00
parent 2b45f69ff4
commit 32deea8341
22 changed files with 911 additions and 72 deletions

View File

@ -17,6 +17,12 @@ public:
virtual bool valid() const { return true; }
virtual void unloadData() {}
virtual ProjectModel::INode* currentNode() const { return nullptr; }
public slots:
virtual bool isItemEditEnabled() const { return false; }
virtual void itemCutAction() {}
virtual void itemCopyAction() {}
virtual void itemPasteAction() {}
virtual void itemDeleteAction() {}
};
class EditorUndoCommand : public QUndoCommand

View File

@ -483,7 +483,7 @@ int KeymapEditor::allocateConfigIdx(uint64_t key)
++search->second.second;
return search->second.first;
}
for (int i = 0; i < 128; ++i)
for (int i = 0; i < 129; ++i)
if (!m_idxBitmap[i])
{
m_configToIdx[key] = std::make_pair(i, 1);
@ -514,7 +514,7 @@ int KeymapEditor::getConfigIdx(uint64_t key) const
auto search = m_configToIdx.find(key);
if (search != m_configToIdx.end())
return search->second.first;
for (int i = 0; i < 128; ++i)
for (int i = 0; i < 129; ++i)
if (!m_idxBitmap[i])
return i;
Q_UNREACHABLE();
@ -585,7 +585,7 @@ KeymapEditor::KeymapEditor(QWidget* parent)
int k = 0;
for (int i = 0; i < 13; ++i)
for (int j = 0; j < 10 && k < 128; ++j)
for (int j = 0; j < 10 && k < 129; ++j)
m_paintPalette[k++].setHsv(HueTable[j], SaturationTable[i], ValueTable[i]);
m_scrollArea->setWidget(m_kmView);

View File

@ -87,10 +87,10 @@ Q_OBJECT
QScrollArea* m_scrollArea;
KeymapView* m_kmView;
KeymapControls* m_controls;
QColor m_paintPalette[128];
QColor m_paintPalette[129];
amuse::Keymap m_controlKeymap;
std::unordered_map<uint64_t, std::pair<int, int>> m_configToIdx;
std::bitset<128> m_idxBitmap;
std::bitset<129> m_idxBitmap;
bool m_inPaint = false;
void _touch();
void touchKey(int key, bool bulk = false);

View File

@ -1,12 +1,470 @@
#include "LayersEditor.hpp"
#include "MainWindow.hpp"
#include <QVBoxLayout>
#include <QScrollBar>
#include <QMimeData>
bool LayersEditor::loadData(ProjectModel::LayersNode* node)
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) {}
QWidget* SoundMacroDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
const LayersModel* model = static_cast<const LayersModel*>(index.model());
ProjectModel::GroupNode* group = g_MainWindow->projectModel()->getGroupNode(model->m_node.get());
EditorFieldProjectNode* cb =
new EditorFieldProjectNode(group->getCollectionOfType(ProjectModel::INode::Type::SoundMacro), parent);
connect(cb, SIGNAL(currentIndexChanged(int)), this, SLOT(smIndexChanged()));
return cb;
}
void SoundMacroDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const
{
const LayersModel* model = static_cast<const LayersModel*>(index.model());
const amuse::LayerMapping& layer = (*model->m_node->m_obj)[index.row()];
ProjectModel::GroupNode* group = g_MainWindow->projectModel()->getGroupNode(model->m_node.get());
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();
}
void SoundMacroDelegate::setModelData(QWidget* editor, QAbstractItemModel* m, const QModelIndex& index) const
{
const LayersModel* model = static_cast<const LayersModel*>(m);
amuse::LayerMapping& layer = (*model->m_node->m_obj)[index.row()];
ProjectModel::GroupNode* group = g_MainWindow->projectModel()->getGroupNode(model->m_node.get());
ProjectModel::CollectionNode* smColl = group->getCollectionOfType(ProjectModel::INode::Type::SoundMacro);
int idx = static_cast<EditorFieldProjectNode*>(editor)->currentIndex();
if (idx == 0)
layer.macro.id = amuse::SoundMacroId();
else
layer.macro.id = smColl->idOfIndex(idx - 1);
}
void SoundMacroDelegate::smIndexChanged()
{
emit commitData(static_cast<QWidget*>(sender()));
}
void LayersModel::loadData(ProjectModel::LayersNode* node)
{
beginResetModel();
m_node = node;
endResetModel();
}
void LayersModel::unloadData()
{
beginResetModel();
m_node.reset();
endResetModel();
}
int LayersModel::rowCount(const QModelIndex& parent) const
{
if (parent.isValid())
return 0;
if (!m_node)
return 0;
return int(m_node->m_obj->size()) + 1;
}
int LayersModel::columnCount(const QModelIndex& parent) const
{
if (parent.isValid())
return 0;
return 8;
}
QVariant LayersModel::data(const QModelIndex& index, int role) const
{
if (!m_node)
return QVariant();
if (index.row() == m_node->m_obj->size())
return QVariant();
const amuse::LayerMapping& layer = (*m_node->m_obj)[index.row()];
if (role == Qt::DisplayRole || role == Qt::EditRole)
{
switch (index.column())
{
case 0:
{
ProjectModel::GroupNode* group = g_MainWindow->projectModel()->getGroupNode(m_node.get());
ProjectModel::CollectionNode* smColl = group->getCollectionOfType(ProjectModel::INode::Type::SoundMacro);
if (ProjectModel::BasePoolObjectNode* node = smColl->nodeOfId(layer.macro.id))
return node->text();
return QVariant();
}
case 1:
return layer.keyLo;
case 2:
return layer.keyHi;
case 3:
return layer.transpose;
case 4:
return layer.volume;
case 5:
return layer.prioOffset;
case 6:
return layer.span;
case 7:
return layer.pan;
default:
break;
}
}
return QVariant();
}
bool LayersModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
if (!m_node || role != Qt::EditRole)
return false;
amuse::LayerMapping& layer = (*m_node->m_obj)[index.row()];
switch (index.column())
{
case 1:
layer.keyLo = value.toInt();
return true;
case 2:
layer.keyHi = value.toInt();
return true;
case 3:
layer.transpose = value.toInt();
return true;
case 4:
layer.volume = value.toInt();
return true;
case 5:
layer.prioOffset = value.toInt();
return true;
case 6:
layer.span = value.toInt();
return true;
case 7:
layer.pan = value.toInt();
return true;
default:
break;
}
return false;
}
LayersEditor::LayersEditor(QWidget* parent)
: EditorWidget(parent)
QVariant LayersModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
{
switch (section)
{
case 0:
return tr("SoundMacro");
case 1:
return tr("Key Lo");
case 2:
return tr("Key Hi");
case 3:
return tr("Transpose");
case 4:
return tr("Volume");
case 5:
return tr("Prio Off");
case 6:
return tr("Span");
case 7:
return tr("Pan");
default:
break;
}
}
return QVariant();
}
Qt::ItemFlags LayersModel::flags(const QModelIndex& index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
if (index.row() == m_node->m_obj->size())
return Qt::NoItemFlags;
return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
}
Qt::DropActions LayersModel::supportedDropActions() const
{
return Qt::MoveAction;
}
Qt::DropActions LayersModel::supportedDragActions() const
{
return Qt::MoveAction;
}
bool LayersModel::dropMimeData(const QMimeData* data, Qt::DropAction action,
int row, int column, const QModelIndex& parent)
{
// check if the action is supported
if (!data || action != Qt::MoveAction)
return false;
// check if the format is supported
QStringList types = mimeTypes();
if (types.isEmpty())
return false;
QString format = types.at(0);
if (!data->hasFormat(format))
return false;
// decode and insert
QByteArray encoded = data->data(format);
QDataStream stream(&encoded, QIODevice::ReadOnly);
std::unordered_set<int> rows;
int lastRow = -1;
while (!stream.atEnd()) {
int r, c;
QMap<int, QVariant> v;
stream >> r >> c >> v;
rows.insert(r);
lastRow = r;
}
if (lastRow == -1)
return false;
int start = lastRow;
while (rows.find(start - 1) != rows.cend())
start -= 1;
int count = lastRow - start + 1;
while (rows.find(start + count) != rows.cend())
count += 1;
int dest = parent.row();
if (dest >= start)
{
if (dest - start < count)
return false;
dest += 1;
}
moveRows(QModelIndex(), start, count, QModelIndex(), dest);
return true;
}
bool LayersModel::insertRows(int row, int count, const QModelIndex& parent)
{
if (!m_node)
return false;
beginInsertRows(parent, row, row + count - 1);
std::vector<amuse::LayerMapping>& layers = *m_node->m_obj;
layers.insert(layers.begin() + row, count, amuse::LayerMapping());
endInsertRows();
return true;
}
bool LayersModel::moveRows(const QModelIndex& sourceParent, int sourceRow, int count,
const QModelIndex& destinationParent, int destinationChild)
{
if (!m_node)
return false;
beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1, destinationParent, destinationChild);
std::vector<amuse::LayerMapping>& layers = *m_node->m_obj;
if (destinationChild < sourceRow)
{
for (int i = 0; i < count; ++i)
{
amuse::LayerMapping tmp = std::move(layers[sourceRow]);
for (int j = sourceRow; j != destinationChild; --j)
layers[j] = std::move(layers[j - 1]);
layers[destinationChild] = std::move(tmp);
++sourceRow;
++destinationChild;
}
}
else if (destinationChild > sourceRow)
{
for (int i = 0; i < count; ++i)
{
amuse::LayerMapping tmp = std::move(layers[sourceRow]);
for (int j = sourceRow; j != destinationChild - 1; ++j)
layers[j] = std::move(layers[j + 1]);
layers[destinationChild - 1] = std::move(tmp);
}
}
endMoveRows();
return true;
}
bool LayersModel::removeRows(int row, int count, const QModelIndex& parent)
{
if (!m_node)
return false;
beginRemoveRows(parent, row, row + count - 1);
std::vector<amuse::LayerMapping>& layers = *m_node->m_obj;
layers.erase(layers.begin() + row, layers.begin() + row + count);
endRemoveRows();
return true;
}
LayersModel::LayersModel(QObject* parent)
: QAbstractTableModel(parent)
{}
void LayersTableView::deleteSelection()
{
QModelIndexList list;
while (!(list = selectionModel()->selectedRows()).isEmpty())
model()->removeRow(list.back().row());
}
void LayersTableView::doItemsLayout()
{
horizontalHeader()->setMinimumSectionSize(75);
horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
horizontalHeader()->setSectionResizeMode(1, QHeaderView::Fixed);
horizontalHeader()->resizeSection(1, 75);
horizontalHeader()->setSectionResizeMode(2, QHeaderView::Fixed);
horizontalHeader()->resizeSection(2, 75);
horizontalHeader()->setSectionResizeMode(3, QHeaderView::Fixed);
horizontalHeader()->resizeSection(3, 75);
horizontalHeader()->setSectionResizeMode(4, QHeaderView::Fixed);
horizontalHeader()->resizeSection(4, 75);
horizontalHeader()->setSectionResizeMode(5, QHeaderView::Fixed);
horizontalHeader()->resizeSection(5, 75);
horizontalHeader()->setSectionResizeMode(6, QHeaderView::Fixed);
horizontalHeader()->resizeSection(6, 75);
horizontalHeader()->setSectionResizeMode(7, QHeaderView::Fixed);
horizontalHeader()->resizeSection(7, 75);
QTableView::doItemsLayout();
}
LayersTableView::LayersTableView(QWidget* parent)
: QTableView(parent)
{
setSelectionBehavior(QAbstractItemView::SelectRows);
setSelectionMode(QAbstractItemView::ExtendedSelection);
setDragDropMode(QAbstractItemView::InternalMove);
setDefaultDropAction(Qt::MoveAction);
setDragEnabled(true);
setGridStyle(Qt::NoPen);
}
bool LayersEditor::loadData(ProjectModel::LayersNode* node)
{
m_model.loadData(node);
return true;
}
void LayersEditor::unloadData()
{
m_model.unloadData();
}
ProjectModel::INode* LayersEditor::currentNode() const
{
return m_model.m_node.get();
}
void LayersEditor::resizeEvent(QResizeEvent* ev)
{
m_tableView.setGeometry(QRect({}, ev->size()));
m_addButton.move(0, ev->size().height() - 32);
m_removeButton.move(32, ev->size().height() - 32);
}
void LayersEditor::doAdd()
{
QModelIndex idx = m_tableView.selectionModel()->currentIndex();
if (!idx.isValid())
m_model.insertRow(m_model.rowCount() - 1);
else
m_model.insertRow(idx.row());
}
void LayersEditor::doSelectionChanged(const QItemSelection& selected)
{
m_removeAction.setDisabled(selected.isEmpty());
g_MainWindow->updateFocus();
}
bool LayersEditor::isItemEditEnabled() const
{
return !m_tableView.selectionModel()->selectedRows().isEmpty();
}
void LayersEditor::itemCutAction()
{
}
void LayersEditor::itemCopyAction()
{
}
void LayersEditor::itemPasteAction()
{
}
void LayersEditor::itemDeleteAction()
{
m_tableView.deleteSelection();
}
LayersEditor::LayersEditor(QWidget* parent)
: EditorWidget(parent), 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_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_removeButton.setDefaultAction(&m_removeAction);
m_removeButton.setFixedSize(32, 32);
connect(&m_removeAction, SIGNAL(triggered(bool)), this, SLOT(itemDeleteAction()));
m_removeAction.setEnabled(false);
}

View File

@ -2,14 +2,116 @@
#define AMUSE_LAYERS_EDITOR_HPP
#include "EditorWidget.hpp"
#include <QAbstractTableModel>
#include <QTableView>
#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
{
Q_OBJECT
public:
explicit SoundMacroDelegate(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 smIndexChanged();
};
class LayersModel : public QAbstractTableModel
{
Q_OBJECT
friend class LayersEditor;
friend class SoundMacroDelegate;
amuse::ObjToken<ProjectModel::LayersNode> m_node;
public:
explicit LayersModel(QObject* parent = Q_NULLPTR);
void loadData(ProjectModel::LayersNode* 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;
Qt::DropActions supportedDropActions() const;
Qt::DropActions supportedDragActions() const;
bool dropMimeData(const QMimeData *data, Qt::DropAction action,
int row, int column, const QModelIndex &parent);
bool insertRows(int row, int count, const QModelIndex& parent = QModelIndex());
bool moveRows(const QModelIndex& sourceParent, int sourceRow, int count,
const QModelIndex& destinationParent, int destinationChild);
bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex());
};
class LayersTableView : public QTableView
{
Q_OBJECT
public:
explicit LayersTableView(QWidget* parent = Q_NULLPTR);
void doItemsLayout();
void deleteSelection();
};
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;
QAction m_removeAction;
QToolButton m_removeButton;
public:
explicit LayersEditor(QWidget* parent = Q_NULLPTR);
bool loadData(ProjectModel::LayersNode* node);
void unloadData();
ProjectModel::INode* currentNode() const;
void resizeEvent(QResizeEvent* ev);
public slots:
void doAdd();
void doSelectionChanged(const QItemSelection& selected);
bool isItemEditEnabled() const;
void itemCutAction();
void itemCopyAction();
void itemPasteAction();
void itemDeleteAction();
};
#endif //AMUSE_LAYERS_EDITOR_HPP

View File

@ -256,7 +256,7 @@ bool MainWindow::setProjectPath(const QString& path)
m_ui.actionExport_GameCube_Groups->setEnabled(true);
setWindowFilePath(path);
updateWindowTitle();
onFocusChanged(nullptr, focusWidget());
updateFocus();
m_undoStack->clear();
QSettings settings;
@ -314,7 +314,7 @@ void MainWindow::timerEvent(QTimerEvent* ev)
if (m_voxEngine && m_engine)
{
m_voxEngine->pumpAndMixVoices();
m_ui.statusbar->setVoiceCount(int(m_engine->getActiveVoices().size()));
m_ui.statusbar->setVoiceCount(int(m_engine->getNumTotalActiveVoices()));
if (m_engine->getActiveVoices().empty() && m_uiDisabled)
{
m_ui.projectOutline->setEnabled(true);
@ -548,6 +548,11 @@ void MainWindow::pushUndoCommand(QUndoCommand* cmd)
m_undoStack->push(cmd);
}
void MainWindow::updateFocus()
{
onFocusChanged(nullptr, focusWidget());
}
void MainWindow::aboutToDeleteNode(ProjectModel::INode* node)
{
if (getEditorNode() == node)
@ -1000,7 +1005,7 @@ void MainWindow::onFocusChanged(QWidget* old, QWidget* now)
if (now == m_ui.projectOutline || m_ui.projectOutline->isAncestorOf(now))
{
setOutlineEditEnabled(canEditOutline());
setItemEditEnabled(canEditOutline());
if (m_projectModel)
{
m_cutConn = connect(m_ui.actionCut, SIGNAL(triggered()), this, SLOT(outlineCutAction()));
@ -1011,12 +1016,23 @@ void MainWindow::onFocusChanged(QWidget* old, QWidget* now)
}
else if (now == m_ui.editorContents || m_ui.editorContents->isAncestorOf(now))
{
setOutlineEditEnabled(false);
setItemEditEnabled(false);
if (EditorWidget* editor = getEditorWidget())
{
if (editor->isItemEditEnabled())
{
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()));
}
}
}
}
void MainWindow::setOutlineEditEnabled(bool enabled)
void MainWindow::setItemEditEnabled(bool enabled)
{
m_ui.actionCut->setEnabled(enabled);
m_ui.actionCopy->setEnabled(enabled);
@ -1040,10 +1056,10 @@ void MainWindow::onOutlineSelectionChanged(const QItemSelection& selected, const
return;
if (selected.indexes().empty())
{
setOutlineEditEnabled(false);
setItemEditEnabled(false);
return;
}
setOutlineEditEnabled(m_projectModel->canEdit(selected.indexes().front()));
setItemEditEnabled(m_projectModel->canEdit(selected.indexes().front()));
}
void MainWindow::onTextSelect()

View File

@ -147,6 +147,7 @@ public:
EditorWidget* getEditorWidget() const;
amuse::ObjToken<amuse::Voice> startEditorVoice(uint8_t key, uint8_t vel);
void pushUndoCommand(QUndoCommand* cmd);
void updateFocus();
void aboutToDeleteNode(ProjectModel::INode* node);
ProjectModel* projectModel() const { return m_projectModel; }
@ -190,7 +191,7 @@ public slots:
void outlineDeleteAction();
void onFocusChanged(QWidget* old, QWidget* now);
void setOutlineEditEnabled(bool enabled);
void setItemEditEnabled(bool enabled);
bool canEditOutline();
void onOutlineSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected);
void onTextSelect();

View File

@ -94,6 +94,14 @@ ProjectModel::BasePoolObjectNode* ProjectModel::CollectionNode::nodeOfIndex(int
return static_cast<BasePoolObjectNode*>(m_children[idx].get());
}
ProjectModel::BasePoolObjectNode* ProjectModel::CollectionNode::nodeOfId(amuse::ObjectId id) const
{
int idx = indexOfId(id);
if (idx < 0)
return nullptr;
return nodeOfIndex(idx);
}
ProjectModel::ProjectModel(const QString& path, QObject* parent)
: QAbstractItemModel(parent), m_dir(path), m_nullProxy(this)
{

View File

@ -207,6 +207,7 @@ public:
int indexOfId(amuse::ObjectId id) const;
amuse::ObjectId idOfIndex(int idx) const;
BasePoolObjectNode* nodeOfIndex(int idx) const;
BasePoolObjectNode* nodeOfId(amuse::ObjectId id) const;
};
struct BasePoolObjectNode : INode
{

View File

@ -6,6 +6,7 @@
#include "boo/IApplication.hpp"
#include <QResource>
#include <QCommandLineParser>
#include <logvisor/logvisor.hpp>
using namespace std::literals;
@ -91,6 +92,9 @@ int main(int argc, char* argv[])
MacOSSetDarkAppearance();
#endif
logvisor::RegisterConsoleLogger();
logvisor::RegisterStandardExceptions();
BooInterface booApp;
boo::APP = &booApp;

View File

@ -0,0 +1,75 @@
<?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="24"
height="24"
viewBox="0 0 6.3499998 6.3500004"
version="1.1"
id="svg8"
inkscape:version="0.92.2 2405546, 2018-03-11"
sodipodi:docname="IconAdd.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#353535"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="111.23937"
inkscape:cx="14.81887"
inkscape:cy="16.006269"
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"
gridtolerance="4"
showguides="false"
showborder="true"
objecttolerance="13">
<inkscape:grid
type="xygrid"
id="grid817"
empspacing="0"
spacingx="0.52916666"
spacingy="0.52916666"
visible="true" />
</sodipodi:namedview>
<metadata
id="metadata5">
<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,-290.64999)">
<path
style="fill:#66ff00;stroke:none;stroke-width:0.26458335px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 0,292.76666 h 2.1166667 v -2.11667 h 2.1166667 v 2.11667 h 2.1166665 v 2.11666 H 4.2333334 v 2.11667 H 2.1166667 v -2.11667 H 0 Z"
id="path846"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,75 @@
<?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="24"
height="24"
viewBox="0 0 6.3499998 6.3500004"
version="1.1"
id="svg8"
inkscape:version="0.92.2 2405546, 2018-03-11"
sodipodi:docname="IconRemove.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#353535"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="39.327341"
inkscape:cx="14.81887"
inkscape:cy="14.927514"
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"
gridtolerance="4"
showguides="false"
showborder="true"
objecttolerance="13">
<inkscape:grid
type="xygrid"
id="grid817"
empspacing="0"
spacingx="0.52916666"
spacingy="0.52916666"
visible="true" />
</sodipodi:namedview>
<metadata
id="metadata5">
<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,-290.64999)">
<path
style="fill:#ff0000;stroke:none;stroke-width:0.26458335px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 0,292.76666 v 2.11666 h 6.3499999 v -2.11666 z"
id="path867"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -181,6 +181,62 @@
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>LayersEditor</name>
<message>
<location filename="../LayersEditor.cpp" line="442"/>
<source>Add Row</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../LayersEditor.cpp" line="442"/>
<source>Remove Row</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>LayersModel</name>
<message>
<location filename="../LayersEditor.cpp" line="186"/>
<source>SoundMacro</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../LayersEditor.cpp" line="188"/>
<source>Key Lo</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../LayersEditor.cpp" line="190"/>
<source>Key Hi</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../LayersEditor.cpp" line="192"/>
<source>Transpose</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../LayersEditor.cpp" line="194"/>
<source>Volume</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../LayersEditor.cpp" line="196"/>
<source>Prio Off</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../LayersEditor.cpp" line="198"/>
<source>Span</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../LayersEditor.cpp" line="200"/>
<source>Pan</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MainWindow</name>
<message>
@ -396,13 +452,13 @@
</message>
<message>
<location filename="../MainWindow.cpp" line="225"/>
<location filename="../MainWindow.cpp" line="576"/>
<location filename="../MainWindow.cpp" line="581"/>
<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="577"/>
<location filename="../MainWindow.cpp" line="582"/>
<source>Directory empty</source>
<translation type="unfinished"></translation>
</message>
@ -447,122 +503,122 @@
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="559"/>
<location filename="../MainWindow.cpp" line="564"/>
<source>New Project</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="582"/>
<location filename="../MainWindow.cpp" line="587"/>
<source>The directory at &apos;%1&apos; does not exist.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="583"/>
<location filename="../MainWindow.cpp" line="588"/>
<source>Bad Directory</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="598"/>
<location filename="../MainWindow.cpp" line="603"/>
<source>Opening</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="598"/>
<location filename="../MainWindow.cpp" line="676"/>
<location filename="../MainWindow.cpp" line="759"/>
<location filename="../MainWindow.cpp" line="804"/>
<location filename="../MainWindow.cpp" line="603"/>
<location filename="../MainWindow.cpp" line="681"/>
<location filename="../MainWindow.cpp" line="764"/>
<location filename="../MainWindow.cpp" line="809"/>
<source>Scanning Project</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="610"/>
<location filename="../MainWindow.cpp" line="615"/>
<source>Opening %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="622"/>
<location filename="../MainWindow.cpp" line="627"/>
<source>Open Project</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="676"/>
<location filename="../MainWindow.cpp" line="681"/>
<source>Reloading Samples</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="688"/>
<location filename="../MainWindow.cpp" line="693"/>
<source>Scanning %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="698"/>
<location filename="../MainWindow.cpp" line="703"/>
<source>Import Project</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="707"/>
<location filename="../MainWindow.cpp" line="712"/>
<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="708"/>
<location filename="../MainWindow.cpp" line="713"/>
<source>Unsupported MusyX Container</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="713"/>
<location filename="../MainWindow.cpp" line="718"/>
<source>Sample Import Mode</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="714"/>
<location filename="../MainWindow.cpp" line="719"/>
<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="718"/>
<location filename="../MainWindow.cpp" line="723"/>
<source>Import Compressed</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="718"/>
<location filename="../MainWindow.cpp" line="723"/>
<source>Import WAVs</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="718"/>
<location filename="../MainWindow.cpp" line="723"/>
<source>Import Both</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="734"/>
<location filename="../MainWindow.cpp" line="739"/>
<source>Raw Import Mode</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="735"/>
<location filename="../MainWindow.cpp" line="740"/>
<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="745"/>
<location filename="../MainWindow.cpp" line="750"/>
<source>Project Name</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="745"/>
<location filename="../MainWindow.cpp" line="750"/>
<source>What should this project be named?</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="759"/>
<location filename="../MainWindow.cpp" line="804"/>
<location filename="../MainWindow.cpp" line="764"/>
<location filename="../MainWindow.cpp" line="809"/>
<source>Importing</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="771"/>
<location filename="../MainWindow.cpp" line="813"/>
<location filename="../MainWindow.cpp" line="776"/>
<location filename="../MainWindow.cpp" line="818"/>
<source>Importing %1</source>
<translation type="unfinished"></translation>
</message>
@ -594,37 +650,37 @@
<context>
<name>ProjectModel</name>
<message>
<location filename="../ProjectModel.cpp" line="226"/>
<location filename="../ProjectModel.cpp" line="234"/>
<source>Sound Macros</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ProjectModel.cpp" line="245"/>
<location filename="../ProjectModel.cpp" line="253"/>
<source>ADSRs</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ProjectModel.cpp" line="256"/>
<location filename="../ProjectModel.cpp" line="264"/>
<source>Curves</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ProjectModel.cpp" line="268"/>
<location filename="../ProjectModel.cpp" line="276"/>
<source>Keymaps</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ProjectModel.cpp" line="275"/>
<location filename="../ProjectModel.cpp" line="283"/>
<source>Layers</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ProjectModel.cpp" line="282"/>
<location filename="../ProjectModel.cpp" line="290"/>
<source>Samples</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ProjectModel.cpp" line="434"/>
<location filename="../ProjectModel.cpp" line="442"/>
<source>Delete %1</source>
<translation type="unfinished"></translation>
</message>

View File

@ -26,6 +26,8 @@
<file>IconKill.svg</file>
<file>IconSample.svg</file>
<file>IconPaintbrush.svg</file>
<file>IconAdd.svg</file>
<file>IconRemove.svg</file>
</qresource>
<qresource prefix="/bg">
<file>FaceGrey.svg</file>

View File

@ -1358,13 +1358,13 @@ struct LayerMapping : BigDNA
{
AT_DECL_DNA_YAML
SoundMacroIdDNA<athena::Big> macro;
Value<atInt8> keyLo;
Value<atInt8> keyHi;
Value<atInt8> transpose;
Value<atInt8> volume;
Value<atInt8> prioOffset;
Value<atInt8> span;
Value<atInt8> pan;
Value<atInt8> keyLo = 0;
Value<atInt8> keyHi = 127;
Value<atInt8> transpose = 0;
Value<atInt8> volume = 127;
Value<atInt8> prioOffset = 0;
Value<atInt8> span = 0;
Value<atInt8> pan = 64;
LayerMapping() = default;

View File

@ -162,6 +162,9 @@ public:
/** Obtain list of active voices */
std::list<ObjToken<Voice>>& getActiveVoices() { return m_activeVoices; }
/** Obtain total active voice count (including child voices) */
size_t getNumTotalActiveVoices() const;
/** Obtain list of active sequencers */
std::list<ObjToken<Sequencer>>& getActiveSequencers() { return m_activeSequencers; }

View File

@ -351,6 +351,8 @@ public:
{
m_ctrlValsSelf.reset();
m_extCtrlVals = cvs;
for (ObjToken<Voice>& vox : m_childVoices)
vox->installCtrlValues(cvs);
}
/** Get MIDI pitch wheel value on voice */

View File

@ -353,11 +353,6 @@ AudioGroupPool AudioGroupPool::CreateAudioGroupPool(SystemStringView groupPath)
}
}
amuse::KeymapId id = 42;
amuse::KeymapId::CurNameDB->registerPair("test", id);
auto& kmOut = ret.m_keymaps[id];
kmOut = MakeObj<std::array<Keymap, 128>>();
return ret;
}

View File

@ -35,11 +35,17 @@ static void ReadRangedObjectIds(NameDB* db, athena::io::IStreamReader& r, NameDB
ObjectId useId = i;
if (tp == NameDB::Type::Layer)
useId.id |= 0x8000;
else if (tp == NameDB::Type::Keymap)
useId.id |= 0x4000;
db->registerPair(NameDB::generateName(useId, tp), useId);
}
}
else
{
if (tp == NameDB::Type::Layer)
id |= 0x8000;
else if (tp == NameDB::Type::Keymap)
id |= 0x4000;
db->registerPair(NameDB::generateName(id, tp), id);
}
}

View File

@ -1,6 +1,8 @@
#include "amuse/Common.hpp"
#include "logvisor/logvisor.hpp"
using namespace std::literals;
namespace amuse
{
static logvisor::Module Log("amuse");
@ -180,11 +182,16 @@ void PageObjectIdDNA<DNAE>::_write(athena::io::YAMLDocWriter& w)
std::string_view name = LayersId::CurNameDB->resolveNameFromId(id);
w.writeString(nullptr, name);
}
else
else if (id.id & 0x4000)
{
std::string_view name = KeymapId::CurNameDB->resolveNameFromId(id);
w.writeString(nullptr, name);
}
else
{
std::string_view name = SoundMacroId::CurNameDB->resolveNameFromId(id);
w.writeString(nullptr, name);
}
}
template <athena::Endian DNAE>
const char* PageObjectIdDNA<DNAE>::DNAType()
@ -307,7 +314,10 @@ std::string_view NameDB::resolveNameFromId(ObjectId id) const
{
auto search = m_idToString.find(id);
if (search == m_idToString.cend())
Log.report(logvisor::Fatal, "Unable to resolve ID 0x%04X", id.id);
{
Log.report(logvisor::Error, "Unable to resolve ID 0x%04X", id.id);
return ""sv;
}
return search->second;
}
@ -315,7 +325,10 @@ ObjectId NameDB::resolveIdFromName(std::string_view str) const
{
auto search = m_stringToId.find(std::string(str));
if (search == m_stringToId.cend())
Log.report(logvisor::Fatal, "Unable to resolve name %s", str.data());
{
Log.report(logvisor::Error, "Unable to resolve name %s", str.data());
return {};
}
return search->second;
}

View File

@ -496,4 +496,12 @@ void Engine::sendMacroMessage(ObjectId macroId, int32_t val)
for (ObjToken<Sequencer>& seq : m_activeSequencers)
seq->sendMacroMessage(macroId, val);
}
size_t Engine::getNumTotalActiveVoices() const
{
size_t ret = 0;
for (const auto& vox : m_activeVoices)
ret += vox->getTotalVoices();
return ret;
}
}

View File

@ -776,6 +776,8 @@ ObjToken<Voice> Voice::_startChildMacro(ObjectId macroId, int macroStep, double
(*vox)->setVolume(m_targetUserVol);
(*vox)->setPan(m_curPan);
(*vox)->setSurroundPan(m_curSpan);
if (m_extCtrlVals)
(*vox)->installCtrlValues(m_extCtrlVals);
return *vox;
}
@ -835,7 +837,7 @@ bool Voice::_loadLayer(const std::vector<LayerMapping>& layer, double ticksPerSe
if (m_voxState != VoiceState::Playing)
{
ret |= loadMacroObject(mapping.macro.id, 0, ticksPerSec, mappingKey, midiVel, midiMod, pushPc);
m_curVol = mapping.volume / 127.f;
m_curUserVol = m_targetUserVol = mapping.volume / 127.f;
_setPan((mapping.pan - 64) / 64.f);
_setSurroundPan((mapping.span - 64) / 64.f);
}
@ -845,7 +847,7 @@ bool Voice::_loadLayer(const std::vector<LayerMapping>& layer, double ticksPerSe
_startChildMacro(mapping.macro.id, 0, ticksPerSec, mappingKey, midiVel, midiMod, pushPc);
if (vox)
{
vox->m_curVol = mapping.volume / 127.f;
vox->m_curUserVol = vox->m_targetUserVol = mapping.volume / 127.f;
vox->_setPan((mapping.pan - 64) / 64.f);
vox->_setSurroundPan((mapping.span - 64) / 64.f);
ret = true;
@ -892,12 +894,18 @@ bool Voice::loadPageObject(ObjectId objectId, double ticksPerSec, uint8_t midiKe
if (layer)
return _loadLayer(*layer, ticksPerSec, midiKey, midiVel, midiMod);
}
else
else if (objectId.id & 0x4000)
{
const Keymap* keymap = m_audioGroup.getPool().keymap(objectId);
if (keymap)
return _loadKeymap(keymap, ticksPerSec, midiKey, midiVel, midiMod);
}
else
{
const SoundMacro* sm = m_audioGroup.getPool().soundMacro(objectId);
if (sm)
return _loadSoundMacro(objectId, sm, 0, ticksPerSec, midiKey, midiVel, midiMod);
}
return false;
}