235 lines
8.3 KiB
C++
235 lines
8.3 KiB
C++
#include "patchboardscene.h"
|
|
using Xybrid::UI::PatchboardScene;
|
|
|
|
#include <cmath>
|
|
|
|
#include <QDebug>
|
|
#include <QScrollBar>
|
|
#include <QGraphicsItem>
|
|
#include <QMainWindow>
|
|
#include <QKeyEvent>
|
|
#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"
|
|
#include "data/project.h"
|
|
using namespace Xybrid::Data;
|
|
#include "config/uiconfig.h"
|
|
#include "config/pluginregistry.h"
|
|
using namespace Xybrid::Config;
|
|
#include "fileops.h"
|
|
|
|
#include "audio/audioengine.h"
|
|
using namespace Xybrid::Audio;
|
|
|
|
#include "util/keys.h"
|
|
|
|
#include "ui/patchboard/nodeobject.h"
|
|
|
|
#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) : GadgetScene(parent) {
|
|
graph = g;
|
|
|
|
connect(view->horizontalScrollBar(), &QScrollBar::valueChanged, this, &PatchboardScene::queueResize);
|
|
connect(view->verticalScrollBar(), &QScrollBar::valueChanged, this, &PatchboardScene::queueResize);
|
|
connect(view->horizontalScrollBar(), &QScrollBar::rangeChanged, this, &PatchboardScene::queueResize);
|
|
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();
|
|
}
|
|
|
|
void PatchboardScene::drawBackground(QPainter* painter, const QRectF& rect) {
|
|
painter->setBrush(QBrush(Config::colorScheme.patternBg));
|
|
painter->setPen(QPen(Qt::PenStyle::NoPen));
|
|
painter->drawRect(rect);
|
|
|
|
const constexpr int step = 32; // grid size
|
|
painter->setPen(QPen(QColor(127, 127, 127, 63), 1));
|
|
for (auto y = std::floor(rect.top() / step) * step + .5; y < rect.bottom(); y += step) // NOLINT
|
|
painter->drawLine(QPointF(rect.left(), y), QPointF(rect.right(), y));
|
|
for (auto x = std::floor(rect.left() / step) * step + .5; x < rect.right(); x += step) // NOLINT
|
|
painter->drawLine(QPointF(x, rect.top()), QPointF(x, rect.bottom()));
|
|
}
|
|
|
|
void PatchboardScene::contextMenuEvent(QGraphicsSceneContextMenuEvent* e) {
|
|
// only override if nothing inside picks it up
|
|
QGraphicsScene::contextMenuEvent(e);
|
|
if (e->isAccepted()) return;
|
|
|
|
auto p = e->scenePos();
|
|
auto* m = new QMenu();
|
|
PluginRegistry::populatePluginMenu(m->addMenu("Add..."), [this, p](std::shared_ptr<Node> n) {
|
|
n->x = static_cast<int>(p.x());
|
|
n->y = static_cast<int>(p.y());
|
|
n->parentTo(graph);
|
|
|
|
addItem(new NodeObject(n));
|
|
}, graph.get());
|
|
m->addAction("Import...", this, [this, p] {
|
|
if (auto fileName = FileOps::showOpenDialog(nullptr, "Import node...", Config::Directories::presets, FileOps::Filter::node); !fileName.isEmpty()) {
|
|
auto n = FileOps::loadNode(fileName, graph);
|
|
if (!n) return; // right, that can return null
|
|
n->x = static_cast<int>(p.x());
|
|
n->y = static_cast<int>(p.y());
|
|
|
|
addItem(new NodeObject(n));
|
|
}
|
|
});
|
|
|
|
m->setAttribute(Qt::WA_DeleteOnClose);
|
|
m->popup(e->screenPos());
|
|
}
|
|
|
|
void PatchboardScene::keyPressEvent(QKeyEvent* e) {
|
|
QGraphicsScene::keyPressEvent(e);
|
|
if (!e->isAccepted() && !e->isAutoRepeat()) {
|
|
auto note = Util::keyToNote(e->key());
|
|
if (note >= 0) {
|
|
if (e->modifiers() & Qt::Modifier::SHIFT) note += 24;
|
|
startPreview(Util::unshiftedKey(e->key()), note);
|
|
}
|
|
}
|
|
}
|
|
|
|
void PatchboardScene::keyReleaseEvent(QKeyEvent* e) {
|
|
QGraphicsScene::keyReleaseEvent(e);
|
|
if (!e->isAccepted() && !e->isAutoRepeat()) stopPreview(Util::unshiftedKey(e->key()));
|
|
}
|
|
|
|
void PatchboardScene::startPreview(int key, int16_t note) {
|
|
stopPreview(key); // end current preview first, if applicable
|
|
auto p = graph->project->shared_from_this();
|
|
bool subgraph = true && graph->parent.lock();
|
|
previewKey[key] = {audioEngine->previewPort(), audioEngine->preview(p, -1, note, 0, subgraph ? graph.get() : nullptr)};
|
|
|
|
}
|
|
|
|
void PatchboardScene::stopPreview(int key) {
|
|
if (auto k = previewKey.find(key); k != previewKey.end()) {
|
|
auto p = graph->project->shared_from_this();
|
|
bool subgraph = true && graph->parent.lock();
|
|
//audioEngine->preview(p, k->second[0], k->second[1], false);
|
|
audioEngine->preview(p, k->second.first, -2, k->second.second, subgraph ? graph.get() : nullptr);
|
|
previewKey.erase(k);
|
|
}
|
|
}
|
|
|
|
void PatchboardScene::queueResize() {
|
|
if (!resizeQueued) {
|
|
resizeQueued = true;
|
|
QTimer::singleShot(1, this, &PatchboardScene::autoResize);
|
|
}
|
|
}
|
|
|
|
void PatchboardScene::autoResize() {
|
|
if (!view) return;
|
|
auto vrect = view->mapToScene(view->viewport()->visibleRegion().boundingRect()).boundingRect();
|
|
auto rect = itemsBoundingRect()
|
|
.united(vrect)
|
|
.united(QRectF(0, 0, 1, 1));
|
|
rect.setTopLeft(QPointF(0, 0));
|
|
setSceneRect(rect);
|
|
graph->viewX = static_cast<int>(vrect.left());
|
|
graph->viewY = static_cast<int>(vrect.top());
|
|
resizeQueued = false;
|
|
}
|
|
|
|
void PatchboardScene::refresh() {
|
|
// build scene from graph
|
|
clear();
|
|
|
|
for (auto n : graph->children) {
|
|
auto* o = new NodeObject(n);
|
|
addItem(o);
|
|
}
|
|
|
|
autoResize();
|
|
}
|