xybrid/xybrid/ui/patchboard/patchboardscene.cpp

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();
}