void GraphEditor::handleBlockXcrement(const int adj) { if (not this->isVisible()) return; auto draw = this->getCurrentGraphDraw(); GraphObjectList changedObjects; for (auto obj : draw->getObjectsSelected(GRAPH_BLOCK)) { auto block = dynamic_cast<GraphBlock *>(obj); assert(block != nullptr); for (const auto &propKey : block->getProperties()) { auto paramDesc = block->getParamDesc(propKey); if (paramDesc->has("widgetType") and paramDesc->getValue<std::string>("widgetType") == "SpinBox") { const auto newValue = block->getPropertyValue(propKey).toInt() + adj; block->setPropertyValue(propKey, QString("%1").arg(newValue)); changedObjects.push_back(block); break; } } } if (changedObjects.empty()) return; const auto desc = (changedObjects.size() == 1)? changedObjects.front()->getId() : tr("selected"); if (adj > 0) handleStateChange(GraphState("list-add", tr("Increment %1").arg(desc))); if (adj < 0) handleStateChange(GraphState("list-remove", tr("Decrement %1").arg(desc))); }
void GraphEditor::load(void) { auto fileName = this->getCurrentFilePath().toStdString(); if (fileName.empty()) { _stateManager->resetToDefault(); handleStateChange(GraphState("document-new", tr("Create new topology"))); _stateManager->saveCurrent(); this->render(); return; } try { poco_information_f1(Poco::Logger::get("PothosGui.GraphEditor.load"), "Loading %s from file", fileName); postStatusMessage(tr("Loading %1").arg(QString::fromStdString(fileName))); std::ifstream inFile(fileName.c_str()); this->loadState(inFile); } catch (const std::exception &ex) { poco_error_f2(Poco::Logger::get("PothosGui.GraphEditor.load"), "Error loading %s: %s", fileName, std::string(ex.what())); } _stateManager->resetToDefault(); handleStateChange(GraphState("document-new", tr("Load topology from file"))); _stateManager->saveCurrent(); this->updateGraphEditorMenus(); this->render(); }
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 BlockPropertiesPanel::handleCommitButton(void) { _updateTimer->stop(); //were there changes? std::vector<QString> propertiesModified; for (const auto &prop : _block->getProperties()) { if (_block->getPropertyValue(prop.getKey()) != _propIdToOriginal[prop.getKey()]) propertiesModified.push_back(prop.getName()); } //was the ID changed? if (_idOriginal != _block->getId()) propertiesModified.push_back(tr("ID")); //was the affinity zone changed? if (_affinityZoneOriginal != _block->getAffinityZone()) propertiesModified.push_back(tr("Affinity Zone")); if (propertiesModified.empty()) return this->handleCancelButton(); //emit a new graph state event auto desc = (propertiesModified.size() == 1)? propertiesModified.front() : tr("properties"); emit this->stateChanged(GraphState("document-properties", tr("Edit %1 %2").arg(_block->getId()).arg(desc))); //done with this panel this->deleteLater(); }
void ConnectionPropertiesPanel::handleCommit(void) { const auto createdPairs = mySetDifference(_conn->getSigSlotPairs(), _originalKeyPairs); const auto removedPairs = mySetDifference(_originalKeyPairs, _conn->getSigSlotPairs()); //description of the change QString desc; if (createdPairs.empty() and removedPairs.empty()) return this->handleCancel(); else if (createdPairs.empty() and removedPairs.size() == 1) { desc = tr("Removed %1->%2") .arg(_conn->getKeyName(removedPairs.at(0).first, _conn->getOutputEndpoint())) .arg(_conn->getKeyName(removedPairs.at(0).second, _conn->getInputEndpoint())); } else if (createdPairs.size() == 1 and removedPairs.empty()) { desc = tr("Created %1->%2") .arg(_conn->getKeyName(createdPairs.at(0).first, _conn->getOutputEndpoint())) .arg(_conn->getKeyName(createdPairs.at(0).second, _conn->getInputEndpoint())); } else { desc = tr("Changed signals->slots"); } //emit a new graph state event emit this->stateChanged(GraphState("document-properties", desc)); }
void GraphDraw::mouseReleaseEvent(QMouseEvent *event) { QGraphicsView::mouseReleaseEvent(event); //mouse released from a pressed state - alter selections at point if (_selectionState == SELECTION_STATE_PRESS) { this->doClickSelection(this->mapToScene(event->pos())); } //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 GraphEditor::handleSetEnabled(const bool enb) { if (not this->isVisible()) return; auto draw = this->getCurrentGraphDraw(); auto objs = draw->getObjectsSelected(); if (objs.isEmpty()) return; for (auto obj : objs) { obj->setEnabled(enb); } if (enb) handleStateChange(GraphState("document-import", tr("Enable %1").arg(draw->getSelectionDescription()))); else handleStateChange(GraphState("document-export", tr("Disable %1").arg(draw->getSelectionDescription()))); }
void GraphEditor::handleAddBlock(const Poco::JSON::Object::Ptr &blockDesc, const QPointF &where) { if (not blockDesc) return; auto draw = this->getCurrentGraphDraw(); auto block = new GraphBlock(draw); block->setBlockDesc(blockDesc); QString hint; const auto title = block->getTitle(); for (int i = 0; i < title.length(); i++) { if (i == 0 and title.at(i).isNumber()) hint.append(QChar('_')); if (title.at(i).isLetterOrNumber() or title.at(i).toLatin1() == '_') { hint.append(title.at(i)); } } block->setId(this->newId(hint)); //set highest z-index on new block block->setZValue(draw->getMaxZValue()+1); block->setPos(where); block->setRotation(0); handleStateChange(GraphState("list-add", tr("Create block %1").arg(title))); }
void BlockPropertiesPanel::handleCancelButton(void) { _updateTimer->stop(); //empty state causes reset to the last known point emit this->stateChanged(GraphState()); this->deleteLater(); }
void GraphEditor::handleDeleteGraphPage(void) { if (not this->isVisible()) return; const auto oldName = this->tabText(this->currentIndex()); this->removeTab(this->currentIndex()); if (this->count() == 0) this->makeDefaultPage(); this->updateGraphEditorMenus(); handleStateChange(GraphState("edit-delete", tr("Delete graph page ") + oldName)); }
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 GraphEditor::handleRotateRight(void) { if (not this->isVisible()) return; auto draw = this->getCurrentGraphDraw(); //TODO rotate group of objects around central point for (auto obj : draw->getObjectsSelected(~GRAPH_CONNECTION)) { obj->rotateRight(); } handleStateChange(GraphState("object-rotate-right", tr("Rotate %1 right").arg(draw->getSelectionDescription(~GRAPH_CONNECTION)))); }
void GraphEditor::handleCreateGraphPage(void) { if (not this->isVisible()) return; const QString newName = QInputDialog::getText(this, tr("Create page"), tr("New page name"), QLineEdit::Normal, tr("untitled")); if (newName.isEmpty()) return; this->addTab(new GraphDraw(this), newName); this->updateGraphEditorMenus(); handleStateChange(GraphState("document-new", tr("Create graph page ") + newName)); }
void GraphEditor::handleRenameGraphPage(void) { if (not this->isVisible()) return; const auto oldName = this->tabText(this->currentIndex()); const QString newName = QInputDialog::getText(this, tr("Rename page"), tr("New page name"), QLineEdit::Normal, oldName); if (newName.isEmpty()) return; this->setTabText(this->currentIndex(), newName); this->updateGraphEditorMenus(); handleStateChange(GraphState("edit-rename", tr("Rename graph page ") + oldName + " -> " + newName)); }
void GraphEditor::handleAffinityZoneClicked(const QString &zone) { if (not this->isVisible()) return; auto draw = this->getCurrentGraphDraw(); for (auto obj : draw->getObjectsSelected(GRAPH_BLOCK)) { auto block = dynamic_cast<GraphBlock *>(obj); assert(block != nullptr); block->setAffinityZone(zone); } handleStateChange(GraphState("document-export", tr("Set %1 affinity zone").arg(draw->getSelectionDescription(GRAPH_BLOCK)))); }
void GraphEditor::handleDelete(void) { if (not this->isVisible()) return; auto draw = this->getCurrentGraphDraw(); auto desc = tr("Delete %1").arg(draw->getSelectionDescription()); //delete all selected graph objects for (auto obj : draw->getObjectsSelected()) { delete obj; } this->deleteFlagged(); handleStateChange(GraphState("edit-delete", desc)); }
void GraphEditor::handleInsertGraphWidget(QObject *obj) { auto block = dynamic_cast<GraphBlock *>(obj); assert(block != nullptr); assert(block->isGraphWidget()); auto draw = this->getCurrentGraphDraw(); auto display = new GraphWidget(draw); display->setGraphBlock(block); display->setId(this->newId("Widget"+block->getId())); display->setZValue(draw->getMaxZValue()+1); display->setPos(draw->getLastContextMenuPos()); display->setRotation(0); handleStateChange(GraphState("insert-image", tr("Insert widget %1").arg(block->getId()))); }
void GraphEditor::handleCreateBreaker(const bool isInput) { if (not this->isVisible()) return; const auto dirName = isInput?tr("input"):tr("output"); const auto newName = QInputDialog::getText(this, tr("Create %1 breaker").arg(dirName), tr("New breaker node name"), QLineEdit::Normal, tr("untitled")); if (newName.isEmpty()) return; auto draw = this->getCurrentGraphDraw(); auto breaker = new GraphBreaker(draw); breaker->setInput(isInput); breaker->setNodeName(newName); breaker->setId(this->newId(newName)); breaker->setPos(draw->getLastContextMenuPos()); handleStateChange(GraphState("document-new", tr("Create %1 breaker %2").arg(dirName, newName))); }
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::handlePaste(void) { if (not this->isVisible()) return; auto draw = this->getCurrentGraphDraw(); auto mimeData = QApplication::clipboard()->mimeData(); const bool canPaste = mimeData->hasFormat("text/json/pothos_object_array") and not mimeData->data("text/json/pothos_object_array").isEmpty(); if (not canPaste) return; //extract object array const auto data = mimeData->data("text/json/pothos_object_array"); const std::string dataStr(data.constData(), data.size()); std::istringstream iss(dataStr); Poco::JSON::Parser p; p.parse(iss); auto graphObjects = p.getHandler()->asVar().extract<Poco::JSON::Array::Ptr>(); assert(graphObjects); //rewrite ids std::map<std::string, std::string> oldIdToNew; for (size_t objIndex = 0; objIndex < graphObjects->size(); objIndex++) { const auto jGraphObj = graphObjects->getObject(objIndex); auto oldId = jGraphObj->getValue<std::string>("id"); oldIdToNew[oldId] = this->newId(QString::fromStdString(oldId)).toStdString(); } for (size_t objIndex = 0; objIndex < graphObjects->size();) { for (auto &pair : *graphObjects->getObject(objIndex)) { if (QString::fromStdString(pair.first).endsWith("id", Qt::CaseInsensitive)) { //if not in oldIdToNew, remove from list if (oldIdToNew.count(pair.second) == 0) { graphObjects->remove(objIndex); goto nextObj; } pair.second = oldIdToNew[pair.second]; } } objIndex++; nextObj: continue; } //unselect all objects draw->deselectAllObjs(); //create objects GraphObjectList objsToMove; objsToMove.append(handlePasteType(draw, graphObjects, "Block")); objsToMove.append(handlePasteType(draw, graphObjects, "Breaker")); handlePasteType(draw, graphObjects, "Connection"); //dont append, connection position doesnt matter objsToMove.append(handlePasteType(draw, graphObjects, "Widget")); //deal with initial positions of pasted objects QPointF cornerest(1e6, 1e6); for (auto obj : objsToMove) { cornerest.setX(std::min(cornerest.x(), obj->pos().x())); cornerest.setY(std::min(cornerest.y(), obj->pos().y())); } //determine an acceptable position to center the paste auto view = dynamic_cast<QGraphicsView *>(this->currentWidget()); auto pastePos = view->mapToScene(view->mapFromGlobal(QCursor::pos())); if (not view->sceneRect().contains(pastePos)) { pastePos = view->mapToScene(this->size().width()/2, this->size().height()/2); } //move objects into position for (auto obj : objsToMove) obj->setPos(obj->pos()-cornerest+pastePos); handleStateChange(GraphState("edit-paste", tr("Paste %1").arg(draw->getSelectionDescription()))); }
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)); }