новый файл: 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:
Gregory Bednov 2026-02-23 00:21:10 +03:00
commit 53afa3f728
87 changed files with 48249 additions and 0 deletions

151
src/items/BlockItem.cpp Normal file
View file

@ -0,0 +1,151 @@
#include "BlockItem.h"
#include "ArrowItem.h"
#include <algorithm>
#include <limits>
#include <cmath>
#include <QPainter>
#include <QGraphicsSceneMouseEvent>
#include <QInputDialog>
BlockItem::BlockItem(QString title, QGraphicsItem* parent, int id)
: QGraphicsObject(parent),
m_title(std::move(title)),
m_rect(0, 0, 200, 100),
m_id(id)
{
setFlags(ItemIsMovable | ItemIsSelectable | ItemSendsGeometryChanges);
}
QRectF BlockItem::boundingRect() const {
// небольшой запас под обводку/выделение
return m_rect.adjusted(-2, -2, 2, 2);
}
void BlockItem::paint(QPainter* p, const QStyleOptionGraphicsItem*, QWidget*) {
p->setRenderHint(QPainter::Antialiasing, true);
// фон
p->setPen(Qt::NoPen);
p->setBrush(isSelected() ? QColor(240,240,255) : QColor(250,250,250));
p->drawRoundedRect(m_rect, 6, 6);
// рамка
QPen pen(QColor(30,30,30));
pen.setWidthF(1.5);
p->setPen(pen);
p->setBrush(Qt::NoBrush);
p->drawRoundedRect(m_rect, 6, 6);
// заголовок
p->setPen(QColor(20,20,20));
p->drawText(m_rect.adjusted(10, 8, -10, -8), Qt::AlignLeft | Qt::AlignTop, m_title);
}
void BlockItem::setTitle(const QString& t) {
if (t == m_title) return;
m_title = t;
update();
}
QPointF BlockItem::portLocalPos(Port p) const {
const qreal x0 = m_rect.left();
const qreal x1 = m_rect.right();
const qreal y0 = m_rect.top();
const qreal y1 = m_rect.bottom();
const qreal yc = m_rect.center().y();
const qreal xc = m_rect.center().x();
switch (p) {
case Port::Input: return QPointF(x0, yc);
case Port::Output: return QPointF(x1, yc);
case Port::Control: return QPointF(xc, y0);
case Port::Mechanism: return QPointF(xc, y1);
}
return QPointF();
}
QPair<QPointF, QPointF> BlockItem::portSegment(Port p) const {
const qreal x0 = m_rect.left();
const qreal x1 = m_rect.right();
const qreal y0 = m_rect.top();
const qreal y1 = m_rect.bottom();
switch (p) {
case Port::Input: return {QPointF(x0, y0), QPointF(x0, y1)};
case Port::Output: return {QPointF(x1, y0), QPointF(x1, y1)};
case Port::Control: return {QPointF(x0, y0), QPointF(x1, y0)};
case Port::Mechanism: return {QPointF(x0, y1), QPointF(x1, y1)};
}
return {};
}
QPointF BlockItem::portScenePos(Port p) const {
return mapToScene(portLocalPos(p));
}
static qreal distToSegmentLocal(const QPointF& p, const QPointF& a, const QPointF& b, QPointF* outProj = nullptr) {
const QPointF ab = b - a;
const qreal ab2 = ab.x()*ab.x() + ab.y()*ab.y();
if (ab2 < 1e-6) {
if (outProj) *outProj = a;
return std::hypot(p.x() - a.x(), p.y() - a.y());
}
const qreal t = std::clamp(((p - a).x()*ab.x() + (p - a).y()*ab.y()) / ab2, 0.0, 1.0);
const QPointF proj = a + ab * t;
if (outProj) *outProj = proj;
return std::hypot(p.x() - proj.x(), p.y() - proj.y());
}
bool BlockItem::hitTestPort(const QPointF& scenePos, Port* outPort, QPointF* outLocalPos, qreal radiusPx) const {
const QPointF local = mapFromScene(scenePos);
qreal bestDist = std::numeric_limits<qreal>::max();
Port bestPort = Port::Input;
QPointF bestProj;
const Port ports[] = {Port::Input, Port::Control, Port::Output, Port::Mechanism};
for (Port p : ports) {
const auto seg = portSegment(p);
QPointF proj;
const qreal d = distToSegmentLocal(local, seg.first, seg.second, &proj);
if (d < bestDist) {
bestDist = d;
bestPort = p;
bestProj = proj;
}
}
if (bestDist <= radiusPx) {
if (outPort) *outPort = bestPort;
if (outLocalPos) *outLocalPos = bestProj;
return true;
}
return false;
}
void BlockItem::addArrow(ArrowItem* a) {
m_arrows.insert(a);
}
void BlockItem::removeArrow(ArrowItem* a) {
m_arrows.remove(a);
}
QVariant BlockItem::itemChange(GraphicsItemChange change, const QVariant& value) {
if (change == ItemPositionHasChanged || change == ItemTransformHasChanged) {
// уведомим стрелки, что нужно пересчитать геометрию
for (auto& a : m_arrows) {
if (a) a->updatePath();
}
emit geometryChanged();
}
return QGraphicsObject::itemChange(change, value);
}
void BlockItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* e) {
bool ok = false;
const QString t = QInputDialog::getText(nullptr, "Rename function", "Title:", QLineEdit::Normal, m_title, &ok);
if (ok) setTitle(t);
QGraphicsObject::mouseDoubleClickEvent(e);
}