void GraphConnection::deserialize(Poco::JSON::Object::Ptr obj) { auto outputId = QString::fromStdString(obj->getValue<std::string>("outputId")); auto inputId = QString::fromStdString(obj->getValue<std::string>("inputId")); auto outputKey = QString::fromStdString(obj->getValue<std::string>("outputKey")); auto inputKey = QString::fromStdString(obj->getValue<std::string>("inputKey")); auto draw = dynamic_cast<GraphDraw *>(this->parent()); assert(draw != nullptr); //resolve IO objects by id QPointer<GraphObject> inputObj = nullptr; QPointer<GraphObject> outputObj = nullptr; for (const auto obj : draw->getGraphObjects(false)) { if (obj->getId() == inputId) inputObj = obj; if (obj->getId() == outputId) outputObj = obj; } if (inputObj.isNull()) throw Pothos::Exception("GraphConnection::deserialize()", "cant resolve object with ID: '"+inputId.toStdString()+"'"); if (outputObj.isNull()) throw Pothos::Exception("GraphConnection::deserialize()", "cant resolve object with ID: '"+outputId.toStdString()+"'"); this->setupEndpoint(GraphConnectionEndpoint(inputObj, GraphConnectableKey(inputKey, true))); this->setupEndpoint(GraphConnectionEndpoint(outputObj, GraphConnectableKey(outputKey, false))); assert(this->getInputEndpoint().isValid()); assert(this->getOutputEndpoint().isValid()); GraphObject::deserialize(obj); }
GraphConnectionEndpoint GraphDraw::mousedEndpoint(const QPoint &pos) { auto objs = this->items(pos); if (_connectLineItem) objs.removeOne(_connectLineItem.get()); if (objs.empty()) return GraphConnectionEndpoint(); auto topObj = dynamic_cast<GraphObject *>(objs.front()); if (topObj == nullptr) return GraphConnectionEndpoint(); const auto point = topObj->mapFromParent(this->mapToScene(pos)); return GraphConnectionEndpoint(topObj, topObj->isPointingToConnectable(point)); }
void GraphDraw::doClickSelection(const QPointF &point) { const bool ctrlDown = QApplication::keyboardModifiers() & Qt::ControlModifier; const auto objs = this->items(this->mapFromScene(point)); //nothing selected, clear the last selected endpoint if (objs.empty()) _lastClickSelectEp = GraphConnectionEndpoint(); //connection creation logic if (not ctrlDown and not objs.empty()) { auto topObj = dynamic_cast<GraphObject *>(objs.front()); if (topObj == nullptr) return; GraphConnectionEndpoint thisEp(topObj, topObj->isPointingToConnectable(topObj->mapFromParent(point))); //valid keys, attempt to make a connection QPointer<GraphConnection> conn; if (thisEp.isValid() and _lastClickSelectEp.isValid() and not (thisEp == _lastClickSelectEp) and //end points valid (_lastClickSelectEp.getConnectableAttrs().direction == GRAPH_CONN_OUTPUT or _lastClickSelectEp.getConnectableAttrs().direction == GRAPH_CONN_SIGNAL) and //last endpoint is output (thisEp.getConnectableAttrs().direction == GRAPH_CONN_INPUT or thisEp.getConnectableAttrs().direction == GRAPH_CONN_SLOT)) //this click endpoint is input { try { conn = this->getGraphEditor()->makeConnection(thisEp, _lastClickSelectEp); this->getGraphEditor()->handleStateChange(GraphState("connect-arrow", tr("Connect %1[%2] to %3[%4]").arg( conn->getOutputEndpoint().getObj()->getId(), conn->getOutputEndpoint().getKey().id, conn->getInputEndpoint().getObj()->getId(), conn->getInputEndpoint().getKey().id ))); } catch (const Pothos::Exception &ex) { poco_warning(Poco::Logger::get("PothosGui.GraphDraw.connect"), Poco::format("Cannot connect port %s[%s] to port %s[%s]: %s", _lastClickSelectEp.getObj()->getId().toStdString(), _lastClickSelectEp.getKey().id.toStdString(), thisEp.getObj()->getId().toStdString(), thisEp.getKey().id.toStdString(), ex.message())); } } //cleanup after new connection if (not conn.isNull()) { _lastClickSelectEp = GraphConnectionEndpoint(); this->deselectAllObjs(); } //otherwise save the click select else { _lastClickSelectEp = thisEp; } } }
void GraphDraw::mouseReleaseEvent(QMouseEvent *event) { QGraphicsView::mouseReleaseEvent(event); if (QApplication::keyboardModifiers() & Qt::ControlModifier) return; //releasing the connection line to create if (_connectLineItem) { const auto thisEp = this->mousedEndpoint(event->pos()); this->tryToMakeConnection(thisEp); auto actions = MainActions::global(); if (not actions->clickConnectModeAction->isChecked()) _lastClickSelectEp = GraphConnectionEndpoint(); } //emit the move event up to the graph editor if (_selectionState == SELECTION_STATE_MOVE) { bool moved = false; for (const auto &pair : _preMovePositions) { if (pair.first->pos() != pair.second) moved = true; } if (moved) this->getGraphEditor()->handleStateChange( GraphState("transform-move", tr("Move %1").arg(this->getSelectionDescription(~GRAPH_CONNECTION)))); } this->clearSelectionState(); this->render(); }
void GraphDraw::mousePressEvent(QMouseEvent *event) { QGraphicsView::mousePressEvent(event); if (QApplication::keyboardModifiers() & Qt::ControlModifier) return; //record the conditions of this press event, nothing is changed if (event->button() == Qt::LeftButton) { _selectionState = SELECTION_STATE_PRESS; const auto objs = this->getObjectsAtPos(event->pos()); if (objs.empty()) { //perform deselect when background is clicked _lastClickSelectEp = GraphConnectionEndpoint(); this->deselectAllObjs(); } else { //make the clicked object topmost objs.front()->setZValue(this->getMaxZValue()+1); } //handle click selection for connections const auto thisEp = this->mousedEndpoint(event->pos()); //enter the connect drag mode and object immobilization //slots are exempt because they are the block's body if (thisEp.isValid()) { auto topObj = thisEp.getObj(); if (thisEp.getKey().direction != GRAPH_CONN_SLOT) { _connectLineItem.reset(new QGraphicsLineItem(topObj)); _connectLineItem->setPen(QPen(QColor(GraphObjectDefaultPenColor), ConnectModeLineWidth)); _connectModeImmobilizer.reset(new GraphObjectImmobilizer(topObj)); } //if separate clicks to connect when try to make connection auto actions = MainActions::global(); if (actions->clickConnectModeAction->isChecked()) { if (not this->tryToMakeConnection(thisEp)) { //stash this endpoint when connection is not made _lastClickSelectEp = thisEp; } } //stash the connection endpoint for a drag+release connection else _lastClickSelectEp = thisEp; } } this->render(); }
bool GraphDraw::tryToMakeConnection(const GraphConnectionEndpoint &thisEp) { QPointer<GraphConnection> conn; const auto &lastEp = _lastClickSelectEp; //valid keys, attempt to make a connection when the endpoints differ in direction if (thisEp.isValid() and lastEp.isValid() and //both are valid lastEp.getKey().isInput() != thisEp.getKey().isInput()) //directions differ { try { conn = this->getGraphEditor()->makeConnection(thisEp, _lastClickSelectEp); this->getGraphEditor()->handleStateChange(GraphState("connect-arrow", tr("Connect %1[%2] to %3[%4]").arg( conn->getOutputEndpoint().getObj()->getId(), conn->getOutputEndpoint().getKey().id, conn->getInputEndpoint().getObj()->getId(), conn->getInputEndpoint().getKey().id ))); } catch (const Pothos::Exception &ex) { static auto &logger = Poco::Logger::get("PothosFlow.GraphDraw"); logger.warning("Cannot connect port %s[%s] to port %s[%s]: %s", _lastClickSelectEp.getObj()->getId().toStdString(), _lastClickSelectEp.getKey().id.toStdString(), thisEp.getObj()->getId().toStdString(), thisEp.getKey().id.toStdString(), ex.message()); } //cleanup regardless of failure _lastClickSelectEp = GraphConnectionEndpoint(); this->deselectAllObjs(); } //if this is a signal or slot connection //open the properties panel for configuration if (conn and conn->isSignalOrSlot()) { emit this->modifyProperties(conn); } return bool(conn); }
void GraphEditor::handleMoveGraphObjects(const int index) { if (not this->isVisible()) return; if (index >= this->count()) return; auto draw = this->getCurrentGraphDraw(); auto desc = tr("Move %1 to %2").arg(draw->getSelectionDescription(~GRAPH_CONNECTION), this->tabText(index)); //move all selected objects for (auto obj : draw->getObjectsSelected()) { obj->setSelected(false); this->getGraphDraw(index)->scene()->addItem(obj); } //reparent all connections based on endpoints: std::vector<GraphConnection *> boundaryConnections; for (auto obj : this->getGraphObjects(GRAPH_CONNECTION)) { auto conn = dynamic_cast<GraphConnection *>(obj); assert(conn != nullptr); //Connection has the same endpoints, so make sure that the parent is corrected to the endpoint if (conn->getOutputEndpoint().getObj()->scene() == conn->getInputEndpoint().getObj()->scene()) { if (conn->getOutputEndpoint().getObj()->scene() != conn->scene()) { conn->getInputEndpoint().getObj()->scene()->addItem(conn); } } //otherwise stash it for more processing else { boundaryConnections.push_back(conn); } } //create breakers for output endpoints that have to cross for (auto conn : boundaryConnections) { const auto &epOut = conn->getOutputEndpoint(); const auto &epIn = conn->getInputEndpoint(); auto sigSlotPairs = conn->getSigSlotPairs(); if (sigSlotPairs.empty()) sigSlotPairs.resize(1); //add empty for (const auto &sigSlotPair : sigSlotPairs) { auto breaker = findInputBreaker(this, epOut); if (breaker != nullptr) continue; breaker = new GraphBreaker(epOut.getObj()->draw()); breaker->setInput(true); const auto name = QString("%1[%2]").arg(epOut.getObj()->getId(), epOut.getKey().id); breaker->setId(this->newId(name)); breaker->setNodeName(breaker->getId()); //the first of its name breaker->setRotation(epIn.getObj()->rotation()); breaker->setPos(epIn.getObj()->pos()); auto outConn = this->makeConnection(epOut, GraphConnectionEndpoint(breaker, breaker->getConnectableKeys().at(0))); if (not sigSlotPair.first.isEmpty()) outConn->addSigSlotPair(std::make_pair(sigSlotPair.first, breaker->getConnectableKeys().at(0).id)); if (outConn->scene() != breaker->scene()) breaker->scene()->addItem(outConn); //use desired parent } } //create breakers for input endpoints that have to cross for (auto conn : boundaryConnections) { const auto &epOut = conn->getOutputEndpoint(); const auto &epIn = conn->getInputEndpoint(); auto sigSlotPairs = conn->getSigSlotPairs(); if (sigSlotPairs.empty()) sigSlotPairs.resize(1); //add empty for (const auto &sigSlotPair : sigSlotPairs) { //find the existing breaker or make a new one const auto name = findInputBreaker(this, epOut)->getNodeName(); GraphBreaker *breaker = nullptr; for (auto obj : this->getGraphObjects(GRAPH_BREAKER)) { if (obj->draw() != epIn.getObj()->draw()) continue; auto outBreaker = dynamic_cast<GraphBreaker *>(obj); assert(outBreaker != nullptr); if (outBreaker->isInput()) continue; if (outBreaker->getNodeName() != name) continue; breaker = outBreaker; break; } //make a new output breaker if (breaker == nullptr) { breaker = new GraphBreaker(epIn.getObj()->draw()); breaker->setInput(false); breaker->setId(this->newId(name)); breaker->setNodeName(name); breaker->setRotation(epOut.getObj()->rotation()); breaker->setPos(epOut.getObj()->pos()); } //connect to this breaker auto inConn = this->makeConnection(epIn, GraphConnectionEndpoint(breaker, breaker->getConnectableKeys().at(0))); if (not sigSlotPair.second.isEmpty()) inConn->addSigSlotPair(std::make_pair(breaker->getConnectableKeys().at(0).id, sigSlotPair.second)); if (inConn->scene() != breaker->scene()) breaker->scene()->addItem(inConn); //use desired parent } delete conn; } handleStateChange(GraphState("transform-move", desc)); }