diff --git a/app/common/xmlutils.cpp b/app/common/xmlutils.cpp index 087648822..9bea8f3b0 100644 --- a/app/common/xmlutils.cpp +++ b/app/common/xmlutils.cpp @@ -31,6 +31,7 @@ Node* XMLLoadNode(QXmlStreamReader* reader) QString node_id; quintptr node_ptr = 0; QPointF node_pos; + QString node_label; XMLAttributeLoop(reader, attr) { if (attr.name() == QStringLiteral("id")) { @@ -45,6 +46,8 @@ Node* XMLLoadNode(QXmlStreamReader* reader) node_pos.setX(pos.at(0).toDouble()); node_pos.setY(pos.at(1).toDouble()); } + } else if (attr.name() == QStringLiteral("label")) { + node_label = attr.value().toString(); } } @@ -58,6 +61,7 @@ Node* XMLLoadNode(QXmlStreamReader* reader) if (node) { node->setProperty("xml_ptr", node_ptr); node->SetPosition(node_pos); + node->SetLabel(node_label); } else { qWarning() << "Failed to load" << node_id << "- no node with that ID is installed"; } diff --git a/app/node/node.cpp b/app/node/node.cpp index 0aa807240..5b1111eba 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -107,6 +107,9 @@ void Node::Save(QXmlStreamWriter *writer, const QString &custom_name) const QStringLiteral("%1:%2").arg(QString::number(GetPosition().x()), QString::number(GetPosition().y()))); + writer->writeAttribute(QStringLiteral("label"), + GetLabel()); + foreach (NodeParam* param, parameters()) { param->Save(writer); } @@ -277,6 +280,20 @@ QList Node::GetOutputs() const return {output_}; } +const QString &Node::GetLabel() const +{ + return label_; +} + +void Node::SetLabel(const QString &s) +{ + if (label_ != s) { + label_ = s; + + emit LabelChanged(label_); + } +} + void Node::CopyInputs(Node *source, Node *destination, bool include_connections) { Q_ASSERT(source->id() == destination->id()); @@ -295,6 +312,9 @@ void Node::CopyInputs(Node *source, Node *destination, bool include_connections) NodeInput::CopyValues(src, dst, include_connections); } } + + destination->SetPosition(source->GetPosition()); + destination->SetLabel(source->GetLabel()); } bool Node::CanBeDeleted() const diff --git a/app/node/node.h b/app/node/node.h index 6497082cb..29d96313f 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -359,6 +359,9 @@ public: QList GetOutputs() const; + const QString& GetLabel() const; + void SetLabel(const QString& s); + protected: void AddInput(NodeInput* input); @@ -396,6 +399,11 @@ signals: */ void PositionChanged(const QPointF& pos); + /** + * @brief Signal emitted when SetLabel() is called + */ + void LabelChanged(const QString& s); + private: /** * @brief Add a parameter to this node @@ -431,6 +439,11 @@ private: */ QPointF position_; + /** + * @brief Custom user label for node + */ + QString label_; + private slots: void InputChanged(const OLIVE_NAMESPACE::TimeRange &range); diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp index 6d7a2f0ee..326f674b3 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.cpp +++ b/app/widget/nodeparamview/nodeparamviewitem.cpp @@ -73,6 +73,8 @@ NodeParamViewItem::NodeParamViewItem(Node *node, QWidget *parent) : connect(title_bar_collapse_btn_, &QPushButton::toggled, body_, &NodeParamViewItemBody::setVisible); main_layout->addWidget(body_); + connect(node_, &Node::LabelChanged, this, &NodeParamViewItem::Retranslate); + Retranslate(); } @@ -111,7 +113,11 @@ void NodeParamViewItem::Retranslate() { node_->Retranslate(); - title_bar_lbl_->setText(node_->Name()); + if (node_->GetLabel().isEmpty()) { + title_bar_lbl_->setText(node_->Name()); + } else { + title_bar_lbl_->setText(tr("%1 (%2)").arg(node_->GetLabel(), node_->Name())); + } body_->Retranslate(); } diff --git a/app/widget/nodeparamview/nodeparamviewitem.h b/app/widget/nodeparamview/nodeparamviewitem.h index 5209d68ed..6d6996219 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.h +++ b/app/widget/nodeparamview/nodeparamviewitem.h @@ -129,8 +129,6 @@ protected: virtual void changeEvent(QEvent *e) override; private: - void Retranslate(); - NodeParamViewItemTitleBar* title_bar_; QLabel* title_bar_lbl_; @@ -143,6 +141,9 @@ private: rational time_; +private slots: + void Retranslate(); + }; OLIVE_NAMESPACE_EXIT diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index d6582cff9..4c8c9733a 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -20,6 +20,7 @@ #include "nodeview.h" +#include #include #include "core.h" @@ -208,7 +209,6 @@ void NodeView::Duplicate() Node* copy = n->copy(); Node::CopyInputs(n, copy, false); - copy->SetPosition(n->GetPosition()); duplicated_nodes.append(copy); @@ -401,6 +401,15 @@ void NodeView::ShowContextMenu(const QPoint &pos) m.addSeparator(); if (itemAt(pos)) { + QList selected = scene_.GetSelectedItems(); + + if (selected.size() == 1) { + QAction* label_action = m.addAction(tr("Label")); + connect(label_action, &QAction::triggered, this, &NodeView::ContextMenuLabelNode); + + m.addSeparator(); + } + QAction* autopos = m.addAction(tr("Auto-Position")); connect(autopos, &QAction::triggered, this, &NodeView::AutoPositionDescendents); } else { @@ -462,6 +471,30 @@ void NodeView::AutoPositionDescendents() } } +void NodeView::ContextMenuLabelNode() +{ + QList nodes = scene_.GetSelectedNodes(); + + if (nodes.isEmpty()) { + return; + } + + Node* n = nodes.first(); + + bool ok; + + QString s = QInputDialog::getText(this, + tr("Label Node"), + tr("Set node label"), + QLineEdit::Normal, + n->GetLabel(), + &ok); + + if (ok) { + n->SetLabel(s); + } +} + void NodeView::PlaceNode(NodeViewItem *n, const QPointF &pos) { QRectF destination_rect = n->rect(); diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index c8ae334a0..2701f4a1d 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -141,6 +141,11 @@ private slots: */ void AutoPositionDescendents(); + /** + * @brief Receiver for labelling a node from the context menu + */ + void ContextMenuLabelNode(); + }; OLIVE_NAMESPACE_EXIT diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 76b875fe0..45eafa7e2 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -135,6 +135,11 @@ int NodeViewItem::DefaultItemWidth() return QFontMetricsWidth(QFontMetrics(QFont()), "HHHHHHHHHH");; } +int NodeViewItem::DefaultMaximumTextWidth() +{ + return QFontMetricsWidth(QFontMetrics(QFont()), "HHHHHHHH");; +} + int NodeViewItem::DefaultItemBorder() { return QFontMetrics(QFont()).height() / 12; @@ -262,8 +267,36 @@ void NodeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti painter->setPen(app_pal.color(QPalette::Text)); + QString node_label; + + if (node_->GetLabel().isEmpty()) { + node_label = node_->ShortName(); + } else { + node_label = node_->GetLabel(); + } + + { + QFont f; + QFontMetrics fm(f); + + int max_text_width = DefaultMaximumTextWidth(); + + if (QFontMetricsWidth(fm, node_label) > max_text_width) { + QString concatenated; + + do { + node_label.chop(1); + concatenated = QCoreApplication::translate("NodeViewItem", "%1...").arg(node_label); + } while (QFontMetricsWidth(fm, concatenated) > max_text_width); + + node_label = concatenated; + } + } + // Draw the text in a rect (the rect is sized around text already in the constructor) - painter->drawText(title_bar_rect_, Qt::AlignCenter, node_->ShortName()); + painter->drawText(title_bar_rect_, + Qt::AlignCenter, + node_label); } diff --git a/app/widget/nodeview/nodeviewitem.h b/app/widget/nodeview/nodeviewitem.h index 2d76eaa98..0a02b7e3e 100644 --- a/app/widget/nodeview/nodeviewitem.h +++ b/app/widget/nodeview/nodeviewitem.h @@ -86,6 +86,8 @@ public: static int DefaultItemWidth(); + static int DefaultMaximumTextWidth(); + static int DefaultItemBorder(); qreal DefaultItemHorizontalPadding() const; diff --git a/app/widget/nodeview/nodeviewscene.cpp b/app/widget/nodeview/nodeviewscene.cpp index 628e72517..b7e848996 100644 --- a/app/widget/nodeview/nodeviewscene.cpp +++ b/app/widget/nodeview/nodeviewscene.cpp @@ -180,10 +180,12 @@ void NodeViewScene::AddNode(Node* node) } connect(node, &Node::PositionChanged, this, &NodeViewScene::NodePositionChanged); + connect(node, &Node::LabelChanged, this, &NodeViewScene::NodeLabelChanged); } void NodeViewScene::RemoveNode(Node *node) { + disconnect(node, &Node::LabelChanged, this, &NodeViewScene::NodeLabelChanged); disconnect(node, &Node::PositionChanged, this, &NodeViewScene::NodePositionChanged); delete item_map_.take(node); @@ -243,4 +245,9 @@ void NodeViewScene::NodePositionChanged(const QPointF &pos) item_map_.value(static_cast(sender()))->SetNodePosition(pos); } +void NodeViewScene::NodeLabelChanged() +{ + item_map_.value(static_cast(sender()))->update(); +} + OLIVE_NAMESPACE_EXIT diff --git a/app/widget/nodeview/nodeviewscene.h b/app/widget/nodeview/nodeviewscene.h index ee4040c5f..0aa7b67ca 100644 --- a/app/widget/nodeview/nodeviewscene.h +++ b/app/widget/nodeview/nodeviewscene.h @@ -127,6 +127,10 @@ private slots: */ void NodePositionChanged(const QPointF& pos); + /** + * @brief Receiver for when a node's label has changed + */ + void NodeLabelChanged(); };