11#include <QGraphicsSceneDragDropEvent>
12#include <QGraphicsSceneHelpEvent>
44constexpr qint64 kWireAaBudgetNs = 50'000'000;
48constexpr int kWireAaDebouncePasses = 2;
51constexpr qint64 kWireAaPassIdleMs = 300;
56constexpr qint64 kWireAaFlipIdleMs = 3000;
62constexpr qint64 kWireAaWorstCaseRatio = 10;
66constexpr int kWireAaHeadroomRestorePasses = 10;
70 : QGraphicsScene(parent)
71 , m_simulation(this, this)
75 installEventFilter(
this);
79 m_interaction.attachToScene();
81 m_undoAction =
new QAction(tr(
"&Undo"),
this);
82 m_undoAction->setEnabled(
false);
83 m_undoAction->setIcon(QIcon(
":/Interface/Toolbar/undo.svg"));
84 m_undoAction->setShortcut(QKeySequence::Undo);
85 connect(&m_undoStack, &QUndoStack::canUndoChanged, m_undoAction, &QAction::setEnabled);
86 connect(&m_undoStack, &QUndoStack::undoTextChanged,
this, &Scene::updateUndoText);
87 connect(m_undoAction, &QAction::triggered, &m_undoStack, &QUndoStack::undo);
89 m_redoAction =
new QAction(tr(
"&Redo"),
this);
90 m_redoAction->setEnabled(
false);
91 m_redoAction->setIcon(QIcon(
":/Interface/Toolbar/redo.svg"));
92 m_redoAction->setShortcut(QKeySequence::Redo);
93 connect(&m_undoStack, &QUndoStack::canRedoChanged, m_redoAction, &QAction::setEnabled);
94 connect(&m_undoStack, &QUndoStack::redoTextChanged,
this, &Scene::updateRedoText);
95 connect(m_redoAction, &QAction::triggered, &m_undoStack, &QUndoStack::redo);
99 connect(&m_undoStack, &QUndoStack::indexChanged,
this, &Scene::checkUpdateRequest);
103 m_wireAaIdleTimer.setSingleShot(
true);
104 connect(&m_wireAaIdleTimer, &QTimer::timeout,
this, [
this] { checkWireIdleRestore(); });
105 m_wireFlipTimer.start();
106 m_wirePassTimer.start();
109void Scene::checkUpdateRequest()
113 if (m_autosaveRequired) {
115 m_autosaveRequired =
false;
124 QGraphicsScene::addItem(item);
125 if (
auto *iwid =
dynamic_cast<ItemWithId *
>(item)) {
126 if (iwid->id() < 0) {
138 auto *elm = qgraphicsitem_cast<GraphicElement *>(item);
143 m_inlineLabelEditor.start(element);
145 if (
auto *ic = qobject_cast<IC *>(elm)) {
146 if (!ic->file().isEmpty()) {
147 m_icRegistry.watchFile(ic->file());
154 }
else if (
auto *tt = qobject_cast<TruthTable *>(elm)) {
165 if (
auto *iwid =
dynamic_cast<ItemWithId *
>(item)) {
166 unregisterItem(iwid);
168 QGraphicsScene::removeItem(item);
173 return m_itemRegistry.itemById(
id);
178 return m_itemRegistry.contains(
id);
183 return m_itemRegistry.lastId();
188 m_itemRegistry.setLastId(newLastId);
193 return m_itemRegistry.nextId();
198 m_itemRegistry.updateItemId(item, newId);
203 m_itemRegistry.forgetItemId(
id);
213 m_itemRegistry.unregisterItem(item);
223void Scene::drawBackground(QPainter *painter,
const QRectF &rect)
230 if (m_visibilityDirty) {
231 m_visibilityDirty =
false;
237 if (
view() and
view()->transform().m11() < 0.3) {
241 QGraphicsScene::drawBackground(painter, rect);
244 const int left =
static_cast<int>(std::floor(rect.left() / gridSize)) * gridSize;
245 const int top =
static_cast<int>(std::floor(rect.top() / gridSize)) * gridSize;
246 const int right =
static_cast<int>(std::ceil(rect.right() / gridSize)) * gridSize;
247 const int bottom =
static_cast<int>(std::ceil(rect.bottom() / gridSize)) * gridSize;
258 const qint64 columns = (
static_cast<qint64
>(right) - left) / gridSize + 1;
259 const qint64 rows = (
static_cast<qint64
>(bottom) - top) / gridSize + 1;
260 constexpr qint64 kMaxGridPoints = 1'000'000;
261 if (columns * rows > kMaxGridPoints) {
265 painter->setPen(m_dots);
272 points.reserve(
static_cast<int>(columns * rows));
274 for (
int x = left; x <= right; x +=
gridSize) {
275 for (
int y = top; y <= bottom; y +=
gridSize) {
276 points.append(QPoint(x, y));
280 painter->drawPoints(points);
283void Scene::setDots(
const QPen &dots)
290 return &m_simulation;
295 m_autosaveRequired =
true;
310 m_simulation.restart();
321 m_visibilityDirty =
true;
325 m_autosaveRequired =
true;
326 m_itemsBoundingRectDirty =
true;
331 const auto visibleRect = m_view->mapToScene(m_view->viewport()->geometry()).boundingRect();
343 const auto items_ =
items();
344 QVector<GraphicElement *> elements_;
345 elements_.reserve(items_.size());
347 for (
auto *item : items_) {
349 elements_.append(qgraphicsitem_cast<GraphicElement *>(item));
358 const auto items_ =
items(rect);
359 QVector<GraphicElement *> elements_;
360 elements_.reserve(items_.size());
362 for (
auto *item : items_) {
364 elements_.append(qgraphicsitem_cast<GraphicElement *>(item));
373 QHash<GraphicElement *, QVector<GraphicElement *>> successors;
375 for (
auto *port : elm->outputs()) {
376 for (
auto *conn : port->connections()) {
377 if (
auto *endPort = conn->endPort()) {
378 if (
auto *successor = endPort->graphicElement()) {
379 auto &vec = successors[elm];
380 if (!vec.contains(successor)) {
381 vec.append(successor);
389 QHash<GraphicElement *, int> priorities;
392 std::stable_sort(
elements.begin(),
elements.end(), [&priorities](
const auto &e1,
const auto &e2) {
393 return priorities.value(e1) > priorities.value(e2);
401 QHash<QString, InputPort *> txMap;
403 if (elm->wirelessMode() == WirelessMode::Tx && !elm->label().isEmpty() && elm->inputPort(0)) {
404 if (!txMap.contains(elm->label())) {
405 txMap.insert(elm->label(), elm->inputPort(0));
412const QVector<Connection *> Scene::connections()
const
414 const auto items_ =
items();
415 QVector<Connection *> conns;
416 conns.reserve(items_.size());
418 for (
auto *item : items_) {
420 conns.append(qgraphicsitem_cast<Connection *>(item));
429 const auto items_ = selectedItems();
430 QList<GraphicElement *> elements_;
432 for (
auto *item : items_) {
434 elements_.append(qgraphicsitem_cast<GraphicElement *>(item));
443 auto items_ =
items(pos);
446 items_.append(itemsAt(pos));
449 for (
auto *item : std::as_const(items_)) {
456 for (
auto *item : std::as_const(items_)) {
463 for (
auto *item : std::as_const(items_)) {
464 if (item->type() > QGraphicsItem::UserType) {
472QList<QGraphicsItem *> Scene::itemsAt(
const QPointF pos)
const
476 QRectF rect(pos - QPointF(4, 4), QSize(9, 9));
477 return items(rect.normalized());
493 const QRectF rect(pos - QPointF(4, 4), QSize(9, 9));
494 const auto candidates =
items(rect.normalized(), Qt::IntersectsItemBoundingRect);
496 Port *nearby =
nullptr;
498 for (
auto *item : candidates) {
506 auto *port =
static_cast<Port *
>(item);
508 if (port->shape().contains(port->mapFromScene(pos))) {
522 sentryBreadcrumb(
"command", QStringLiteral(
"Command: %1").arg(cmd->text()));
523 m_undoStack.push(cmd);
532 if (m_interaction.isDraggingElement()) {
533 return itemsBoundingRect();
536 if (m_itemsBoundingRectDirty) {
537 m_cachedItemsBoundingRect = itemsBoundingRect();
538 m_itemsBoundingRectDirty =
false;
541 return m_cachedItemsBoundingRect;
546 return m_wireAntialiasing;
551 m_wirePassTimer.restart();
553 if (m_wireAntialiasing) {
554 if (elapsedNs > kWireAaBudgetNs) {
555 if (++m_wireAaSlowPasses >= kWireAaDebouncePasses) {
556 m_wireAntialiasing =
false;
557 m_wireAaSlowPasses = 0;
558 m_wireAaHeadroomPasses = 0;
559 m_wireAaIdleTimer.start(
static_cast<int>(kWireAaPassIdleMs));
562 m_wireAaSlowPasses = 0;
569 if (elapsedNs < kWireAaBudgetNs / kWireAaWorstCaseRatio) {
570 if (++m_wireAaHeadroomPasses >= kWireAaHeadroomRestorePasses) {
574 m_wireAaHeadroomPasses = 0;
580 m_wireFlipTimer.restart();
583void Scene::checkWireIdleRestore()
585 const qint64 passRemainingMs = kWireAaPassIdleMs - m_wirePassTimer.elapsed();
586 const qint64 flipRemainingMs = kWireAaFlipIdleMs - m_wireFlipTimer.elapsed();
587 const qint64 remainingMs = qMax(passRemainingMs, flipRemainingMs);
589 if (remainingMs <= 0) {
596 m_wireAaIdleTimer.start(
static_cast<int>(remainingMs));
601 m_wireAaIdleTimer.stop();
602 m_wireAaSlowPasses = 0;
603 m_wireAaHeadroomPasses = 0;
605 if (m_wireAntialiasing) {
609 m_wireAntialiasing =
true;
623constexpr qreal kSceneRectQuantum = 256.0;
625QRectF quantizeOutward(
const QRectF &rect)
627 const qreal left = std::floor(rect.left() / kSceneRectQuantum) * kSceneRectQuantum;
628 const qreal top = std::floor(rect.top() / kSceneRectQuantum) * kSceneRectQuantum;
629 const qreal right = std::ceil(rect.right() / kSceneRectQuantum) * kSceneRectQuantum;
630 const qreal bottom = std::ceil(rect.bottom() / kSceneRectQuantum) * kSceneRectQuantum;
631 return QRectF(QPointF(left, top), QPointF(right, bottom));
639 if (m_interaction.isDraggingElement()) {
643 setSceneRect(quantizeOutward(sceneRect().united(bounds)));
649 auto tightRect = bounds;
650 const auto viewList = views();
651 if (!viewList.isEmpty()) {
652 auto *
view = viewList.first();
653 constexpr qreal margin = 100.0;
654 const auto visibleScene =
view->mapToScene(
view->viewport()->rect()).boundingRect()
655 .adjusted(-margin, -margin, margin, margin);
656 tightRect = tightRect.united(visibleScene);
661 const int hVal =
view->horizontalScrollBar()->value();
662 const int vVal =
view->verticalScrollBar()->value();
663 setSceneRect(quantizeOutward(tightRect));
664 view->horizontalScrollBar()->setValue(hVal);
665 view->verticalScrollBar()->setValue(vVal);
667 setSceneRect(quantizeOutward(tightRect));
701 qCDebug(zero) <<
"Updating theme.";
705 m_interaction.applyTheme(theme);
708 element->updateTheme();
711 for (
auto *conn : connections()) {
715 qCDebug(zero) <<
"Finished updating theme.";
720 return QGraphicsScene::items(order);
723QList<QGraphicsItem *>
Scene::items(QPointF pos, Qt::ItemSelectionMode mode, Qt::SortOrder order,
const QTransform &deviceTransform)
const
725 return QGraphicsScene::items(pos, mode, order, deviceTransform);
728QList<QGraphicsItem *>
Scene::items(
const QRectF &rect, Qt::ItemSelectionMode mode, Qt::SortOrder order,
const QTransform &deviceTransform)
const
730 return QGraphicsScene::items(rect, mode, order, deviceTransform);
735 m_visibilityManager.showGates(checked);
740 m_visibilityManager.showWires(checked);
765 updateUndoText(m_undoStack.undoText());
766 updateRedoText(m_undoStack.redoText());
769void Scene::updateUndoText(
const QString &text)
771 const QString prefix = tr(
"&Undo");
772 m_undoAction->setText(text.isEmpty() ? prefix : prefix + QLatin1Char(
' ') + text);
775void Scene::updateRedoText(
const QString &text)
777 const QString prefix = tr(
"&Redo");
778 m_redoAction->setText(text.isEmpty() ? prefix : prefix + QLatin1Char(
' ') + text);
783 if (
auto *item =
itemAt(m_interaction.lastMousePos())) {
785 if (selectedItems().
contains(item)) {
791 item->setSelected(
true);
797 auto *
pasteAction = menu.addAction(QIcon(QPixmap(
":/Interface/Toolbar/paste.svg")), tr(
"Paste"));
798 const auto *mimeData = QApplication::clipboard()->mimeData();
806 auto *selectAllAction = menu.addAction(tr(
"Select all"));
807 selectAllAction->setEnabled(!
elements().isEmpty());
810 menu.exec(screenPos);
817 m_clipboardManager.copy();
823 m_clipboardManager.cut();
829 m_clipboardManager.paste();
835 m_clipboardManager.duplicate();
841 const auto selectedItems_ = selectedItems();
846 if (!selectedItems_.isEmpty()) {
856 const auto items_ =
items();
858 for (
auto *item : items_) {
859 item->setSelected(
true);
875void Scene::rotate(
const int angle)
879 if (!elements_.isEmpty()) {
887 if (
auto *audioElement = qobject_cast<AudioOutputElement *>(element)) {
888 audioElement->mute(
mute);
898 if (!elements_.isEmpty()) {
908 if (!elements_.isEmpty()) {
918void moveElementsTo(
Scene *scene,
const QList<GraphicElement *> &elements,
const QList<QPointF> &newPositions)
920 QList<QPointF> oldPositions;
921 oldPositions.reserve(elements.size());
922 for (
auto *elm : elements) {
923 oldPositions.append(elm->pos());
926 for (
int i = 0; i < elements.size(); ++i) {
927 elements.at(i)->setPos(newPositions.at(i));
938 if (elements_.size() < 2) {
942 qreal target = elements_.constFirst()->sceneBoundingRect().left();
943 for (
auto *elm : elements_) {
944 target = std::min(target, elm->sceneBoundingRect().left());
947 QList<QPointF> newPositions;
948 newPositions.reserve(elements_.size());
949 for (
auto *elm : elements_) {
950 const qreal delta = target - elm->sceneBoundingRect().left();
951 newPositions.append(elm->pos() + QPointF(delta, 0));
954 moveElementsTo(
this, elements_, newPositions);
960 if (elements_.size() < 2) {
964 qreal target = elements_.constFirst()->sceneBoundingRect().right();
965 for (
auto *elm : elements_) {
966 target = std::max(target, elm->sceneBoundingRect().right());
969 QList<QPointF> newPositions;
970 newPositions.reserve(elements_.size());
971 for (
auto *elm : elements_) {
972 const qreal delta = target - elm->sceneBoundingRect().right();
973 newPositions.append(elm->pos() + QPointF(delta, 0));
976 moveElementsTo(
this, elements_, newPositions);
982 if (elements_.size() < 2) {
986 qreal target = elements_.constFirst()->sceneBoundingRect().top();
987 for (
auto *elm : elements_) {
988 target = std::min(target, elm->sceneBoundingRect().top());
991 QList<QPointF> newPositions;
992 newPositions.reserve(elements_.size());
993 for (
auto *elm : elements_) {
994 const qreal delta = target - elm->sceneBoundingRect().top();
995 newPositions.append(elm->pos() + QPointF(0, delta));
998 moveElementsTo(
this, elements_, newPositions);
1004 if (elements_.size() < 2) {
1008 qreal target = elements_.constFirst()->sceneBoundingRect().bottom();
1009 for (
auto *elm : elements_) {
1010 target = std::max(target, elm->sceneBoundingRect().bottom());
1013 QList<QPointF> newPositions;
1014 newPositions.reserve(elements_.size());
1015 for (
auto *elm : elements_) {
1016 const qreal delta = target - elm->sceneBoundingRect().bottom();
1017 newPositions.append(elm->pos() + QPointF(0, delta));
1020 moveElementsTo(
this, elements_, newPositions);
1026 if (elements_.size() < 2) {
1031 for (
auto *elm : elements_) {
1032 sum += elm->sceneBoundingRect().center().x();
1034 const qreal target = sum /
static_cast<qreal
>(elements_.size());
1036 QList<QPointF> newPositions;
1037 newPositions.reserve(elements_.size());
1038 for (
auto *elm : elements_) {
1039 const qreal delta = target - elm->sceneBoundingRect().center().x();
1040 newPositions.append(elm->pos() + QPointF(delta, 0));
1043 moveElementsTo(
this, elements_, newPositions);
1049 if (elements_.size() < 2) {
1054 for (
auto *elm : elements_) {
1055 sum += elm->sceneBoundingRect().center().y();
1057 const qreal target = sum /
static_cast<qreal
>(elements_.size());
1059 QList<QPointF> newPositions;
1060 newPositions.reserve(elements_.size());
1061 for (
auto *elm : elements_) {
1062 const qreal delta = target - elm->sceneBoundingRect().center().y();
1063 newPositions.append(elm->pos() + QPointF(0, delta));
1066 moveElementsTo(
this, elements_, newPositions);
1072 if (elements_.size() < 3) {
1077 return a->sceneBoundingRect().left() < b->sceneBoundingRect().left();
1080 const qreal spanStart = elements_.constFirst()->sceneBoundingRect().left();
1081 const qreal spanEnd = elements_.constLast()->sceneBoundingRect().right();
1083 qreal totalWidth = 0;
1084 for (
auto *elm : elements_) {
1085 totalWidth += elm->sceneBoundingRect().width();
1087 const qreal gap = (spanEnd - spanStart - totalWidth) /
static_cast<qreal
>(elements_.size() - 1);
1089 QList<QPointF> newPositions;
1090 newPositions.reserve(elements_.size());
1091 newPositions.append(elements_.constFirst()->pos());
1093 qreal cursor = elements_.constFirst()->sceneBoundingRect().right() + gap;
1094 for (
int i = 1; i < elements_.size() - 1; ++i) {
1095 auto *elm = elements_.at(i);
1096 const qreal delta = cursor - elm->sceneBoundingRect().left();
1097 newPositions.append(elm->pos() + QPointF(delta, 0));
1098 cursor += elm->sceneBoundingRect().width() + gap;
1101 newPositions.append(elements_.constLast()->pos());
1103 moveElementsTo(
this, elements_, newPositions);
1109 if (elements_.size() < 3) {
1114 return a->sceneBoundingRect().top() < b->sceneBoundingRect().top();
1117 const qreal spanStart = elements_.constFirst()->sceneBoundingRect().top();
1118 const qreal spanEnd = elements_.constLast()->sceneBoundingRect().bottom();
1120 qreal totalHeight = 0;
1121 for (
auto *elm : elements_) {
1122 totalHeight += elm->sceneBoundingRect().height();
1124 const qreal gap = (spanEnd - spanStart - totalHeight) /
static_cast<qreal
>(elements_.size() - 1);
1126 QList<QPointF> newPositions;
1127 newPositions.reserve(elements_.size());
1128 newPositions.append(elements_.constFirst()->pos());
1130 qreal cursor = elements_.constFirst()->sceneBoundingRect().bottom() + gap;
1131 for (
int i = 1; i < elements_.size() - 1; ++i) {
1132 auto *elm = elements_.at(i);
1133 const qreal delta = cursor - elm->sceneBoundingRect().top();
1134 newPositions.append(elm->pos() + QPointF(0, delta));
1135 cursor += elm->sceneBoundingRect().height() + gap;
1138 newPositions.append(elements_.constLast()->pos());
1140 moveElementsTo(
this, elements_, newPositions);
1145 if (!mimeData->hasUrls()) {
1148 for (
const QUrl &url : mimeData->urls()) {
1149 if (!url.isLocalFile()) {
1152 const QString path = url.toLocalFile();
1153 if (path.endsWith(QLatin1String(
".panda"), Qt::CaseInsensitive)) {
1164 event->acceptProposedAction();
1168 QGraphicsScene::dragEnterEvent(event);
1175 event->acceptProposedAction();
1179 QGraphicsScene::dragMoveEvent(event);
1188 if (!pandaFile.isEmpty()) {
1189 event->acceptProposedAction();
1196 m_dropHandler.handleNewElementDrop(event);
1201 m_dropHandler.handleCloneDrag(event);
1204 QGraphicsScene::dropEvent(event);
1207bool Scene::nudgeSelection(QKeyEvent *event)
1210 if (event->modifiers().testFlag(Qt::ControlModifier) || event->modifiers().testFlag(Qt::AltModifier)) {
1216 switch (event->key()) {
1217 case Qt::Key_Left: dx = -1;
break;
1218 case Qt::Key_Right: dx = 1;
break;
1219 case Qt::Key_Up: dy = -1;
break;
1220 case Qt::Key_Down: dy = 1;
break;
1221 default:
return false;
1225 if (selected.isEmpty()) {
1232 const QPointF delta(dx * step, dy * step);
1234 QList<QPointF> oldPositions;
1235 oldPositions.reserve(selected.size());
1236 for (
auto *elm : selected) {
1237 oldPositions.append(elm->pos());
1239 for (
auto *elm : selected) {
1240 elm->setPos(elm->pos() + delta);
1253 if (event->isAutoRepeat()) {
1254 QGraphicsScene::keyPressEvent(event);
1260 if (nudgeSelection(event)) {
1265 if (!(event->modifiers().testFlag(Qt::ControlModifier))) {
1267 if (element->hasTrigger() && !element->trigger().isEmpty() && element->trigger().matches(event->key())) {
1268 if (
auto *input = qobject_cast<GraphicElementInput *>(element); input && !input->isLocked()) {
1275 QGraphicsScene::keyPressEvent(event);
1282 if (event->isAutoRepeat()) {
1283 QGraphicsScene::keyReleaseEvent(event);
1287 if (!(event->modifiers().testFlag(Qt::ControlModifier))) {
1289 if (element->hasTrigger() && !element->trigger().isEmpty() && element->trigger().matches(event->key())) {
1291 if (
auto *input = qobject_cast<GraphicElementInput *>(element); input && !input->isLocked() && (element->elementType() == ElementType::InputButton)) {
1298 QGraphicsScene::keyReleaseEvent(event);
1303 if (
auto *mouseEvent =
dynamic_cast<QGraphicsSceneMouseEvent *
>(event)) {
1308 if (mouseEvent->modifiers().testFlag(Qt::ShiftModifier)) {
1309 mouseEvent->setModifiers(Qt::ControlModifier);
1310 return QGraphicsScene::eventFilter(watched, event);
1315 if ((mouseEvent->button() == Qt::LeftButton) && mouseEvent->modifiers().testFlag(Qt::ControlModifier)) {
1317 item->setSelected(
true);
1318 m_clipboardManager.cloneDrag(mouseEvent->scenePos());
1324 return QGraphicsScene::eventFilter(watched, event);
1329 if (!m_interaction.mousePress(event)) {
1330 QGraphicsScene::mousePressEvent(event);
1344 if (m_handlingMouseMove) {
1347 m_handlingMouseMove =
true;
1348 const auto resetFlag = qScopeGuard([
this] { m_handlingMouseMove =
false; });
1350 if (!m_interaction.mouseMove(event)) {
1351 QGraphicsScene::mouseMoveEvent(event);
1357 if (!m_interaction.mouseRelease(event)) {
1358 QGraphicsScene::mouseReleaseEvent(event);
1364 if (!m_interaction.mouseDoubleClick(event)) {
1365 QGraphicsScene::mouseDoubleClickEvent(event);
1375 if (
auto *port =
portAt(event->scenePos())) {
1376 m_connectionManager.showHoverLabels(port);
1380 QGraphicsScene::helpEvent(event);
1385 m_dropHandler.addFromMimeData(mimeData, scenePos);
Shared base class for audio output elements (AudioBox and Buzzer).
All QUndoCommand subclasses and the CommandUtils helper namespace.
Common logging utilities, the Pandaception error type, and helper macros.
#define qCDebug(category)
Connection: a wire that connects an output port to an input port in the circuit scene.
Shared numeric constants used across layers.
Singleton factory for all circuit element types.
Abstract base class for all graphical circuit elements.
Extended QGraphicsView with zoom, pan, and fast-rendering modes.
Integrated Circuit (IC) graphic element that encapsulates a sub-circuit file.
Defines ItemWithId, the base class for all uniquely identified circuit items.
MIME type string constants for drag-and-drop operations.
Graph algorithms for topological priority assignment and cycle detection.
void calculatePriorities(const QVector< T * > &elements, const QHash< T *, QVector< T * > > &successors, QHash< T *, int > &outPriorities)
Priority calculation for directed graphs.
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)
Deserialization/serialization context structs passed through load()/save() call chains.
SerializationPurpose
What a serialization operation is for – shared by both load() and save(), so the same distinction is ...
Circuit and waveform file serialization/deserialization utilities.
Theme management types and singleton ThemeManager.
Graphic element for a user-programmable truth table.
static bool canPaste(const QMimeData *mimeData)
Returns whether mimeData carries circuit data paste() accepts.
static bool isConnectionAllowed(OutputPort *startPort, InputPort *endPort)
Returns true if a wire from startPort to endPort is permitted.
Undo command that removes a list of items from the scene.
Undo command that flips a selection of elements along a horizontal or vertical axis.
Abstract base class for all graphical circuit elements in wiRedPanda.
void inlineEditRequested(GraphicElement *element)
Extended QGraphicsView with enhanced navigation capabilities.
void previewHideRequested()
void previewRequested(IC *ic, const QPoint &screenPos)
void previewMoved(IC *ic, const QPoint &screenPos)
void requestOpenSubCircuit(int elementId, const QString &blobName, const QString &filePath)
Emitted on double-click to request opening the sub-circuit in a new tab.
void previewCancelRequested(IC *ic)
Base class providing a unique integer identifier for circuit items.
Undo command that records a drag-move of a set of elements.
A port that drives a signal (the source end of a wire).
Abstract base class for circuit element ports (connection endpoints).
Undo command that rotates a list of elements by a fixed angle.
static bool isSupportedDropFormat(const QMimeData *mimeData)
Returns true if mimeData carries any recognised wiRedPanda drop format.
void registerItem(ItemWithId *item)
Registers item under its current id.
Main circuit editing scene.
void flipHorizontally()
Flips selected elements horizontally.
const QVector< GraphicElement * > elements() const
Returns all graphic elements in the scene.
void alignBottom()
Aligns selected elements' bottom edges to the bottommost selected edge. No-op below 2 elements.
void icPreviewRequested(IC *ic, const QPoint &screenPos)
void restoreWireAntialiasing()
void setLastId(int newLastId)
Advances the scene's ID counter to at least newLastId.
void dropEvent(QGraphicsSceneDragDropEvent *event) override
void recordWirePaintPass(qint64 elapsedNs)
void pasteAction()
Pastes items from the internal clipboard into the scene.
void fileDropRequested(const QString &filePath)
Emitted when a .panda file is dropped onto the canvas from the file manager.
QRectF cachedItemsBoundingRect() const
void resizeScene()
Tightens the scene rect to item bounds while preserving the viewport position.
bool wireAntialiasingEnabled() const
true while wires should stroke with antialiasing; Connection::paint() consults this.
void keyPressEvent(QKeyEvent *event) override
void nextSecndPropShortcut()
Advances the secondary property of selected elements to the next value.
void showStatusMessageRequested(const QString &message)
GraphicsView * view() const
Returns the GraphicsView currently displaying this scene.
void contextMenuPos(QPoint screenPos, QGraphicsItem *itemAtMouse)
Emitted when a context menu should appear.
void mousePressEvent(QGraphicsSceneMouseEvent *event) override
bool eventFilter(QObject *watched, QEvent *event) override
void alignRight()
Aligns selected elements' right edges to the rightmost selected edge. No-op below 2 elements.
void setCircuitUpdateRequired()
Marks the simulation mapping as stale so it is rebuilt on the next tick.
Port * portAt(QPointF pos) const
void prevElm()
Cycles selection backward to the previous element.
void forgetItemId(int id)
void removeItem(QGraphicsItem *item)
Removes item from the scene and unregisters it from the per-scene ID registry.
void copyAction()
Copies the selected items to the internal clipboard.
QAction * redoAction() const
Returns the redo QAction bound to the undo stack.
void icOpenRequested(int elementId, const QString &blobName, const QString &filePath)
Emitted when an IC element is double-clicked to request opening its sub-circuit.
void showStatusMessage(const QString &message)
void icPreviewMoved(IC *ic, const QPoint &screenPos)
static QHash< QString, InputPort * > wirelessTxInputPorts(const QVector< GraphicElement * > &elements)
Returns a map from wireless channel label to the Tx node's input port.
void rotateLeft()
Rotates selected elements 90 degrees counter-clockwise.
void receiveCommand(QUndoCommand *cmd)
Pushes cmd onto the undo stack (immediately executes its redo()).
int lastId() const
Returns the current highest ID in use by this scene.
void contextMenu(const QPoint screenPos)
Opens the element/selection context menu at screenPos (driven by SceneInteraction on right-click).
const QList< GraphicElement * > selectedElements() const
Returns the list of currently selected graphic elements.
static bool isConnectionAllowed(OutputPort *startPort, InputPort *endPort)
Returns true if a wire from startPort to endPort is permitted.
void distributeVertically()
void duplicateAction()
Duplicates the selection in place (leaves the system clipboard untouched).
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override
QString contextDir() const override
Returns the directory of the .panda file associated with this scene.
void icPreviewCancelRequested(IC *ic)
void prevMainPropShortcut()
Retreats the main property of selected elements to the previous value.
void prevSecndPropShortcut()
Retreats the secondary property of selected elements to the previous value.
void dragEnterEvent(QGraphicsSceneDragDropEvent *event) override
void setAutosaveRequired()
Schedules an autosave of the current circuit state.
bool contains(int id) const
Returns true if an item with id is registered in this scene.
void selectAll()
Selects all items in the scene.
QGraphicsItem * itemAt(QPointF pos) const
Returns the topmost item at pos, prioritising ports over elements.
ItemWithId * itemById(int id) const
Returns the item registered under id, or nullptr if not found.
void rotateRight()
Rotates selected elements 90 degrees clockwise.
void alignVerticalCenter()
static QVector< GraphicElement * > sortByTopology(QVector< GraphicElement * > elements)
Returns elements sorted in topological dependency order (inputs first).
void setPropertyUpdateRequired()
void retranslateUi()
Updates undo/redo action text to reflect the current UI language.
void flipVertically()
Flips selected elements vertically.
void addItem(QGraphicsItem *item)
Adds item to the scene and registers it in the per-scene ID registry.
void mute(const bool mute=true)
Mutes or unmutes selected elements according to mute.
void keyReleaseEvent(QKeyEvent *event) override
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override
void showWires(bool checked)
Shows or hides connection wires. Delegates to VisibilityManager.
void nextMainPropShortcut()
Advances the main property of selected elements to the next value.
void nextElm()
Cycles selection forward to the next element.
void icPreviewHideRequested()
void deleteAction()
Deletes the currently selected items.
void helpEvent(QGraphicsSceneHelpEvent *event) override
int nextId()
Returns the next available scene-local ID (does not assign it).
Scene(QObject *parent=nullptr)
Constructs a Scene and initialises the undo stack and simulation.
void alignTop()
Aligns selected elements' top edges to the topmost selected edge. No-op below 2 elements.
QList< QGraphicsItem * > items(Qt::SortOrder order=Qt::AscendingOrder) const
void alignLeft()
Aligns selected elements' left edges to the leftmost selected edge. No-op below 2 elements.
void updateItemId(ItemWithId *item, int newId)
Reassigns the ID of item to newId without adding it to the scene.
void circuitHasChanged()
Emitted whenever the circuit changes (element added/removed/moved).
static QString droppedPandaFile(const QMimeData *mimeData)
void distributeHorizontally()
void openTruthTableRequested()
Emitted when a TruthTable element is double-clicked to request opening its editor.
void dragMoveEvent(QGraphicsSceneDragDropEvent *event) override
void showGates(bool checked)
Shows or hides gate elements. Delegates to VisibilityManager.
void alignHorizontalCenter()
const QVector< GraphicElement * > visibleElements() const
Returns all visible (non-hidden) graphic elements in the scene.
void updateTheme()
Propagates the current theme to all elements and connections.
SerializationContext deserializationContext(QHash< quint64, Port * > &portMap, const QVersionNumber &version, SerializationPurpose purpose)
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override
void cutAction()
Cuts the selected items to the internal clipboard.
const QVector< GraphicElement * > unsortedElements() const
void setView(GraphicsView *view)
Sets the GraphicsView that displays this scene to view.
QAction * undoAction() const
Returns the undo QAction bound to the undo stack.
Simulation * simulation()
Returns the simulation engine associated with this scene.
QUndoStack * undoStack()
Returns the scene's undo stack.
Manages the digital circuit simulation loop.
Contains all color attributes for a theme.
static ThemeManager & instance()
Returns the singleton ThemeManager instance.
void themeChanged()
Emitted whenever the active theme changes.
static const ThemeAttributes & attributes()
Returns the current ThemeAttributes color set.
void requestOpenTruthTableEditor()
Emitted on double-click to request opening the truth table editor.
void reapply()
Reapplies current visibility state (used after structural changes).
constexpr int gridSize
Scene grid unit in pixels (elements snap to gridSize/2).
constexpr const char * CloneDragLegacy
Legacy clone-drag MIME type retained for backward compatibility.
constexpr const char * CloneDrag
Current MIME type for clone-drag (Ctrl+drag within the scene).
constexpr const char * DragDropLegacy
Legacy drag-and-drop MIME type retained for backward compatibility.
constexpr const char * DragDrop
Current drag-and-drop MIME type for element palette → scene drops.
Bundles all per-deserialization state so that load() overrides receive it through one explicit parame...
QMap< QString, QByteArray > * blobRegistry
Blob registry for resolving embedded IC blobNames during deserialization.