initial pattern data copy+paste support

portability/boost
zetaPRIME 2019-01-08 19:11:44 -05:00
parent 875a65d977
commit 7001adb132
4 changed files with 94 additions and 5 deletions

3
notes
View File

@ -46,7 +46,8 @@ project data {
TODO {
immediate frontburner {
- transpose selection (alt+up/down for semitone/0x01, left+right for octave/0x10)
pattern cut+copy+paste
- pattern cut+copy+paste
^ channel-column splitting
add metadata and pattern properties (artist, song title, project bpm; pattern name, length etc.)
probably move the "process all nodes" part of tick processing into its own function?

View File

@ -57,7 +57,7 @@ namespace Xybrid::Data {
else params->push_back(p);
}
void insertParam(size_t index, char c = '.', unsigned char v = 0) {
void insertParam(size_t index, char c = ' ', unsigned char v = 0) {
std::array<unsigned char, 2> p {static_cast<unsigned char>(c), v};
if (!this->params) this->params.reset(new std::vector<std::array<unsigned char, 2>>({p}));
else {
@ -66,6 +66,13 @@ namespace Xybrid::Data {
}
}
/// Sets parameter at given index, adding struts before if necessary
void setParam(size_t index, char c = ' ', unsigned char v = 0) {
if (!this->params) this->params.reset(new std::vector<std::array<unsigned char, 2>>());
if (params->size() <= index) params->resize(index+1, {' ', 0});
(*params)[index] = {static_cast<unsigned char>(c), v};
}
void removeParam(size_t index) {
if (!this->params || this->params->size() <= index) return; // invalid index
if (this->params->size() == 1) { // deallocate vector entirely if empty

View File

@ -80,9 +80,7 @@ bool Xybrid::FileOps::saveProject(std::shared_ptr<Project> project, QString file
}
QCborArray row;
row << r.port << r.note;
if (r.params) for (auto p : *r.params) {
row << p[0] << p[1];
}
if (r.params) for (auto p : *r.params) row << p[0] << p[1];
rows << row;
}
if (!ch.rows.back().isEmpty()) needsCount = false; // omit extra count if any channel has data on the last row

View File

@ -26,6 +26,11 @@ using namespace Xybrid::Audio;
#include <QKeyEvent>
#include <QShortcut>
#include <QGuiApplication>
#include <QClipboard>
#include <QMimeData>
#include <QCborArray>
#include <QCborMap>
#include <QDebug>
#include <QHeaderView>
@ -90,6 +95,7 @@ PatternEditorView::PatternEditorView(QWidget *parent) : QTableView(parent) {
hdr->setModel(&*mdl->hprox);
{ /* set up hotkeys */ } {
// transpose notes
auto transpose = [this](int amt, int key = Qt::Key_Alt) {
auto p = mdl->getPattern();
auto sel = this->selectionModel()->selection().first();
@ -125,6 +131,83 @@ PatternEditorView::PatternEditorView(QWidget *parent) : QTableView(parent) {
connect(new QShortcut(QKeySequence("Alt+Down"), this), &QShortcut::activated, this, [transpose] { transpose(-1, Qt::Key_Down); });
connect(new QShortcut(QKeySequence("Alt+Right"), this), &QShortcut::activated, this, [transpose] { transpose(100, Qt::Key_Right); });
connect(new QShortcut(QKeySequence("Alt+Left"), this), &QShortcut::activated, this, [transpose] { transpose(-100, Qt::Key_Left); });
// cut/copy/paste
auto copy = [this] {
auto* clip = QGuiApplication::clipboard();
auto* data = new QMimeData();
auto p = mdl->getPattern();
auto sel = selectionModel()->selection()[0];
auto chMin = Util::channelForColumn(sel.left());
auto chMax = Util::channelForColumn(sel.right());
// cbor data format: (may change between versions, assume a given system only has one Xybrid version installed)
// [ integer first, last, n*[ integer port, note, (param, val)... ]... ]...
QCborArray root;
for (int ch = chMin; ch <= chMax; ch++) {
QCborArray chm;
int first = 0; // TODO ... later
int last = 255;
chm << first << last;
for (int r = sel.top(); r <= sel.bottom(); r++) {
QCborArray rm;
auto& row = p->rowAt(ch, r);
rm << row.port;
rm << row.note;
if (row.params) for (auto p : *row.params) rm << p[0] << p[1];
chm << rm;
}
root << chm;
}
data->setData("xybrid-internal/x-pattern-copy", root.toCborValue().toCbor());
clip->setMimeData(data);
};
connect(new QShortcut(QKeySequence(QKeySequence::StandardKey::Cut), this), &QShortcut::activated, this, [this, copy] {
copy();
this->edit(currentIndex(), EditTrigger::EditKeyPressed, new QKeyEvent(QKeyEvent::Type::KeyPress, Qt::Key_Delete, Qt::KeyboardModifier::NoModifier));
});
connect(new QShortcut(QKeySequence(QKeySequence::StandardKey::Copy), this), &QShortcut::activated, this, [copy] { copy(); });
connect(new QShortcut(QKeySequence(QKeySequence::StandardKey::Paste), this), &QShortcut::activated, this, [this] {
const auto* clip = QGuiApplication::clipboard();
const auto* data = clip->mimeData();
if (!data->hasFormat("xybrid-internal/x-pattern-copy")) return; // no pattern data
auto p = mdl->getPattern();
auto idx = currentIndex();
auto chMin = Util::channelForColumn(idx.column());
auto rMin = idx.row();
auto root = QCborValue::fromCbor(data->data("xybrid-internal/x-pattern-copy")).toArray();
auto cc = new CompositeCommand();
for (int ch = 0; ch < static_cast<int>(p->numChannels()) - chMin && ch < root.size(); ch++) {
auto chm = root[ch].toArray();
//int first = static_cast<int>(chm[0].toInteger());
//int last = static_cast<int>(chm[1].toInteger());
for (int r = 0; r < p->rows - rMin && r < chm.size() - 2; r++) {
auto rm = chm[r+2].toArray();
auto c = new PatternDeltaCommand(p, ch+chMin, r+rMin);
auto& row = c->row;
row.port = static_cast<int16_t>(rm[0].toInteger());
row.note = static_cast<int16_t>(rm[1].toInteger());
for (int p = 0; p < (rm.size() - 2) / 2; p++)
row.setParam(static_cast<size_t>(p), static_cast<char>(rm[p*2+2].toInteger()), static_cast<unsigned char>(rm[p*2+3].toInteger()));
cc->compose(c);
}
}
cc->commit("paste pattern data");
});
}
}