patchboard cut+copy+paste (and delete shortcut)

portability/boost
zetaPRIME 2019-01-25 20:07:04 -05:00
parent 7238916c24
commit 6c6c62e4ab
9 changed files with 232 additions and 19 deletions

16
notes
View File

@ -45,13 +45,13 @@ project data {
TODO {
immediate frontburner {
patchboard copy+paste! cbor array of node objects
position: copy relative to centroid, paste relative to center of viewport
also record connections between those particular nodes!
- patchboard copy+paste! cbor array of node objects
- position: copy relative to centroid, paste relative to center of viewport
- also record connections between those particular nodes!
- provide conversions to/from qcborvalue in node.cpp?
- while we're here, add file import/export of any given node (.xyn)
probably exclude i/o ports from copy+paste lest things futz up on the parent
- probably exclude i/o ports from copy+paste lest things futz up on the parent
}
-? return the latency/buffer size to 100ms once multithreading is streamlined
@ -65,7 +65,7 @@ TODO {
switch InstrumentCore over to central storage for notes/tweens
bugs to fix {
graph connections sometimes spawn in duplicated :|
-? graph connections sometimes spawn in duplicated :|
on starting playback, sometimes a "thunk" sneaks into the waveform?
@ -112,6 +112,12 @@ TODO {
}
}
Xynamo (hybrid? synth) {
3? 5? oscillators; several different waveforms available, independent filtering, wavefolding and (hard) wrapping...
individually controllable??
rail system!
}
- dumb per-cycle atomic memory allocator from fixed pool for port buffer allocations
? can also set up a tlsf pool per worker; prefix allocations with single byte identifier indicating which one they came from,
? and defer freeing operations via message queues

View File

@ -93,6 +93,7 @@ void Graph::loadData(const QCborMap& m) {
{ /* children */ } {
QCborArray c = m.value("children").toArray();
children.reserve(static_cast<size_t>(c.size()));
for (auto chmv : c) Node::fromCbor(chmv, g); // oh, that's it?
}

View File

@ -12,14 +12,17 @@ using namespace Xybrid::Config;
#include "audio/audioengine.h"
using namespace Xybrid::Audio;
#include "util/strings.h"
#include "util/ycombinator.h"
#include <algorithm>
#include <QDebug>
#include <QThread>
#include <QMetaEnum>
#include <QCborValue>
#include <QCborMap>
#include <QCborArray>
Port::~Port() {
// clean up others' connection lists
@ -77,17 +80,6 @@ std::shared_ptr<Port> Port::makePort(DataType dt) {
return std::make_shared<Port>();
}
std::shared_ptr<Node> Node::fromCbor(const QCborMap& m, std::shared_ptr<Graph> parent) {
auto ch = PluginRegistry::createInstance(m.value("id").toString().toStdString());
ch->parentTo(parent);
ch->name = m.value("name").toString().toStdString();
ch->x = static_cast<int>(m.value("x").toInteger());
ch->y = static_cast<int>(m.value("y").toInteger());
ch->loadData(m);
return ch;
}
std::shared_ptr<Node> Node::fromCbor(const QCborValue& m, std::shared_ptr<Graph> parent) { return fromCbor(m.toMap(), parent); }
QCborMap Node::toCbor() const {
QCborMap m;
@ -100,6 +92,123 @@ QCborMap Node::toCbor() const {
return m;
}
std::shared_ptr<Node> Node::fromCbor(const QCborMap& m, std::shared_ptr<Graph> parent) {
auto ch = PluginRegistry::createInstance(m.value("id").toString().toStdString());
ch->parentTo(parent);
ch->name = m.value("name").toString().toStdString();
ch->x = static_cast<int>(m.value("x").toInteger());
ch->y = static_cast<int>(m.value("y").toInteger());
ch->loadData(m);
return ch;
}
std::shared_ptr<Node> Node::fromCbor(const QCborValue& m, std::shared_ptr<Graph> parent) { return fromCbor(m.toMap(), parent); }
QCborMap Node::multiToCbor(std::vector<std::shared_ptr<Node>>& v) {
QCborMap m;
std::unordered_map<Node*, int> indices;
{ /* nodes */ } {
QCborArray nm;
int idx = 0;
for (auto n : v) {
if (n->isVolatile()) continue; // skip things with volatile locality (i/o ports etc.)
indices[n.get()] = idx++;
nm << n->toCbor();
}
m.insert(QString("nodes"), nm);
}
{ /* connections */ } {
QCborArray cm;
for (auto n : v) {
if (n->isVolatile()) continue; // already skipped
int idx = indices[n.get()];
for (auto dt : n->outputs) {
for (auto op : dt.second) {
auto o = op.second;
o->cleanConnections(); // let's just do some groundskeeping here
for (auto iw : o->connections) {
auto i = iw.lock();
if (auto in = indices.find(i->owner.lock().get()); in != indices.end()) { // only connections within the collection
QCborArray c;
c << idx;
c << Util::enumName(o->dataType());
c << o->index;
c << in->second;
c << Util::enumName(i->dataType());
c << i->index;
cm << c;
}
}
}
}
}
m.insert(QString("connections"), cm);
}
{ /* center */ } {
int count = 0;
QPoint center;
for (auto n : v) {
if (n->isVolatile()) continue;
center += QPoint(n->x, n->y);
count++;
}
center /= count;
m.insert(QString("center"), QCborArray({center.x(), center.y()}));
}
return m;
}
std::vector<std::shared_ptr<Node>> Node::multiFromCbor(const QCborMap& m, std::shared_ptr<Graph> parent, QPoint cp) {
std::vector<std::shared_ptr<Node>> v;
QPoint center;
{ /* center point */ } {
QCborArray c = m.value("center").toArray();
center.setX(static_cast<int>(c.at(0).toInteger()));
center.setY(static_cast<int>(c.at(1).toInteger()));
}
{ /* nodes */ } {
QCborArray n = m.value("nodes").toArray();
v.reserve(static_cast<size_t>(n.size()));
for (auto nm : n) v.push_back(Node::fromCbor(nm, parent));
}
{ /* connections */ } {
QCborArray cn = m.value("connections").toArray();
auto pmt = QMetaType::metaObjectForType(QMetaType::type("Xybrid::Data::Port"));
auto dtm = pmt->enumerator(pmt->indexOfEnumerator("DataType"));
for (auto cv : cn) {
auto c = cv.toArray();
if (c.empty()) continue;
auto on = v[static_cast<size_t>(c[0].toInteger())];
auto in = v[static_cast<size_t>(c[3].toInteger())];
auto op = on->port(Port::Output, static_cast<Port::DataType>(dtm.keyToValue(c[1].toString().toStdString().c_str())), static_cast<uint8_t>(c[2].toInteger()));
auto ip = in->port(Port::Input, static_cast<Port::DataType>(dtm.keyToValue(c[4].toString().toStdString().c_str())), static_cast<uint8_t>(c[5].toInteger()));
if (op && ip) op->connect(ip);
}
}
if (!cp.isNull()) { // offset and such
QPoint off = cp - center;
for (auto n : v) {
n->x += off.x();
n->y += off.y();
}
}
return v;
}
std::vector<std::shared_ptr<Node>> Node::multiFromCbor(const QCborValue& m, std::shared_ptr<Graph> parent, QPoint cp) { return multiFromCbor(m.toMap(), parent, cp); }
void Node::parentTo(std::shared_ptr<Graph> graph) {
auto t = shared_from_this(); // keep alive during reparenting
if (auto p = parent.lock(); p) {

View File

@ -10,6 +10,7 @@
#include <QMutex>
#include <QPointer>
#include <QObject>
#include <QPoint>
class QPainter;
class QStyleOptionGraphicsItem;
@ -98,10 +99,13 @@ namespace Xybrid::Data {
Node() = default;
virtual ~Node() = default;
QCborMap toCbor() const;
static std::shared_ptr<Node> fromCbor(const QCborMap&, std::shared_ptr<Graph> = nullptr);
static std::shared_ptr<Node> fromCbor(const QCborValue&, std::shared_ptr<Graph> = nullptr);
QCborMap toCbor() const;
static QCborMap multiToCbor(std::vector<std::shared_ptr<Node>>&);
static std::vector<std::shared_ptr<Node>> multiFromCbor(const QCborMap&, std::shared_ptr<Graph> = nullptr, QPoint = QPoint());
static std::vector<std::shared_ptr<Node>> multiFromCbor(const QCborValue&, std::shared_ptr<Graph> = nullptr, QPoint = QPoint());
void parentTo(std::shared_ptr<Graph>);
@ -116,11 +120,13 @@ namespace Xybrid::Data {
virtual void init() { }
virtual void reset() { }
virtual void process() { }
virtual void saveData(QCborMap&) const { }
virtual void loadData(const QCborMap&) { }
virtual void process() { }
virtual std::string pluginName() const;
/// If true, excluded from copy/paste operations.
virtual bool isVolatile() const { return false; }
virtual void onGadgetCreated() { }
virtual void drawCustomChrome(QPainter*, const QStyleOptionGraphicsItem*) { }

View File

@ -468,6 +468,8 @@ bool MainWindow::selectPatternForEditing(Pattern* pattern) {
void MainWindow::openGraph(const std::shared_ptr<Data::Graph>& g) {
if (!g) return; // invalid
QPointF scrollPt(g->viewX, g->viewY);
delete ui->patchboardView->scene();
//ui->patchboardView->setScene(nullptr);
ui->patchboardView->setScene(new PatchboardScene(ui->patchboardView, g));
QScroller::scroller(ui->patchboardView)->scrollTo(scrollPt, 0);
QScroller::scroller(ui->patchboardView)->scrollTo(scrollPt, 1);

View File

@ -17,6 +17,8 @@ namespace Xybrid::Gadgets {
void setPort(Data::Port::Type type, Data::Port::DataType dataType, uint8_t index);
bool isVolatile() const override { return true; }
void process() override;
void onRename() override;

View File

@ -197,7 +197,7 @@ void NodeObject::setGadgetSize(QPointF p) {
void NodeObject::promptDelete() {
QPointer<NodeObject> t = this;
if (QMessageBox::warning(nullptr, "Are you sure?", QString("Remove node? (This cannot be undone!)"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) return;
if (QMessageBox::warning(nullptr, "Are you sure?", QString("Remove node? (This cannot be undone!)"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) != QMessageBox::Yes) return;
if (t) {
t->node->parentTo(nullptr); // unparent node so it gets deleted
delete t;

View File

@ -11,6 +11,16 @@ using Xybrid::UI::PatchboardScene;
#include <QMenu>
#include <QTimer>
#include <QShortcut>
#include <QGuiApplication>
#include <QClipboard>
#include <QMimeData>
#include <QMessageBox>
#include <QCborValue>
#include <QCborMap>
#include <QCborArray>
#include <QGraphicsSceneContextMenuEvent>
#include "data/graph.h"
@ -29,6 +39,12 @@ using namespace Xybrid::Audio;
#include "config/colorscheme.h"
QShortcut* PatchboardScene::shortcut(QKeySequence s) {
auto c = new QShortcut(s, view);
connect(this, &QObject::destroyed, c, [c] {delete c;});
return c;
}
PatchboardScene::PatchboardScene(QGraphicsView* parent, const std::shared_ptr<Xybrid::Data::Graph>& g) : QGraphicsScene(parent) {
graph = g;
view = parent;
@ -39,6 +55,73 @@ PatchboardScene::PatchboardScene(QGraphicsView* parent, const std::shared_ptr<Xy
connect(view->verticalScrollBar(), &QScrollBar::rangeChanged, this, &PatchboardScene::queueResize);
connect(this, &QGraphicsScene::changed, this, &PatchboardScene::queueResize);
auto copy = [this] {
auto sel = this->selectedItems();
if (sel.empty()) return; // nope
auto* clip = QGuiApplication::clipboard();
auto* data = new QMimeData();
std::vector<std::shared_ptr<Node>> v;
v.reserve(static_cast<size_t>(sel.size()));
QRectF r;
for (auto s : sel) {
auto n = static_cast<NodeObject*>(s)->getNode();
if (n->isVolatile()) continue;
v.push_back(n);
r = r.united(s->sceneBoundingRect());
}
auto cbor = Node::multiToCbor(v);
{ /* override center point */ } {
auto c = r.center().toPoint();
cbor[QString("center")] = QCborArray({c.x(), c.y()});
}
data->setData("xybrid-internal/x-graph-copy", cbor.toCborValue().toCbor());
clip->setMimeData(data);
};
connect(shortcut(QKeySequence(QKeySequence::StandardKey::Delete)), &QShortcut::activated, this, [this] {
auto sel = this->selectedItems();
if (sel.empty()) return;
if (QMessageBox::warning(nullptr, "Are you sure?", QString("Remove %1 node(s)? (This cannot be undone!)").arg(sel.size()), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) != QMessageBox::Yes) return;
for (auto n : sel) {
if (!n) continue;
static_cast<NodeObject*>(n)->getNode()->parentTo(nullptr);
delete n;
}
});
connect(shortcut(QKeySequence(QKeySequence::StandardKey::Cut)), &QShortcut::activated, this, [this, copy] {
auto sel = this->selectedItems();
if (sel.empty()) return;
copy();
if (QMessageBox::warning(nullptr, "Are you sure?", QString("Remove %1 node(s)? (This cannot be undone!)").arg(sel.size()), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) != QMessageBox::Yes) return;
for (auto n : sel) {
if (!n) continue;
static_cast<NodeObject*>(n)->getNode()->parentTo(nullptr);
delete n;
}
});
connect(shortcut(QKeySequence(QKeySequence::StandardKey::Copy)), &QShortcut::activated, this, [copy] { copy(); });
connect(shortcut(QKeySequence(QKeySequence::StandardKey::Paste)), &QShortcut::activated, this, [this] {
const auto* clip = QGuiApplication::clipboard();
const auto* data = clip->mimeData();
if (!data->hasFormat("xybrid-internal/x-graph-copy")) return; // no pattern data
auto center = view->mapToScene(view->viewport()->visibleRegion().boundingRect()).boundingRect().center().toPoint();
auto v = Node::multiFromCbor(QCborValue::fromCbor(data->data("xybrid-internal/x-graph-copy")), graph, center);
setSelectionArea(QPainterPath()); // deselect all
for (auto n : v) { // and select pasted objects
auto o = new NodeObject(n);
addItem(o);
o->setSelected(true);
}
});
refresh();
}

View File

@ -6,6 +6,8 @@
#include <QGraphicsScene>
#include <QGraphicsView>
class QShortcut;
namespace Xybrid::Data { class Graph; }
namespace Xybrid::UI {
@ -22,6 +24,8 @@ namespace Xybrid::UI {
void startPreview(int, int16_t);
void stopPreview(int);
QShortcut* shortcut(QKeySequence);
public:
PatchboardScene(QGraphicsView* view, const std::shared_ptr<Data::Graph>& graph);
~PatchboardScene() override = default;