новый файл: CMakeLists.txt
новый файл: shell.nix новый файл: src/MainWindow.cpp новый файл: src/MainWindow.h новый файл: src/items/ArrowItem.cpp новый файл: src/items/ArrowItem.h новый файл: src/items/BlockItem.cpp новый файл: src/items/BlockItem.h новый файл: src/items/DiagramScene.cpp новый файл: src/items/DiagramScene.h новый файл: src/items/JunctionItem.cpp новый файл: src/items/JunctionItem.h новый файл: src/main.cpp изменено: src/items/ArrowItem.cpp изменено: src/items/ArrowItem.h изменено: src/items/BlockItem.h изменено: src/items/DiagramScene.cpp изменено: src/items/DiagramScene.h
This commit is contained in:
commit
53afa3f728
87 changed files with 48249 additions and 0 deletions
604
src/items/DiagramScene.cpp
Normal file
604
src/items/DiagramScene.cpp
Normal file
|
|
@ -0,0 +1,604 @@
|
|||
#include "DiagramScene.h"
|
||||
#include "items/BlockItem.h"
|
||||
#include "items/ArrowItem.h"
|
||||
#include "items/JunctionItem.h"
|
||||
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QKeyEvent>
|
||||
#include <QTimer>
|
||||
#include <QHash>
|
||||
#include <QSet>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
DiagramScene::DiagramScene(QObject* parent)
|
||||
: QGraphicsScene(parent)
|
||||
{
|
||||
// Ограничиваем рабочую область A4 (210x297 мм) в пикселях при 96 dpi (~793x1122).
|
||||
// По умолчанию используем альбомную ориентацию (ширина больше высоты).
|
||||
const qreal w = 1122;
|
||||
const qreal h = 793;
|
||||
setSceneRect(-w * 0.5, -h * 0.5, w, h);
|
||||
auto* frame = addRect(sceneRect(), QPen(QColor(80, 80, 80), 1.5, Qt::DashLine), Qt::NoBrush);
|
||||
frame->setZValue(-5);
|
||||
frame->setEnabled(false);
|
||||
frame->setAcceptedMouseButtons(Qt::NoButton);
|
||||
frame->setData(0, QVariant(QStringLiteral("static-frame")));
|
||||
pushSnapshot();
|
||||
}
|
||||
|
||||
BlockItem* DiagramScene::createBlockAt(const QPointF& scenePos) {
|
||||
auto* b = new BlockItem("Function", nullptr, m_nextBlockId++);
|
||||
addItem(b);
|
||||
b->setPos(scenePos - QPointF(100, 50)); // центрируем
|
||||
pushSnapshot();
|
||||
return b;
|
||||
}
|
||||
|
||||
BlockItem* DiagramScene::createBlockWithId(const QPointF& scenePos, int id, const QString& title) {
|
||||
m_nextBlockId = std::max(m_nextBlockId, id + 1);
|
||||
auto* b = new BlockItem(title, nullptr, id);
|
||||
addItem(b);
|
||||
b->setPos(scenePos);
|
||||
return b;
|
||||
}
|
||||
|
||||
DiagramScene::Edge DiagramScene::hitTestEdge(const QPointF& scenePos, QPointF* outScenePoint) const {
|
||||
const QRectF r = sceneRect();
|
||||
const qreal tol = 12.0;
|
||||
Edge edge = Edge::None;
|
||||
QPointF proj = scenePos;
|
||||
|
||||
if (scenePos.y() >= r.top() && scenePos.y() <= r.bottom()) {
|
||||
if (std::abs(scenePos.x() - r.left()) <= tol) {
|
||||
edge = Edge::Left;
|
||||
proj = QPointF(r.left(), std::clamp(scenePos.y(), r.top(), r.bottom()));
|
||||
} else if (std::abs(scenePos.x() - r.right()) <= tol) {
|
||||
edge = Edge::Right;
|
||||
proj = QPointF(r.right(), std::clamp(scenePos.y(), r.top(), r.bottom()));
|
||||
}
|
||||
}
|
||||
if (scenePos.x() >= r.left() && scenePos.x() <= r.right()) {
|
||||
if (std::abs(scenePos.y() - r.top()) <= tol) {
|
||||
edge = Edge::Top;
|
||||
proj = QPointF(std::clamp(scenePos.x(), r.left(), r.right()), r.top());
|
||||
} else if (std::abs(scenePos.y() - r.bottom()) <= tol) {
|
||||
edge = Edge::Bottom;
|
||||
proj = QPointF(std::clamp(scenePos.x(), r.left(), r.right()), r.bottom());
|
||||
}
|
||||
}
|
||||
|
||||
if (outScenePoint) *outScenePoint = proj;
|
||||
return edge;
|
||||
}
|
||||
|
||||
bool DiagramScene::tryStartArrowDrag(const QPointF& scenePos) {
|
||||
const auto itemsUnder = items(scenePos);
|
||||
// проверяем, не стартуем ли с существующего junction
|
||||
for (QGraphicsItem* it : itemsUnder) {
|
||||
if (auto* j = qgraphicsitem_cast<JunctionItem*>(it)) {
|
||||
if (!j->hitTest(scenePos, 8.0)) continue;
|
||||
auto* a = new ArrowItem();
|
||||
addItem(a);
|
||||
ArrowItem::Endpoint from;
|
||||
from.junction = j;
|
||||
a->setFrom(from);
|
||||
a->setTempEndPoint(scenePos);
|
||||
m_dragArrow = a;
|
||||
m_dragFromBlock.clear();
|
||||
m_dragFromJunction = j;
|
||||
m_dragFromPort = BlockItem::Port::Output;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// ищем блок под курсором
|
||||
for (QGraphicsItem* it : itemsUnder) {
|
||||
auto* b = qgraphicsitem_cast<BlockItem*>(it);
|
||||
if (!b) continue;
|
||||
|
||||
BlockItem::Port port{};
|
||||
QPointF localPos;
|
||||
if (!b->hitTestPort(scenePos, &port, &localPos)) continue;
|
||||
if (port != BlockItem::Port::Output) continue; // стартуем только с выходной стороны
|
||||
|
||||
// Стартуем стрелку из порта
|
||||
m_dragFromBlock = b;
|
||||
m_dragFromJunction.clear();
|
||||
m_dragFromPort = port;
|
||||
|
||||
auto* a = new ArrowItem();
|
||||
addItem(a);
|
||||
|
||||
ArrowItem::Endpoint from;
|
||||
from.block = b;
|
||||
from.port = port;
|
||||
from.localPos = localPos;
|
||||
a->setFrom(from);
|
||||
a->setTempEndPoint(scenePos);
|
||||
m_dragArrow = a;
|
||||
return true;
|
||||
}
|
||||
|
||||
QPointF edgePoint;
|
||||
Edge edge = hitTestEdge(scenePos, &edgePoint);
|
||||
if (edge != Edge::None && edge != Edge::Right) { // разрешаем старт с левой/верхней/нижней границы
|
||||
auto* a = new ArrowItem();
|
||||
addItem(a);
|
||||
ArrowItem::Endpoint from;
|
||||
from.scenePos = edgePoint;
|
||||
from.port = BlockItem::Port::Input; // ориентация не важна для свободной точки
|
||||
a->setFrom(from);
|
||||
a->setTempEndPoint(scenePos);
|
||||
m_dragArrow = a;
|
||||
m_dragFromBlock.clear();
|
||||
m_dragFromJunction.clear();
|
||||
m_dragFromPort = BlockItem::Port::Output;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DiagramScene::tryFinishArrowDrag(const QPointF& scenePos) {
|
||||
if (!m_dragArrow) return false;
|
||||
|
||||
const auto itemsUnder = items(scenePos);
|
||||
// сначала пробуем попасть в блок
|
||||
for (QGraphicsItem* it : itemsUnder) {
|
||||
auto* b = qgraphicsitem_cast<BlockItem*>(it);
|
||||
if (!b) continue;
|
||||
|
||||
BlockItem::Port port{};
|
||||
QPointF localPos;
|
||||
if (!b->hitTestPort(scenePos, &port, &localPos)) continue;
|
||||
|
||||
// запретим соединять в тот же самый порт того же блока (упрощение)
|
||||
if (b == m_dragFromBlock && port == m_dragFromPort) continue;
|
||||
|
||||
ArrowItem::Endpoint to;
|
||||
to.block = b;
|
||||
to.port = port;
|
||||
to.localPos = localPos;
|
||||
m_dragArrow->setTo(to);
|
||||
m_dragArrow->finalize();
|
||||
|
||||
m_dragArrow = nullptr;
|
||||
m_dragFromBlock.clear();
|
||||
m_dragFromJunction.clear();
|
||||
pushSnapshot();
|
||||
return true;
|
||||
}
|
||||
|
||||
// затем — уже существующий junction
|
||||
for (QGraphicsItem* it : itemsUnder) {
|
||||
auto* j = qgraphicsitem_cast<JunctionItem*>(it);
|
||||
if (!j) continue;
|
||||
if (!j->hitTest(scenePos, 8.0)) continue;
|
||||
if (j == m_dragFromJunction) continue;
|
||||
|
||||
ArrowItem::Endpoint to;
|
||||
to.junction = j;
|
||||
to.port = BlockItem::Port::Input;
|
||||
m_dragArrow->setTo(to);
|
||||
m_dragArrow->finalize();
|
||||
|
||||
m_dragArrow = nullptr;
|
||||
m_dragFromBlock.clear();
|
||||
m_dragFromJunction.clear();
|
||||
pushSnapshot();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Попали в существующую стрелку — создаем junction и расщепляем.
|
||||
ArrowItem* targetArrow = nullptr;
|
||||
QPointF splitPoint;
|
||||
qreal bestDist = 8.0;
|
||||
for (QGraphicsItem* it : itemsUnder) {
|
||||
auto* arrow = qgraphicsitem_cast<ArrowItem*>(it);
|
||||
if (!arrow || arrow == m_dragArrow) continue;
|
||||
auto proj = arrow->hitTest(scenePos, bestDist);
|
||||
if (proj) {
|
||||
bestDist = std::hypot(proj->x() - scenePos.x(), proj->y() - scenePos.y());
|
||||
splitPoint = *proj;
|
||||
targetArrow = arrow;
|
||||
}
|
||||
}
|
||||
if (targetArrow) {
|
||||
auto* junction = new JunctionItem(nullptr, m_nextJunctionId++);
|
||||
addItem(junction);
|
||||
junction->setPos(splitPoint);
|
||||
|
||||
ArrowItem::Endpoint mid;
|
||||
mid.junction = junction;
|
||||
mid.port = BlockItem::Port::Input;
|
||||
|
||||
const ArrowItem::Endpoint origTo = targetArrow->to();
|
||||
targetArrow->setTo(mid);
|
||||
targetArrow->finalize();
|
||||
|
||||
auto* forward = new ArrowItem();
|
||||
addItem(forward);
|
||||
forward->setFrom(mid);
|
||||
forward->setTo(origTo);
|
||||
forward->finalize();
|
||||
|
||||
m_dragArrow->setTo(mid);
|
||||
m_dragArrow->finalize();
|
||||
|
||||
m_dragArrow = nullptr;
|
||||
m_dragFromBlock.clear();
|
||||
m_dragFromJunction.clear();
|
||||
pushSnapshot();
|
||||
return true;
|
||||
}
|
||||
|
||||
// если не попали в блок — попробуем закрепиться на границе сцены
|
||||
QPointF edgePoint;
|
||||
Edge edge = hitTestEdge(scenePos, &edgePoint);
|
||||
if (edge != Edge::None) {
|
||||
// правая граница может быть только приемником (уже гарантировано)
|
||||
ArrowItem::Endpoint to;
|
||||
to.scenePos = edgePoint;
|
||||
to.port = BlockItem::Port::Input;
|
||||
m_dragArrow->setTo(to);
|
||||
m_dragArrow->finalize();
|
||||
m_dragArrow = nullptr;
|
||||
m_dragFromBlock.clear();
|
||||
m_dragFromJunction.clear();
|
||||
pushSnapshot();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DiagramScene::tryBranchAtArrow(const QPointF& scenePos) {
|
||||
ArrowItem* targetArrow = nullptr;
|
||||
QPointF splitPoint;
|
||||
qreal bestDist = 8.0;
|
||||
|
||||
const auto itemsUnder = items(scenePos);
|
||||
for (QGraphicsItem* it : itemsUnder) {
|
||||
auto* arrow = qgraphicsitem_cast<ArrowItem*>(it);
|
||||
if (!arrow || arrow == m_dragArrow) continue;
|
||||
auto proj = arrow->hitTest(scenePos, bestDist);
|
||||
if (proj) {
|
||||
bestDist = std::hypot(proj->x() - scenePos.x(), proj->y() - scenePos.y());
|
||||
splitPoint = *proj;
|
||||
targetArrow = arrow;
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetArrow) return false;
|
||||
|
||||
auto* junction = new JunctionItem(nullptr, m_nextJunctionId++);
|
||||
addItem(junction);
|
||||
junction->setPos(splitPoint);
|
||||
|
||||
ArrowItem::Endpoint mid;
|
||||
mid.junction = junction;
|
||||
mid.port = BlockItem::Port::Input;
|
||||
|
||||
const ArrowItem::Endpoint origTo = targetArrow->to();
|
||||
targetArrow->setTo(mid);
|
||||
targetArrow->finalize();
|
||||
|
||||
auto* forward = new ArrowItem();
|
||||
addItem(forward);
|
||||
forward->setFrom(mid);
|
||||
forward->setTo(origTo);
|
||||
forward->finalize();
|
||||
|
||||
auto* branch = new ArrowItem();
|
||||
addItem(branch);
|
||||
branch->setFrom(mid);
|
||||
branch->setTempEndPoint(scenePos);
|
||||
m_dragArrow = branch;
|
||||
m_dragFromBlock.clear();
|
||||
m_dragFromJunction = junction;
|
||||
m_dragFromPort = BlockItem::Port::Output;
|
||||
return true;
|
||||
}
|
||||
|
||||
DiagramScene::Snapshot DiagramScene::captureSnapshot() const {
|
||||
Snapshot s;
|
||||
|
||||
QSet<BlockItem*> blockSet;
|
||||
QSet<JunctionItem*> junctionSet;
|
||||
for (QGraphicsItem* it : items()) {
|
||||
if (auto* b = qgraphicsitem_cast<BlockItem*>(it)) blockSet.insert(b);
|
||||
if (auto* j = qgraphicsitem_cast<JunctionItem*>(it)) junctionSet.insert(j);
|
||||
}
|
||||
|
||||
auto encodeEp = [&](const ArrowItem::Endpoint& ep) {
|
||||
Snapshot::Endpoint out;
|
||||
if (ep.block && blockSet.contains(ep.block) && ep.block->scene() == this) {
|
||||
out.kind = Snapshot::Endpoint::Kind::Block;
|
||||
out.id = ep.block->id();
|
||||
out.port = ep.port;
|
||||
if (ep.localPos) out.localPos = *ep.localPos;
|
||||
} else if (ep.junction && junctionSet.contains(ep.junction) && ep.junction->scene() == this) {
|
||||
out.kind = Snapshot::Endpoint::Kind::Junction;
|
||||
out.id = ep.junction->id();
|
||||
} else if (ep.scenePos) {
|
||||
out.kind = Snapshot::Endpoint::Kind::Scene;
|
||||
out.scenePos = *ep.scenePos;
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
for (QGraphicsItem* it : items()) {
|
||||
if (auto* b = qgraphicsitem_cast<BlockItem*>(it)) {
|
||||
s.blocks.push_back({b->id(), b->title(), b->pos()});
|
||||
}
|
||||
}
|
||||
|
||||
for (QGraphicsItem* it : items()) {
|
||||
if (auto* j = qgraphicsitem_cast<JunctionItem*>(it)) {
|
||||
s.junctions.push_back({j->id(), j->pos()});
|
||||
}
|
||||
}
|
||||
|
||||
for (QGraphicsItem* it : items()) {
|
||||
if (auto* a = qgraphicsitem_cast<ArrowItem*>(it)) {
|
||||
Snapshot::Arrow ar;
|
||||
ar.from = encodeEp(a->from());
|
||||
ar.to = encodeEp(a->to());
|
||||
if (ar.from.kind == Snapshot::Endpoint::Kind::None || ar.to.kind == Snapshot::Endpoint::Kind::None) {
|
||||
continue; // skip incomplete arrows
|
||||
}
|
||||
ar.bend = a->bendOffset();
|
||||
ar.top = a->topOffset();
|
||||
ar.bottom = a->bottomOffset();
|
||||
ar.label = a->label();
|
||||
ar.labelOffset = a->labelOffset();
|
||||
s.arrows.push_back(std::move(ar));
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
void DiagramScene::restoreSnapshot(const Snapshot& snap) {
|
||||
m_restoringSnapshot = true;
|
||||
cancelCurrentDrag();
|
||||
// Clear dynamic items but keep the static frame.
|
||||
QList<QGraphicsItem*> toRemove;
|
||||
for (QGraphicsItem* it : items()) {
|
||||
if (it->data(0).toString() == QStringLiteral("static-frame")) continue;
|
||||
toRemove.append(it);
|
||||
}
|
||||
for (QGraphicsItem* it : toRemove) {
|
||||
removeItem(it);
|
||||
delete it;
|
||||
}
|
||||
|
||||
m_nextBlockId = 1;
|
||||
m_nextJunctionId = 1;
|
||||
|
||||
QHash<int, BlockItem*> blockMap;
|
||||
QHash<int, JunctionItem*> junctionMap;
|
||||
|
||||
for (const auto& b : snap.blocks) {
|
||||
auto* blk = createBlockWithId(b.pos, b.id, b.title);
|
||||
blockMap.insert(b.id, blk);
|
||||
m_nextBlockId = std::max(m_nextBlockId, b.id + 1);
|
||||
}
|
||||
for (const auto& j : snap.junctions) {
|
||||
m_nextJunctionId = std::max(m_nextJunctionId, j.id + 1);
|
||||
auto* jun = new JunctionItem(nullptr, j.id);
|
||||
addItem(jun);
|
||||
jun->setPos(j.pos);
|
||||
junctionMap.insert(j.id, jun);
|
||||
}
|
||||
|
||||
auto decodeEp = [&](const Snapshot::Endpoint& ep) {
|
||||
ArrowItem::Endpoint out;
|
||||
switch (ep.kind) {
|
||||
case Snapshot::Endpoint::Kind::Block:
|
||||
out.block = blockMap.value(ep.id, nullptr);
|
||||
out.port = ep.port;
|
||||
out.localPos = ep.localPos;
|
||||
break;
|
||||
case Snapshot::Endpoint::Kind::Junction:
|
||||
out.junction = junctionMap.value(ep.id, nullptr);
|
||||
break;
|
||||
case Snapshot::Endpoint::Kind::Scene:
|
||||
out.scenePos = ep.scenePos;
|
||||
out.port = BlockItem::Port::Input;
|
||||
break;
|
||||
case Snapshot::Endpoint::Kind::None:
|
||||
break;
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
for (const auto& a : snap.arrows) {
|
||||
auto* ar = new ArrowItem();
|
||||
addItem(ar);
|
||||
ar->setFrom(decodeEp(a.from));
|
||||
ar->setTo(decodeEp(a.to));
|
||||
ar->setOffsets(a.bend, a.top, a.bottom);
|
||||
ar->setLabel(a.label);
|
||||
ar->setLabelOffset(a.labelOffset);
|
||||
ar->finalize();
|
||||
}
|
||||
|
||||
m_restoringSnapshot = false;
|
||||
}
|
||||
|
||||
void DiagramScene::pushSnapshot() {
|
||||
if (m_restoringSnapshot) return;
|
||||
Snapshot s = captureSnapshot();
|
||||
if (m_historyIndex + 1 < m_history.size()) {
|
||||
m_history.resize(m_historyIndex + 1);
|
||||
}
|
||||
m_history.push_back(std::move(s));
|
||||
m_historyIndex = m_history.size() - 1;
|
||||
}
|
||||
|
||||
void DiagramScene::scheduleSnapshot() {
|
||||
if (m_restoringSnapshot || m_snapshotScheduled) return;
|
||||
m_snapshotScheduled = true;
|
||||
QTimer::singleShot(0, this, [this]{
|
||||
m_snapshotScheduled = false;
|
||||
pushSnapshot();
|
||||
});
|
||||
}
|
||||
|
||||
void DiagramScene::undo() {
|
||||
if (m_historyIndex <= 0) return;
|
||||
m_historyIndex -= 1;
|
||||
restoreSnapshot(m_history[m_historyIndex]);
|
||||
}
|
||||
|
||||
void DiagramScene::mousePressEvent(QGraphicsSceneMouseEvent* e) {
|
||||
if (e->button() == Qt::LeftButton) {
|
||||
if (e->modifiers().testFlag(Qt::ControlModifier)) {
|
||||
if (tryBranchAtArrow(e->scenePos())) {
|
||||
e->accept();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// если кликнули на порт — начинаем протягивание стрелки
|
||||
if (tryStartArrowDrag(e->scenePos())) {
|
||||
e->accept();
|
||||
return;
|
||||
}
|
||||
// начало перемещения существующих элементов — запомним позиции
|
||||
m_pressPositions.clear();
|
||||
const auto sel = selectedItems();
|
||||
for (QGraphicsItem* it : sel) {
|
||||
if (it->flags() & QGraphicsItem::ItemIsMovable) {
|
||||
m_pressPositions.insert(it, it->pos());
|
||||
}
|
||||
}
|
||||
m_itemDragActive = !m_pressPositions.isEmpty();
|
||||
}
|
||||
|
||||
if (e->button() == Qt::RightButton) {
|
||||
// правой кнопкой — добавить блок
|
||||
createBlockAt(e->scenePos());
|
||||
e->accept();
|
||||
return;
|
||||
}
|
||||
|
||||
QGraphicsScene::mousePressEvent(e);
|
||||
}
|
||||
|
||||
void DiagramScene::mouseMoveEvent(QGraphicsSceneMouseEvent* e) {
|
||||
if (m_dragArrow) {
|
||||
m_dragArrow->setTempEndPoint(e->scenePos());
|
||||
e->accept();
|
||||
return;
|
||||
}
|
||||
QGraphicsScene::mouseMoveEvent(e);
|
||||
}
|
||||
|
||||
void DiagramScene::mouseReleaseEvent(QGraphicsSceneMouseEvent* e) {
|
||||
if (m_dragArrow && e->button() == Qt::LeftButton) {
|
||||
if (!tryFinishArrowDrag(e->scenePos())) {
|
||||
// не получилось — удаляем временную стрелку
|
||||
cancelCurrentDrag();
|
||||
}
|
||||
e->accept();
|
||||
}
|
||||
if (m_itemDragActive && e->button() == Qt::LeftButton) {
|
||||
maybeSnapshotMovedItems();
|
||||
m_itemDragActive = false;
|
||||
}
|
||||
QGraphicsScene::mouseReleaseEvent(e);
|
||||
}
|
||||
|
||||
void DiagramScene::keyPressEvent(QKeyEvent* e) {
|
||||
if (e->key() == Qt::Key_Escape) {
|
||||
cancelCurrentDrag();
|
||||
e->accept();
|
||||
return;
|
||||
}
|
||||
if (e->key() == Qt::Key_Delete || e->key() == Qt::Key_Backspace) {
|
||||
deleteSelection();
|
||||
e->accept();
|
||||
return;
|
||||
}
|
||||
if (e->matches(QKeySequence::Undo)) {
|
||||
undo();
|
||||
e->accept();
|
||||
return;
|
||||
}
|
||||
QGraphicsScene::keyPressEvent(e);
|
||||
}
|
||||
|
||||
void DiagramScene::cancelCurrentDrag() {
|
||||
if (m_dragArrow) {
|
||||
removeItem(m_dragArrow);
|
||||
delete m_dragArrow;
|
||||
m_dragArrow = nullptr;
|
||||
m_dragFromBlock.clear();
|
||||
m_dragFromJunction.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void DiagramScene::deleteSelection() {
|
||||
if (m_dragArrow) {
|
||||
cancelCurrentDrag();
|
||||
return;
|
||||
}
|
||||
|
||||
QSet<QGraphicsItem*> toDelete;
|
||||
const auto sel = selectedItems();
|
||||
for (QGraphicsItem* it : sel) {
|
||||
if (qgraphicsitem_cast<BlockItem*>(it) || qgraphicsitem_cast<JunctionItem*>(it) || qgraphicsitem_cast<ArrowItem*>(it)) {
|
||||
toDelete.insert(it);
|
||||
}
|
||||
}
|
||||
|
||||
// also delete arrows connected to selected blocks/junctions
|
||||
for (QGraphicsItem* it : items()) {
|
||||
auto* arrow = qgraphicsitem_cast<ArrowItem*>(it);
|
||||
if (!arrow) continue;
|
||||
const auto f = arrow->from();
|
||||
const auto t = arrow->to();
|
||||
if ((f.block && toDelete.contains(f.block)) ||
|
||||
(t.block && toDelete.contains(t.block)) ||
|
||||
(f.junction && toDelete.contains(f.junction)) ||
|
||||
(t.junction && toDelete.contains(t.junction))) {
|
||||
toDelete.insert(arrow);
|
||||
}
|
||||
}
|
||||
|
||||
// delete arrows first to avoid dangling removal from deleted blocks/junctions
|
||||
for (QGraphicsItem* it : toDelete) {
|
||||
if (qgraphicsitem_cast<ArrowItem*>(it)) {
|
||||
removeItem(it);
|
||||
delete it;
|
||||
}
|
||||
}
|
||||
for (QGraphicsItem* it : toDelete) {
|
||||
if (!qgraphicsitem_cast<ArrowItem*>(it)) {
|
||||
removeItem(it);
|
||||
delete it;
|
||||
}
|
||||
}
|
||||
|
||||
if (!toDelete.isEmpty()) pushSnapshot();
|
||||
}
|
||||
|
||||
void DiagramScene::requestSnapshot() {
|
||||
pushSnapshot();
|
||||
}
|
||||
|
||||
void DiagramScene::maybeSnapshotMovedItems() {
|
||||
bool moved = false;
|
||||
for (auto it = m_pressPositions.cbegin(); it != m_pressPositions.cend(); ++it) {
|
||||
if (!it.key()) continue;
|
||||
const QPointF cur = it.key()->pos();
|
||||
const QPointF old = it.value();
|
||||
if (!qFuzzyCompare(cur.x(), old.x()) || !qFuzzyCompare(cur.y(), old.y())) {
|
||||
moved = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_pressPositions.clear();
|
||||
if (moved) pushSnapshot();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue