diff --git a/examples/styles/models.hpp b/examples/styles/models.hpp index 1352e4bfc..199f80bee 100644 --- a/examples/styles/models.hpp +++ b/examples/styles/models.hpp @@ -36,6 +36,8 @@ class MyDataModel : public NodeDelegateModel QString name() const override { return QString("MyDataModel"); } + bool labelEditable() const override { return true; } + public: QJsonObject save() const override { diff --git a/include/QtNodes/internal/AbstractGraphModel.hpp b/include/QtNodes/internal/AbstractGraphModel.hpp index 716550114..b4d928291 100644 --- a/include/QtNodes/internal/AbstractGraphModel.hpp +++ b/include/QtNodes/internal/AbstractGraphModel.hpp @@ -1,8 +1,8 @@ #pragma once -#include "Export.hpp" #include "ConnectionIdHash.hpp" #include "Definitions.hpp" +#include "Export.hpp" #include #include @@ -10,7 +10,6 @@ #include - namespace QtNodes { /** @@ -51,8 +50,7 @@ class NODE_EDITOR_PUBLIC AbstractGraphModel : public QObject */ virtual std::unordered_set connections(NodeId nodeId, PortType portType, - PortIndex index) const - = 0; + PortIndex index) const = 0; /// Checks if two nodes with the given `connectionId` are connected. virtual bool connectionExists(ConnectionId const connectionId) const = 0; @@ -132,8 +130,10 @@ class NODE_EDITOR_PUBLIC AbstractGraphModel : public QObject * @returns Port Data Type, Port Data, Connection Policy, Port * Caption. */ - virtual QVariant portData(NodeId nodeId, PortType portType, PortIndex index, PortRole role) const - = 0; + virtual QVariant portData(NodeId nodeId, + PortType portType, + PortIndex index, + PortRole role) const = 0; /** * A utility function that unwraps the `QVariant` value returned from the @@ -188,6 +188,8 @@ class NODE_EDITOR_PUBLIC AbstractGraphModel : public QObject virtual bool loopsEnabled() const { return true; } + virtual bool nodeZoomFitMenu(NodeId) { return false; } + public: /** * Function clears connections attached to the ports that are scheduled to be diff --git a/include/QtNodes/internal/AbstractNodeGeometry.hpp b/include/QtNodes/internal/AbstractNodeGeometry.hpp index acc60def5..c3be96301 100644 --- a/include/QtNodes/internal/AbstractNodeGeometry.hpp +++ b/include/QtNodes/internal/AbstractNodeGeometry.hpp @@ -36,8 +36,7 @@ class NODE_EDITOR_PUBLIC AbstractNodeGeometry /// Port position in node's coordinate system. virtual QPointF portPosition(NodeId const nodeId, PortType const portType, - PortIndex const index) const - = 0; + PortIndex const index) const = 0; /// A convenience function using the `portPosition` and a given transformation. virtual QPointF portScenePosition(NodeId const nodeId, @@ -48,8 +47,7 @@ class NODE_EDITOR_PUBLIC AbstractNodeGeometry /// Defines where to draw port label. The point corresponds to a font baseline. virtual QPointF portTextPosition(NodeId const nodeId, PortType const portType, - PortIndex const portIndex) const - = 0; + PortIndex const portIndex) const = 0; /** * Defines where to start drawing the caption. The point corresponds to a font @@ -60,6 +58,15 @@ class NODE_EDITOR_PUBLIC AbstractNodeGeometry /// Caption rect is needed for estimating the total node size. virtual QRectF captionRect(NodeId const nodeId) const = 0; + /** + * Defines where to start drawing the label. The point corresponds to a font + * baseline. + */ + virtual QPointF labelPosition(NodeId const nodeId) const = 0; + + /// Caption rect is needed for estimating the total node size. + virtual QRectF labelRect(NodeId const nodeId) const = 0; + /// Position for an embedded widget. Return any value if you don't embed. virtual QPointF widgetPosition(NodeId const nodeId) const = 0; @@ -69,6 +76,8 @@ class NODE_EDITOR_PUBLIC AbstractNodeGeometry virtual QRect resizeHandleRect(NodeId const nodeId) const = 0; + virtual int getPortSpacing() = 0; + protected: AbstractGraphModel &_graphModel; }; diff --git a/include/QtNodes/internal/BasicGraphicsScene.hpp b/include/QtNodes/internal/BasicGraphicsScene.hpp index 568835ff7..21fea8389 100644 --- a/include/QtNodes/internal/BasicGraphicsScene.hpp +++ b/include/QtNodes/internal/BasicGraphicsScene.hpp @@ -17,7 +17,6 @@ #include #include - class QUndoStack; namespace QtNodes { @@ -112,6 +111,8 @@ class NODE_EDITOR_PUBLIC BasicGraphicsScene : public QGraphicsScene */ virtual QMenu *createSceneMenu(QPointF const scenePos); + QMenu *createZoomMenu(QPointF const scenePos); + Q_SIGNALS: void modified(BasicGraphicsScene *); void nodeMoved(NodeId const nodeId, QPointF const &newLocation); @@ -125,6 +126,9 @@ class NODE_EDITOR_PUBLIC BasicGraphicsScene : public QGraphicsScene /// Signal allows showing custom context menu upon clicking a node. void nodeContextMenu(NodeId const nodeId, QPointF const pos); + /// Signals to call Graphics View's zoomFit methods + void zoomFitAllClicked(); + void zoomFitSelectedClicked(); private: /** diff --git a/include/QtNodes/internal/DataFlowGraphModel.hpp b/include/QtNodes/internal/DataFlowGraphModel.hpp index ff93c6eb4..f604b5ecc 100644 --- a/include/QtNodes/internal/DataFlowGraphModel.hpp +++ b/include/QtNodes/internal/DataFlowGraphModel.hpp @@ -11,10 +11,14 @@ #include #include +#include +#include namespace QtNodes { -class NODE_EDITOR_PUBLIC DataFlowGraphModel : public AbstractGraphModel, public Serializable +class NODE_EDITOR_PUBLIC DataFlowGraphModel + : public AbstractGraphModel + , public Serializable { Q_OBJECT @@ -43,7 +47,6 @@ class NODE_EDITOR_PUBLIC DataFlowGraphModel : public AbstractGraphModel, public NodeId addNode(QString const nodeType) override; - bool connectionPossible(ConnectionId const connectionId) const override; void addConnection(ConnectionId const connectionId) override; @@ -75,7 +78,6 @@ class NODE_EDITOR_PUBLIC DataFlowGraphModel : public AbstractGraphModel, public void loadNode(QJsonObject const &nodeJson) override; - // From Serializable QJsonObject save() const override; @@ -101,6 +103,8 @@ class NODE_EDITOR_PUBLIC DataFlowGraphModel : public AbstractGraphModel, public /// Loops do not make any sense in uni-direction data propagation bool loopsEnabled() const override { return false; } + bool nodeZoomFitMenu(NodeId) override; + Q_SIGNALS: void inPortDataWasSet(NodeId const, PortType const, PortIndex const); @@ -137,6 +141,9 @@ private Q_SLOTS: std::unordered_set _connectivity; mutable std::unordered_map _nodeGeometryData; + + std::unordered_map _labels; + std::unordered_map _labelsVisible; }; } // namespace QtNodes diff --git a/include/QtNodes/internal/DefaultHorizontalNodeGeometry.hpp b/include/QtNodes/internal/DefaultHorizontalNodeGeometry.hpp index 33367e109..b26555282 100644 --- a/include/QtNodes/internal/DefaultHorizontalNodeGeometry.hpp +++ b/include/QtNodes/internal/DefaultHorizontalNodeGeometry.hpp @@ -28,14 +28,21 @@ class NODE_EDITOR_PUBLIC DefaultHorizontalNodeGeometry : public AbstractNodeGeom QPointF portTextPosition(NodeId const nodeId, PortType const portType, PortIndex const PortIndex) const override; + QPointF captionPosition(NodeId const nodeId) const override; QRectF captionRect(NodeId const nodeId) const override; + QPointF labelPosition(const NodeId nodeId) const override; + + QRectF labelRect(NodeId const nodeId) const override; + QPointF widgetPosition(NodeId const nodeId) const override; QRect resizeHandleRect(NodeId const nodeId) const override; + int getPortSpacing() override { return _portSpacing; } + private: QRectF portTextRect(NodeId const nodeId, PortType const portType, @@ -52,7 +59,7 @@ class NODE_EDITOR_PUBLIC DefaultHorizontalNodeGeometry : public AbstractNodeGeom // constness of the Node. mutable unsigned int _portSize; - unsigned int _portSpasing; + unsigned int _portSpacing; mutable QFontMetrics _fontMetrics; mutable QFontMetrics _boldFontMetrics; }; diff --git a/include/QtNodes/internal/DefaultNodePainter.hpp b/include/QtNodes/internal/DefaultNodePainter.hpp index 953faa065..7a4f21a2c 100644 --- a/include/QtNodes/internal/DefaultNodePainter.hpp +++ b/include/QtNodes/internal/DefaultNodePainter.hpp @@ -28,6 +28,8 @@ class NODE_EDITOR_PUBLIC DefaultNodePainter : public AbstractNodePainter void drawNodeCaption(QPainter *painter, NodeGraphicsObject &ngo) const; + void drawNodeLabel(QPainter *painter, NodeGraphicsObject &ngo) const; + void drawEntryLabels(QPainter *painter, NodeGraphicsObject &ngo) const; void drawResizeRect(QPainter *painter, NodeGraphicsObject &ngo) const; diff --git a/include/QtNodes/internal/DefaultVerticalNodeGeometry.hpp b/include/QtNodes/internal/DefaultVerticalNodeGeometry.hpp index ce4dd9f17..d20636567 100644 --- a/include/QtNodes/internal/DefaultVerticalNodeGeometry.hpp +++ b/include/QtNodes/internal/DefaultVerticalNodeGeometry.hpp @@ -33,10 +33,16 @@ class NODE_EDITOR_PUBLIC DefaultVerticalNodeGeometry : public AbstractNodeGeomet QRectF captionRect(NodeId const nodeId) const override; + QPointF labelPosition(const NodeId nodeId) const override; + + QRectF labelRect(NodeId const nodeId) const override; + QPointF widgetPosition(NodeId const nodeId) const override; QRect resizeHandleRect(NodeId const nodeId) const override; + int getPortSpacing() override { return _portSpacing; } + private: QRectF portTextRect(NodeId const nodeId, PortType const portType, @@ -54,7 +60,7 @@ class NODE_EDITOR_PUBLIC DefaultVerticalNodeGeometry : public AbstractNodeGeomet // constness of the Node. mutable unsigned int _portSize; - unsigned int _portSpasing; + unsigned int _portSpacing; mutable QFontMetrics _fontMetrics; mutable QFontMetrics _boldFontMetrics; }; diff --git a/include/QtNodes/internal/Definitions.hpp b/include/QtNodes/internal/Definitions.hpp index 8c01475f9..19a3b16e3 100644 --- a/include/QtNodes/internal/Definitions.hpp +++ b/include/QtNodes/internal/Definitions.hpp @@ -18,23 +18,27 @@ NODE_EDITOR_PUBLIC Q_NAMESPACE Q_NAMESPACE_EXPORT(NODE_EDITOR_PUBLIC) #endif -/** + /** * Constants used for fetching QVariant data from GraphModel. */ -enum class NodeRole { - Type = 0, ///< Type of the current node, usually a string. - Position = 1, ///< `QPointF` positon of the node on the scene. - Size = 2, ///< `QSize` for resizable nodes. - CaptionVisible = 3, ///< `bool` for caption visibility. - Caption = 4, ///< `QString` for node caption. - Style = 5, ///< Custom NodeStyle as QJsonDocument - InternalData = 6, ///< Node-stecific user data as QJsonObject - InPortCount = 7, ///< `unsigned int` - OutPortCount = 9, ///< `unsigned int` - Widget = 10, ///< Optional `QWidget*` or `nullptr` - ValidationState = 11, ///< Enum NodeValidationState of the node - ProcessingStatus = 12 ///< Enum NodeProcessingStatus of the node -}; + enum class NodeRole { + Type = 0, ///< Type of the current node, usually a string. + Position = 1, ///< `QPointF` positon of the node on the scene. + Size = 2, ///< `QSize` for resizable nodes. + CaptionVisible = 3, ///< `bool` for caption visibility. + Caption = 4, ///< `QString` for node caption. + Style = 5, ///< Custom NodeStyle as QJsonDocument + InternalData = 6, ///< Node-stecific user data as QJsonObject + InPortCount = 7, ///< `unsigned int` + OutPortCount = 9, ///< `unsigned int` + Widget = 10, ///< Optional `QWidget*` or `nullptr` + ValidationState = 11, ///< Enum NodeValidationState of the node + LabelVisible = 12, ///< `bool` for label visibility. + ProcessingStatus = 13, ///< Enum NodeProcessingStatus of the node + Label = 14, ///< `QString` for node label. + LabelEditable = 15, ///< `bool` to indicate label editing support. + }; + Q_ENUM_NS(NodeRole) /** diff --git a/include/QtNodes/internal/GraphicsView.hpp b/include/QtNodes/internal/GraphicsView.hpp index f21c58799..dae3801c5 100644 --- a/include/QtNodes/internal/GraphicsView.hpp +++ b/include/QtNodes/internal/GraphicsView.hpp @@ -2,8 +2,11 @@ #include +#include "Definitions.hpp" #include "Export.hpp" +class QLineEdit; + namespace QtNodes { class BasicGraphicsScene; @@ -58,6 +61,10 @@ public Q_SLOTS: virtual void onPasteObjects(); + void zoomFitAll(); + + void zoomFitSelected(); + Q_SIGNALS: void scaleChanged(double scale); @@ -93,5 +100,8 @@ public Q_SLOTS: QPointF _clickPos; ScaleRange _scaleRange; + + QLineEdit *_labelEdit = nullptr; + NodeId _editingNodeId = InvalidNodeId; }; } // namespace QtNodes diff --git a/include/QtNodes/internal/NodeDelegateModel.hpp b/include/QtNodes/internal/NodeDelegateModel.hpp index 04cc78a12..91e744aed 100644 --- a/include/QtNodes/internal/NodeDelegateModel.hpp +++ b/include/QtNodes/internal/NodeDelegateModel.hpp @@ -4,7 +4,6 @@ #include #include -#include #include #include "Definitions.hpp" @@ -12,6 +11,7 @@ #include "NodeData.hpp" #include "NodeStyle.hpp" #include "Serializable.hpp" +#include namespace QtNodes { @@ -83,6 +83,15 @@ class NODE_EDITOR_PUBLIC NodeDelegateModel /// Validation State will default to Valid, but you can manipulate it by overriding in an inherited class virtual NodeValidationState validationState() const { return _nodeValidationState; } + /// Nicknames can be assigned to nodes and shown in GUI + virtual QString label() const { return QString(); } + + /// It is possible to hide the nickname in GUI + virtual bool labelVisible() const { return true; } + + /// Controls whether the label can be edited or not + virtual bool labelEditable() const { return false; } + /// Returns the curent processing status virtual NodeProcessingStatus processingStatus() const { return _processingStatus; } @@ -132,6 +141,10 @@ class NODE_EDITOR_PUBLIC NodeDelegateModel virtual bool resizable() const { return false; } + bool zoomFitMenu() const { return _zoomFitMenu; } + + void setZoomFitMenu(bool state) { _zoomFitMenu = state; } + public Q_SLOTS: virtual void inputConnectionCreated(ConnectionId const &) {} virtual void inputConnectionDeleted(ConnectionId const &) {} @@ -188,6 +201,8 @@ public Q_SLOTS: NodeValidationState _nodeValidationState; NodeProcessingStatus _processingStatus{NodeProcessingStatus::NoStatus}; + + bool _zoomFitMenu{false}; }; } // namespace QtNodes diff --git a/include/QtNodes/internal/UndoCommands.hpp b/include/QtNodes/internal/UndoCommands.hpp index 7aed4d60b..870478618 100644 --- a/include/QtNodes/internal/UndoCommands.hpp +++ b/include/QtNodes/internal/UndoCommands.hpp @@ -3,9 +3,9 @@ #include "Definitions.hpp" #include "Export.hpp" +#include #include #include -#include #include diff --git a/src/BasicGraphicsScene.cpp b/src/BasicGraphicsScene.cpp index 84bf9a314..2fbed3369 100644 --- a/src/BasicGraphicsScene.cpp +++ b/src/BasicGraphicsScene.cpp @@ -11,6 +11,10 @@ #include +#include +#include +#include +#include #include #include @@ -203,6 +207,70 @@ QMenu *BasicGraphicsScene::createSceneMenu(QPointF const scenePos) return nullptr; } +QMenu *BasicGraphicsScene::createZoomMenu(QPointF const scenePos) +{ + Q_UNUSED(scenePos); + + QMenu *menu = new QMenu(); + + auto *txtBox = new QLineEdit(menu); + txtBox->setPlaceholderText(QStringLiteral("Filter")); + txtBox->setClearButtonEnabled(true); + + auto *txtBoxAction = new QWidgetAction(menu); + txtBoxAction->setDefaultWidget(txtBox); + menu->addAction(txtBoxAction); + + QTreeWidget *treeView = new QTreeWidget(menu); + treeView->header()->close(); + + treeView->setMaximumHeight(100); + treeView->setMaximumWidth(150); + + auto *treeViewAction = new QWidgetAction(menu); + treeViewAction->setDefaultWidget(treeView); + menu->addAction(treeViewAction); + + auto freezeItem = new QTreeWidgetItem(treeView); + freezeItem->setText(0, "Zoom Fit All"); + + auto unfreezeItem = new QTreeWidgetItem(treeView); + unfreezeItem->setText(0, "Zoom Fit Selected"); + + treeView->expandAll(); + + connect(treeView, &QTreeWidget::itemClicked, [this, menu](QTreeWidgetItem *item, int) { + if (item->text(0) == "Zoom Fit All") { + Q_EMIT zoomFitAllClicked(); + + menu->close(); + return; + } + if (item->text(0) == "Zoom Fit Selected") { + Q_EMIT zoomFitSelectedClicked(); + + menu->close(); + return; + } + }); + + // Filter + connect(txtBox, &QLineEdit::textChanged, [treeView](const QString &text) { + QTreeWidgetItemIterator it(treeView); + while (*it) { + auto modelName = (*it)->text(0); + const bool match = (modelName.contains(text, Qt::CaseInsensitive)); + (*it)->setHidden(!match); + ++it; + } + }); + + txtBox->setFocus(); + menu->setAttribute(Qt::WA_DeleteOnClose); + + return menu; +} + void BasicGraphicsScene::traverseGraphAndPopulateGraphicsObjects() { auto allNodeIds = _graphModel.allNodeIds(); diff --git a/src/DataFlowGraphModel.cpp b/src/DataFlowGraphModel.cpp index fa5655cde..e5c707b3c 100644 --- a/src/DataFlowGraphModel.cpp +++ b/src/DataFlowGraphModel.cpp @@ -63,51 +63,48 @@ NodeId DataFlowGraphModel::addNode(QString const nodeType) { std::unique_ptr model = _registry->create(nodeType); - if (model) { - NodeId newId = newNodeId(); + if (!model) + return InvalidNodeId; - connect(model.get(), - &NodeDelegateModel::dataUpdated, - [newId, this](PortIndex const portIndex) { - onOutPortDataUpdated(newId, portIndex); - }); + NodeId newId = newNodeId(); - connect(model.get(), - &NodeDelegateModel::portsAboutToBeDeleted, - this, - [newId, this](PortType const portType, PortIndex const first, PortIndex const last) { - portsAboutToBeDeleted(newId, portType, first, last); - }); + connect(model.get(), &NodeDelegateModel::dataUpdated, [newId, this](PortIndex const portIndex) { + onOutPortDataUpdated(newId, portIndex); + }); - connect(model.get(), - &NodeDelegateModel::portsDeleted, - this, - &DataFlowGraphModel::portsDeleted); + connect(model.get(), + &NodeDelegateModel::portsAboutToBeDeleted, + this, + [newId, this](PortType const portType, PortIndex const first, PortIndex const last) { + portsAboutToBeDeleted(newId, portType, first, last); + }); - connect(model.get(), - &NodeDelegateModel::portsAboutToBeInserted, - this, - [newId, this](PortType const portType, PortIndex const first, PortIndex const last) { - portsAboutToBeInserted(newId, portType, first, last); - }); + connect(model.get(), &NodeDelegateModel::portsDeleted, this, &DataFlowGraphModel::portsDeleted); - connect(model.get(), - &NodeDelegateModel::portsInserted, - this, - &DataFlowGraphModel::portsInserted); + connect(model.get(), + &NodeDelegateModel::portsAboutToBeInserted, + this, + [newId, this](PortType const portType, PortIndex const first, PortIndex const last) { + portsAboutToBeInserted(newId, portType, first, last); + }); - connect(model.get(), &NodeDelegateModel::requestNodeUpdate, this, [newId, this]() { - Q_EMIT nodeUpdated(newId); - }); + connect(model.get(), + &NodeDelegateModel::portsInserted, + this, + &DataFlowGraphModel::portsInserted); - _models[newId] = std::move(model); + connect(model.get(), &NodeDelegateModel::requestNodeUpdate, this, [newId, this]() { + Q_EMIT nodeUpdated(newId); + }); - Q_EMIT nodeCreated(newId); + _models[newId] = std::move(model); - return newId; - } + _labels[newId] = _models[newId]->label(); + _labelsVisible[newId] = _models[newId]->labelVisible(); - return InvalidNodeId; + Q_EMIT nodeCreated(newId); + + return newId; } bool DataFlowGraphModel::connectionPossible(ConnectionId const connectionId) const @@ -299,6 +296,18 @@ QVariant DataFlowGraphModel::nodeData(NodeId nodeId, NodeRole role) const result = QVariant::fromValue(validationState); } break; + case NodeRole::LabelVisible: + result = _labelsVisible.at(nodeId); + break; + + case NodeRole::Label: + result = _labels.at(nodeId); + break; + + case NodeRole::LabelEditable: + result = model->labelEditable(); + break; + case NodeRole::ProcessingStatus: { auto processingStatus = model->processingStatus(); result = QVariant::fromValue(processingStatus); @@ -382,6 +391,21 @@ bool DataFlowGraphModel::setNodeData(NodeId nodeId, NodeRole role, QVariant valu } Q_EMIT nodeUpdated(nodeId); } break; + + case NodeRole::LabelVisible: { + _labelsVisible[nodeId] = value.toBool(); + Q_EMIT nodeUpdated(nodeId); + result = true; + } break; + + case NodeRole::Label: { + _labels[nodeId] = value.toString(); + Q_EMIT nodeUpdated(nodeId); + result = true; + } break; + + case NodeRole::LabelEditable: + break; } return result; @@ -489,6 +513,8 @@ bool DataFlowGraphModel::deleteNode(NodeId const nodeId) } _nodeGeometryData.erase(nodeId); + _labels.erase(nodeId); + _labelsVisible.erase(nodeId); _models.erase(nodeId); Q_EMIT nodeDeleted(nodeId); @@ -504,6 +530,9 @@ QJsonObject DataFlowGraphModel::saveNode(NodeId const nodeId) const nodeJson["internal-data"] = _models.at(nodeId)->save(); + nodeJson["label"] = _labels.at(nodeId); + nodeJson["labelVisible"] = _labelsVisible.at(nodeId); + { QPointF const pos = nodeData(nodeId, NodeRole::Position).value(); @@ -599,7 +628,13 @@ void DataFlowGraphModel::loadNode(QJsonObject const &nodeJson) setNodeData(restoredNodeId, NodeRole::Position, pos); - _models[restoredNodeId]->load(internalDataJson); + auto *restoredModel = _models[restoredNodeId].get(); + _labels[restoredNodeId] = nodeJson["label"].toString(restoredModel->label()); + _labelsVisible[restoredNodeId] = nodeJson.contains("labelVisible") + ? nodeJson["labelVisible"].toBool() + : restoredModel->labelVisible(); + + restoredModel->load(internalDataJson); } else { throw std::logic_error(std::string("No registered model with name ") + delegateModelName.toLocal8Bit().data()); @@ -646,4 +681,10 @@ void DataFlowGraphModel::propagateEmptyDataTo(NodeId const nodeId, PortIndex con setPortData(nodeId, PortType::In, portIndex, emptyData, PortRole::Data); } +bool DataFlowGraphModel::nodeZoomFitMenu(NodeId const nodeId) +{ + auto delegate = delegateModel(nodeId); + return delegate && delegate->zoomFitMenu(); +} + } // namespace QtNodes diff --git a/src/DefaultHorizontalNodeGeometry.cpp b/src/DefaultHorizontalNodeGeometry.cpp index a30121d2a..bd2d510c7 100644 --- a/src/DefaultHorizontalNodeGeometry.cpp +++ b/src/DefaultHorizontalNodeGeometry.cpp @@ -11,7 +11,7 @@ namespace QtNodes { DefaultHorizontalNodeGeometry::DefaultHorizontalNodeGeometry(AbstractGraphModel &graphModel) : AbstractNodeGeometry(graphModel) , _portSize(20) - , _portSpasing(10) + , _portSpacing(10) , _fontMetrics(QFont()) , _boldFontMetrics(QFont()) { @@ -26,7 +26,7 @@ QRectF DefaultHorizontalNodeGeometry::boundingRect(NodeId const nodeId) const { QSize s = size(nodeId); - qreal marginSize = 2.0 * _portSpasing; + qreal marginSize = 2.0 * _portSpacing; QMargins margins(marginSize, marginSize, marginSize, marginSize); QRectF r(QPointF(0, 0), s); @@ -48,11 +48,15 @@ void DefaultHorizontalNodeGeometry::recomputeSize(NodeId const nodeId) const } QRectF const capRect = captionRect(nodeId); + QRectF const lblRect = labelRect(nodeId); height += capRect.height(); + if (!lblRect.isNull()) { + height += lblRect.height(); + height += _portSpacing / 2; + } - height += _portSpasing; // space above caption - height += _portSpasing; // space below caption + height += _portSpacing; // space above caption QVariant var = _graphModel.nodeData(nodeId, NodeRole::ProcessingStatus); auto processingStatusValue = var.value(); @@ -63,13 +67,17 @@ void DefaultHorizontalNodeGeometry::recomputeSize(NodeId const nodeId) const unsigned int inPortWidth = maxPortsTextAdvance(nodeId, PortType::In); unsigned int outPortWidth = maxPortsTextAdvance(nodeId, PortType::Out); - unsigned int width = inPortWidth + outPortWidth + 4 * _portSpasing; + unsigned int width = inPortWidth + outPortWidth + 4 * _portSpacing; if (auto w = _graphModel.nodeData(nodeId, NodeRole::Widget)) { width += w->width(); } - width = std::max(width, static_cast(capRect.width()) + 2 * _portSpasing); + unsigned int textWidth = static_cast(capRect.width()); + if (!lblRect.isNull()) + textWidth = std::max(textWidth, static_cast(lblRect.width())); + + width = std::max(width, textWidth + 2 * _portSpacing); QSize size(width, height); @@ -80,14 +88,20 @@ QPointF DefaultHorizontalNodeGeometry::portPosition(NodeId const nodeId, PortType const portType, PortIndex const portIndex) const { - unsigned int const step = _portSize + _portSpasing; + unsigned int const step = _portSize + _portSpacing; QPointF result; double totalHeight = 0.0; totalHeight += captionRect(nodeId).height(); - totalHeight += _portSpasing; + + if (_graphModel.nodeData(nodeId, NodeRole::LabelVisible)) { + totalHeight += labelRect(nodeId).height(); + totalHeight += _portSpacing / 2.0; + } + + totalHeight += _portSpacing; totalHeight += step * portIndex; totalHeight += step / 2.0; @@ -130,11 +144,11 @@ QPointF DefaultHorizontalNodeGeometry::portTextPosition(NodeId const nodeId, switch (portType) { case PortType::In: - p.setX(_portSpasing); + p.setX(_portSpacing); break; case PortType::Out: - p.setX(size.width() - _portSpasing - rect.width()); + p.setX(size.width() - _portSpacing - rect.width()); break; default: @@ -157,8 +171,48 @@ QRectF DefaultHorizontalNodeGeometry::captionRect(NodeId const nodeId) const QPointF DefaultHorizontalNodeGeometry::captionPosition(NodeId const nodeId) const { QSize size = _graphModel.nodeData(nodeId, NodeRole::Size); - return QPointF(0.5 * (size.width() - captionRect(nodeId).width()), - 0.5 * _portSpasing + captionRect(nodeId).height()); + + QRectF cap = captionRect(nodeId); + QRectF lbl = labelRect(nodeId); + + double y = 0.5 * _portSpacing + cap.height(); + y += _portSpacing / 2.0 + lbl.height(); + + return QPointF(0.5 * (size.width() - captionRect(nodeId).width()), y); +} + +QRectF DefaultHorizontalNodeGeometry::labelRect(NodeId const nodeId) const +{ + if (!_graphModel.nodeData(nodeId, NodeRole::LabelVisible)) + return QRect(); + + QString nickname = _graphModel.nodeData(nodeId, NodeRole::Label); + + QRectF nickRect = _boldFontMetrics.boundingRect(nickname); + + nickRect.setWidth(nickRect.width() * 0.5); + nickRect.setHeight(nickRect.height() * 0.5); + + return nickRect; +} + +QPointF DefaultHorizontalNodeGeometry::labelPosition(NodeId const nodeId) const +{ + QRectF cap = captionRect(nodeId); + QRectF lbl = labelRect(nodeId); + + double y = 0.5 * _portSpacing + cap.height(); + y += _portSpacing / 2.0 + lbl.height(); + + if (!_graphModel.nodeData(nodeId, NodeRole::CaptionVisible)) { + return QPointF(captionPosition(nodeId).x() + + 0.5 * (captionRect(nodeId).width() - 2 * labelRect(nodeId).width()), + y); + } + + return QPointF(captionPosition(nodeId).x() + + 0.5 * (captionRect(nodeId).width() - 2 * labelRect(nodeId).width()), + 0.5 * _portSpacing + captionRect(nodeId).height()); } QPointF DefaultHorizontalNodeGeometry::widgetPosition(NodeId const nodeId) const @@ -166,15 +220,17 @@ QPointF DefaultHorizontalNodeGeometry::widgetPosition(NodeId const nodeId) const QSize size = _graphModel.nodeData(nodeId, NodeRole::Size); unsigned int captionHeight = captionRect(nodeId).height(); + if (_graphModel.nodeData(nodeId, NodeRole::LabelVisible)) + captionHeight += labelRect(nodeId).height() + _portSpacing / 2; if (auto w = _graphModel.nodeData(nodeId, NodeRole::Widget)) { // If the widget wants to use as much vertical space as possible, // place it immediately after the caption. if (w->sizePolicy().verticalPolicy() & QSizePolicy::ExpandFlag) { - return QPointF(2.0 * _portSpasing + maxPortsTextAdvance(nodeId, PortType::In), - _portSpasing + captionHeight); + return QPointF(2.0 * _portSpacing + maxPortsTextAdvance(nodeId, PortType::In), + _portSpacing + captionHeight); } else { - return QPointF(2.0 * _portSpasing + maxPortsTextAdvance(nodeId, PortType::In), + return QPointF(2.0 * _portSpacing + maxPortsTextAdvance(nodeId, PortType::In), (captionHeight + size.height() - w->height()) / 2.0); } } @@ -187,7 +243,7 @@ QRect DefaultHorizontalNodeGeometry::resizeHandleRect(NodeId const nodeId) const unsigned int rectSize = 7; - return QRect(size.width() - _portSpasing, size.height() - _portSpasing, rectSize, rectSize); + return QRect(size.width() - _portSpacing, size.height() - _portSpacing, rectSize, rectSize); } QRectF DefaultHorizontalNodeGeometry::portTextRect(NodeId const nodeId, @@ -213,7 +269,7 @@ unsigned int DefaultHorizontalNodeGeometry::maxVerticalPortsExtent(NodeId const PortCount nOutPorts = _graphModel.nodeData(nodeId, NodeRole::OutPortCount); unsigned int maxNumOfEntries = std::max(nInPorts, nOutPorts); - unsigned int step = _portSize + _portSpasing; + unsigned int step = _portSize + _portSpacing; return step * maxNumOfEntries; } diff --git a/src/DefaultNodePainter.cpp b/src/DefaultNodePainter.cpp index d313d3a82..66180fe1c 100644 --- a/src/DefaultNodePainter.cpp +++ b/src/DefaultNodePainter.cpp @@ -38,6 +38,8 @@ void DefaultNodePainter::paint(QPainter *painter, NodeGraphicsObject &ngo) const drawResizeRect(painter, ngo); drawValidationIcon(painter, ngo); + + drawNodeLabel(painter, ngo); } void DefaultNodePainter::drawNodeRect(QPainter *painter, NodeGraphicsObject &ngo) const @@ -222,12 +224,21 @@ void DefaultNodePainter::drawNodeCaption(QPainter *painter, NodeGraphicsObject & if (!model.nodeData(nodeId, NodeRole::CaptionVisible).toBool()) return; + QString const nickname = model.nodeData(nodeId, NodeRole::Label).toString(); QString const name = model.nodeData(nodeId, NodeRole::Caption).toString(); QFont f = painter->font(); - f.setBold(true); + f.setBold(nickname.isEmpty()); + f.setItalic(!nickname.isEmpty()); + + QFontMetricsF metrics(f); + + QRectF bounding = metrics.boundingRect(name); + QRectF capRect = geometry.captionRect(nodeId); + QPointF capPos = geometry.captionPosition(nodeId); + double centerX = capPos.x() + capRect.width() / 2.0; - QPointF position = geometry.captionPosition(nodeId); + QPointF position(centerX - bounding.width() / 2.0, capPos.y()); QJsonDocument json = QJsonDocument::fromVariant(model.nodeData(nodeId, NodeRole::Style)); NodeStyle nodeStyle(json.object()); @@ -236,6 +247,45 @@ void DefaultNodePainter::drawNodeCaption(QPainter *painter, NodeGraphicsObject & painter->setPen(nodeStyle.FontColor); painter->drawText(position, name); + f.setBold(false); + f.setItalic(false); + painter->setFont(f); +} + +void DefaultNodePainter::drawNodeLabel(QPainter *painter, NodeGraphicsObject &ngo) const +{ + AbstractGraphModel &model = ngo.graphModel(); + NodeId const nodeId = ngo.nodeId(); + AbstractNodeGeometry &geometry = ngo.nodeScene()->nodeGeometry(); + + if (!model.nodeData(nodeId, NodeRole::LabelVisible).toBool()) + return; + + QString const nickname = model.nodeData(nodeId, NodeRole::Label).toString(); + + QFont f = painter->font(); + f.setBold(true); + f.setItalic(false); + + QFontMetricsF metrics(f); + + QRectF bounding = metrics.boundingRect(nickname); + QRectF capRect = geometry.captionRect(nodeId); + QPointF capPos = geometry.captionPosition(nodeId); + double centerX = capPos.x() + capRect.width() / 2.0; + + double textHeight = metrics.height(); + double y = capPos.y() - textHeight - 2.0; + + QPointF position(centerX - bounding.width() / 2.0, y); + + QJsonDocument json = QJsonDocument::fromVariant(model.nodeData(nodeId, NodeRole::Style)); + NodeStyle nodeStyle(json.object()); + + painter->setFont(f); + painter->setPen(nodeStyle.FontColor); + painter->drawText(position, nickname); + f.setBold(false); painter->setFont(f); } diff --git a/src/DefaultVerticalNodeGeometry.cpp b/src/DefaultVerticalNodeGeometry.cpp index f20617f25..589f5cdcb 100644 --- a/src/DefaultVerticalNodeGeometry.cpp +++ b/src/DefaultVerticalNodeGeometry.cpp @@ -12,7 +12,7 @@ namespace QtNodes { DefaultVerticalNodeGeometry::DefaultVerticalNodeGeometry(AbstractGraphModel &graphModel) : AbstractNodeGeometry(graphModel) , _portSize(20) - , _portSpasing(10) + , _portSpacing(10) , _fontMetrics(QFont()) , _boldFontMetrics(QFont()) { @@ -27,7 +27,7 @@ QRectF DefaultVerticalNodeGeometry::boundingRect(NodeId const nodeId) const { QSize s = size(nodeId); - qreal marginSize = 2.0 * _portSpasing; + qreal marginSize = 2.0 * _portSpacing; QMargins margins(marginSize, marginSize, marginSize, marginSize); QRectF r(QPointF(0, 0), s); @@ -42,18 +42,23 @@ QSize DefaultVerticalNodeGeometry::size(NodeId const nodeId) const void DefaultVerticalNodeGeometry::recomputeSize(NodeId const nodeId) const { - unsigned int height = _portSpasing; // maxHorizontalPortsExtent(nodeId); + unsigned int height = _portSpacing; // maxHorizontalPortsExtent(nodeId); if (auto w = _graphModel.nodeData(nodeId, NodeRole::Widget)) { height = std::max(height, static_cast(w->height())); } QRectF const capRect = captionRect(nodeId); + QRectF const lblRect = labelRect(nodeId); height += capRect.height(); + if (!lblRect.isNull()) { + height += lblRect.height(); + height += _portSpacing / 2; + } - height += _portSpasing; - height += _portSpasing; + height += _portSpacing; + height += _portSpacing; PortCount nInPorts = _graphModel.nodeData(nodeId, NodeRole::InPortCount); PortCount nOutPorts = _graphModel.nodeData(nodeId, NodeRole::OutPortCount); @@ -67,11 +72,11 @@ void DefaultVerticalNodeGeometry::recomputeSize(NodeId const nodeId) const unsigned int outPortWidth = maxPortsTextAdvance(nodeId, PortType::Out); unsigned int totalInPortsWidth = nInPorts > 0 - ? inPortWidth * nInPorts + _portSpasing * (nInPorts - 1) + ? inPortWidth * nInPorts + _portSpacing * (nInPorts - 1) : 0; unsigned int totalOutPortsWidth = nOutPorts > 0 ? outPortWidth * nOutPorts - + _portSpasing * (nOutPorts - 1) + + _portSpacing * (nOutPorts - 1) : 0; unsigned int width = std::max(totalInPortsWidth, totalOutPortsWidth); @@ -80,10 +85,14 @@ void DefaultVerticalNodeGeometry::recomputeSize(NodeId const nodeId) const width = std::max(width, static_cast(w->width())); } - width = std::max(width, static_cast(capRect.width())); + unsigned int textWidth = static_cast(capRect.width()); + if (!lblRect.isNull()) + textWidth = std::max(textWidth, static_cast(lblRect.width())); + + width = std::max(width, textWidth); - width += _portSpasing; - width += _portSpasing; + width += _portSpacing; + width += _portSpacing; QSize size(width, height); @@ -100,7 +109,7 @@ QPointF DefaultVerticalNodeGeometry::portPosition(NodeId const nodeId, switch (portType) { case PortType::In: { - unsigned int inPortWidth = maxPortsTextAdvance(nodeId, PortType::In) + _portSpasing; + unsigned int inPortWidth = maxPortsTextAdvance(nodeId, PortType::In) + _portSpacing; PortCount nInPorts = _graphModel.nodeData(nodeId, NodeRole::InPortCount); @@ -114,7 +123,7 @@ QPointF DefaultVerticalNodeGeometry::portPosition(NodeId const nodeId, } case PortType::Out: { - unsigned int outPortWidth = maxPortsTextAdvance(nodeId, PortType::Out) + _portSpasing; + unsigned int outPortWidth = maxPortsTextAdvance(nodeId, PortType::Out) + _portSpacing; PortCount nOutPorts = _graphModel.nodeData(nodeId, NodeRole::OutPortCount); double x = (size.width() - (nOutPorts - 1) * outPortWidth) / 2.0 + portIndex * outPortWidth; @@ -176,26 +185,56 @@ QPointF DefaultVerticalNodeGeometry::captionPosition(NodeId const nodeId) const QSize size = _graphModel.nodeData(nodeId, NodeRole::Size); unsigned int step = portCaptionsHeight(nodeId, PortType::In); - step += _portSpasing; + step += _portSpacing; auto rect = captionRect(nodeId); return QPointF(0.5 * (size.width() - rect.width()), step + rect.height()); } +QPointF DefaultVerticalNodeGeometry::labelPosition(const NodeId nodeId) const +{ + QSize size = _graphModel.nodeData(nodeId, NodeRole::Size); + + QRectF rect = labelRect(nodeId); + + unsigned int step = portCaptionsHeight(nodeId, PortType::In); + step += _portSpacing; + step += captionRect(nodeId).height(); + step += _portSpacing / 2; + + return QPointF(0.5 * (size.width() - rect.width()), step + rect.height()); +} + +QRectF DefaultVerticalNodeGeometry::labelRect(NodeId const nodeId) const +{ + if (!_graphModel.nodeData(nodeId, NodeRole::LabelVisible)) + return QRectF(); + + QString nickname = _graphModel.nodeData(nodeId, NodeRole::Label); + + QRectF rect = _boldFontMetrics.boundingRect(nickname); + rect.setWidth(rect.width() * 0.5); + rect.setHeight(rect.height() * 0.5); + + return rect; +} + QPointF DefaultVerticalNodeGeometry::widgetPosition(NodeId const nodeId) const { QSize size = _graphModel.nodeData(nodeId, NodeRole::Size); unsigned int captionHeight = captionRect(nodeId).height(); + if (_graphModel.nodeData(nodeId, NodeRole::LabelVisible)) + captionHeight += labelRect(nodeId).height() + _portSpacing / 2; if (auto w = _graphModel.nodeData(nodeId, NodeRole::Widget)) { // If the widget wants to use as much vertical space as possible, // place it immediately after the caption. if (w->sizePolicy().verticalPolicy() & QSizePolicy::ExpandFlag) { - return QPointF(_portSpasing + maxPortsTextAdvance(nodeId, PortType::In), captionHeight); + return QPointF(_portSpacing + maxPortsTextAdvance(nodeId, PortType::In), captionHeight); } else { - return QPointF(_portSpasing + maxPortsTextAdvance(nodeId, PortType::In), + return QPointF(_portSpacing + maxPortsTextAdvance(nodeId, PortType::In), (captionHeight + size.height() - w->height()) / 2.0); } } @@ -234,7 +273,7 @@ unsigned int DefaultVerticalNodeGeometry::maxHorizontalPortsExtent(NodeId const PortCount nOutPorts = _graphModel.nodeData(nodeId, NodeRole::OutPortCount); unsigned int maxNumOfEntries = std::max(nInPorts, nOutPorts); - unsigned int step = _portSize + _portSpasing; + unsigned int step = _portSize + _portSpacing; return step * maxNumOfEntries; } @@ -284,7 +323,7 @@ unsigned int DefaultVerticalNodeGeometry::portCaptionsHeight(NodeId const nodeId PortCount nInPorts = _graphModel.nodeData(nodeId, NodeRole::InPortCount); for (PortIndex i = 0; i < nInPorts; ++i) { if (_graphModel.portData(nodeId, PortType::In, i, PortRole::CaptionVisible)) { - h += _portSpasing; + h += _portSpacing; break; } } @@ -295,7 +334,7 @@ unsigned int DefaultVerticalNodeGeometry::portCaptionsHeight(NodeId const nodeId PortCount nOutPorts = _graphModel.nodeData(nodeId, NodeRole::OutPortCount); for (PortIndex i = 0; i < nOutPorts; ++i) { if (_graphModel.portData(nodeId, PortType::Out, i, PortRole::CaptionVisible)) { - h += _portSpasing; + h += _portSpacing; break; } } diff --git a/src/GraphicsView.cpp b/src/GraphicsView.cpp index c587f081c..8ac93567f 100644 --- a/src/GraphicsView.cpp +++ b/src/GraphicsView.cpp @@ -1,21 +1,19 @@ #include "GraphicsView.hpp" - #include "BasicGraphicsScene.hpp" #include "ConnectionGraphicsObject.hpp" +#include "DataFlowGraphModel.hpp" +#include "Definitions.hpp" #include "NodeGraphicsObject.hpp" #include "StyleCollection.hpp" #include "UndoCommands.hpp" - -#include - -#include -#include - -#include - #include #include #include +#include +#include +#include +#include +#include #include #include @@ -23,7 +21,9 @@ #include using QtNodes::BasicGraphicsScene; +using QtNodes::DataFlowGraphModel; using QtNodes::GraphicsView; +using QtNodes::NodeGraphicsObject; GraphicsView::GraphicsView(QWidget *parent) : QGraphicsView(parent) @@ -75,8 +75,7 @@ QAction *GraphicsView::deleteSelectionAction() const void GraphicsView::setScene(BasicGraphicsScene *scene) { QGraphicsView::setScene(scene); - if (!scene) - { + if (!scene) { // Clear actions. delete _clearSelectionAction; delete _deleteSelectionAction; @@ -162,6 +161,13 @@ void GraphicsView::setScene(BasicGraphicsScene *scene) auto redoAction = scene->undoStack().createRedoAction(this, tr("&Redo")); redoAction->setShortcuts(QKeySequence::Redo); addAction(redoAction); + + /// Connections to context menu funcionality + connect(scene, &BasicGraphicsScene::zoomFitAllClicked, this, &GraphicsView::zoomFitAll); + connect(scene, + &BasicGraphicsScene::zoomFitSelectedClicked, + this, + &GraphicsView::zoomFitSelected); } void GraphicsView::centerScene() @@ -181,16 +187,23 @@ void GraphicsView::centerScene() void GraphicsView::contextMenuEvent(QContextMenuEvent *event) { - if (itemAt(event->pos())) { - QGraphicsView::contextMenuEvent(event); - return; - } + QGraphicsView::contextMenuEvent(event); + QMenu *menu = nullptr; - if (!nodeScene()) return; + bool isZoomFitMenu = false; - auto const scenePos = mapToScene(event->pos()); + auto *dfModel = &nodeScene()->graphModel(); + auto n = qgraphicsitem_cast(itemAt(event->pos())); + + if (dfModel && n) { + isZoomFitMenu = dfModel->nodeZoomFitMenu(n->nodeId()); + } - QMenu *menu = nodeScene()->createSceneMenu(scenePos); + if (itemAt(event->pos()) && isZoomFitMenu) { + menu = nodeScene()->createZoomMenu(mapToScene(event->pos())); + } else if (!itemAt(event->pos())) { + menu = nodeScene()->createSceneMenu(mapToScene(event->pos())); + } if (menu) { menu->exec(event->globalPos()); @@ -291,14 +304,16 @@ void GraphicsView::setupScale(double scale) void GraphicsView::onDeleteSelectedObjects() { - if (!nodeScene()) return; + if (!nodeScene()) + return; nodeScene()->undoStack().push(new DeleteCommand(nodeScene())); } void GraphicsView::onDuplicateSelectedObjects() { - if (!nodeScene()) return; + if (!nodeScene()) + return; QPointF const pastePosition = scenePastePosition(); @@ -308,14 +323,16 @@ void GraphicsView::onDuplicateSelectedObjects() void GraphicsView::onCopySelectedObjects() { - if (!nodeScene()) return; + if (!nodeScene()) + return; nodeScene()->undoStack().push(new CopyCommand(nodeScene())); } void GraphicsView::onPasteObjects() { - if (!nodeScene()) return; + if (!nodeScene()) + return; QPointF const pastePosition = scenePastePosition(); nodeScene()->undoStack().push(new PasteCommand(nodeScene(), pastePosition)); @@ -324,6 +341,73 @@ void GraphicsView::onPasteObjects() void GraphicsView::keyPressEvent(QKeyEvent *event) { switch (event->key()) { + case Qt::Key_F2: { + BasicGraphicsScene *sc = nodeScene(); + + if (sc) { + QList items = sc->selectedItems(); + NodeGraphicsObject *ngo = nullptr; + for (QGraphicsItem *it : items) { + ngo = qgraphicsitem_cast(it); + + if (ngo) + break; + } + + if (ngo) { + bool const labelEditable + = sc->graphModel().nodeData(ngo->nodeId(), NodeRole::LabelEditable).toBool(); + + if (!labelEditable) + break; + + if (!_labelEdit) { + _labelEdit = new QLineEdit(this); + _labelEdit->setMaxLength(32); + + connect(_labelEdit, &QLineEdit::editingFinished, [this]() { + if (_editingNodeId != InvalidNodeId) { + nodeScene()->graphModel().setNodeData(_editingNodeId, + NodeRole::LabelVisible, + true); + nodeScene()->graphModel().setNodeData(_editingNodeId, + NodeRole::Label, + _labelEdit->text()); + } + + _labelEdit->hide(); + _editingNodeId = InvalidNodeId; + }); + } + + _editingNodeId = ngo->nodeId(); + + sc->graphModel().setNodeData(_editingNodeId, NodeRole::LabelVisible, true); + + AbstractNodeGeometry &geom = sc->nodeGeometry(); + QPointF labelPos = geom.labelPosition(_editingNodeId); + QPointF scenePos = ngo->mapToScene(labelPos); + QSize sz = _labelEdit->sizeHint(); + QPoint viewPos = mapFromScene(scenePos); + _labelEdit->move(viewPos.x() - sz.width() / 2, viewPos.y() - sz.height() / 2); + bool visible + = sc->graphModel().nodeData(_editingNodeId, NodeRole::LabelVisible).toBool(); + QString current + = sc->graphModel().nodeData(_editingNodeId, NodeRole::Label).toString(); + + if (!visible && current.isEmpty()) + _labelEdit->clear(); + else + _labelEdit->setText(current); + _labelEdit->resize(sz); + _labelEdit->show(); + _labelEdit->setFocus(); + return; + } + } + } + + break; case Qt::Key_Shift: setDragMode(QGraphicsView::RubberBandDrag); break; @@ -360,7 +444,8 @@ void GraphicsView::mouseMoveEvent(QMouseEvent *event) { QGraphicsView::mouseMoveEvent(event); - if (!scene()) return; + if (!scene()) + return; if (scene()->mouseGrabberItem() == nullptr && event->buttons() == Qt::LeftButton) { // Make sure shift is not being pressed @@ -434,3 +519,22 @@ QPointF GraphicsView::scenePastePosition() return mapToScene(origin); } + +void GraphicsView::zoomFitAll() +{ + fitInView(scene()->itemsBoundingRect(), Qt::KeepAspectRatio); +} + +void GraphicsView::zoomFitSelected() +{ + if (scene()->selectedItems().count() > 0) { + QRectF unitedBoundingRect{}; + + for (QGraphicsItem *item : scene()->selectedItems()) { + unitedBoundingRect = unitedBoundingRect.united( + item->mapRectToScene(item->boundingRect())); + } + + fitInView(unitedBoundingRect, Qt::KeepAspectRatio); + } +}