9#include <QGraphicsSceneMouseEvent>
10#include <QGraphicsView>
11#include <QPainterPath>
32 m_selectionRect.setFlag(QGraphicsItem::ItemIsSelectable,
false);
33 m_scene->addItem(&m_selectionRect);
42 m_selectionRect.setPen(QPen(theme.
m_selectionPen, 1, Qt::SolidLine));
45void SceneInteraction::startSelectionRect()
47 m_selectionStartPoint = m_mousePos;
48 m_markingSelectionBox =
true;
49 m_selectionRect.setRect(QRectF(m_selectionStartPoint, m_selectionStartPoint));
50 m_selectionRect.show();
51 m_selectionRect.update();
56 m_mousePos =
event->scenePos();
57 m_scene->connectionManager()->updateHover(m_mousePos);
59 auto *item = m_scene->itemAt(m_mousePos);
63 if (event->button() == Qt::LeftButton) {
64 if (event->modifiers().testFlag(Qt::ControlModifier)) {
66 item->setSelected(!item->isSelected());
69 auto selectedElements_ = m_scene->selectedElements();
73 if (
auto *element = qgraphicsitem_cast<GraphicElement *>(item)) {
74 selectedElements_ << element;
78 if (m_draggingElement) {
79 sentryBreadcrumb(
"ui", QStringLiteral(
"Drag started: %1 element(s)").arg(selectedElements_.size()));
85 m_dragSnapshot.clear();
86 m_dragSnapshot.reserve(selectedElements_.size());
88 for (
auto *element : std::as_const(selectedElements_)) {
89 m_dragSnapshot.append({element, element->pos()});
97 if (m_scene->connectionManager()->hasEditedConnection()) {
99 m_scene->connectionManager()->tryComplete(m_mousePos);
103 auto *pressedPort = qgraphicsitem_cast<Port *>(item);
105 if (
auto *startPort =
dynamic_cast<OutputPort *
>(pressedPort)) {
106 m_scene->connectionManager()->startFromOutput(startPort);
110 if (
auto *endPort =
dynamic_cast<InputPort *
>(pressedPort)) {
112 endPort->connections().isEmpty() ? m_scene->connectionManager()->startFromInput(endPort) : m_scene->connectionManager()->detach(endPort);
119 m_scene->connectionManager()->cancel();
121 if (!item && (event->button() == Qt::LeftButton)) {
122 startSelectionRect();
125 if (event->button() == Qt::RightButton) {
126 m_scene->contextMenu(event->screenPos());
135 m_mousePos =
event->scenePos();
136 m_scene->connectionManager()->updateHover(m_mousePos);
140 if (m_draggingElement) {
141 m_scene->resizeScene();
143 if (m_timer.elapsed() > 50) {
144 const auto viewList = m_scene->views();
145 if (!viewList.isEmpty()) {
146 viewList.first()->ensureVisible(m_mousePos.x(), m_mousePos.y(), 1, 1, 50, 50);
153 if (m_scene->connectionManager()->hasEditedConnection()) {
154 m_scene->connectionManager()->updateEditedEnd(m_mousePos);
159 if (m_markingSelectionBox) {
160 const QRectF rect = QRectF(m_selectionStartPoint, m_mousePos).normalized();
161 m_selectionRect.setRect(rect);
162 QPainterPath selectionBox;
163 selectionBox.addRect(rect);
164 m_scene->setSelectionArea(selectionBox);
172 if (m_draggingElement && (event->button() == Qt::LeftButton)) {
175 if (!m_dragSnapshot.empty()) {
178 QList<GraphicElement *> liveElements;
179 QList<QPointF> liveOldPositions;
180 liveElements.reserve(m_dragSnapshot.size());
181 liveOldPositions.reserve(m_dragSnapshot.size());
183 for (
const auto &[ptr, oldPos] : std::as_const(m_dragSnapshot)) {
185 liveElements.append(ptr.data());
186 liveOldPositions.append(oldPos);
192 for (
int i = 0; i < liveElements.size(); ++i) {
193 if (liveElements.at(i)->pos() != liveOldPositions.at(i)) {
200 m_scene->receiveCommand(
new MoveCommand(liveElements, liveOldPositions, m_scene));
207 m_scene->resizeScene();
211 sentryBreadcrumb(
"ui", moved ? QStringLiteral(
"Drag ended: moved") : QStringLiteral(
"Drag ended: no move"));
212 m_draggingElement =
false;
213 m_dragSnapshot.clear();
216 m_selectionRect.hide();
217 m_markingSelectionBox =
false;
221 if (m_scene->connectionManager()->hasEditedConnection() && (event->buttons() == Qt::NoButton)) {
222 m_scene->connectionManager()->tryComplete(m_mousePos);
231 if (event->modifiers().testFlag(Qt::ControlModifier)) {
237 if (
auto *connection = qgraphicsitem_cast<Connection *>(m_scene->itemAt(m_mousePos)); connection && connection->startPort() && connection->endPort()) {
238 m_scene->receiveCommand(
new SplitCommand(connection, m_mousePos, m_scene));
All QUndoCommand subclasses and the CommandUtils helper namespace.
ConnectionManager: manages wire creation, deletion, validation and hover feedback.
Connection: a wire that connects an output port to an input port in the circuit scene.
Abstract base class for all graphical circuit elements.
Port classes: Port (base), InputPort, and OutputPort.
SceneInteraction: owns the scene's mouse-driven editing state and gestures.
Main circuit editing scene with undo/redo and user interaction.
Lightweight Sentry helpers gated behind HAVE_SENTRY.
void sentryBreadcrumb(const char *category, const QString &message)
Theme management types and singleton ThemeManager.
Undo command that records a drag-move of a set of elements.
A port that drives a signal (the source end of a wire).
bool mouseMove(QGraphicsSceneMouseEvent *event)
bool mousePress(QGraphicsSceneMouseEvent *event)
SceneInteraction(Scene *scene)
void applyTheme(const ThemeAttributes &theme)
Applies the theme's selection-rectangle brush and pen.
bool mouseRelease(QGraphicsSceneMouseEvent *event)
bool mouseDoubleClick(QGraphicsSceneMouseEvent *event)
Main circuit editing scene.
Undo command that inserts a Node junction into an existing connection.
Contains all color attributes for a theme.