diff --git a/app/node/CMakeLists.txt b/app/node/CMakeLists.txt index d10d5746f..6cd34ca5b 100644 --- a/app/node/CMakeLists.txt +++ b/app/node/CMakeLists.txt @@ -34,6 +34,8 @@ set(OLIVE_SOURCES node/globals.h node/graph.cpp node/graph.h + node/group.cpp + node/group.h node/hashtraverser.cpp node/hashtraverser.h node/inputdragger.cpp diff --git a/app/node/group.cpp b/app/node/group.cpp new file mode 100644 index 000000000..7b7ab0c87 --- /dev/null +++ b/app/node/group.cpp @@ -0,0 +1,30 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "group.h" + +namespace olive { + +NodeGroup::NodeGroup() +{ + +} + +} diff --git a/app/node/group.h b/app/node/group.h new file mode 100644 index 000000000..2f15bbb71 --- /dev/null +++ b/app/node/group.h @@ -0,0 +1,43 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef NODEGROUP_H +#define NODEGROUP_H + +#include "node.h" + +namespace olive { + +class NodeGroup : public Node +{ + Q_OBJECT +public: + NodeGroup(); + + void SetNodes(Node *nodes); + +private: + QVector nodes_; + +}; + +} + +#endif // NODEGROUP_H diff --git a/app/node/output/track/track.h b/app/node/output/track/track.h index 97bb1e7b6..d6179eaf4 100644 --- a/app/node/output/track/track.h +++ b/app/node/output/track/track.h @@ -168,17 +168,46 @@ public: QString ToString() const { - QString type_string; - - if (type_ == Track::kVideo) { - type_string = QStringLiteral("v"); - } else if (type_ == Track::kAudio) { - type_string = QStringLiteral("a"); - } else { + QString type_string = TypeToString(type_); + if (type_string.isEmpty()) { return QString(); + } else { + return QStringLiteral("%1:%2").arg(type_string, QString::number(index_)); + } + } + + /// For IDs that shouldn't change between localizations + static QString TypeToString(Type type) + { + switch (type) { + case kVideo: + return QStringLiteral("v"); + case kAudio: + return QStringLiteral("a"); + case kSubtitle: + return QStringLiteral("s"); + case kCount: + break; } - return QStringLiteral("%1:%2").arg(type_string, QString::number(index_)); + return QString(); + } + + /// For human-facing strings + static QString TypeToTranslatedString(Type type) + { + switch (type) { + case kVideo: + return tr("V"); + case kAudio: + return tr("A"); + case kSubtitle: + return tr("S"); + case kCount: + break; + } + + return QString(); } static Type TypeFromString(const QString& s) diff --git a/app/widget/nodeview/CMakeLists.txt b/app/widget/nodeview/CMakeLists.txt index e73798ae5..e7f94c762 100644 --- a/app/widget/nodeview/CMakeLists.txt +++ b/app/widget/nodeview/CMakeLists.txt @@ -19,6 +19,8 @@ set(OLIVE_SOURCES widget/nodeview/nodeview.cpp widget/nodeview/nodeview.h widget/nodeview/nodeviewcommon.h + widget/nodeview/nodeviewcontext.cpp + widget/nodeview/nodeviewcontext.h widget/nodeview/nodeviewedge.cpp widget/nodeview/nodeviewedge.h widget/nodeview/nodeviewitem.cpp diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index a375ede4f..b694992b3 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -29,6 +29,7 @@ #include "node/audio/volume/volume.h" #include "node/distort/transform/transformdistortnode.h" #include "node/factory.h" +#include "node/group.h" #include "node/traverser.h" #include "widget/menu/menushared.h" #include "widget/timebased/timebasedview.h" @@ -62,7 +63,7 @@ NodeView::NodeView(QWidget *parent) : ConnectSelectionChangedSignal(); - SetFlowDirection(NodeViewCommon::kTopToBottom); + SetFlowDirection(NodeViewCommon::kLeftToRight); UpdateSceneBoundingRect(); connect(&scene_, &QGraphicsScene::changed, this, &NodeView::UpdateSceneBoundingRect); @@ -85,7 +86,23 @@ NodeView::~NodeView() void NodeView::SetGraph(NodeGraph *graph, const QVector &nodes) { - bool graph_changed = graph_ != graph; + // Remove contexts that are no longer in the list + foreach (Node *n, filter_nodes_) { + if (!nodes.contains(n)) { + scene_.RemoveContext(n); + } + } + + // Add contexts that are now in the list + foreach (Node *n, nodes) { + if (!filter_nodes_.contains(n)) { + scene_.AddContext(n); + } + } + + filter_nodes_ = nodes; + + /*bool graph_changed = graph_ != graph; bool context_changed = last_set_filter_nodes_ != nodes; if (graph_changed || context_changed) { @@ -144,7 +161,7 @@ void NodeView::SetGraph(NodeGraph *graph, const QVector &nodes) // Center on something QMetaObject::invokeMethod(this, &NodeView::CenterOnItemsBoundingRect, Qt::QueuedConnection); } - } + }*/ } void NodeView::ClearGraph() @@ -850,6 +867,15 @@ void NodeView::ShowContextMenu(const QPoint &pos) Core::instance()->LabelNodes(scene_.GetSelectedNodes()); }); + // Grouping + if (selected.size() == 1 && dynamic_cast(selected.first()->GetNode())) { + QAction *ungroup_action = m.addAction(tr("Ungroup")); + connect(ungroup_action, &QAction::triggered, this, &NodeView::UngroupNodes); + } else { + QAction *group_action = m.addAction(tr("Group")); + connect(group_action, &QAction::triggered, this, &NodeView::GroupNodes); + } + // Color menu MenuShared::instance()->AddColorCodingMenu(&m); @@ -1603,6 +1629,17 @@ void NodeView::RepositionContexts() } } +void NodeView::GroupNodes() +{ + /*NodeGroup *group = new NodeGroup(); + selected_nodes_*/ +} + +void NodeView::UngroupNodes() +{ + static_cast(selected_nodes_.first()); +} + NodeViewItem *NodeView::UpdateNodeItem(Node *node, bool ignore_own_context) { // Get UI item or create if it doesn't exist diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index db03afc08..941120361 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -27,6 +27,7 @@ #include "node/graph.h" #include "node/nodecopypaste.h" #include "nodeviewedge.h" +#include "nodeviewcontext.h" #include "nodeviewminimap.h" #include "nodeviewscene.h" #include "widget/handmovableview/handmovableview.h" @@ -303,6 +304,10 @@ private slots: void RepositionContexts(); + void GroupNodes(); + + void UngroupNodes(); + }; } diff --git a/app/widget/nodeview/nodeviewcontext.cpp b/app/widget/nodeview/nodeviewcontext.cpp new file mode 100644 index 000000000..ad1bdc6ac --- /dev/null +++ b/app/widget/nodeview/nodeviewcontext.cpp @@ -0,0 +1,114 @@ +#include "nodeviewcontext.h" + +#include +#include +#include +#include +#include + +#include "node/block/block.h" +#include "node/output/track/track.h" +#include "nodeviewitem.h" +#include "ui/colorcoding.h" + +namespace olive { + +#define super QGraphicsRectItem + +NodeViewContext::NodeViewContext(QGraphicsItem *item) : + super(item) +{ + setFlag(ItemIsMovable); + setFlag(ItemIsSelectable); + + // Set default label text + SetContext(nullptr); +} + +void NodeViewContext::SetContext(Node *node) +{ + context_ = node; + + if (context_) { + if (Block *block = dynamic_cast(node)) { + lbl_ = QCoreApplication::translate("NodeViewContext", + "%1 [%2] :: %3 - %4").arg(block->GetLabelAndName(), + Track::Reference::TypeToTranslatedString(block->track()->type()), + block->in().toString(), + block->out().toString()); + } else { + lbl_ = node->GetLabelAndName(); + } + + Color c = node->color(); + setPen(QPen(c.toQColor(), 2)); + + c.set_alpha(0.5f); + setBrush(c.toQColor()); + } else { + lbl_ = QCoreApplication::translate("NodeViewContext", "(None)"); + } +} + +void NodeViewContext::AddChild(Node *node) +{ + NodeViewItem *item = new NodeViewItem(this); + item->SetNode(node); +} + +qreal GetTextOffset(const QFontMetricsF &fm) +{ + return fm.height()/2; +} + +void NodeViewContext::UpdateRect() +{ + QFont f; + QFontMetricsF fm(f); + qreal lbl_offset = GetTextOffset(fm); + + QRectF rect = childrenBoundingRect(); + int pad = NodeViewItem::DefaultItemHeight(); + rect.adjust(-pad, - lbl_offset*2 - fm.height() - pad, pad, pad); + setRect(rect); +} + +void NodeViewContext::SetFlowDirection(NodeViewCommon::FlowDirection dir) +{ + auto children = childItems(); + foreach (auto child, children) { + if (NodeViewItem *item = dynamic_cast(child)) { + item->SetFlowDirection(dir); + } + } +} + +void NodeViewContext::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + QPen p = pen(); + + if (option->state & QStyle::State_Selected) { + p.setStyle(Qt::DotLine); + } + + painter->setPen(p); + painter->setBrush(brush()); + + int rounded = painter->fontMetrics().height(); + painter->drawRoundedRect(rect(), rounded, rounded); + + painter->setPen(context_ ? ColorCoding::GetUISelectorColor(context_->color()) : Qt::white); + + int offset = GetTextOffset(painter->fontMetrics()); + + QRectF text_rect = rect(); + text_rect.adjust(offset, offset, -offset, -offset); + painter->drawText(text_rect, lbl_); +} + +QVariant NodeViewContext::itemChange(GraphicsItemChange change, const QVariant &value) +{ + return super::itemChange(change, value); +} + +} diff --git a/app/widget/nodeview/nodeviewcontext.h b/app/widget/nodeview/nodeviewcontext.h new file mode 100644 index 000000000..4e1a8a866 --- /dev/null +++ b/app/widget/nodeview/nodeviewcontext.h @@ -0,0 +1,39 @@ +#ifndef NODEVIEWCONTEXT_H +#define NODEVIEWCONTEXT_H + +#include +#include + +#include "node/node.h" +#include "nodeviewcommon.h" + +namespace olive { + +class NodeViewContext : public QGraphicsRectItem +{ +public: + NodeViewContext(QGraphicsItem *item = nullptr); + + void SetContext(Node *node); + + void AddChild(Node *node); + + void UpdateRect(); + + void SetFlowDirection(NodeViewCommon::FlowDirection dir); + + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; + +protected: + virtual QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override; + +private: + Node *context_; + + QString lbl_; + +}; + +} + +#endif // NODEVIEWCONTEXT_H diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 73e2d8019..fa362af72 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -406,6 +406,10 @@ QVariant NodeViewItem::itemChange(QGraphicsItem::GraphicsItemChange change, cons { if (change == ItemPositionHasChanged && node_) { ReadjustAllEdges(); + + if (NodeViewContext *ctx = dynamic_cast(parentItem())) { + ctx->UpdateRect(); + } } return QGraphicsItem::itemChange(change, value); diff --git a/app/widget/nodeview/nodeviewscene.cpp b/app/widget/nodeview/nodeviewscene.cpp index 878fd52a4..56de3d250 100644 --- a/app/widget/nodeview/nodeviewscene.cpp +++ b/app/widget/nodeview/nodeviewscene.cpp @@ -194,6 +194,31 @@ void NodeViewScene::RemoveEdge(Node *output, const NodeInput &input) } } +NodeViewContext *NodeViewScene::AddContext(Node *node) +{ + NodeViewContext *context_item = context_map_.value(node); + + if (!context_item) { + context_item = new NodeViewContext(); + context_item->SetContext(node); + context_item->setPos(0, 0); + addItem(context_item); + + const NodeGraph::PositionMap &map = node->parent()->GetNodesForContext(node); + for (auto it=map.cbegin(); it!=map.cend(); it++) { + context_item->AddChild(it.key()); + } + context_item->UpdateRect(); + } + + return context_item; +} + +void NodeViewScene::RemoveContext(Node *node) +{ + delete context_map_.take(node); +} + int NodeViewScene::DetermineWeight(Node *n) { QVector inputs = n->GetImmediateDependencies(); diff --git a/app/widget/nodeview/nodeviewscene.h b/app/widget/nodeview/nodeviewscene.h index 29cf5bbb7..4f6357dee 100644 --- a/app/widget/nodeview/nodeviewscene.h +++ b/app/widget/nodeview/nodeviewscene.h @@ -25,6 +25,7 @@ #include #include "node/graph.h" +#include "nodeviewcontext.h" #include "nodeviewedge.h" #include "nodeviewitem.h" @@ -99,6 +100,9 @@ public slots: NodeViewEdge *AddEdge(Node *output, const NodeInput& input); void RemoveEdge(Node *output, const NodeInput& input); + NodeViewContext *AddContext(Node *node); + void RemoveContext(Node *node); + /** * @brief Set whether edges in this scene should be curved or not */ @@ -113,6 +117,8 @@ private: void DisconnectNode(Node *n); + QHash context_map_; + QHash item_map_; QVector edges_;